Blog
Feature
14 février 20269 min

i18n en solo : 1993 cles, 6 langues, 9 passes LLM

Points cles a retenir : Internationaliser une app mobile avec 1993 cles de traduction dans 6 langues, c'est faisable en solo grace a l'automatisation LLM. Le script npm run translate detecte le delta, traduit uniquement les nouvelles cles via OpenRouter, et maintient la synchro en quasi-temps reel pour quelques centimes par passe. Resultat : un canal d'acquisition multiplie par 6 sans effort humain continu.

TAMSIV etait ne en francais. Chaque string, chaque message d'erreur, chaque placeholder — tout etait code en dur dans le JSX. Quand j'ai decide de supporter 6 langues (FR, EN, DE, ES, IT, PT), je ne me doutais pas que ca representerait 1993 cles de traduction reparties dans 35 fichiers. Et encore moins que j'aurais besoin de 9 passes successives pour arriver a un resultat stable.

Voici le recit complet de cette aventure i18n, du premier t('key') au pipeline automatise qui tourne aujourd'hui.

Espace de travail developpeur avec plusieurs ecrans affichant des fichiers de traduction dans differentes langues
L'internationalisation, c'est beaucoup d'ecrans et beaucoup de fichiers JSON ouverts en parallele.

Pourquoi internationaliser une app indie en solo ?

La reponse courte : l'acquisition utilisateur. Une app disponible uniquement en francais se prive de 95% du marche mondial. Meme en Europe, limiter son audience au francais revient a ignorer l'Allemagne (83M d'habitants), l'Espagne (47M), l'Italie (60M), le Portugal (10M) et bien sur l'ensemble du monde anglophone.

Mais au-dela du marche, il y a une raison technique : les stores (Google Play, App Store) indexent les metadonnees dans chaque langue. Une fiche traduite en 6 langues, c'est 6 fois plus de surface de decouverte organique. C'est un levier recommande par Google lui-meme.

Pour un dev solo comme moi, c'est aussi un avantage competitif face aux grosses apps qui negligent la localisation au-dela de l'anglais. Si un utilisateur allemand cherche "Aufgaben-App mit Spracheingabe" et que TAMSIV est la seule a avoir une fiche en allemand, c'est gagne.

Comment extraire 1993 cles de traduction sans perdre la raison ?

Premiere etape : remplacer chaque string francaise hardcodee par un appel t('key'). Dans 35 fichiers. A la main. Il n'existe pas de raccourci fiable pour cette etape — chaque string a un contexte, et le nommage de la cle doit refleter ce contexte.

J'ai structure les cles par ecran 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 selecteur de langue dans les parametres
  • dictaphone.recording.title — le titre de l'ecran 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 cles du type button_text_3 qui ne veulent rien dire trois mois plus tard. J'ai suivi les recommandations de i18next sur le nommage hierarchique.

Conseil pratique : j'ai commence par les ecrans les plus visites (Dictaphone, Feed, Agenda) avant de m'attaquer aux parametres et aux ecrans secondaires. Ca permet de tester la traduction sur les parcours principaux rapidement.

Quel outil de traduction automatique choisir quand on est seul ?

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

J'ai ecrit un script npm run translate qui envoie les cles francaises a OpenRouter (Gemini 2.5 Flash en modele principal). Le LLM comprend le contexte applicatif et produit des traductions naturelles :

  • "Memo vocal" → "Voice memo" (EN), "Sprachnotiz" (DE), "Nota de voz" (ES)
  • "Ajouter une tache" → "Add a task", "Aufgabe hinzufugen", "Agregar una tarea"
  • "Tout voir" → "See all", "Alle anzeigen", "Ver todo"

Le script envoie les cles par batch de 50, avec le contexte de l'application en system prompt : "Tu traduis les strings d'une application mobile de gestion de taches par la voix avec IA." Ce contexte fait toute la difference en qualite.

Carte du monde avec des epingles colorees marquant les pays couverts par la traduction
Six langues, six marches europeens : la traduction comme strategie d'acquisition.

Pourquoi 9 passes et pas une seule ?

La premiere passe a traduit le gros du corpus — environ 1600 cles. Mais une app vivante evolue. Chaque nouvelle feature ajoute des cles :

  • Passe 2 : gamification — 47 nouvelles cles (niveaux, badges, streaks)
  • Passe 3 : agenda — 62 cles (evenements, filtres, recurrence)
  • Passe 4 : groupes collaboratifs — 89 cles (hierarchie, permissions, assignations)
  • Passe 5-7 : corrections contextuelles et ajustements apres retours utilisateurs
  • Passe 8 : onboarding et ecrans de premiere utilisation
  • Passe 9 : site web (tamsiv.com) — un deuxieme corpus de traduction separe

Le script detecte les cles manquantes en comparant fr.json (la reference) avec chaque fichier cible. Il ne retraduit que le delta — les cles nouvelles ou modifiees. Pas de gaspillage de tokens, pas de risque d'ecraser une traduction deja validee.

Comment fonctionne le pipeline de traduction concretement ?

Le pipeline complet suit 4 etapes :

  1. Diff : le script compare fr.json avec en.json, de.json, es.json, it.json, pt.json. Chaque cle presente en FR mais absente dans une cible est marquee comme "a traduire".
  2. Batch : les cles a traduire sont regroupees par paquets de 50 (pour rester dans les limites de tokens du LLM).
  3. Traduction : chaque batch est envoye a OpenRouter avec un prompt structure. Le LLM retourne un JSON valide avec les traductions.
  4. Merge : les traductions sont fusionnees dans les fichiers cibles, en preservant l'ordre alphabetique et la structure existante.

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

Comment gerer la detection automatique de langue au premier lancement ?

Au premier lancement, TAMSIV detecte la langue du systeme via getLocales() de React Native. Si la langue est supportee (FR, EN, DE, ES, IT, PT), on l'utilise directement. Sinon, fallback sur l'anglais.

Le choix est ensuite sauvegarde en base (Supabase) dans le profil utilisateur. Ca permet de retrouver la preference meme en changeant de telephone. Et ca evite le probleme classique : l'utilisateur change la langue de son OS pour une raison X, et toutes ses apps switchent sans prevenir.

J'ai aussi ajoute un selecteur de langue dans les parametres, pour que l'utilisateur puisse choisir independamment de la langue du systeme. C'est un detail, mais ca fait une difference pour les bilingues ou les expats.

Quels sont les pieges de la traduction automatique par LLM ?

La traduction par LLM n'est pas parfaite. Voici les pieges que j'ai rencontres :

  • Les faux amis : "Classeur" traduit en "Binder" au lieu de "Folder" — le LLM manquait de contexte applicatif au debut.
  • Le genre grammatical : en allemand, "die Aufgabe" (feminin) vs "der Memo" (masculin) — les articles doivent correspondre.
  • Les pluriels : certaines langues ont des regles 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 francais. "Einstellungen" vs "Parametres". Ca casse les layouts si le design UI n'est pas flexible.
  • Les caracteres speciaux : les guillemets varient selon les langues (" " en anglais, « » en francais, „ “ en allemand).

La solution : un system prompt detaille qui precise le domaine applicatif, des exemples de traductions validees en few-shot, et une passe de relecture automatique ou le LLM verifie la coherence de ses propres traductions.

Chaine de production automatisee symbolisant le pipeline de traduction continue
L'automatisation transforme un processus de traduction penible en pipeline quasi-instantane.

Comment maintenir la synchronisation sur la duree ?

Le plus dur n'est pas la traduction initiale — c'est la maintenance. Chaque modification en francais doit etre propagee. Si je change "Ajouter une tache" en "Creer une tache", les 5 autres langues doivent suivre.

Mon systeme de synchronisation :

  1. Detection du changement : un hash SHA256 de chaque valeur FR est stocke. Si le hash change, la cle est marquee pour retraduction.
  2. CI automatique : via GitHub Actions, a chaque push sur main qui modifie fr.json, le script tourne et cree un commit avec les traductions mises a jour.
  3. Verification manuelle : je parcours les diff de traduction dans la PR pour detecter les erreurs evidentes.

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

Quel impact concret sur l'acquisition utilisateur ?

Les chiffres parlent d'eux-memes. Apres l'internationalisation :

  • Surface de decouverte x6 : la fiche Play Store est indexee dans 6 langues, ce qui multiplie les requetes de recherche couvertes.
  • Taux de conversion store : un utilisateur qui voit une description dans sa langue a 2 a 3 fois plus de chances d'installer.
  • Retention : l'experience in-app dans la langue maternelle reduit drastiquement le churn des 7 premiers jours.
  • SEO du site web : le site tamsiv.com est disponible en 6 langues avec des URLs localisees (/fr/, /en/, /de/, etc.), ce qui booste le referencement international.

Pour un dev solo, c'est le levier d'acquisition au meilleur ratio cout/efficacite. Pas de budget pub, pas de growth hacking obscur — juste de la traduction automatisee intelligente.

Comment appliquer cette approche a ton propre projet ?

Si tu developpes une app et que tu hesites a internationaliser, voici mon conseil : fais-le tot. Plus tu attends, plus il y a de strings a extraire. Voici les etapes cles :

  1. Structure tes cles des le depart avec une convention ecran.section.element.
  2. Choisis tes langues cibles en fonction de ton marche. Pour l'Europe, FR/EN/DE/ES/IT/PT couvrent la majorite.
  3. Automatise la traduction avec un LLM via API (OpenRouter, OpenAI, etc.). Le cout est negligeable.
  4. Integre dans ta CI pour que la synchro soit automatique a chaque push.
  5. Teste avec des vrais utilisateurs natifs si possible — meme un rapide coup d'oeil vaut mieux que rien.

Le parcours build in public de TAMSIV montre que meme en solo, on peut atteindre un niveau de localisation comparable aux grosses equipes.

FAQ

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

Pour TAMSIV, l'extraction initiale des 1993 cles a pris environ 3 jours de travail concentre. La mise en place du script de traduction automatique, une demi-journee. Les passes suivantes prennent quelques minutes chacune grace a 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 precautions. Le system prompt doit inclure le contexte applicatif, des exemples de traductions validees, et des instructions sur le ton. Gemini 2.5 Flash via OpenRouter produit des traductions de qualite superieure a Google Translate pour les strings d'interface, parce qu'il comprend le contexte. Le fallback entre modeles garantit la disponibilite.

Faut-il traduire les contenus generes par l'utilisateur ?

Non. Les taches, memos et evenements restes dans la langue de l'utilisateur. Seule l'interface (boutons, labels, messages systeme, notifications) est traduite. Traduire le contenu utilisateur serait a la fois couteux et source de confusion.

Comment gerer les langues avec des alphabets differents (arabe, japonais) ?

TAMSIV se concentre sur les langues latines pour le moment. Les langues RTL (arabe, hebreu) ou les ideogrammes (chinois, japonais) necessitent des adaptations de layout supplementaires (direction du texte, polices, espacement). C'est prevu pour une phase ulterieure, mais chaque alphabet est un chantier a part entiere.

Le script de traduction peut-il etre reutilise sur un autre projet ?

Absolument. Le script est generique : 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 cles) fonctionne pour n'importe quel projet utilisant des fichiers JSON de traduction.