Sommaire
- L’objectif
- Le gain, concrètement
- Ce dont vous avez besoin
- La mise en place en trois phrases
- Le contexte l’emporte sur la granularité
- Trois modèles, trois caractères
- TranslateGemma n’est pas un interlocuteur
- Qwen3.5 9B traduit un champ de trop
- gpt-oss-20b a besoin d’une instruction en anglais
- L’erreur que nous aurions presque manquée
- Ce qui nous a encore surpris
- Pour reproduire l’exemple
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.
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.
- Télécharger LM Studio et l’installer, charger un modèle Instruct
(
openai/gpt-oss-20best un bon point de départ) et démarrer le serveur sous Developer. - 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
- 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
À propos de l'auteur
Ingénierie

