Localisation React – Internationalisation avec i18next
Lever la barrière de la langue pour les utilisateurs de votre logiciel est un sujet important. L'anglais n'est plus la langue universelle d'Internet. En mars 2020, seuls 25,9 % des internautes étaient anglophones. Il y a de fortes chances que vos utilisateurs quittent votre site s'il n'est pas localisé. Sans site multilingue, vous risquez donc de passer à côté d'une grande partie d'utilisateurs potentiels.
Dans l'écosystème JavaScript, il existe de nombreux frameworks d'internationalisation. Ici vous trouverez des détails sur plusieurs d'entre eux. Si vous souhaitez comparer les configurations des frameworks côte à côte, consultez notre guide de localisation JavaScript. Dans cet article, nous utiliserons le framework i18next pour internationaliser une application React. Ce guide pas à pas est pour vous si vous cherchez un moyen de créer une application React internationalisée (avec ou sans Create React App).
Cet article a été publié à l'origine en 2021 et mis à jour le 23 décembre 2025 pour refléter les changements dans l'écosystème i18next et React.
Sommaire
- Tout d'abord : «Pourquoi i18next ?»
- Quoi de neuf (depuis 2021) ?
- Au travail…
- 🎉🥳 Félicitations 🎊🎁
Simplifiez le processus de configuration d'i18next avec ce guide pas à pas. Mettez votre framework de localisation en route en un rien de temps et offrez à vos utilisateurs des changements de langue fluides. Suivez les instructions et évitez les complications de configuration pour votre prochain projet international.
Tout d'abord : «Pourquoi i18next ?»
Quand on parle de localisation React, i18next, avec son extension React react-i18next, est l'un des frameworks les plus populaires, et pour de bonnes raisons :
i18next a été créé fin 2011. Il est plus ancien que la plupart des bibliothèques que vous utilisez aujourd'hui, y compris votre technologie frontend principale (React, Angular, Vue, …).
➡️ pérenne
Comme i18next est disponible en open source depuis si longtemps, il n'existe pas de cas i18n qui ne puisse être résolu avec i18next.
➡️ mature
i18next peut être utilisé dans n'importe quel environnement JavaScript (et certains non-JavaScript : .NET, Elm, iOS, Android, Ruby, …), avec n'importe quel framework UI, n'importe quel format i18n, … les possibilités sont infinies.
➡️ extensible
Avec i18next, vous obtenez bien plus de fonctionnalités et de possibilités qu'avec les autres frameworks i18n classiques.
➡️ riche
Ici vous trouverez plus d'informations sur ce qui rend i18next spécial et sur comment il fonctionne.
Quoi de neuf (depuis 2021) ?
En 2025, i18next a introduit une nouvelle fonctionnalité puissante : la TypeScript selector API. Elle apporte un accès type-safe à vos traductions et permet une autocomplétion robuste. Plus de détails dans How to translate with i18next: typescript et Typesafe i18next with the new typescript selector API.
En 2024, i18next a introduit un nouveau plugin pour s'intégrer aux applications Express : i18next-http-middleware. Si vous travaillez avec React dans un environnement Express, cela peut être un ajout utile.
Besoin d'une gestion professionnelle des traductions ? Jetez un œil à How to translate with i18next: the CLI pour un workflow optimisé.
Intéressé par un workflow de localisation continue ? Consultez Modern continuous localization.
Au travail (react-i18next)…
Prérequis
Assurez-vous d'avoir Node.js et npm installés. Idéalement, vous avez un peu d'expérience avec du HTML simple, JavaScript et les bases de React.js avant de passer à react-i18next, le puissant framework i18n pour React. Cet exemple de localisation avec react-i18next n'est pas conçu comme un tutoriel d'initiation à React.
Premiers pas
Prenez votre propre projet React ou créez-en un nouveau, par exemple avec create-react-app.
npx create-react-app my-app
Nous allons adapter l'application pour qu'elle détecte la langue selon les préférences de l'utilisateur. Et nous créerons un sélecteur de langue pour faire basculer le contenu entre différentes langues.
Installons quelques dépendances i18next :
npm install i18next react-i18next i18next-browser-languagedetector
Préparons un fichier i18n.js :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
// here we will place our translations...
}
}
}
});
export default i18n;Importons ce fichier quelque part dans notre index.js :
Pour React >= 18.0.0 :
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
// import i18n (needs to be bundled ;))
import './i18n';
const root = createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);Pour les anciennes versions de React :
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// import i18n (needs to be bundled ;))
import './i18n';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);Déplaçons maintenant un peu de texte codé en dur dans les traductions.
Nous avons utilisé le composant Trans pour le premier texte et le hook useTranslation pour le second :
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
function App() {
const { t } = useTranslation();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
</div>
);
}
export default App;Si vous souhaitez utiliser les Higher-Order Components (HOC), il faut un peu plus de travail. Une autre option ici est le HOC withTranslation.
Les textes font désormais partie des ressources de traduction :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
}
}
}
}
});
export default i18n;Sélecteur de langue
Définissons maintenant un sélecteur de langue :
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
const lngs = {
en: { nativeName: 'English' },
de: { nativeName: 'Deutsch' }
};
function App() {
const { t, i18n } = useTranslation();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div>
{Object.keys(lngs).map((lng) => (
<button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => i18n.changeLanguage(lng)}>
{lngs[lng].nativeName}
</button>
))}
</div>
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
</div>
);
}
export default App;Et ajoutons quelques traductions pour la nouvelle langue :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
}
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
}
}
}
}
});
export default i18n;
🥳 Super, vous venez de créer votre premier sélecteur de langue !
Grâce à i18next-browser-languagedetector, l'application essaie maintenant de détecter la langue du navigateur et de l'utiliser automatiquement si vous avez fourni les traductions correspondantes. La langue sélectionnée manuellement dans le sélecteur est persistée dans localStorage ; lors de la prochaine visite, cette langue sera utilisée comme langue préférée.
Comment obtenir la langue courante ?
Depuis i18next v21, il existe i18next.resolvedLanguage.
Cette propriété est définie sur la langue actuellement résolue et peut être utilisée comme langue principale, par exemple dans un sélecteur de langue.
Si la langue détectée est par exemple en-US et que vous n'avez fourni les traductions que pour en (fallbackLng), i18next.resolvedLanguage renverra alors en.
i18next.language vs. i18next.languages vs. i18next.resolvedLanguage
/* language */
i18next.language;
// Is set to the current detected or set language.
/* languages */
i18next.languages;
// Is set to an array of language codes that will be used to look up the translation value.
// When the language is set, this array is populated with the new language codes.
// Unless overridden, this array is populated with less-specific versions of that code for fallback purposes, followed by the list of fallback languages
// initialize with fallback languages
i18next.init({
fallbackLng: ["es", "fr", "en-US", "dev"]
});
// change the language
i18next.changeLanguage("en-US-xx");
// new language and its more generic forms, followed by fallbacks
i18next.languages; // ["en-US-xx", "en-US", "en", "es", "fr", "dev"]
// change the language again
i18next.changeLanguage("de-DE");
// previous language is not retained
i18next.languages; // ["de-DE", "de", "es", "fr", "en-US", "dev"]
/* resolvedLanguage */
i18next.resolvedLanguage;
// Is set to the current resolved language.
// It can be used as primary used language,
// for example in a language switcher.Interpolation et pluriels
i18next va au-delà des fonctionnalités i18n standard. Mais bien sûr, il sait gérer les pluriels et l'interpolation.
Comptons chaque fois que la langue est changée :
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState } from 'react';
const lngs = {
en: { nativeName: 'English' },
de: { nativeName: 'Deutsch' }
};
function App() {
const { t, i18n } = useTranslation();
const [count, setCounter] = useState(0);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div>
{Object.keys(lngs).map((lng) => (
<button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
i18n.changeLanguage(lng);
setCounter(count + 1);
}}>
{lngs[lng].nativeName}
</button>
))}
</div>
<p>
<i>{t('counter', { count })}</i>
</p>
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
</div>
);
}
export default App;…et étendons les ressources de traduction :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
},
counter_one: 'Changed language just once',
counter_other: 'Changed language already {{count}} times'
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
},
counter_one: 'Die Sprache wurde erst ein mal gewechselt',
counter_other: 'Die Sprache wurde {{count}} mal gewechselt'
}
}
}
});
export default i18n;En fonction de la valeur de count, i18next choisira la forme plurielle correcte. Plus d'informations sur les pluriels et l'interpolation dans la documentation officielle d'i18next.
💡 i18next sait aussi gérer les langues avec plusieurs formes plurielles, par exemple l'arabe :
// translation resources:
{
"key_zero": "zero",
"key_one": "singular",
"key_two": "two",
"key_few": "few",
"key_many": "many",
"key_other": "other"
}
// usage:
t('key', {count: 0}); // -> "zero"
t('key', {count: 1}); // -> "singular"
t('key', {count: 2}); // -> "two"
t('key', {count: 3}); // -> "few"
t('key', {count: 4}); // -> "few"
t('key', {count: 5}); // -> "few"
t('key', {count: 11}); // -> "many"
t('key', {count: 99}); // -> "many"
t('key', {count: 100}); // -> "other"Pourquoi mes clés plurielles ne fonctionnent-elles pas ?
Voyez-vous cet avertissement dans la console de développement (debug: true) ?
i18next::pluralResolver: Your environment seems not to be Intl API compatible, use an Intl.PluralRules polyfill. Will fallback to the compatibilityJSON v3 format handling.
Avec la v21, i18next a aligné le suffixe sur celui utilisé par l'Intl API. Dans les environnements où l'API Intl.PluralRules n'est pas disponible (par exemple sur les anciens appareils Android), vous pourriez avoir besoin d'un polyfill pour l'API Intl.PluralRules. Si elle n'est pas disponible, on retombe sur la gestion des pluriels du format JSON v3 d'i18next. Et si votre JSON utilise déjà les nouveaux suffixes, vos clés plurielles ne s'afficheront probablement pas.
tl;dr :
npm install intl-pluralrules
import 'intl-pluralrules'Formatage
À partir d'i18next v21.3.0, des formatters intégrés sont disponibles pour formater nombres, dates, listes et plus encore via l'Intl API. Pour les détails et un usage avancé, consultez la documentation officielle sur le formatage d'i18next.
Voyons maintenant comment utiliser différents formats de date avec l'aide d'i18next et de Luxon pour la date et l'heure.
{
"key": "Some format {{value, formatname}}",
"keyWithOptions": "Some format {{value, formatname(option1Name: option1Value; option2Name: option2Value)}}"
}Vous pouvez passer des options sous forme de liste séparée par des points-virgules.
Utilisation en JavaScript :
// JSON
{
"intlNumber": "Some {{val, number}}",
"intlNumberWithOptions": "Some {{val, number(minimumFractionDigits: 2)}}"
}
i18next.t($ => $.intlNumber, { val: 1000 });
// --> Some 1,000
i18next.t($ => $.intlNumber, { val: 1000.1, minimumFractionDigits: 3 });
// --> Some 1,000.100
i18next.t($ => $.intlNumber, { val: 1000.1, formatParams: { val: { minimumFractionDigits: 3 } } });
// --> Some 1,000.100
i18next.t($ => $.intlNumberWithOptions, { val: 2000 });
// --> Some 2,000.00
i18next.t($ => $.intlNumberWithOptions, { val: 2000, minimumFractionDigits: 3 });
// --> Some 2,000.000Vous pouvez surcharger la langue en passant lng ou locale dans les options :
i18next.t($ => $.intlNumber, { val: 1000.1, lng: 'de' });
i18next.t($ => $.intlNumber, { val: 1000.1, formatParams: { val: { locale: 'de' } } });Ajouter des fonctions de format personnalisées :
// after i18next.init(options);
i18next.services.formatter.add('lowercase', (value, lng, options) => value.toLowerCase());
i18next.services.formatter.add('underscore', (value, lng, options) => value.replace(/\s+/g, '_'));Ajoutez vos propres fonctions de format après l'appel à i18next.init().
Formats intégrés :
- Number :
number - Currency :
currency - DateTime :
datetime - RelativeTime :
relativetime - List :
list
Plus d'exemples et d'options dans la documentation sur le formatage d'i18next.
Voyons maintenant comment gérer différents formats de date avec l'aide d'i18next et de Luxon.
npm install luxon
Nous voulons un footer affichant la date courante :
import './Footer.css';
const Footer = ({ t }) => (
<div className="Footer">
<div>{t('footer.date', { date: new Date() })}</div>
</div>
);
export default Footer;
// imported in our App.js and used like this
// <Footer t={t} />Maintenant importons Luxon et définissons une fonction de format (comme documenté dans la doc sur le formatage), puis ajoutons la nouvelle clé de traduction :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { DateTime } from 'luxon';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
// format: (value, format, lng) => { // legacy usage
// if (value instanceof Date) {
// return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime[format])
// }
// return value;
// }
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
},
counter_one: 'Changed language just once',
counter_other: 'Changed language already {{count}} times',
footer: {
date: 'Today is {{date, DATE_HUGE}}'
}
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
},
counter_one: 'Die Sprache wurde erst ein mal gewechselt',
counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
footer: {
date: 'Heute ist {{date, DATE_HUGE}}'
}
}
}
}
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;😎 Cool, maintenant nous avons un formatage de date spécifique à la langue !
Anglais :

Allemand :

Contexte
Et si on avait un message de salutation spécifique selon l'heure de la journée ? Par exemple, matin, soir, etc. C'est possible grâce à la fonctionnalité context d'i18next.
Créons une fonction getGreetingTime et utilisons le résultat comme information de contexte pour la traduction du footer :
import { DateTime } from 'luxon';
import './Footer.css';
const getGreetingTime = (d = DateTime.now()) => {
const split_afternoon = 12; // 24hr time to split the afternoon
const split_evening = 17; // 24hr time to split the evening
const currentHour = parseFloat(d.toFormat('hh'));
if (currentHour >= split_afternoon && currentHour <= split_evening) {
return 'afternoon';
} else if (currentHour >= split_evening) {
return 'evening';
}
return 'morning';
}
const Footer = ({ t }) => (
<div className="Footer">
<div>{t('footer.date', { date: new Date(), context: getGreetingTime() })}</div>
</div>
);
export default Footer;Et ajoutons quelques clés de traduction spécifiques au contexte :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';
i18n
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
resources: {
en: {
translation: {
description: {
part1: 'Edit <1>src/App.js</1> and save to reload.',
part2: 'Learn React'
},
counter_one: 'Changed language just once',
counter_other: 'Changed language already {{count}} times',
footer: {
date: 'Today is {{date, DATE_HUGE}}',
date_morning: 'Good morning! Today is {{date, DATE_HUGE}} | Have a nice day!',
date_afternoon: 'Good afternoon! It\'s {{date, DATE_HUGE}}',
date_evening: 'Good evening! Today was the {{date, DATE_HUGE}}'
}
}
},
de: {
translation: {
description: {
part1: 'Ändere <1>src/App.js</1> und speichere um neu zu laden.',
part2: 'Lerne React'
},
counter_one: 'Die Sprache wurde erst ein mal gewechselt',
counter_other: 'Die Sprache wurde {{count}} mal gewechselt',
footer: {
date: 'Heute ist {{date, DATE_HUGE}}',
date_morning: 'Guten Morgen! Heute ist {{date, DATE_HUGE}} | Wünsche einen schönen Tag!',
date_afternoon: 'Guten Tag! Es ist {{date, DATE_HUGE}}',
date_evening: 'Guten Abend! Heute war {{date, DATE_HUGE}}'
}
}
}
}
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;😁 Oui, ça marche !
Séparer les traductions du code
Avoir les traductions dans notre fichier i18n.js fonctionne, mais ce n'est pas idéal pour les traducteurs.
Séparons les traductions du code et plaçons-les dans des fichiers JSON dédiés.
S'agissant d'une application web, i18next-http-backend nous y aidera.
npm install i18next-http-backend
Déplaçons les traductions dans le dossier public :
Adaptons le fichier i18n.js pour utiliser i18next-http-backend :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { DateTime } from 'luxon';
i18n
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
}
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;Les traductions sont maintenant chargées de manière asynchrone : assurez-vous d'envelopper votre app dans un composant Suspense pour éviter l'erreur : Uncaught Error: App suspended while rendering, but no fallback UI was specified.
import { Suspense } from 'react';
function App() {
// your app's code...
}
// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
return (
<Suspense fallback="...is loading">
<App />
</Suspense>
);
}Maintenant votre app a la même apparence, mais vos traductions sont séparées. Si vous voulez prendre en charge une nouvelle langue, créez simplement un nouveau dossier et un nouveau fichier JSON de traduction. Cela vous donne la possibilité d'envoyer les traductions à des traducteurs. Ou, si vous travaillez avec un système de gestion des traductions, vous pouvez simplement synchroniser les fichiers via une CLI.
🧑💻 Le code de cette première partie se trouve ici.
Plusieurs namespaces
💡 au fait : vous pouvez aussi avoir plusieurs fichiers de traduction grâce à la fonctionnalité namespaces d'i18next.
L'un des avantages de react-i18next, hérité d'i18next, est la prise en charge de la séparation des traductions en plusieurs fichiers, appelés «namespaces» dans i18next.
Pour utiliser plusieurs namespaces / fichiers de traduction, vous devez le préciser lors de l'appel à useTranslation :
const { t } = useTranslation(['translation', 'common']);
// ...
// t('look.deep', { ns: 'common' })withTranslation(['translation', 'common'])(MyComponent);
// ...
// t('look.deep', { ns: 'common' })ou Translation :
<Translation ns={['translation', 'common']}>
{
(t) => <p>{t('look.deep', { ns: 'common' })}</p>
}
</Translation>Meilleure gestion des traductions
En envoyant les traductions à des traducteurs ou à une agence de traduction, vous gardez plus de contrôle et un contact direct. Mais cela signifie aussi plus de travail pour vous. C'est la méthode classique. Mais sachez que l'échange de fichiers crée toujours de la friction.
Existe-t-il une meilleure option ?
Bien sûr !
i18next aide à faire traduire l'application, et c'est génial, mais il y a plus à considérer.
- Comment intégrez-vous des services / agences de traduction ?
- Comment gardez-vous trace du contenu nouveau ou supprimé ?
- Comment gérez-vous correctement le versioning ?
- Comment déployez-vous des modifications de traduction sans redéployer l'application complète ?
- et bien d'autres choses encore…
Vous cherchez quelque chose comme ça ❓
- Déploiement continu ? Localisation continue !
- Gérez facilement les fichiers de traduction
- Analytique et statistiques
- et bien d'autres choses encore…
À quoi ça ressemble ?
Inscrivez-vous d'abord sur Locize et connectez-vous. Puis créez un nouveau projet dans Locize et ajoutez vos traductions. Vous pouvez les ajouter via la CLI, en important les fichiers JSON un par un ou via l'API.
Ensuite, remplaçons i18next-http-backend par i18next-locize-backend.
npm install i18next-locize-backend
Après avoir importé les traductions dans Locize, supprimez le dossier locales :
Adaptez le fichier i18n.js pour utiliser i18next-locize-backend, et veillez à copier le project-id et l'api-key depuis votre projet Locize :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';
const locizeOptions = {
projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
referenceLng: 'en',
};
i18n
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
backend: locizeOptions
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;i18next-locize-backend offre une fonctionnalité pour récupérer les langues disponibles directement depuis Locize. Utilisons-la :
import logo from './logo.svg';
import './App.css';
import { useTranslation, Trans } from 'react-i18next';
import { useState, Suspense, useEffect } from 'react';
import Footer from './Footer'
function App() {
const { t, i18n } = useTranslation();
const [count, setCounter] = useState(0);
const [lngs, setLngs] = useState({ en: { nativeName: 'English' }});
useEffect(() => {
i18n.services.backendConnector.backend.getLanguages((err, ret) => {
if (err) return // TODO: handle err...
setLngs(ret);
});
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div>
{Object.keys(lngs).map((lng) => (
<button key={lng} style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} type="submit" onClick={() => {
i18n.changeLanguage(lng);
setCounter(count + 1);
}}>
{lngs[lng].nativeName}
</button>
))}
</div>
<p>
<i>{t('counter', { count })}</i>
</p>
<p>
<Trans i18nKey="description.part1">
Edit <code>src/App.js</code> and save to reload.
</Trans>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{t('description.part2')}
</a>
</header>
<Footer t={t} />
</div>
);
}
// here app catches the suspense from page in case translations are not yet loaded
export default function WrappedApp() {
return (
<Suspense fallback="...is loading">
<App />
</Suspense>
);
}Sauvegarder les traductions manquantes
Grâce à la fonctionnalité saveMissing, les nouvelles clés sont ajoutées automatiquement à Locize pendant le développement de l'app.
Passez simplement saveMissing: true dans les options i18next :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import { DateTime } from 'luxon';
const locizeOptions = {
projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
referenceLng: 'en',
};
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
backend: locizeOptions,
saveMissing: true
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;Chaque fois que vous utilisez une nouvelle clé, elle est envoyée à Locize, par exemple :
<div>{t('new.key', 'this will be added automatically')}</div>donne dans Locize :
👀 Mais il y a plus…
Grâce au plugin locize-lastused, vous pouvez dans Locize détecter et filtrer quelles clés sont utilisées ou ne le sont plus.
Avec l'aide du plugin Locize, vous pouvez utiliser votre app dans l'éditeur InContext de Locize.
Enfin, avec le workflow de traduction automatique et la fonctionnalité saveMissing, les nouvelles clés sont non seulement ajoutées automatiquement à Locize pendant le développement de l'app, mais aussi traduites automatiquement dans les langues cibles via la traduction automatique.
Regardez cette vidéo pour voir à quoi ressemble le workflow de traduction automatique !
npm install locize-lastused locize
Utilisez-les dans i18n.js :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';
const locizeOptions = {
projectId: '0bbc223a-9aba-4a90-ab93-ab9d7bf7f780',
apiKey: 'aaad4141-54ba-4625-ae37-657538fe29e7', // YOU should not expose your apps API key to production!!!
referenceLng: 'en',
};
i18n
// locize-lastused
// sets a timestamp of last access on every translation segment on locize
// -> safely remove the ones not being touched for weeks/months
// https://github.com/locize/locize-lastused
.use(LastUsed)
// locize-editor
// InContext Editor of locize
.use(locizePlugin)
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
backend: locizeOptions,
locizeLastUsed: locizeOptions,
saveMissing: true
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;
Filtre des traductions par dernière utilisation :
📦 Prêt pour la production 🚀
Préparons maintenant l'app pour la mise en production.
Tout d'abord, dans Locize, créez une version dédiée à la production. N'activez pas l'auto-publish pour cette version ; publiez manuellement, via l'API ou via la CLI. Enfin, activez Cache-Control max-age pour cette version de production.
Utilisons la fonctionnalité environment de react-scripts.
Créons un fichier environment par défaut, ainsi qu'un pour le développement et un pour la production :
.env :
SKIP_PREFLIGHT_CHECK=true
REACT_APP_VERSION=$npm_package_version
## locize
REACT_APP_LOCIZE_PROJECTID=0bbc223a-9aba-4a90-ab93-ab9d7bf7f780
REACT_APP_LOCIZE_REFLNG=en.env.development :
REACT_APP_LOCIZE_VERSION=latest
REACT_APP_LOCIZE_APIKEY=aaad4141-54ba-4625-ae37-657538fe29e7.env.production :
REACT_APP_LOCIZE_VERSION=productionAdaptons maintenant le fichier i18n.js :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-locize-backend';
import LastUsed from 'locize-lastused';
import { locizePlugin } from 'locize';
import { DateTime } from 'luxon';
const isProduction = process.env.NODE_ENV === 'production';
const locizeOptions = {
projectId: process.env.REACT_APP_LOCIZE_PROJECTID,
apiKey: process.env.REACT_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
referenceLng: process.env.REACT_APP_LOCIZE_REFLNG,
version: process.env.REACT_APP_LOCIZE_VERSION
};
if (!isProduction) {
// locize-lastused
// sets a timestamp of last access on every translation segment on locize
// -> safely remove the ones not being touched for weeks/months
// https://github.com/locize/locize-lastused
i18n.use(LastUsed);
}
i18n
// locize-editor
// InContext Editor of locize
.use(locizePlugin)
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
backend: locizeOptions,
locizeLastUsed: locizeOptions,
saveMissing: !isProduction // you should not use saveMissing in production
});
// new usage
i18n.services.formatter.add('DATE_HUGE', (value, lng, options) => {
return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
});
export default i18n;Pendant le développement, vous continuerez à sauvegarder les clés manquantes et à utiliser la fonctionnalité LastUsed. => npm run start
En production en revanche, saveMissing et LastUsed sont désactivés, et l'api-key n'est pas exposée. => npm run build && npm run serve
Caching :
🧑💻 Le code complet de cet exemple React se trouve ici. Et une version TypeScript ici.
Regardez aussi la partie sur l'intégration du code dans cette vidéo YouTube.
Il existe aussi une vidéo crash course sur i18next.
Il existe également une traduction espagnole de cet article.
🎉🥳 Félicitations 🎊🎁
J'espère que vous avez appris quelques nouvelles choses sur i18next, la localisation React.js et les workflows modernes de localisation.
Donc, si vous voulez passer votre sujet i18n au niveau supérieur, cela vaut la peine d'essayer la plateforme de gestion des traductions Locize.
Les fondateurs de Locize sont aussi les créateurs d'i18next. En utilisant Locize, vous soutenez donc directement l'avenir d'i18next.