DEV Community

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

Posted on

3 1

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😉

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

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.

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series