Aller au contenu

Système de traduction

Ce site est publié en 17 langues. Non pas par une équipe de traducteurs, mais grâce à un script Python, un modèle Claude Opus et une structure de fichiers conçue pour que tout s’emboîte parfaitement. Voici comment l’ensemble fonctionne.

La documentation réside dans src/content/docs/. L’anglais est la racine — chaque autre langue en est le miroir exact :

src/content/docs/
├── index.mdx ← English (root)
├── getting-started.md
├── features/
│ ├── play-chess.md
│ ├── multiplayer.md
│ └── ...
├── setup/
│ ├── tts-overview.md
│ └── ...
├── under-the-hood/
│ ├── architecture.md
│ └── ...
├── fr/ ← French
│ ├── index.mdx
│ ├── getting-started.md
│ ├── features/
│ │ ├── play-chess.md
│ │ └── ...
│ └── ...
├── ja/ ← Japanese
│ ├── index.mdx
│ ├── getting-started.md
│ └── ...
└── ... (16 language directories total)

Chaque fichier traduit est un miroir structurel de sa source anglaise. Même nom de fichier, même chemin de sous-répertoire, mêmes clés de frontmatter. La seule différence est que le texte est dans une autre langue.

Starlight (le framework de documentation) repose sur cette symétrie. Lorsqu’un utilisateur change de langue, Starlight remplace /docs/getting-started/ par /fr/docs/getting-started/ — même chemin, préfixe de locale différent. Si le fichier français n’existe pas exactement à fr/getting-started.md, le sélecteur de langue se casse ou revient silencieusement à l’anglais.

Les langues sont classées par population mondiale de joueurs d’échecs, sur la base de données provenant de Lichess, Chess.com et des inscriptions FIDE. L’anglais vient en premier en tant que langue source ; tout le reste suit le classement échiquéen :

RangCodeLangueStyle
1enAnglaisSource de référence
2esEspagnolStandard formel
3hiHindiÉcriture devanagari
4ruRusseStandard formel
5deAllemandStandard formel
6frFrançaisStandard formel
7ptPortugaisPortugais européen
11plPolonaisStandard formel
12itItalienStandard formel
13ukUkrainienStandard formel
14trTurcStandard formel
17koCoréenForme 합니다/습니다
18zhChinois (simplifié)Caractères simplifiés
zh-twChinois (traditionnel)Caractères traditionnels
23nbNorvégien bokmålBokmål standard
beBiélorusseBiélorusse standard
34jaJaponaisForme です/ます

La colonne « style » est importante. Le japonais et le coréen comportent des choix de registre formel qui affectent chaque phrase. Le prompt de traduction inclut ces instructions afin que le modèle produise un texte naturel et soigné — et non une traduction mécanique rigide.

Cet ordre contrôle également le menu déroulant des langues dans l’en-tête du site. Les langues les plus parlées dans le monde des échecs apparaissent en premier, pour que les utilisateurs trouvent plus facilement la leur sans avoir à défiler.

Toute la documentation commence sous forme de markdown en anglais dans src/content/docs/. Le frontmatter contient un title et une description :

---
title: "Getting Started"
description: "Install En Parlant~ and play your first game."
---
Download the latest release...

Un script Python (scripts/translate-docs.py) lit chaque fichier source en anglais, l’envoie à l’API Claude et écrit le markdown traduit :

Fenêtre de terminal
python3 scripts/translate-docs.py \
--anthropic-key $ANTHROPIC_API_KEY \
--model claude-opus-4-6 \
--workers 5

Le script prend environ 60 à 70 minutes pour traduire les 28 fichiers source dans les 16 langues cibles (448 fichiers au total). Il exécute 5 appels API en parallèle pour rester dans les limites de débit.

Pour une seule nouvelle langue, cela prend environ 4 minutes.

Fenêtre de terminal
pnpm build

Astro lit le markdown source, le rend via les templates de Starlight et produit du HTML statique dans dist/. La compilation prend environ 30 secondes pour l’ensemble des ~500 pages.

Fenêtre de terminal
pnpm run deploy

Pousse le site compilé vers Cloudflare Workers.

Le script de traduction est volontairement simple — environ 300 lignes de Python. Voici le flux :

┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Read English │────▶│ Claude API │────▶│ Write target │
│ source file │ │ (Opus 4.6) │ │ language file │
│ │ │ │ │ │
│ getting- │ │ "Translate into │ │ fr/getting- │
│ started.md │ │ French..." │ │ started.md │
└─────────────────┘ └──────────────────┘ └─────────────────┘
× 5 parallel
× 16 languages
× 28 files

Le prompt indique à Claude exactement ce qu’il faut traduire et ce qu’il faut laisser tel quel :

  • Traduire : le texte, les titres, les libellés de tableaux, le title et la description du frontmatter
  • Conserver en anglais : les blocs de code, le code en ligne, les exemples de commandes, les chemins de fichiers, les URL, les noms de produits, les noms de personnes, les diagrammes ASCII

Cette séparation est cruciale. Une commande comme pnpm build doit rester pnpm build dans toutes les langues. Un nom de produit comme « En Parlant~ » ou « Stockfish » reste non traduit. Mais « Getting Started » devient « はじめに » en japonais et « Начало работы » en russe.

Claude Opus 4.6 produit des traductions sensiblement meilleures que les modèles plus rapides. La différence se manifeste dans :

  • La fluidité des formulations — Opus écrit comme un locuteur natif, pas comme un traducteur. Il restructure les phrases lorsque l’ordre des mots anglais sonnerait maladroit dans la langue cible.
  • La précision technique — La terminologie échiquéenne, le jargon TTS et les concepts logiciels sont traduits avec les termes spécifiques au domaine.
  • La cohérence — Le registre formel reste constant tout au long du texte. Le japonais utilise la forme です/ます partout, sans alterner entre registre familier et poli en plein paragraphe.
  • La gestion du MDX — Opus préserve correctement les balises de composants JSX (<Card>, <CardGrid>) et les instructions import dans les fichiers .mdx sans les altérer.

La différence de coût est réelle — environ 28 $ pour une traduction complète du site avec Opus contre 5 $ avec Sonnet — mais pour 448 fichiers que les utilisateurs vont réellement lire, la qualité en vaut la peine.

C’est la partie qui a pris le plus de temps à faire fonctionner correctement.

Starlight détecte la langue d’une page en vérifiant le premier segment de son slug de contenu. Quand il voit fr/docs/getting-started, il sait que c’est du français parce que fr est le premier segment. Mais l’implémentation initiale produisait des slugs comme docs/fr/getting-started — la locale enfouie sous docs/. Starlight voyait docs comme premier segment, traitait tout comme de l’anglais et générait plus de 7 000 pages en double au lieu de ~500.

Une fonction generateId personnalisée dans src/content.config.ts contrôle la façon dont les chemins de fichiers deviennent des slugs de contenu :

generateId({ entry }) {
const slug = entry.replace(/\.[^.]+$/, "");
const firstSeg = slug.split("/")[0];
if (firstSeg && localeKeys.includes(firstSeg)) {
return `${firstSeg}/docs/${slug.slice(firstSeg.length + 1)}`;
}
return `docs/${slug}`;
}

Cela place le préfixe de locale avant docs/ :

Chemin du fichierSlugURL
getting-started.mddocs/getting-started/docs/getting-started/
fr/getting-started.mdfr/docs/getting-started/fr/docs/getting-started/
ja/features/puzzles.mdja/docs/features/puzzles/ja/docs/features/puzzles/

L’anglais utilise defaultLocale: "root", ce qui signifie aucun préfixe — il réside directement à /docs/, et non à /en/docs/.

Un tableau localeKeys dans le même fichier doit lister chaque locale non anglaise. Si une locale existe dans la configuration d’Astro mais pas dans ce tableau, son contenu traduit est traité comme du contenu anglais — le sélecteur de langue se casse et le nombre de pages explose.

Astro met en cache les correspondances de slugs dans .astro/data-store.json. Après toute modification de configuration des locales, ce fichier doit être supprimé avant de recompiler, sinon la compilation réussit avec un routage obsolète (incorrect).

La barre latérale est définie dans astro.config.mjs. Les éléments qui pointent vers une page (propriété slug) utilisent automatiquement le titre du frontmatter de la page traduite — aucune traduction manuelle nécessaire :

{ slug: "docs/getting-started" }
// English: "Getting Started" (from English frontmatter)
// French: "Premiers pas" (from French frontmatter)
// Japanese: "はじめに" (from Japanese frontmatter)

Mais les libellés de groupes et les liens externes nécessitent des traductions explicites :

{
label: "Features",
translations: {
fr: "Fonctionnalités",
es: "Características",
de: "Funktionen",
ja: "機能",
// ... all 16 languages
},
items: [
{ slug: "docs/features/play-chess" },
{ slug: "docs/features/multiplayer" },
// ...
],
}

Sept éléments de la barre latérale nécessitent des traductions manuelles : Welcome, Features, App Menus, Setup Guides, Under the Hood, Credits et Accessibility. Ajouter une nouvelle langue signifie ajouter une entrée dans chacun de ces sept blocs.

En Parlant~ renvoie vers cette documentation depuis son menu Aide. Le lien est sensible à la locale — si vous utilisez l’application en français, il ouvre la documentation en français :

const docsLocalePrefix = useMemo(() => {
const lang = i18n.language; // e.g. "fr_FR", "zh_TW"
if (!lang || lang.startsWith("en")) return "";
if (lang === "zh_TW") return "/zh-tw";
return `/${lang.slice(0, 2)}`;
}, [i18n.language]);

Chaque langue disponible dans l’application a une traduction correspondante sur le site. La correspondance est automatique — fr_FR pointe vers /fr/docs/, ja_JP pointe vers /ja/docs/. Le seul cas particulier est le chinois traditionnel : zh_TW pointe vers /zh-tw/docs/ (avec trait d’union).

Les parties difficiles sont résolues :

  1. L’architecture de routage est en place. La fonction generateId, le tableau localeKeys et la configuration defaultLocale: "root" fonctionnent ensemble pour que Starlight génère la structure d’URL correcte. C’était le point le plus douloureux — il a fallu remonter à travers plus de 6 fichiers source dans Starlight et Astro pour trouver et corriger le problème.

  2. Le script de traduction gère tout. Retraduction complète du site, ajout d’une seule langue, mise à jour de fichiers individuels — tout passe par le même script avec des options différentes. Il relance automatiquement en cas de limite de débit, parallélise les tâches entre les workers et signale clairement les erreurs.

  3. Ajouter une nouvelle langue, c’est quatre modifications de configuration et une commande. Ajouter la locale dans astro.config.mjs, content.config.ts et translate-docs.py, ajouter les traductions de la barre latérale, lancer le script. Environ 10 minutes de travail plus 4 minutes de traduction.

  4. Mettre à jour le contenu est encore plus simple. Modifier la source en anglais, lancer le script avec --files et --overwrite uniquement pour les fichiers modifiés, recompiler. Ou pour des corrections mineures de texte, modifier directement les fichiers traduits.

  5. L’ensemble du pipeline est capturé dans un skill. Lancer /translate_docs guide à travers tout le processus — quel mode utiliser, quelles options passer, vérifications préalables, vérification post-traduction. Aucune connaissance institutionnelle requise.

La traduction totale de 448 fichiers dans 16 langues a coûté environ 28 $ et a pris environ 70 minutes. Une seule nouvelle langue coûte environ 1,70 $. Un seul fichier retraduit dans toutes les langues coûte environ 1 $. Ce sont les coûts récurrents pour maintenir la documentation à jour en 17 langues.