Internationalization using React-i18next legacy version (v9)
Tags: react, typescript, i18next, javascript
Published: 2020-07-10 14:09:59

# What is i18next?

i18next is a popular framework for internationalization. React-i18next is the version of i18next for React applications.

# Why legacy version React-i18next?

To use the latest version of React-i18next, the requirements are:

* react >= v16.8.0
* react-dom >= v16.8.0
* react-native >= v0.59.0
* i18next >= v10.0.0(typescript users: >= v17.0.9)

# Requirements for React-i18next v9

* react >= v0.14.0
* i18next >= v2.0.0

# Installation

```
npm install react-i18next@legacy i18next --save
npm install i18next-browser-languagedetector --save
npm install i18next-xhr-backend --save
npm install i18next --save
```
For TypeScript users, install the type definitions manually.

**Warning: Type definitions for `i18next-xhr-backend` and `i18next-browser-languagedetector` are deprecated.**

```
npm install @types/i18next-xhr-backend --save
npm install @types/i18next-browser-languagedetector --save
```

#Folder structure
```
i18n-project/
├── src/
│   ├── components/
│   │   ├── component1/
│   │   │   ├── locales/
│   │   │   │   ├── default.de.json
│   │   │   │   └── default.en.json
│   │   │   └── Component1.tsx
│   │   ├── component2/
│   │   │   ├── locales/
│   │   │   │   ├── default.de.json
│   │   │   │   └── default.en.json
│   │   │   └── Component2.tsx
│   │   └── App.tsx
│   ├── i18n/
│   │   ├── locales/
│   │   │   ├── default.de.json
│   │   │   ├── default.en.json
│   │   │   └── index.tsx
│   │   └── index.tsx
│   ├── types/
│   │   └── module.d.ts
│   └── index.tsx
│
├── package.json
└── tsconfig.json
```
# Configure TypeScript to import JSON files

*i18n-project/src/types/module.d.ts*

```javascript
declare module "*.json"{
    const value: any;
    export default value;
}
```

*i18n-project/tsconfig.json*
```javascript
{
    "compilerOptions":{
        ...
        "resolveJsonModule": true
    }
}
```

# Initialize main translation files

All other translation files are later concatenated onto the main translation file as we will see later.

*i18n-project/src/i18n/locales/default.de.json*
```javascript
{
    "de": {}
}
```

*i18n-project/src/i18n/locales/default.en.json*
```javascript
{
    "en": {}
}
```

# i18next Configuration file

*i18n-project/src/i18n/index.tsx*

```javascript
import i18n from "i18next";
import * as detector from "i18next-browser-languagedetector";
import * as Backend from "i18next-xhr-backend";
import { de, en } from "./locales";

i18n.use(Backend)
    .use(detector) //Browser Language Detector
    .init({
        interpolation: {
            escapeValue: false
        },

        debug: true,

        resources: {
            de: {
                common: de.de
            },
            en: {
                common: en.en
            }
        },

        fallbackLng: "en", //Fallback Language: English

        ns: ["common"],

        defaultNS: "common",

        react: {
            wait: false,
            bindI18n: "languageChanged loaded",
            bindStore: "added removed",
            nsMode: "default"
        }
    });
export default i18n;
```

# Setup provider in App

*i18n-project/src/index.tsx*
```jsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { I18nextProvider } from "react-i18next";
import i18n from "./i18n";

ReactDOM.render(
    <I18nextProvider i18n={i18n}>
        <Provider store={store}>
            ...
        </Provider>
    </I18nextProvider>,
    document.getElementById("root")
);
```

# Configure local translation files for each component

*i18n-project/src/components/component1/locales/default.de.json*

```javascript
{
    "de": {
        "header": "Willkommen in Comp1",
        "body": "Comp1 ist auf Deutsch.",
        "...": "..."
    }
}
```

*i18n-project/src/components/component1/locales/default.en.json*

```javascript
{
    "en": {
        "header": "Welcome in Comp1",
        "body": "Comp1 is in English.",
        "...": "..."
    }
}
```

*i18n-project/src/components/component2/locales/default.de.json*

```javascript
{
    "de": {
        "header": "Willkommen in Comp2",
        "link": "Comp2 ist auf <1>Deutsch</1>.",
        "...": "..."
    }
}
```

*i18n-project/src/components/component2/locales/default.en.json*

```javascript
{
    "en": {
        "header": "Welcome in Comp2",
        "link": "Comp2 is in <1>English</1>.",
        "...": "..."
    }
}
```

_Notice we used `<1>...</1>` tags to feature Trans component. To learn more about the numbering, checkout this fantastic resource:_
<https://github.com/arkross/arkross.github.io/wiki/Using-react-i18next-Trans-Component>

# Change constants from each component to load from i18n


*i18n-project/src/components/component1/Component1.tsx*

```tsx
import * as React from "react";
import i18n from "./../../i18n";

export class Component1 extends React.PureComponent<..., ...> {
    render() {
        return(
            <h1>{i18n.t("Component1.header")}</h1>
            <p>{i18n.t("Component1.body")}</p>
        );
    }
}
```

*i18n-project/src/components/component2/Component2.tsx*

```tsx
import * as React from "react";
import i18n from "./../../i18n";
import { Trans } from "react-i18next";

export class Component2 extends React.PureComponent<..., ...> {
    render() {
        return(
            <h1>{i18n.t("Component2.header")}</h1>
            <Trans i18nKey="Component2.link" i18n={i18n}>
                Comp2 is in <a href="..." >English</a>.
            </Trans>
        );
    }
}
```
_Notice we used `<a>...</a>` tags in place of `<1>...</1>`._

# Combine all translation files onto main translation file

*i18n-project/src/i18n/locales/index.tsx*

```javascript
let de: any = require("./default.de.json");
let en: any = require("./default.en.json");

import * as Component1De from "./../../components/component1/locales/default.de.json";
import * as Component1En from "./../../components/component1/locales/default.en.json";

import * as Component2De from "./../../components/component2/locales/default.de.json";
import * as Component2En from "./../../components/component2/locales/default.en.json";

...

de["de"].Component1 = Component1["de"];
en["en"].Component1 = Component1["en"];

de["de"].Component2 = Component2["de"];
en["en"].Component2 = Component2["en"];

...

export { de, en };
```

# Language Change feature (optional)

*i18next-project/src/components/component-of-your-choice*
```tsx
import * as React from "react";
import i18n from "./../i18n";

interface ChangeLngState{
    language: string;
}

export class ChangeLng extends React.Component<..., ChangeLngState> {
    state = {
        language: i18n.language
    };
    
    render(){
        return(
            <div>
                <Input
                    type="select"
                    name="language"
                    id="language"
                    onChange={this.languageChanged.bind(this)}
                    defaultValue={this.getDefaultValue()}
                >
                    <option>Deutsch<option>
                    <option>English<option>
                </Input>
                <Button onClick={this.onApply.bind(this)}>
                    Apply
                </Button>
            </div>
        );
    }

    languageChanged(event: any) {
        if (event.target.value === "Deutsch") {
            this.setState({
                language: "de"
            });
        } else {
            this.setState({
                language: "en"
            });
        }
    }

    getDefaultValue() {
        if (i18n.language === "de") {
            return "Deutsch";
        } else {
            return "English";
        }
    }

    onApply() {
        if (i18n.language != this.state.language) {
            i18n.changeLanguage(this.state.language, () => {
                location.reload();
            });
        }
    }
}
```

# **_Bonus: Change title according to language_**

*i18next-project/src/components/App.tsx*

```javascript
import * as React from "react";
import i18n from "./../i18n";
...

export class App extends React.Component<...> {
    componentDidMount(): void {
        if (i18n.language == "de") {
            document.title = "Titel auf Deutsch";
        } else {
            document.title = "Title in English";
        }
    }
}

```

# Advantages of using this approach

* Translations can be split into multiple files for multiple languages.
* Translation files are split for every component inside the component folder.
* Automatic browser default language detection.
* Change language with ease.

# Drawbacks of the approach

* React version must be greater than 16.8.0 to use latest version of i18next.
* Splitted translations should be combined in a main translation file.
* Translation keys must match exactly for translation to work.
* All translation keys must exist on fallback language.

# References:
* Official documentation for React-i18next legacy version: <https://react.i18next.com/legacy-v9/step-by-step-guide>
* Language codes can be found in:
<https://www.w3.org/International/O-charset-lang.html>
* <https://medium.com/rd-shipit/internationalizing-a-front-end-application-88f1baae3d82>


Github: <https://github.com/pssingh21>
`#first_post`😉