Comparing react i18n: react-i18next (part 2)

The biggest guy on the block

2019/01/28 2019/02/20    react javascript

Installing and configuring

First we will need to install the package, so yarn add i18next react-i18next (or nom if you prefer). Now, we need to create a configuration file for the library /i18n.js, where we state the available localisations and some other features:

//i18n.js
import i18n from 'i18next';
import { reactI18nextModule } from 'react-i18next';
import en from './locale/en.json';
import se from './locale/se.json';
import es from './locale/es.json';
import zh from './locale/zh.json';
i18n
.use(reactI18nextModule)
.init({
resources: {
	en,
	se,
	es,
	zh
},
fallbackLng: 'en',
debug: true,
ns: ['translations'],
defaultNS: 'translations',
keySeparator: false,
interpolation: {
	escapeValue: false,
	formatSeparator: ',',
},
react: {
	wait: true,
},
});
export default i18n;

There, now we can have an English locale, as well ass a Swedish one, as well as Spanish and Chinese just because it’s fun. Now, there is a way to extract translations from the existing app - but it requires a lot of set up and the configuration is bigger than our example app – times five. So, rather than doing that we’ll have to make the translations manually. They will live in /locale/ and they are simple JSON-files.

Adding our translations

Some things to note: interpolations are in double squiggly brackets, and if you are going to have formatted text inside a translation, it need to have a key. It seems to be so, that a number must be passed down with the name count for i18n to make the plural conversion.

//en.json
{
  "translations": {
    "Testing i18next": "Testing i18next",
    "Other language": "Other language",
    "puppyWish": "I want {(number)} puppy",
    "puppyWish_plural": "I want {(count)} puppies",
    "I want more puppies!": "I want more puppies!"
  }
}
//es.json
{
  "translations": {
    "Testing i18next": "Estoy probando i18next",
    "Other language": "Otro idioma",
    "puppyWish": "Yo quiero {{count}} cachorro",
    "puppyWish_plural": "Yo quiero {{count}} cachorros",
    "I want more puppies!": "Yo quiero mas cachorros!"
  }
}
//se.json
{
  "translations": {
    "Testing i18next": "Testar i18next",
    "Other language": "Andra språk",
    "puppyWish": "Jag vill ha {{count}} valp",
    "puppyWish_plural": "Jag vill ha {{count}} valpar",
    "I want more puppies!": "Jag vill ha fler valpar!"
  }
}
{
  "translations": {
    "Testing i18next": "接下来测试 i18next",
    "Other language": "其他语言",
    "puppyWish": "我想{{count}}个小狗崽",
    "puppyWish_plural": "我想要{{count}}个小狗崽",
    "I want more puppies!": "我想要更多的小狗崽!"
  }
}

Connecting everything together

Now we need to connect our app to the library and our settings:

//index.js
import React from "react";
import ReactDOM from "react-dom";

import { I18nextProvider } from "react-i18next";
import i18n from "../i18n";

import "./index.css";
import App from "./App";

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

Lastly we need to change the strings inside our little app, so that they use the translation engine. We need to import some things, and we need to run it through a HOC.

//App.js
import React, { Component } from "react";
import { withNamespaces, Trans } from "react-i18next";
import "./App.css";

class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }
 addNumber = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };
  render() {
    const { t } = this.props;
    const { count } = this.state;
    return (
      <>
        <header>
          <h1>{t("Testing i18next")}</h1>
          <button>{t("Other language")}</button>
        </header>
        <main>
          <p>
            <Trans i18nKey="puppyWish" count={count}>
              I want {{ count }} puppies
            </Trans>
          </p>
          <button onClick={this.addNumber}>{t("I want more puppies!")}</button>
        </main>
      </>
    );
  }
}

export default withNamespaces("translations")(App); //HOC goes here

If you run the app now, it will look exactly like it did before - with the exception that it correctly write it out as 1 puppy rather than 1 puppies.

Toggling between languages

We want to be able to change language dynamically (otherwise what would be the purpose of this), so, let’s try to make the button change between the languages. Or rather, let’s make a button for every language.

To make it easier for ourself, let’s store all the locales in a separate file:

//locales.js
export const locales = [
  { value: "en", label: "English" },
  { value: "se", label: "Svenska" },
  { value: "es", label: "Español" },
  { value: "zh", label: "中文" },
];

And then import it in the beginning of our App.js import { locales } from "./locales.js"

Then, we’ll use the function supplied by i18next called, quite fittingly, changeLanguage(). We can destruct the function from the i18n object that the provider supplies, and we get the value to give it from the buttons.

...
 changeLang = lang => {
    const { i18n } = this.props;
    const { value } = lang;
    i18n.changeLanguage(value);
  };
...

To make a button for each language, we’ll just map over the array that is imported form locales.js.

...
{locales.map(l => (
            <button key={l.value} value={l} onClick={() => this.changeLang(l)}>
              {l.label}
            </button>
          ))}
...

Now, if you start the app and test it out, it will change language when you click on the buttons, and it will show puppies in either plural or singular, depending on the number.

And this concludes our sejour into i18next, let’s try the next library!