<commit_before><commit_msg>Translate react i18next article (#654)<commit_after>---
layout: post
title: React i18next
excerpt: In this article we are going to cover how to set up i18next in your React app
authors:
- marishka
permalink: /en/react-i18next/
categories:
    - react
    - i18n
tags:
    - react
    - i18n
---

# React i18next

**React i18next** is a library that allows to set up internationalization on your website.
This library is based on [i18next](https://www.i18next.com/).

# Initialization

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

```bash
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`:

```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:

```js
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:

```js
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`:

```js
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:

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

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

```js
{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:

```js
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.

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

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

```js
{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:

```bash
npm install i18next-browser-languagedetector --save
```

And we will change our configuration as follows:

```js
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:

```js
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:

```bash
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:

```js
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:

```js
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`:

```bash
npm install i18next-xhr-backend --save
```

Let's update the configuration file:

```js
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](https://blog.eleven-labs.com/en/manage-translations-with-localise.biz/) 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:

```js
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](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](https://www.i18next.com/misc/Creating-own-plugins#backend). We used the `cross-fetch` library that works on both the client and server sides.

{'fraction_non_alphanumeric': 0.12396489927079471, 'fraction_numerical': 0.02014584105796564, 'mean_word_length': 3.712871287128713, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 25, 'https://': 6, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1671809', 'n_tokens_mistral': 2660, 'n_tokens_neox': 2516, 'n_words': 934}