🌐 Aussi en: English · Deutsch · Español

Pendant près de deux ans, ce blog n’a parlé qu’une seule langue. L’anglais est la lingua franca des sysadmins, donc ça semblait suffire. Mais je suis allemand, une bonne partie de mes lecteurs n’ont pas non plus l’anglais pour langue maternelle, et quand on cherche « ZFS RAIDZ1 NAS Debian » en allemand, en français ou en espagnol, on obtient des résultats en allemand, en français ou en espagnol. Pas les miens.
Cette semaine, le blog a donc appris trois nouvelles langues. Chaque article publié existe désormais en anglais, allemand, français et espagnol, avec un sélecteur de langue dans le menu et une petite ligne « 🌐 Également disponible en » au-dessus de chaque article. Cet article en est le making-of : le plugin, le pipeline, le bug qui a envoyé une langue entière en 404, et ce que j’ai appris en faisant tourner en parallèle une petite armée d’agents IA. Même méthode que d’habitude : c’est moi qui décide, mon co-admin IA (Claude) a fait le gros du travail.
🎯 Pourquoi se donner cette peine ? Plus de lecteurs, j’espère
Soyons honnêtes sur la motivation : j’espère plus de visiteurs venant des moteurs de recherche. Google, Bing et DuckDuckGo associent les requêtes à des pages dans la langue de la personne qui cherche. Un admin allemand qui cherche une solution pour fail2ban tapera bien plus probablement des mots-clés en allemand, et une page qui existe en allemand a une vraie chance d’apparaître dans ses résultats. Avec des balises hreflang correctes, le moteur de recherche sait en outre que les quatre versions vont ensemble ; il peut donc montrer à chacun celle dans sa langue au lieu de les traiter comme du contenu dupliqué.
Est-ce que ça va marcher ? Aucune idée pour l’instant. J’ai compté mes vrais lecteurs humains il y a quelque temps (spoiler : bien moins que ce que suggèrent les logs de requêtes), et je recompterai dans quelques mois. Si les pages traduites amènent des gens qui n’auraient jamais trouvé l’original anglais, l’expérience aura payé. Sinon, j’écrirai aussi cet article-là.
🧩 Le plugin : Polylang, et pourquoi
J’ai choisi Polylang, dans sa version gratuite. Mes critères de présélection étaient simples : gratuit, open source (GPLv3), pas de compte, pas de service cloud et pas de traduction automatique qui envoie mon contenu quelque part. Polylang ne traduit rien lui-même. Il sait juste quel article est dans quelle langue et quels articles vont ensemble, et il le fait en stockant la langue comme une taxonomie WordPress tout à fait ordinaire. Pas de tables supplémentaires, rien d’exotique dans la base de données.
Les réglages que j’ai choisis, et pourquoi :
- L’anglais reste la langue par défaut, sans
/en/dans l’URL. Tous les liens, favoris et backlinks existants continuent de fonctionner. Les nouvelles langues vivent sous/de/,/fr/et/es/. - Pas de détection de la langue du navigateur. Les robots des moteurs de recherche n’envoient pas de langue préférée, et je ne veux pas qu’un visiteur qui a cliqué sur un lien anglais soit renvoyé ailleurs. Vous obtenez ce sur quoi vous avez cliqué ; le sélecteur est juste là si vous voulez une autre langue.
- Les médias ne sont pas traduits. Une capture d’écran reste une capture d’écran. Les textes alternatifs et les légendes dans les articles sont traduits, les fichiers image sont partagés.
- Les catégories et les étiquettes existent par langue (« IT Security » devient « IT-Sicherheit », « Sécurité informatique » et « Seguridad informática »), pour que les pages d’archives fonctionnent elles aussi dans chaque langue.
La plomberie SEO était fournie gratuitement : Polylang ajoute les alternatives hreflang à chaque page, et le sitemap XML de Yoast intègre automatiquement les traductions. Le menu a reçu quatre petits drapeaux en guise de sélecteur de langue, et un petit plugin must-use (déployé par mon rôle Ansible, comme tout le reste sur ce serveur) affiche la ligne « Également disponible en », mais uniquement pour les langues dans lesquelles l’article existe vraiment.

🤖 Jouer cartes sur table concernant l’IA
Toutes les traductions sont réalisées avec l’IA. Je le dis sur la page AI Usage Notice, qui comporte désormais une section « Translations » : l’anglais est l’original, les autres langues sont des traductions IA vérifiées par un second passage d’IA indépendant. Si quelque chose sonne bizarrement en allemand, en français ou en espagnol, c’est la version anglaise qui fait foi, et je serai ravi qu’on me le signale.
🧪 Le pilote, et la 404 qui a avalé une langue entière
Avant de toucher à une centaine d’articles, j’en ai traduit exactement un : mon article sur la page d’accueil sur mobile. Heureusement, car le premier résultat a été spectaculaire, mais dans le mauvais sens : chaque page allemande renvoyait une 404. Les articles existaient, Polylang les connaissait, le sélecteur pointait vers eux, et WordPress répondait « connais pas ».
Le coupable : les règles de réécriture. J’avais configuré Polylang via wp-cli et vidé les règles de réécriture en ligne de commande. En CLI, le filtre de préfixe de langue de Polylang n’est pas entièrement en place, si bien que WordPress a tranquillement généré un tout nouveau jeu de règles qui ignorait tout de /de/. Le correctif était presque gênant : supprimer les règles en cache et laisser la prochaine requête front-end normale les reconstruire, cette fois avec Polylang entièrement chargé.
wp eval "delete_option('rewrite_rules');"
# then just load any page once – WordPress rebuilds the rules with the language prefixesLeçon retenue : tout ce qui dépend du contexte de la requête (langues, thèmes, certains plugins de cache) devrait régénérer son état dans une vraie requête, pas dans une session CLI.
🏭 Le pipeline : 111 articles × 3 langues
111 articles publiés fois trois langues, cela fait 333 traductions. Le faire à la main dans l’éditeur n’a jamais été envisageable, c’est donc devenu un pipeline. Chaque article passe par cinq étapes :
- Export. Un script wp-cli exporte chaque article en HTML Gutenberg. Les blocs de code sont découpés et remplacés par des marqueurs comme
<!--PLLCODE 3-->; le code d’origine part dans un fichier JSON séparé. Un traducteur ne peut pas « améliorer » une commande shell qu’il ne voit jamais. - Traduction. Un agent IA traduit le HTML en allemand, en français et en espagnol en suivant un guide de style (plus de détails plus bas), ainsi que le titre, l’extrait et la méta-description pour chaque langue.
- Vérification, mécanique. Un petit script Python compare chaque traduction à l’original : même séquence de commentaires de blocs Gutenberg, mêmes marqueurs, même ensemble de cibles de liens et de sources d’images, même nombre de lignes de tableau et d’éléments de liste. Au moindre écart, la traduction est renvoyée.
- Relecture, indépendante. Un second agent, qui n’a jamais vu la traduction en cours d’écriture, compare l’original et les traductions phrase par phrase et rédige ses corrections sous forme de paires exactes rechercher-remplacer, chacune avec sa justification. Chaque chaîne « ancienne » doit apparaître exactement une fois, sinon la modification est rejetée.
- Import. D’abord un dump de la base de données, puis un script exécuté en tant que
www-dataréinsère les blocs de code, publie les traductions avec la date d’origine, attribue les catégories et étiquettes de chaque langue, réutilise l’image mise en avant, définit la méta-description Yoast et relie les quatre versions entre elles dans Polylang.
Le cœur de la vérification mécanique tient en une douzaine de lignes. Il ne comprend pas un seul mot d’aucune langue, et c’est tout l’intérêt :
def feats(h):
return {
'blocks': re.findall(r'<!-- (/?wp:[a-z0-9/-]+)', h),
'code': sorted(re.findall(r'<!--PLLCODE \d+-->', h)),
'href': sorted(re.findall(r'href="([^"]*)"', h)),
'src': sorted(re.findall(r'src="([^"]*)"', h)),
'tr': h.count('<tr'), 'li': h.count('<li'), 'img': h.count('<img'),
'inline_code': len(re.findall(r'<code>', h)),
}
# original and translation must produce identical features📏 Un guide de style, comme dans une vraie agence de traduction
La cohérence sur 333 textes n’arrive pas par hasard. Le guide de style fixe le ton (du en allemand, vous en français, tú en espagnol), un glossaire (« hardening » se dit toujours « Härtung », « durcissement », « bastionado »), les formats de nombres (2,6 s au lieu de 2.6 s), la typographie française avec espaces insécables avant : ; ? !, et une liste de choses auxquelles on ne touche jamais : commandes, chemins, noms d’hôte, numéros de version, messages d’erreur, noms de produits. Les liens vers les articles liés gardent leur titre anglais, avec une courte mention « (en anglais) », jusqu’à ce que ces articles soient traduits à leur tour.
🔍 La relecture valait-elle le coup ?
Absolument. La plupart des traductions étaient bonnes, mais « bon » ne veut pas dire « juste ». Sur les 26 premiers articles, le relecteur a trouvé une centaine de choses à corriger, réparties à peu près également entre les trois langues ; seuls deux articles sont revenus sans la moindre remarque. Il s’agissait surtout de peaufinage stylistique, mais quelques-unes étaient de vrais contresens qu’un texte fluide dissimule parfaitement :
- « a longer memory » (au sens d’une conservation plus longue des logs) était devenu « plus de RAM » en espagnol.
- « customer-facing service » (un service exposé aux clients) était devenu « service client » en français.
- Une colonne de tableau « Focus » s’était transformée en « Priority ». Plausible, mais faux.
✏️ TODO (Raphael): final numbers when all 111 posts are through.
🐝 Sous-agents : un répartiteur, de nombreux ouvriers
C’est là que ça devient intéressant. Traduire un article demande beaucoup de lecture et d’écriture à une IA : le guide de style, l’original, trois traductions. En enchaîner 111 dans une seule conversation l’aurait ensevelie sous le texte bien avant la fin. La session principale est donc devenue un répartiteur et n’a presque rien traduit elle-même :
- Elle lance un agent traducteur par article. Chacun est une nouvelle instance au contexte vierge : il lit le guide de style et exactement un article, écrit les fichiers, lance la vérification et rend compte en trois lignes.
- Quand un traducteur a terminé, le répartiteur lance un agent relecteur pour cet article, qui tourne sur un modèle plus petit et moins cher (Sonnet), et, aussitôt, le traducteur suivant.
- Quand une relecture revient, le répartiteur applique les corrections, relance la vérification et met en ligne.
Trois à quatre traducteurs et deux ou trois relecteurs tournaient en même temps. Le contexte du répartiteur lui-même restait réduit, puisqu’il ne voyait jamais que de courts comptes rendus. Séparer rédacteur et relecteur présente un second avantage au-delà du parallélisme : le relecteur n’a aucun attachement à la traduction. Il ne l’a pas écrite, il ne se « souvient » pas pourquoi une phrase a été formulée ainsi, il se contente de comparer.
✅ Quand les agents en parallèle sont rentables
- Des tâches indépendantes. L’article 1370 se moque bien de l’article 1373. Pas d’état partagé, pas de problème d’ordre.
- Une entrée claire, une sortie claire. « Lis ces fichiers, écris ceux-là, lance cette vérification. » Un agent doté d’un contrat net n’a pas besoin d’allers-retours.
- Un garde-fou mécanique. Comme la vérification Python détecte toute structure cassée, je n’ai pas besoin de faire confiance à chaque agent ; je fais confiance au garde-fou.
- Un contexte neuf économise le budget. Un ouvrier qui ne lit que ce dont il a besoin coûte moins cher qu’une longue conversation qui traîne derrière elle tous les articles précédents.
❌ Quand ils ne le sont pas
- Des étapes imbriquées. Configurer Polylang, traquer la 404, construire le script d’import : chaque étape dépendait du résultat de la précédente. C’était une seule conversation, un seul cerveau, pas de fan-out.
- Les petites choses. Chaque agent démarre à froid et doit d’abord lire le guide de style. Pour un correctif d’une ligne, le démarrage coûte plus cher que le travail.
- Des consignes confuses. Un relecteur a reçu une mission supplémentaire (« passe aussi ce titre espagnol de vosotros à tú »). Il l’a fait, et a honnêtement signalé n’avoir que survolé l’allemand et le français. Une seconde relecture complète du même article a trouvé neuf vraies erreurs. Leçon : gardez la mission de chaque agent bien nette, et lisez les comptes rendus, pas seulement les chiffres.
🧱 La vraie limite : le quota
Le parallélisme ne rend pas les tokens moins chers ; il vous fait juste foncer plus vite dans le mur. Vers midi le premier jour, après une dizaine d’articles terminés, avec quatre traducteurs et trois relecteurs en cours, mon abonnement a atteint sa limite de session. Les sept agents sont morts à la même seconde, laissant derrière eux des fichiers à moitié écrits. Rien de cassé en ligne, puisque rien n’est mis en ligne avant d’avoir passé la vérification et la relecture. Après la réinitialisation, les traducteurs relancés avaient pour consigne de ne réutiliser un fichier restant qu’après avoir prouvé, original à l’appui, qu’il était complet, et de le réécrire sinon. Depuis, moins d’agents en parallèle et un rythme plus régulier. Avec une limite d’utilisation par session et par semaine, c’est ce budget qui décide de la vitesse, pas le nombre d’agents.
🚫 Ce que je n’ai délibérément pas fait
- Pas de chinois (ni de japonais, ni…). Tentant pour l’audience, mais je publierais du texte que je ne peux même pas vérifier ponctuellement. Trois langues que je peux au moins suivre à peu près, cela m’a semblé la limite honnête.
- Pas de pilote automatique sans surveillance. À un moment, l’idée était un timer systemd qui lance une session IA headless toutes les quelques heures et continue de traduire pendant que personne ne regarde. Les garde-fous de mes outils IA ont refusé de mettre ça en place, et avec le recul, à juste titre : une IA sans surveillance avec un accès SSH en écriture à un serveur de production, c’est quelque chose qu’un humain doit activer délibérément, pas quelque chose qui s’installe en douce pour le confort. Le pipeline tourne donc quand je suis là.
📝 Encore sur la liste
- Terminer les articles restants. ✏️ TODO (Raphael): current state.
- Faire pointer les liens internes et les listes « Related posts » vers les versions traduites et supprimer les mentions « (en anglais) ».
- Traduire les pages statiques (About, mentions légales, politique de confidentialité, AI Usage Notice) et donner à chaque langue son propre menu.
- Regarder les statistiques de recherche dans quelques mois et voir si quelqu’un est vraiment venu.
Si vous lisez ceci en allemand, en français ou en espagnol : willkommen, bienvenue, bienvenido. Et si une phrase sonne comme si elle avait été écrite par un robot, c’est parce que c’est le cas. Dites-le-moi, et un humain la corrigera.





