Internationalize React application with i18n
Tags: react, javascript
Published: 2023-04-19 10:00:00

Internationalization (also known as i18n) allows your application to be available in different locales. The aim of 
internationalization is to remove the barriers to localizing or deploying an application internationally. Application's
users may speak different languages and have varying conventions for numbers, dates or strings.

In Javascript there are plenty of libraries for internationalization: `i18next`, `node-gettext` or `gloablize`.
However, in this article we will focus on using the `i18next` library with React.js.

## React-i18next
`react-i18next` is an internationalization framework that can be used with both React and React Native apps. This framework
is based on `i18next`.

The `i18next` and `react-i18next` is the most popular duo when it comes to React localization. Here are the main 
advantages of this solution for your application:

- `i18next` can be used in any Javascript environment with any UI framework,
- the library was created in 2011 and is available open source,
- there is plenty of extensions that can be used with this solution.

## Setup project
In this article I'll be using simple React project bootstrapped with Create React App (CRA) with the following command:

`npx create-react-app react-i18n-project`

After creating the project you can change the directory and start the server:

```shell
cd react-i18n-project
npm start
```

## Add the dependencies
As stated previously `react-i18next` is based on `i18next`, so we will need both packages to successfully translate 
our app:

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

Speaking of extensions, `i18next-browser-languagedetector` is an `i18next` plugin used to detect user language in the
browser. It also supports features as cookies, localStorage, query strings and more.

## Getting started

Let's start with basic config listed in `i18n.js` file:

```javascript
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',
    resources: {
      en: {
        translation: {
          // here we will place our translations...
        }
      }
    }
  });

export default i18n;
```

We should have it imported in the `index.js` file:

```javascript
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>
);
```

Let's quickly go through each line of the configuration file:
- `.use(LanguageDetector)` - detects user language,
- `.use(initReactI18next)` - passes the `i18n` instance to the module of `react-i18next`,
- `.init({...` - inits i18next,
- `debug: true` - helps in i18n debugging with browser's console,
- `fallbackLng: 'en'` - sets default language to English,

## Create translation files

In this section we are going to learn how to translate normal text from one language to another. We need to have some place
to store translated texts in all languages that we need. Let's create directory named eg. `locales` and inside we should
create JSON files with names corresponding to the language codes of the translations - `es` for Spanish, `fr` for French and 
so on.

```json
// locales/es.json
{
    "welcome": "Angular es mi framework Javascirpt favorito",
    "article": {
        "heading": "Por qué me gusta tanto Angular",
    }
};

// locales/fr.json
{
    "welcome": "Angular est mon framework Javascirpt préféré.",
    "article": {
        "heading": "Pourquoi j'adore Angular",
    }
};
```

> Disclaimer: I have no knowledge of Spanish and French. I used Google Translate to take these texts!

The last part of the setup is to import those JSON files to out config file:
```javascript
// ...
import translation_es from './locales/es.json';
import translation_fr from './locales/fr.json';

// ...
resources: {
    es: {
        translations: translation_es
    },
    fr: {
        translations: translation_fr
    }
}
```

## Using Trans component and useTranslation hook

Now we have the translations ready. Of course the folders structure and file names may differ in your applications. 
In much more complex apps it is recommended to come with better system where you can have separate translation files for
different views in your app or even use lazy loading to drastically improve your application performance. 

To use the translations and translate the content we can use to solutions provided by `i18next-react`.

### Trans component
```javascript
import React from 'react';
import { Trans, useTranslation } from 'react-i18next'

function MyComponent() {
    return <h1><Trans i18nKey="welcome">Angular is my favorite Javascript framework</Trans></h1>;
}
```
Please keep in mind that according to the documentation: "s long you have no React/HTML nodes integrated into a cohesive 
sentence (text formatting like strong, em, link components, maybe others), you won't need it - most of the times you 
will be using the good old `t` function". So, here is an alternative way to translate your content.

### useTranslation 
```javascript
import React, { Suspense } from 'react';
import { useTranslation } from 'react-i18next';

function MyComponent() {
  const { t, i18n } = useTranslation();

  return <h1>{t('welcome')}</h1>
}
```

## How to switch languages
Let's add s simple component with buttons that allows users to switch the language of our app. 

```javascript
import React, { Suspense } from 'react';
import { useTranslation } from 'react-i18next';

const languages = {
    en: {displayName: 'English'},
    es: {displayName: 'Spanish'},
}

function MyComponent() {
    const { t, i18n } = useTranslation();

    return (
        <div>
            <h1>{t("welcome")}</h1>
            <div>
                <button
                    onClick={() => i18n.changeLanguage(languages.en)}
                    style={{
                        fontWeight: i18n.resolvedLanguage === languages.en ? "bold" : "normal",
                    }}
                >
                    {languages.en.displayName}
                </button>
                <button
                    onClick={() => i18n.changeLanguage(languages.es)}
                    style={{
                        fontWeight: i18n.resolvedLanguage === languages.es ? "bold" : "normal",
                    }}
                >
                    {languages.es.displayName}
                </button>
            </div>
        </div>
    );
}
```

As you can see in the code example above `useTranslation` hook gives us possibility to read current active language and 
change the language of the whole app.

Here we have added two buttons that switch the language of the app and the font weight of the text inside of them
indicates the active language.

As you may remember form the previous paragraphs we have installed `i18next-browser-languagedetector` and added it to 
our config file. `i18next` tries to detect the browser language and automatically use that language if the corresponding
translation file is available. 

Speaking of our buttons for switching languages - manually selected language is persisted in the `localStorage` and it will
be used as preferred language even after closing the browser and opening the app the other day.

## Summary

There are many other topics to cover in terms of internationalization React apps using `i18next`. In the next articles 
I'm going to explain how to handle plurals, interpolation and formatting dates. I hope that it was a great introduction
to the i18n, peace 🏻.