i18next est le choix par défaut pour l’internationalisation de React, et il est suffisamment flexible pour vous laisser la plupart des décisions. Ce guide couvre une configuration qui tient à l’échelle.
Installation et initialisation
npm install i18next react-i18next i18next-browser-languagedetector
// src/i18n.js
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import en from "./locales/en/common.json";
import fr from "./locales/fr/common.json";
i18next
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { common: en },
fr: { common: fr },
},
defaultNS: "common",
fallbackLng: "en",
interpolation: { escapeValue: false }, // React échappe déjà les valeurs
returnEmptyString: false,
});
export default i18next;
Importez-le une seule fois, au point d’entrée de l’application, avant tout rendu.
Deux réglages à bien comprendre. escapeValue: false est correct pour React — React échappe lui-même les valeurs interpolées, et laisser l’échappement d’i18next activé double-échappe les apostrophes en '. Et returnEmptyString: false fait qu’une traduction vide se replie sur le fallback plutôt que de n’afficher rien, ce qui est ce que vous voulez quand un traducteur n’a pas encore atteint une clé.
Lire les traductions
import { useTranslation } from "react-i18next";
function Dashboard() {
const { t } = useTranslation();
return (
<>
<h1>{t("dashboard.title")}</h1>
<p>{t("dashboard.welcome", { name: user.firstName })}</p>
</>
);
}
Avec le JSON :
{
"dashboard": {
"title": "Dashboard",
"welcome": "Welcome back, {{name}}"
}
}
Notez que la syntaxe d’interpolation est {{name}} — double accolades, façon Mustache, distincte des accolades simples qu’utilisent ICU et FormatJS. Mélanger les deux est une source fréquente de chaînes qui s’affichent littéralement.
Pluriels
i18next sélectionne une forme de pluriel par suffixe de clé, piloté par l’option count :
{
"messages_one": "1 message",
"messages_other": "{{count}} messages"
}
t("messages", { count: 5 }); // => "5 messages"
Les suffixes sont les noms de catégorie CLDR à partir de la v4 — _zero, _one, _two, _few, _many, _other. Les versions plus anciennes utilisaient des suffixes numériques (_0, _1, _2), et c’est la première chose à vérifier en suivant un tutoriel ancien.
La conséquence importante : l’ensemble des suffixes dépend de la langue. Un fichier polonais a besoin de messages_one, messages_few, messages_many, messages_other. Générer les fichiers cibles en copiant le jeu de clés anglais produit des fichiers auxquels il manque des formes pour la plupart des nombres. Voir les règles de pluriel par langue.
Il existe aussi une prise en charge des ordinaux via { count: 3, ordinal: true }, qui utilise des suffixes _ordinal_ et son propre jeu de catégories CLDR.
Phrases contenant du balisage
C’est le cas qui échoue le plus souvent. Vous avez besoin de :
Lisez nos conditions d’utilisation avant de continuer.
Le réflexe est de découper la phrase en trois chaînes et de les concaténer. Cela produit une chaîne intraduisible, car l’ordre des fragments diffère selon les langues.
Trans résout le problème :
import { Trans } from "react-i18next";
<Trans i18nKey="terms">
Read our <a href="/tos">terms of service</a> before continuing.
</Trans>
{
"terms": "Read our <1>terms of service</1> before continuing."
}
Le traducteur déplace <1>…</1> où la grammaire cible le place. Le href reste dans le code, si bien qu’un traducteur ne peut pas casser le lien, et il n’y a aucun HTML à malmener.
Utilisez Trans chaque fois qu’une phrase traduisible contient un lien, un passage en gras ou un composant imbriqué. Ne l’utilisez pas pour des paragraphes entiers de balisage — à ce stade, le contenu relève d’un CMS plutôt que d’un fichier de traduction.
Namespaces
Les namespaces découpent les traductions en fichiers chargeables séparément :
const { t } = useTranslation("checkout");
t("payment.declined"); // lit depuis checkout.json
Combinés à un plugin backend, ils se chargent à la demande :
npm install i18next-http-backend
i18next.use(HttpBackend).init({
backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" },
ns: ["common"],
defaultNS: "common",
});
Maintenant common.json part avec le bundle initial et checkout.json est récupéré quand la route checkout est montée. Pour une application d’une taille conséquente, c’est la différence entre une charge initiale raisonnable et l’envoi de chaque chaîne dans chaque langue dès le départ.
Découpez les namespaces selon les mêmes lignes que votre découpage de code au niveau des routes. Un namespace par domaine fonctionnel est généralement le bon choix ; un namespace par composant est trop granulaire et produit une cascade de requêtes.
Suspense et chargement
Avec un backend, les traductions arrivent de façon asynchrone. react-i18next s’intègre à Suspense par défaut :
<Suspense fallback={<Spinner />}>
<App />
</Suspense>
Si vous préférez gérer cela manuellement, réglez useSuspense: false dans les options react et vérifiez le flag ready depuis useTranslation. Choisissez-en un — un arbre de composants où certaines parties suspendent et d’autres vérifient ready est difficile à raisonner.
Formater les nombres et les dates
i18next délègue à Intl :
{
"total": "Total: {{amount, currency(EUR)}}",
"updated": "Updated {{date, datetime}}"
}
Ne formatez pas les nombres ou les dates en JavaScript pour interpoler le résultat sous forme de chaîne. Le formatage dépend de la locale, et pré-formater fige les conventions de la mauvaise locale — voir qu’est-ce qu’une locale pour ce qui varie réellement.
Pour les messages où la structure dépend de la valeur — sélection du genre, pluriels imbriqués — envisagez le plugin ICU i18next-icu, qui vous donne le ICU MessageFormat complet plutôt que la syntaxe plus simple propre à i18next.
Garder les fichiers synchronisés
Extrayez les clés automatiquement plutôt que de maintenir le JSON à la main. i18next-parser scanne le code source à la recherche des appels à t() et des composants Trans et écrit le squelette de clés :
npx i18next-parser 'src/**/*.{js,jsx,ts,tsx}' -o src/locales/$LOCALE/$NAMESPACE.json
Puis synchronisez avec votre plateforme de traduction depuis la CI :
wti push # envoie les fichiers source
wti pull # récupère les traductions
wti diff # ce qu’un push changerait
Poussez à chaque merge sur votre branche désignée, récupérez chaque nuit dans une pull request. Voir les flux de travail de localisation basés sur Git pour comprendre pourquoi une seule branche doit pousser.
Sécurité de typage
Par défaut, t("anything.at.all") compile, et une faute de frappe devient une chaîne qui affiche la clé elle-même. TypeScript peut combler cette faille en dérivant l’union de clés à partir de vos resources anglaises :
// src/@types/i18next.d.ts
import "i18next";
import common from "../locales/en/common.json";
import checkout from "../locales/en/checkout.json";
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "common";
resources: {
common: typeof common;
checkout: typeof checkout;
};
}
}
Maintenant, une clé mal saisie est une erreur de compilation, l’autocomplétion liste les clés disponibles, et supprimer une clé du JSON révèle chaque site d’appel qui l’utilisait. Cela coûte une dizaine de lignes et élimine toute une classe de bugs — cela vaut le coup de le faire dès le premier jour plutôt qu’après le premier incident.
Cela signifie aussi que votre JSON anglais est le schéma, ce qui est la bonne relation : la langue source définit ce qui existe, et les autres langues en sont des traductions.
Tests
L’objectif n’est pas de faire des assertions sur le texte traduit — cela rend les tests fragiles et duplique le fichier de locale. C’est de prouver que le câblage fonctionne.
Initialisez une instance de test qui renvoie les clés telles quelles :
i18next.use(initReactI18next).init({
lng: "cimode", // t() renvoie la clé elle-même
resources: {},
});
cimode fait que chaque lookup renvoie sa clé, si bien qu’un test de composant peut faire une assertion sur dashboard.title sans dépendre du texte. Combinez cela avec une vérification séparée que chaque clé référencée dans le code existe dans le fichier anglais, et vous couvrez les deux moitiés.
L’autre test qui vaut le coup d’être écrit est une vérification d’exhaustivité entre les locales : chaque clé de en présente dans chaque autre langue, et chaque clé de pluriel portant le bon jeu de suffixes pour cette langue. Faites-le tourner en CI et une locale à moitié traduite fait échouer le build plutôt que d’être livrée.
Erreurs courantes
escapeValue: truedans React, produisant'à la place des apostrophes.- Concaténer des fragments de phrase au lieu d’utiliser
Trans. - Copier le jeu de clés de pluriel anglais dans chaque langue cible.
- Interpoler des nombres et des dates pré-formatés sous forme de chaînes.
- Des clés qui sont des phrases anglaises.
t("Welcome back")semble pratique jusqu’à ce que le texte change et que chaque clé devienne obsolète. - Un seul namespace géant livré dans chaque langue au premier chargement.
Frequently asked questions
- Comment configurer i18next dans React ?
- Installez i18next et react-i18next, créez un module i18n qui appelle i18next.use(initReactI18next).init() avec vos resources et une langue de repli, importez-le une seule fois au point d’entrée de l’application, et récupérez les traductions avec le hook useTranslation.
- Comment i18next gère-t-il les pluriels ?
- Par suffixe de clé. À partir de la v4, les suffixes sont les noms de catégorie CLDR — key_one, key_few, key_many, key_other — sélectionnés à partir de l’option count que vous passez. Les versions antérieures utilisaient des suffixes numériques, ce qui est la première chose à vérifier en lisant d’anciens exemples.
- À quoi sert le composant Trans ?
- À traduire une chaîne qui contient du balisage ou des composants React imbriqués, comme une phrase avec un lien au milieu. Il permet au traducteur de déplacer la section balisée dans la phrase sans que la traduction ne contienne du HTML brut.
- Faut-il utiliser des namespaces avec i18next ?
- Une fois l’application au-delà de quelques centaines de clés, oui. Les namespaces découpent les traductions en fichiers chargeables séparément, ce qui garde le bundle initial léger et donne une unité naturelle pour le découpage de code. En dessous de cette taille, un seul namespace est plus simple.
Keep reading
-
Internationalisation Next.js
Internationaliser une application Next.js App Router : routage par segment de locale, traduction serveur et client, metadata, hreflang et synchronisation des fichiers de messages.
-
ICU MessageFormat : un guide pratique
La syntaxe des pluriels, de la sélection par genre, du formatage des nombres et des dates dans une seule chaîne de message — avec les règles d’échappement et les erreurs que font systématiquement les traducteurs.
-
Aide-mémoire des formats de placeholders : %s, %@, %{name}, {{name}}, {0}
Toutes les syntaxes de placeholder que vous rencontrerez, quel langage ou framework les utilise, et ce qui casse quand un traducteur en retape une à la main.
-
Traduire des fichiers i18next (documentation)
Comment WebTranslateIt analyse le JSON i18next, y compris les suffixes de pluriel et l’imbrication.
Translate your app without the spreadsheet round-trip
WebTranslateIt reads the file formats and placeholder syntax described on this page, validates them as translators work, and syncs the results straight back into your repository.