Sommaire

Un site web multilingue présente un problème discret : il se périme dans les trois quarts de ses langues. Une phrase sur la page allemande est vite corrigée — mais il faut des semaines avant que la même correction arrive en anglais, en français et en italien. Elle n’est donc pas effectuée. Au bout d’un an, les pages en langues étrangères disent autre chose que la page allemande, et personne ne sait exactement quoi.

La solution habituelle s’appelle agence de traduction ou API de traduction. Les deux fonctionnent, les deux ont le même frein : elles coûtent par opération. Et ce qui coûte par opération ne se fait pas pour une demi-phrase modifiée.

L’objectif

La traduction devrait être aussi bon marché et anodine qu’un correcteur orthographique. C’est exactement ce qu’offre un modèle exécuté sur son propre ordinateur : un appel ne coûte rien d’autre que de l’électricité. Cela change ce qui est pratiquement possible — non pas la qualité d’une traduction isolée, mais la fréquence.

Chez nous, cela fonctionne aujourd’hui ainsi : nous ne maintenons que l’allemand. Une commande, et l’anglais, le français et l’italien suivent. Quiconque modifie une seule phrase obtient les trois traductions après environ vingt secondes — car seule la partie modifiée est retraduite.

Le gain, concrètement

avant avec un pipeline local
Coût par exécution prix par opération uniquement l’électricité
Reporter une petite correction n’en vaut pas la peine ~20 secondes
Répétabilité chaque exécution coûte à nouveau aussi souvent que souhaité
Expérimentation coûteuse sans conséquence
Dépendance quota, grille tarifaire, disponibilité aucune

Le dernier point est facilement sous-estimé. Lorsqu’une exécution ne coûte rien, on peut la jeter. Nous avons remanié nos prompts une douzaine de fois, jeté des cycles entiers et mesuré trois modèles les uns par rapport aux autres — avec des coûts par opération, nous nous serions contentés du résultat après le deuxième essai.

Ce dont vous avez besoin

Rien d’exotique, mais pas rien non plus — la mémoire vive est le facteur limitatif :

Prérequis Concrètement Remarque
Ordinateur Mac avec Apple Silicon, ou PC avec carte graphique Testé sur un MacBook Pro avec 36 Go
Mémoire vive 16 Go suffisent pour les petits modèles, 32 Go pour les grands Le modèle doit y tenir entièrement
Logiciel LM Studio, gratuit Lance le serveur local
Environnement d’exécution Node.js à partir de la version 18 Le script ne nécessite aucune dépendance
Espace disque 6–15 Go par modèle Voir le tableau ci-dessous
Temps de mise en place une demi-journée Charger le modèle, adapter le script, contrôler

Il y a deux choses à savoir à l’avance : un modèle qui ne tient pas dans la mémoire vive ne se charge même pas — LM Studio s’arrête avec un message clair, au lieu de mettre l’ordinateur à genoux. Et traduire ne prend pas quelques secondes : pour l’ensemble de notre site web en trois langues, nous comptons une à trois heures. C’est ponctuel ; chaque exécution suivante ne concerne plus que les modifications.

Le résultat fut moins une question de choix du modèle qu’une question de contrôle. Les erreurs intéressantes n’étaient en effet pas celles où quelque chose se cassait visiblement.

La mise en place en trois phrases

LM Studio lance un serveur local qui utilise la même interface que l’API OpenAI. Un script Node découpe les contenus en blocs, les y envoie et réassemble les réponses. Un cache mémorise chaque traduction via le hash de l’original allemand, de sorte que la deuxième exécution ne traite plus que les phrases modifiées.

L’étape de conception décisive concerne la taille des blocs.

Le contexte l’emporte sur la granularité

Le premier essai découpait tout en éléments de texte individuels — 2 279 éléments pour nos textes d’interface. Chacun était envoyé séparément au modèle. Le résultat était moyen, et ce de manière systématique :

Allemand traduit individuellement traduit dans le bloc
Zur Übersicht For a quick overview Back to overview
Kontakt aufnehmen Contact us. / Alternatively: Get in touch. / Or: Reach out. Contact us
Individuelle Software Custom software. (The best option depends on the context.) Custom software

Un modèle qui voit « Zur Übersicht » sans aucun contexte ne dispose tout simplement pas de l’information qu’il s’agit d’un bouton. Il devine — et, pour les courts fragments, fournit volontiers plusieurs propositions à la suite, ce qui fait éclater la mise en page dans une cellule de tableau.

Nous avons donc fait l’inverse : de grands blocs au lieu de petits éléments. Nous découpons les textes d’interface selon leur structure arborescente en 109 blocs d’environ 2 300 caractères en moyenne, tandis que les articles de blog sont traités entièrement d’un seul tenant. Les 2 279 requêtes deviennent 125. Le découpage se fait toujours sur des clés entières, jamais au milieu d’une valeur — un test vérifie que rien n’est perdu en réassemblant tous les blocs et en les comparant à l’original.

Trois modèles, trois caractères

Testés sur un Mac avec Apple Silicon, tous les modèles via LM Studio, avec à chaque fois le même bloc de 2 701 caractères.

Modèle Sur le disque Débit Textes d’interface Articles complets
TranslateGemma 12B 12 Go ~1,9 textes/s regroupés bien —
gpt-oss-20b 11 Go ~72 caractères/s bien bien
Qwen3.5 9B 5,6 Go ~63 caractères/s bien violation des règles
Qwen3.8 27B 15 Go ~53 caractères/s bien bien

Pour Qwen, il vaut la peine de regarder la décimale : qwen3.5-9b et qwen3.8-27b sont deux générations de la même famille, et les différences ci-dessous concernent précisément ces deux versions — et non « Qwen3 » en général. En production, nous utilisons Qwen3.8 27B.

Les tailles indiquées sont celles réellement occupées — les modèles sont livrés quantifiés, c’est-à-dire avec une précision de calcul réduite. Un modèle de 27 milliards de paramètres occupe ainsi 15 gigaoctets au lieu de plus de 50, avec une perte de qualité à peine perceptible. Pour débuter, le plus petit suffit : pour les textes d’interface, qui constituent la plus grande partie, Qwen3.5 9B a fourni la même qualité que son frère trois fois plus grand.

La mesure s’effectue ici en caractères par seconde, et non en tokens par seconde. C’est volontaire : chaque modèle est livré avec son propre tokenizer, et le texte allemand s’y décompose avec une finesse différente. Les tokens ne sont donc que partiellement comparables entre modèles — la quantité de texte obtenue à la fin l’est. Malgré tout, voici un chiffre pour situer : Qwen3.8 27B a généré 1 714 tokens de sortie pour un article de blog complet en 120 secondes, soit environ 14 tokens par seconde ; gpt-oss-20b se situe à peu près au double.

Les chiffres disent de toute façon moins que ce que l’on pourrait attendre. Un modèle trois fois plus petit n’était qu’environ 20 pour cent plus rapide — le goulot d’étranglement sur ce matériel n’est pas la puissance de calcul, mais la bande passante mémoire. Pour chaque token, tous les poids doivent passer par le bus mémoire. Même davantage de requêtes simultanées n’ont guère aidé : entre deux et vingt requêtes parallèles, la dispersion des mesures était plus élevée que l’effet observé.

Les différences de comportement sont plus intéressantes.

TranslateGemma n’est pas un interlocuteur

Le modèle de traduction de Google ne comprend pas les instructions — il traduit ce qui lui parvient. Si on lui envoie un objet JSON en lui demandant de ne traduire que les valeurs, on reçoit des structures désassemblées. La structure doit donc être préservée par programmation.

Il possède en outre son propre format d’appel : son modèle de chat attend des codes de langue comme champs, et non comme prompt. LM Studio ne transmet toutefois pas ces champs via le point de terminaison de chat, de sorte que chaque appel se termine par HTTP 400. Il faut passer par /v1/completions avec un prompt reconstruit manuellement — le modèle se trouve dans le dossier du modèle.

En contrepartie, il fait ce qu’il doit faire et laisse les termes techniques tranquilles. En revanche, pas les noms de produits : « Exeoflow » devenait systématiquement « Exeflow » ou « Exoflow ». Avec un modèle de traduction pur, les noms propres doivent être masqués avant l’appel.

Qwen3.5 9B traduit un champ de trop

Le petit Qwen respectait presque toutes les règles — date, auteur, listes de technologies, et même les noms de personnes cités restaient intacts. Il échouait de manière reproductible à un seul endroit, lors de chaque exécution :

# Original                              # Traduction
tags: ['ki', 'glossar', 'agents']       tags: ['ai', 'glossary', 'agents']

Les tags sont des identifiants techniques, pas de la prose — une fois traduits, ils cassent le filtrage. Qwen3.8 27B et gpt-oss ne faisaient pas cette erreur. Elle a finalement été corrigée par une seule phrase supplémentaire dans l’instruction : les tags sont des identifiants et ne doivent jamais être traduits.

gpt-oss-20b a besoin d’une instruction en anglais

C’est ici que se trouvait la constatation la plus instructive, et elle a failli passer inaperçue. Avec un prompt système allemand, gpt-oss traduisait docilement le frontmatter et laissait l’intégralité du texte courant inchangé en allemand — lors de cinq exécutions sur cinq. Même lorsque l’instruction indiquait explicitement que le texte complet devait être traduit.

Le même prompt en anglais : correct du premier coup. Le problème n’était pas la clarté de l’instruction, mais sa langue. Les modèles Instruct sont principalement entraînés sur des instructions en anglais. Depuis, tous nos prompts sont en anglais, même si ni la langue source ni la langue cible ne le sont — cela rend le pipeline indépendant du modèle utilisé à un instant donné.

L’erreur que nous aurions presque manquée

L’affaire du texte non traduit a une chute désagréable : notre contrôle indiquait « réussi ».

Il comparait les titres, les lignes de tableau, le texte en gras, les cibles de liens et les blocs de code entre l’original et la traduction. Un texte non traduit passe ce contrôle sans difficulté — sa structure est parfaitement préservée. Treize fichiers au contenu entièrement allemand se sont ainsi retrouvés sur le disque sous le nom en.md, et dans une langue que l’on ne lit pas soi-même, cela ne se remarque que des mois plus tard.

Depuis, le pipeline répond aussi à la plus évidente de toutes les questions : Le texte a-t-il vraiment été traduit ? Il mesure la proportion du texte courant restée identique mot pour mot. Une véritable traduction se situe autour de sept pour cent — certaines lignes peuvent légitimement rester identiques, comme les noms propres ou les termes techniques. Le texte allemand retransmis tel quel atteignait cent.

Déroulement par bloc : le bloc allemand est envoyé au modèle local, puis la réponse passe par quatre contrôles. S’ils réussissent, le fichier est écrit ; s’ils échouent, le bloc avec l’objection retourne au modèle, et après trois tentatives il est rejeté. Ein Block Teilbaum oder ganzer Beitrag Lokales Modell LM Studio, temperature 0 Prüfung Struktur erhalten Felder unverändert Platzhalter intakt überhaupt übersetzt geschrieben en · fr · it Beanstandung zurück ans Modell — max. 3 Versuche verworfen keine Datei

Si un bloc ne passe pas le contrôle, le fichier n’est pas écrit. Cela peut sembler strict, mais c’est l’option la moins dangereuse : le générateur de site ignore simplement un fichier de langue manquant, qui disparaît aussi des références hreflang. Un fichier défectueux, en revanche, est mis en ligne.

La leçon vaut au-delà des traductions : un contrôle qui ne cherche que les dommages ignore les cas où, tout simplement, rien ne se passe.

Ce qui nous a encore surpris

Qwen3.8 réfléchit à voix haute par défaut. Pour une traduction, ce n’est que du temps d’attente — 187 tokens de raisonnement pour une phrase, 12,4 au lieu de 1,4 seconde, pour un résultat identique. Les commutateurs prévus par Qwen pour cela sont les arguments de modèle enable_thinking et preserve_thinking, tous deux actifs par défaut. Dans LM Studio, ils n’ont aucun effet : la réponse contient avec enable_thinking: false exactement autant de tokens de raisonnement que sans le champ, et une clé librement inventée au même endroit ne déclenche ni erreur ni effet — tout le bloc est ignoré, au lieu d’être transmis au modèle de chat. Ce n’est pas le commutateur qui est incorrect, mais l’attente que chaque environnement d’exécution le transmette. Ce qui le désactive ici est reasoning_effort: 'none' — LM Studio consigne même un avertissement indiquant que le réglage est invalide. Il fonctionne malgré tout.

Les nouvelles tentatives n’ont pas besoin d’aléa. Notre premier brouillon augmentait la température en cas d’échec, afin d’obtenir une réponse différente. Pour une tâche soumise à des règles, c’est contre-productif : avec temperature: 0, les deux modèles testés passaient le contrôle, avec 0.2, les deux échouaient. La dispersion coûte précisément la fidélité aux règles qui importe ici. Un modèle Instruct peut en revanche tirer parti d’un retour précis — la seconde tentative reçoit désormais l’objection dans le prompt.

Les tableaux sont un piège discret. En JavaScript, un tableau est lui aussi typeof 'object'. Si l’on y entre récursivement lors du découpage, on transforme sans le remarquer ["a","b"] en {"0":"a","1":"b"} — puis on l’écrit dans le fichier cible.

Pour reproduire l’exemple

Le script d’exemple ci-dessous est directement exécutable, sans dépendances, et montre les trois composants : découper les blocs, vérifier la réponse, mettre le résultat en cache.

  1. Télécharger LM Studio et l’installer, charger un modèle Instruct (openai/gpt-oss-20b est un bon point de départ) et démarrer le serveur sous Developer.
  2. Télécharger le script et l’exécuter :
node lmstudio-translate-demo.mjs
node lmstudio-translate-demo.mjs --model=qwen/qwen3.8-27b --lang=French
node lmstudio-translate-demo.mjs --file=meine-texte.json
  1. Lors de la deuxième exécution, le cache intervient — les textes modifiés sont retraduits, tout le reste provient du fichier.

En usage productif, il ne faut pas omettre trois éléments : le contrôle de la traduction effective, le cache (sans quoi chaque correction coûte un cycle complet) et un test qui vérifie le découpage par rapport à l’original. Tout le reste peut être ajouté en cours de route.

Et la question de principe ? Pour un site web en quatre langues avec des modifications de texte régulières, l’approche locale est rentable ne serait-ce que parce qu’elle est répétable. Il est possible de modifier une phrase puis de mettre à jour les trois traductions vingt secondes plus tard — sans penser au coût par appel.

Vers le projet

Autres articles