Aller au contenu
Sonenta
i18nmobile

L'i18n dans les applications natives : mobile, React Native et Expo

Internationaliser une app mobile native : détection de locale, formats régionaux, pluriels, hors-ligne, et surtout mettre à jour les traductions sans repasser par le store.

Par Sonenta 6 min de lecture

Sur le web, corriger une faute de traduction prend deux minutes : on modifie un fichier, on redéploie, c’est en ligne. Sur mobile, la même correction peut attendre une semaine — le temps qu’une nouvelle version passe la revue de l’App Store. Cette seule différence change toute la façon d’aborder l’internationalisation d’une application native.

Si vous débutez sur le sujet, commencez par notre guide Qu’est-ce que l’i18n ? : clés, namespaces, pluriels et fallback s’y appliquent aussi. Cet article se concentre sur ce qui est spécifique au natif — React Native, Expo, iOS et Android.

Ce qui change par rapport au web

Trois contraintes propres au mobile redéfinissent les règles du jeu.

  • Le bundle. Une app embarque ses ressources au moment du build. Si vos traductions sont figées dans le bundle, ajouter ou corriger une langue impose de reconstruire et de redéployer l’application.
  • La revue du store. Apple et Google relisent chaque soumission. Une coquille livrée en production peut donc rester visible plusieurs jours avant le correctif.
  • Le hors-ligne. Un navigateur est presque toujours connecté ; une app mobile, non. Vos traductions doivent rester disponibles dans le métro comme en avion.

La conséquence est claire : sur mobile, la question n’est pas seulement « comment traduire ? » mais « comment livrer une traduction ? ».

Détecter la langue de l’appareil

Première étape : savoir dans quelle langue afficher l’interface. Sur React Native et Expo, la locale du système se lit en une ligne, puis on la passe au moteur i18n — ici react-i18next, le même que sur le web :

import * as Localization from "expo-localization";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";

i18n.use(initReactI18next).init({
  lng: Localization.getLocales()[0]?.languageCode ?? "fr",
  fallbackLng: "fr",
  resources: { /* … */ },
});

Deux règles d’or. D’abord, toujours prévoir un fallbackLng : un appareil peut être réglé sur une langue que vous ne supportez pas encore. Ensuite, laissez l’utilisateur surcharger ce choix : beaucoup de gens utilisent un téléphone dans une langue mais préfèrent vos contenus dans une autre. Stockez cette préférence localement et relisez-la au démarrage.

Pensez aussi à séparer langue et région. Un appareil peut être en français du Canada (fr-CA) ou de France (fr-FR) : même langue d’interface, mais devise, format de date et même vocabulaire diffèrent. expo-localization expose les deux (languageCode et regionCode) — utilisez la langue pour les textes et la région pour les formats. Enfin, lisez le sens d’écriture du système (Localization.isRTL) pour basculer la mise en page en miroir sur l’arabe ou l’hébreu, plutôt que de le déduire vous-même.

Les formats régionaux

Dates, nombres, devises : comme sur le web, on délègue à l’API standard Intl plutôt qu’à du code maison. Bonne nouvelle, le moteur JavaScript de React Native (Hermes) embarque désormais Intl :

new Intl.NumberFormat("fr-CA", { style: "currency", currency: "CAD" })
  .format(1299.9); // « 1 299,90 $ »

Vérifiez tout de même que Intl est compilé avec les données complètes dans votre configuration Hermes : sur certaines versions, le support des locales est réduit par défaut pour alléger le binaire. C’est un piège typiquement mobile, invisible sur le web.

Pluriels et genres par langue

Les règles de pluriel sont universelles, mais leur exécution dépend du moteur. Le standard ICU MessageFormat reste la bonne réponse : une clé décrit toutes les formes, le moteur choisit selon la langue.

{count, plural, one {# message non lu} other {# messages non lus}}

Sur React Native, assurez-vous simplement que votre bibliothèque i18n applique les règles CLDR de la langue cible et non une logique anglaise codée en dur — sinon le russe, l’arabe ou le polonais s’afficheront mal.

Le vrai défi : mettre à jour les traductions sans repasser par le store

C’est ici que le natif se distingue le plus. Deux approches coexistent.

Traductions embarquées (in-bundle). Les fichiers de langue vivent dans le bundle. Simple, robuste hors-ligne — mais chaque correction de texte exige une nouvelle version et une nouvelle revue du store. Inacceptable pour itérer vite.

Traductions distantes. Les clés sont servies depuis un CDN et récupérées par l’app à l’exécution. Vous publiez une correction côté serveur ; l’application la récupère à son prochain lancement ou rafraîchissement, sans aucune re-soumission au store. C’est l’équivalent mobile du « déployer = pousser un fichier » du web.

// Au démarrage : tenter le CDN, retomber sur le bundle en cas d'échec
const remote = await fetch(CDN_URL).then((r) => r.json()).catch(() => null);
i18n.addResourceBundle("fr", "common", remote ?? bundledFr, true, true);

Une nuance importante, pour rester honnête : récupérer depuis un CDN n’est pas du « live » instantané en production. L’app applique les nouveaux textes au prochain cycle de chargement, pas pendant que l’écran est affiché. C’est largement suffisant pour corriger sans attendre le store — mais ne promettez pas à vos équipes un changement visible à la seconde sur l’appareil d’un utilisateur.

À distinguer aussi des mises à jour OTA de code (comme expo-updates), qui poussent du JavaScript hors store : utiles, mais plus lourdes. Pour du texte, distribuer les seules traductions par CDN est bien plus léger et granulaire.

Gérer le hors-ligne

Le distant ne doit jamais casser l’expérience sans réseau. Le schéma robuste combine les deux approches :

  1. Embarquez une base de traductions dans le bundle — l’app fonctionne dès la première seconde, même hors-ligne.
  2. Rafraîchissez depuis le CDN au lancement quand le réseau est là.
  3. Mettez en cache le dernier bundle reçu pour les prochains démarrages hors ligne.

Ainsi l’utilisateur a toujours quelque chose à lire, et la version la plus à jour dès qu’une connexion est disponible.

Tester chaque langue sur l’appareil

Une traduction correcte sur le papier peut casser l’interface une fois rendue. L’allemand déborde d’un bouton, l’arabe inverse une rangée d’icônes, une chaîne oubliée reste en anglais. Sur mobile, ces défauts ne se voient que sur un écran réel.

  • Pseudo-localisation. Remplacez temporairement chaque texte par une version allongée et accentuée (« [Ajouter au panier] » → « [Ădd• àu • pănĭér…] »). Les textes en dur sautent aux yeux (ils restent en clair) et les débordements apparaissent avant la première vraie traduction.
  • Captures par locale. Automatisez une capture d’écran des écrans clés dans chaque langue à chaque build. C’est le filet le plus efficace contre les régressions visuelles, et c’est aussi ce qui alimente les fiches du store.
  • Un appareil RTL réel. Émuler l’arabe en simulateur ne révèle pas tout : testez au moins un écran sur un téléphone réglé de droite à gauche.

Ne pas oublier la fiche du store

L’internationalisation s’arrête rarement à l’application elle-même. La fiche de l’App Store et du Play Store — titre, description, captures, mots-clés — se localise séparément, dans la console de chaque plateforme. C’est souvent ce qui décide qu’un utilisateur installe ou non votre app : une fiche en anglais sur un store japonais convertit mal, même si l’app est parfaitement traduite à l’intérieur. Traitez ces métadonnées comme un marché à part entière, avec les mêmes traductions de référence que le produit.

Web vs natif : le récapitulatif

AspectWebNatif / mobile
Livrer une correctionRedéploiement, immédiatStore (lent) ou CDN au prochain lancement
Hors-ligneRareÀ prévoir par défaut
Détection de langueEn-tête du navigateurLocale de l’appareil + préférence user
Intl / formatsNatif au navigateurDépend du moteur (vérifier Hermes)
Contrainte de tailleFaibleLe bundle compte

Le vrai sujet, c’est la livraison

Le natif ajoute une dimension que le web ignore : la logistique. Une base embarquée pour démarrer, un CDN pour rester frais, un cache pour survivre au tunnel du métro — c’est ce trio qui concilie itération rapide et robustesse. Et c’est rarement ce qu’on anticipe le premier jour.

C’est le terrain de Sonenta : @sonenta/react-i18next tourne tel quel dans React Native, vos clés arrivent par CDN (donc corrigeables sans repasser par le store), et l’édition in-context va jusque sur l’appareil. Toute la mécanique de cette livraison — versions, releases, rollback — mérite son propre chapitre : c’est l’objet des stratégies de déploiement des traductions.