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
declare module "*.json"{ const value: any; export default value; }
i18n-project/tsconfig.json
{ "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
{ "de": {} }
i18n-project/src/i18n/locales/default.en.json
{ "en": {} }
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;
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") );
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.", "...": "..." } }
i18n-project/src/components/component1/locales/default.en.json
{ "en": { "header": "Welcome in Comp1", "body": "Comp1 is in English.", "...": "..." } }
i18n-project/src/components/component2/locales/default.de.json
{ "de": { "header": "Willkommen in Comp2", "link": "Comp2 ist auf <1>Deutsch</1>.", "...": "..." } }
i18n-project/src/components/component2/locales/default.en.json
{ "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
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
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
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
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
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
😉
Top comments (2)
nice article. dry and effective.
fyi: i18next-xhr-backend is deprecated... you should use i18next-http-backend instead...
i.e. dev.to/adrai/how-to-properly-inter...