Blog
Feature
14 février 20269 min

i18n en solo : 1993 clés, 6 langues, 9 passes LLM

Points clés à retenir : Internationaliser une app mobile avec 1993 clés de traduction dans 6 langues, c'est faisable en solo grâce à l'automatisation LLM. Le script npm run translate détecte le delta, traduit uniquement les nouvelles clés via OpenRouter, et maintient la synchro en quasi-temps réel pour quelques centimes par passe. Résultat : un canal d'acquisition multiplié par 6 sans effort humain continu.

TAMSIV était né en français. Chaque string, chaque message d'erreur, chaque placeholder — tout était codé en dur dans le JSX. Quand j'ai décidé de supporter 6 langues (FR, EN, DE, ES, IT, PT), je ne me doutais pas que ça représenterait 1993 clés de traduction réparties dans 35 fichiers. Et encore moins que j'aurais besoin de 9 passes successives pour arriver à un résultat stable.

Voici le récit complet de cette aventure i18n, du premier t('key') au pipeline automatisé qui tourne aujourd'hui.

Espace de travail développeur avec plusieurs écrans affichant des fichiers de traduction dans différentes langues
L'internationalisation, c'est beaucoup d'écrans et beaucoup de fichiers JSON ouverts en parallèle.

Pourquoi internationaliser une app indie en solo ?

La réponse courte : l'acquisition utilisateur. Une app disponible uniquement en français se prive de 95% du marché mondial. Même en Europe, limiter son audience au français revient à ignorer l'Allemagne (83M d'habitants), l'Espagne (47M), l'Italie (60M), le Portugal (10M) et bien sûr l'ensemble du monde anglophone.

Mais au-delà du marché, il y a une raison technique : les stores (Google Play, App Store) indexent les métadonnées dans chaque langue. Une fiche traduite en 6 langues, c'est 6 fois plus de surface de découverte organique. C'est un levier recommandé par Google lui-même.

Pour un dev solo comme moi, c'est aussi un avantage compétitif face aux grosses apps qui négligent la localisation au-delà de l'anglais. Si un utilisateur allemand cherche "Aufgaben-App mit Spracheingabe" et que TAMSIV est la seule à avoir une fiche en allemand, c'est gagné.

Comment extraire 1993 clés de traduction sans perdre la raison ?

Première étape : remplacer chaque string française hardcodée par un appel t('key'). Dans 35 fichiers. À la main. Il n'existe pas de raccourci fiable pour cette étape — chaque string a un contexte, et le nommage de la clé doit refléter ce contexte.

J'ai structuré les clés par écran et par section :

  • feed.empty_state — le message quand le feed est vide
  • agenda.filter.participating — le filtre "Participe" dans l'agenda
  • profile.settings.language — le sélecteur de langue dans les paramètres
  • dictaphone.recording.title — le titre de l'écran d'enregistrement
  • groups.hierarchy.depth_limit — le message de limite de profondeur des groupes

Cette convention ecran.section.element est cruciale. Sans elle, on se retrouve vite avec des clés du type button_text_3 qui ne veulent rien dire trois mois plus tard. J'ai suivi les recommandations de i18next sur le nommage hiérarchique.

Conseil pratique : j'ai commencé par les écrans les plus visités (Dictaphone, Feed, Agenda) avant de m'attaquer aux paramètres et aux écrans secondaires. Ça permet de tester la traduction sur les parcours principaux rapidement.

Quel outil de traduction automatique choisir quand on est seul ?

Traduire 1993 clés manuellement dans 5 langues ? Soit près de 10 000 traductions individuelles. Impossible pour un dev solo. Les services classiques comme Google Translate API ou DeepL manquent de contexte applicatif — "Mémo vocal" deviendrait "Vocal memo" au lieu de "Voice memo".

J'ai écrit un script npm run translate qui envoie les clés françaises à OpenRouter (Gemini 2.5 Flash en modèle principal). Le LLM comprend le contexte applicatif et produit des traductions naturelles :

  • "Mémo vocal" → "Voice memo" (EN), "Sprachnotiz" (DE), "Nota de voz" (ES)
  • "Ajouter une tâche" → "Add a task", "Aufgabe hinzufügen", "Agregar una tarea"
  • "Tout voir" → "See all", "Alle anzeigen", "Ver todo"

Le script envoie les clés par batch de 50, avec le contexte de l'application en system prompt : "Tu traduis les strings d'une application mobile de gestion de tâches par la voix avec IA." Ce contexte fait toute la différence en qualité.

Carte du monde avec des épingles colorées marquant les pays couverts par la traduction
Six langues, six marchés européens : la traduction comme stratégie d'acquisition.

Pourquoi 9 passes et pas une seule ?

La première passe a traduit le gros du corpus — environ 1600 clés. Mais une app vivante évolue. Chaque nouvelle feature ajoute des clés :

  • Passe 2 : gamification — 47 nouvelles clés (niveaux, badges, streaks)
  • Passe 3 : agenda — 62 clés (événements, filtres, récurrence)
  • Passe 4 : groupes collaboratifs — 89 clés (hiérarchie, permissions, assignations)
  • Passe 5-7 : corrections contextuelles et ajustements après retours utilisateurs
  • Passe 8 : onboarding et écrans de première utilisation
  • Passe 9 : site web (tamsiv.com) — un deuxième corpus de traduction séparé

Le script détecte les clés manquantes en comparant fr.json (la référence) avec chaque fichier cible. Il ne retraduit que le delta — les clés nouvelles ou modifiées. Pas de gaspillage de tokens, pas de risque d'écraser une traduction déjà validée.

Comment fonctionne le pipeline de traduction concrètement ?

Le pipeline complet suit 4 étapes :

  1. Diff : le script compare fr.json avec en.json, de.json, es.json, it.json, pt.json. Chaque clé présente en FR mais absente dans une cible est marquée comme "à traduire".
  2. Batch : les clés à traduire sont regroupées par paquets de 50 (pour rester dans les limites de tokens du LLM).
  3. Traduction : chaque batch est envoyé à OpenRouter avec un prompt structuré. Le LLM retourne un JSON valide avec les traductions.
  4. Merge : les traductions sont fusionnées dans les fichiers cibles, en préservant l'ordre alphabétique et la structure existante.

Le coût ? Environ 0.02 à 0.05 EUR par passe avec Gemini 2.5 Flash via OpenRouter. Pour 5 langues. C'est presque gratuit comparé aux services de traduction professionnelle (qui facturent 0.10-0.20 EUR par mot).

Comment gérer la détection automatique de langue au premier lancement ?

Au premier lancement, TAMSIV détecte la langue du système via getLocales() de React Native. Si la langue est supportée (FR, EN, DE, ES, IT, PT), on l'utilise directement. Sinon, fallback sur l'anglais.

Le choix est ensuite sauvegardé en base (Supabase) dans le profil utilisateur. Ça permet de retrouver la préférence même en changeant de téléphone. Et ça évite le problème classique : l'utilisateur change la langue de son OS pour une raison X, et toutes ses apps switchent sans prévenir.

J'ai aussi ajouté un sélecteur de langue dans les paramètres, pour que l'utilisateur puisse choisir indépendamment de la langue du système. C'est un détail, mais ça fait une différence pour les bilingues ou les expats.

Quels sont les pièges de la traduction automatique par LLM ?

La traduction par LLM n'est pas parfaite. Voici les pièges que j'ai rencontrés :

  • Les faux amis : "Classeur" traduit en "Binder" au lieu de "Folder" — le LLM manquait de contexte applicatif au début.
  • Le genre grammatical : en allemand, "die Aufgabe" (féminin) vs "der Memo" (masculin) — les articles doivent correspondre.
  • Les pluriels : certaines langues ont des règles de pluriel complexes (portugais, allemand). Il faut fournir les formes singulier/pluriel explicitement.
  • La longueur : l'allemand produit des mots 30-50% plus longs que le français. "Einstellungen" vs "Paramètres". Ça casse les layouts si le design UI n'est pas flexible.
  • Les caractères spéciaux : les guillemets varient selon les langues (" " en anglais, « » en français, „ “ en allemand).

La solution : un system prompt détaillé qui précise le domaine applicatif, des exemples de traductions validées en few-shot, et une passe de relecture automatique où le LLM vérifie la cohérence de ses propres traductions.

Chaîne de production automatisée symbolisant le pipeline de traduction continue
L'automatisation transforme un processus de traduction pénible en pipeline quasi-instantané.

Comment maintenir la synchronisation sur la durée ?

Le plus dur n'est pas la traduction initiale — c'est la maintenance. Chaque modification en français doit être propagée. Si je change "Ajouter une tâche" en "Créer une tâche", les 5 autres langues doivent suivre.

Mon système de synchronisation :

  1. Détection du changement : un hash SHA256 de chaque valeur FR est stocké. Si le hash change, la clé est marquée pour retraduction.
  2. CI automatique : via GitHub Actions, à chaque push sur main qui modifie fr.json, le script tourne et crée un commit avec les traductions mises à jour.
  3. Vérification manuelle : je parcours les diff de traduction dans la PR pour détecter les erreurs évidentes.

6 langues maintenues en quasi-temps réel pour quelques centimes par passe. Le rapport effort/impact est imbattable.

Quel impact concret sur l'acquisition utilisateur ?

Les chiffres parlent d'eux-mêmes. Après l'internationalisation :

  • Surface de découverte x6 : la fiche Play Store est indexée dans 6 langues, ce qui multiplie les requêtes de recherche couvertes.
  • Taux de conversion store : un utilisateur qui voit une description dans sa langue a 2 à 3 fois plus de chances d'installer.
  • Rétention : l'expérience in-app dans la langue maternelle réduit drastiquement le churn des 7 premiers jours.
  • SEO du site web : le site tamsiv.com est disponible en 6 langues avec des URLs localisées (/fr/, /en/, /de/, etc.), ce qui booste le référencement international.

Pour un dev solo, c'est le levier d'acquisition au meilleur ratio coût/efficacité. Pas de budget pub, pas de growth hacking obscur — juste de la traduction automatisée intelligente.

Comment appliquer cette approche à ton propre projet ?

Si tu développes une app et que tu hésites à internationaliser, voici mon conseil : fais-le tôt. Plus tu attends, plus il y a de strings à extraire. Voici les étapes clés :

  1. Structure tes clés dès le départ avec une convention ecran.section.element.
  2. Choisis tes langues cibles en fonction de ton marché. Pour l'Europe, FR/EN/DE/ES/IT/PT couvrent la majorité.
  3. Automatise la traduction avec un LLM via API (OpenRouter, OpenAI, etc.). Le coût est négligeable.
  4. Intègre dans ta CI pour que la synchro soit automatique à chaque push.
  5. Teste avec des vrais utilisateurs natifs si possible — même un rapide coup d'œil vaut mieux que rien.

Le parcours build in public de TAMSIV montre que même en solo, on peut atteindre un niveau de localisation comparable aux grosses équipes.

FAQ

Combien de temps prend l'internationalisation d'une app React Native ?

Pour TAMSIV, l'extraction initiale des 1993 clés a pris environ 3 jours de travail concentré. La mise en place du script de traduction automatique, une demi-journée. Les passes suivantes prennent quelques minutes chacune grâce à l'automatisation. Le gros du travail est dans l'extraction — pas dans la traduction.

OpenRouter est-il fiable pour la traduction automatique ?

Oui, avec les bonnes précautions. Le system prompt doit inclure le contexte applicatif, des exemples de traductions validées, et des instructions sur le ton. Gemini 2.5 Flash via OpenRouter produit des traductions de qualité supérieure à Google Translate pour les strings d'interface, parce qu'il comprend le contexte. Le fallback entre modèles garantit la disponibilité.

Faut-il traduire les contenus générés par l'utilisateur ?

Non. Les tâches, mémos et événements restés dans la langue de l'utilisateur. Seule l'interface (boutons, labels, messages système, notifications) est traduite. Traduire le contenu utilisateur serait à la fois coûteux et source de confusion.

Comment gérer les langues avec des alphabets différents (arabe, japonais) ?

TAMSIV se concentre sur les langues latines pour le moment. Les langues RTL (arabe, hébreu) ou les idéogrammes (chinois, japonais) nécessitent des adaptations de layout supplémentaires (direction du texte, polices, espacement). C'est prévu pour une phase ultérieure, mais chaque alphabet est un chantier à part entière.

Le script de traduction peut-il être réutilisé sur un autre projet ?

Absolument. Le script est générique : il prend un fichier JSON source, une liste de langues cibles, et un endpoint LLM. Il suffit d'adapter le system prompt au contexte de ton application. Le pattern delta-only (ne traduire que les nouvelles clés) fonctionne pour n'importe quel projet utilisant des fichiers JSON de traduction.