React i18next
React i18next est une librairie qui permet de mettre en place l'internationalisation sur votre site internet.
Cette librairie est basée sur i18next.
Initialisation
La première chose que nous allons faire est d'ajouter la librairie à notre projet :
npm install react-i18next --save
Ensuite, nous avons besoin de configurer la façon dont nous allons l'utiliser.
Pour cela, je vais créer un fichier helpers/i18n.js
:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
debug: true, // pratique pour voir les événements dans la console pendant le développement
lng: 'en',
fallbackLng: 'en',
resources: {
en: {
translations: { // namespace par défaut, on peut avoir autant de namespaces que l'on souhaite
'home.hello': 'Hello! Welcome to my app!'
},
},
},
});
export default i18n;
Et je vais l'importer dans index.js
de mon application :
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import i18n from './helpers/i18n'; // initialisation de i18next
ReactDOM.render(
<App />,
document.getElementById("root")
);
Utilisation
Traduction simple
Voici le fichier App.js
avec un exemple simple :
import React from 'react';
import { useTranslation } from 'react-i18next';
const App = () => {
const { t } = useTranslation();
return (
{t('home.hello')}
);
};
export default App;
Ici, nous utilisons le hook useTranslation
afin d'accéder au service de traduction, mais vous pouvez également utiliser HOC withTranslation
:
import React from 'react';
import { withTranslation } from 'react-i18next';
const App = ({ t }) => {
return (
{t('home.hello')}
);
};
export default withTranslation()(App);
Si vous souhaitez passer des variables, cela peut également être fait très facilement. Admettons que notre clé de traduction ressemble à ceci :
translations: {
'home.hello': 'Hello, {{ name }}! Welcome to my app!'
},
Pour passer la variable name
dans notre clé de traduction nous pouvons faire :
{t('home.hello'), { name: 'Astronaute' }}
HTML
Si vous devez mettre du HTML dans la traduction, ou si votre clé de traduction en contient, vous pouvez utiliser le composant Trans
:
import React from 'react';
import { Trans } from 'react-i18next';
const App = () => {
return (
<Trans values={{ name: 'Astronaute' }}><h1>home.hello</h1></Trans>
);
};
export default App;
Pluralisation
Bien sûr, nous avons également besoin de prévoir le cas où la traduction varie selon un nombre ou une quantité.
translations: {
'message': 'You have one message',
'message_plural': 'You have several messages',
},
Dans ce cas, nous allons spécifier un argument supplémentaire, count
, comme ceci :
{t('home.hello'), { count: 5 }}
<Trans count={5}><h1>home.hello</h1></Trans>
Détection de la langue de l'utilisateur
Étant donné que nous travaillons sur un site multi-langues, nous souhaitons que la langue préférée de l'utilisateur soit automatiquement détectée. De façon générale, il s'agît de la langue du navigateur. Pour ceci, nous allons ajouter une dépendance :
npm install i18next-browser-languagedetector --save
Et nous allons modifier notre configuration comme ceci :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector'; // <= new
i18n
.use(LanguageDetector) // <= new
.use(initReactI18next)
// ...
;
export default i18n;
Changer de langue
Maintenant que notre application est capable de détecter la langue du navigateur de l'utilisateur, ce dernier peut souhaiter la modifier. Pour ceci, ajoutons un bouton :
import React from 'react';
import { useTranslation } from 'react-i18next';
import Button from '@material-ui/core/Button';
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
const changeLanguage = async (lng) => {
await i18n.changeLanguage(lng); // i18n.changeLanguage retourne une Promise
};
return (
<div>
<Button onClick={() => changeLanguage('en')}>English</Button>
<Button onClick={() => changeLanguage('fr')}>Français</Button>
</div>
);
}
export default LanguageSwitcher;
Fichiers de traduction
Évidemment, nous allons vouloir mettre les traductions dans des fichiers dédiés à cela, plutôt que de les garder dans la configuration directement. Les fichiers de traduction sont de simples fichiers JSON. Nous pouvons imaginer la structure suivante dans notre projet :
public/
locales/
en/
common.json
translations.json
other.json
fr/
common.json
translations.json
other.json
Namespaces
i18next fonctionne avec des namespaces, et on peut avoir plusieurs namespaces par langue. Pour rappel, le namespace par défaut est translations
. Dans notre exemple, common
, translations
et other
représentent des namespaces.
Dans ce cas, à chaque fois que nous voulons accéder aux clés qui se trouvent dans un namespace particulier, nous allons faire :
const { t } = useTranslation(['ns1', 'ns2', 'ns3']);
t('key'); // chargé depuis le namespace 'ns1'
t('ns2:key'); // chargé depuis le namespace 'ns2'
Nous pouvons également définir un namespace par défaut spécifique dans la configuration :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
defaultNS: 'common', // <= new
// ...
});
;
export default i18n;
Chargement des fichiers locaux
Maintenant que les traductions sont dans des fichiers JSON séparés, nous avons besoin d'indiquer dans la configuration i18next comment les récupérer. Pour cela, nous allons utiliser i18next-xhr-backend
:
npm install i18next-xhr-backend --save
Nous modifions le fichier de configuration comme ceci :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-xhr-backend'; // <= new
i18n
.use(LanguageDetector)
.use(Backend) // <= new
.use(initReactI18next)
.init({
backend: { // <= new
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
// ...
});
;
export default i18n;
Notez que le chemin indiqué dans loadPath
est dans le dossier public
à la racine de votre projet.
Fichiers hébergés sur un serveur distant
Nous avons présenté dans un article précédent que nous utilisons le service Localise.biz et enregistrons les fichiers de traduction sur un serveur dans le cloud. Ainsi, nous avons besoin de récupérer les fichiers hébergés sur un serveur distant :
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-xhr-backend';
i18n
.use(LanguageDetector)
.use(Backend)
.use(initReactI18next)
.init({
debug: true,
lng: 'en',
fallbackLng: 'en',
defaultNS: 'common',
backend: {
loadPath: `${process.env.TRANSLATIONS_ENDPOINT_URI}/{{ns}}.{{lng}}.json`, // nous indiquons tout simplement une URL complète
}
});
;
export default i18n;
SSR
React i18next peut être configuré pour fonctionner avec les SSR.
Voici la page qui explique comment mettre cela en place : https://react.i18next.com/latest/ssr
Néanmoins, nous avons eu un soucis en activant le SSR - le chargement des traductions depuis le serveur distant ne se faisait pas côté serveur. Ceci arrive parce que i18next-xhr-backend
utilise fetch
pour récupérer les fichiers, et fetch
n'est pas disponible côté serveur.
Par conséquence, nous avons eu besoin d'écrire un backend custom en nous basant sur la documentation ici : https://www.i18next.com/misc/creating-own-plugins#backend. Nous avons utilisé la librairie cross-fetch
qui fonctionne aussi bien côté client que côté serveur.