DEV Community

Cover image for Internationalization using React-i18next legacy version (v9)
Peeyush Man Singh
Peeyush Man Singh

Posted on

Internationalization using React-i18next legacy version (v9)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Configure TypeScript to import JSON files

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

declare module "*.json"{
    const value: any;
    export default value;
}
Enter fullscreen mode Exit fullscreen mode

i18n-project/tsconfig.json

{
    "compilerOptions":{
        ...
        "resolveJsonModule": true
    }
}
Enter fullscreen mode Exit fullscreen mode

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

{
    "de": {}
}
Enter fullscreen mode Exit fullscreen mode

i18n-project/src/i18n/locales/default.en.json

{
    "en": {}
}
Enter fullscreen mode Exit fullscreen mode

i18next Configuration file

i18n-project/src/i18n/index.tsx

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;
Enter fullscreen mode Exit fullscreen mode

Setup provider in App

i18n-project/src/index.tsx

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")
);
Enter fullscreen mode Exit fullscreen mode

Configure local translation files for each component

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

{
    "de": {
        "header": "Willkommen in Comp1",
        "body": "Comp1 ist auf Deutsch.",
        "...": "..."
    }
}
Enter fullscreen mode Exit fullscreen mode

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

{
    "en": {
        "header": "Welcome in Comp1",
        "body": "Comp1 is in English.",
        "...": "..."
    }
}
Enter fullscreen mode Exit fullscreen mode

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

{
    "de": {
        "header": "Willkommen in Comp2",
        "link": "Comp2 ist auf <1>Deutsch</1>.",
        "...": "..."
    }
}
Enter fullscreen mode Exit fullscreen mode

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

{
    "en": {
        "header": "Welcome in Comp2",
        "link": "Comp2 is in <1>English</1>.",
        "...": "..."
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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>
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

i18n-project/src/components/component2/Component2.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>
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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 };
Enter fullscreen mode Exit fullscreen mode

Language Change feature (optional)

i18next-project/src/components/component-of-your-choice

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();
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Bonus: Change title according to language

i18next-project/src/components/App.tsx

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";
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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:

Github: https://github.com/pssingh21
#first_post😉

Top comments (2)

Collapse
 
adrai profile image
Adriano Raiano

fyi: i18next-xhr-backend is deprecated... you should use i18next-http-backend instead...

i.e. dev.to/adrai/how-to-properly-inter...

Collapse
 
ayushbhusal profile image
Ayush-B

nice article. dry and effective.