Search

React i18next


January 29, 2020 | 10 mins Marie Minasyan

React i18next

React i18next is a library that allows to set up internationalization on your website. This library is based on i18next.

Initialization

The first thing we will do is add the library to our project:

npm install react-i18next --save

Then we need to configure the way we are going to use it. For that, I will create a file helpers/i18n.js:

import i18n from 'i18next';   
import { initReactI18next } from 'react-i18next';  

i18n  
  .use(initReactI18next)  
  .init({
    debug: true, // useful to see events that occur during development
    lng: 'en',
    fallbackLng: 'en',
    resources: {
      en: {
        translations: { // default namespace
          'home.hello': 'Hello! Welcome to my app!'
        },
      },
    },
  });
  
export default i18n;

And I will import it into index.js of my application:

import React from  'react';
import ReactDOM from 'react-dom';
  
import App from './App';
import i18n from './helpers/i18n'; // initialisation of i18next

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

Usage

Simple translations

Here is the App.js file with a simple example:

import React from 'react';
import { useTranslation } from 'react-i18next';

const App = () => {
  const { t } = useTranslation();

  return (
    {t('home.hello')}
  );
};

export default App;

Here we use the useTranslation hook to access the translation service, but you can also use the HOC withTranslation:

import React from 'react';
import { withTranslation } from 'react-i18next';

const App = ({ t }) => {
  return (
    {t('home.hello')}
  );
};

export default withTranslation()(App);

If you want to pass variables, this can also be done very easily. Let’s say our translation key looks like this:

translations: {
  'home.hello': 'Hello, {{ name }}! Welcome to my app!'
},

To pass the name variable in our translation key we can do:

{t('home.hello'), { name: 'Astronaut' }}

HTML

If you need to put HTML in the translation, or if your translation key contains some, you can use the Trans component:

import React from 'react';
import { Trans } from 'react-i18next';

const App = () => {
  return (
    <Trans values={{ name: 'Astronaut' }}><h1>home.hello</h1></Trans>
  );
};

export default App;

Pluralization

Of course, we also need to handle the case where the translation varies according to a number or quantity.

translations: {
  'message': 'You have one message',
  'message_plural': 'You have several messages',
},

In this case, we will specify an additional argument, count, like this:

{t('home.hello'), { count: 5 }}
<Trans count={5}><h1>home.hello</h1></Trans>

Detecting user’s preferred language

Since we are working on a multi-language site, we want the user’s preferred language to be automatically detected. In general, it is the browser’s language. For this, we will add a new dependency:

npm install i18next-browser-languagedetector --save

And we will change our configuration as follows:

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;

Changing the language

Now that our application is able to detect the user’s browser language, the user may want to change it. For this, let’s add a button:

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 returns a Promise
  };

  return (
    <div>
      <Button onClick={() => changeLanguage('en')}>English</Button>
      <Button onClick={() => changeLanguage('fr')}>Français</Button>
    </div>
  );
}  

export default LanguageSwitcher;

Translation files

Obviously, we will want to put the translations into dedicated files, rather than keeping them in the configuration directly. Translation files are simple JSON files. We can imagine the following structure in our project:

public/
  locales/
    en/
      common.json
      translations.json
      other.json
    fr/
      common.json
      translations.json
      other.json

Namespaces

i18next works with namespaces, and you can have multiple namespaces per language. As a reminder, the default namespace is translations. In our example, common, ` translations and other` are namespaces.

In this case, each time we want to access the keys that are in a particular namespace, we will do:

const { t } = useTranslation(['ns1',  'ns2',  'ns3']);
t('key'); // loaded from namespace 'ns1'
t('ns2:key'); // loaded from namespace 'ns2'

It is also possible to define a custom default namespace in the 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;

Loading local files

Now that the translations are in separate JSON files, we need to indicate in the i18next configuration how to retrieve them. For that, we will use i18next-xhr-backend:

npm install i18next-xhr-backend --save

Let’s update the configuration file:

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;

Note that the path specified in loadPath is in the public folder at the root of your project.

Loading remote files

We have presented in a previous article that we use Localise.biz service and save translation files on a server in the cloud. Hence, we need to recover the files hosted on a remote server:

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`, // we simply indicate the full URL to retrieve files
    }
  });
;

export default i18n;

SSR

React i18next can be configured to work with SSR. Here is the page that explains how to put this in place: https://react.i18next.com/latest/ssr.

Nevertheless, we had a problem activating the SSR - the loading of the translations from the remote server was not done on the server side. This happens because i18next-xhr-backend uses fetch to recover the files, and fetch is not available on the server side.

As a result, we needed to write a custom backend based on the documentation here: https://www.i18next.com/misc/creating-own-plugins#backend. We used the cross-fetch library that works on both the client and server sides.

Author(s)

Marie Minasyan

Astronaute Raccoon @ ElevenLabs_🚀 De retour dans la Galaxie.

Site is Ready for Offline Use