Comparing react i18n: js-lingui (part 4)

The save newcomer

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

Installing and configuring

So, as usual, we need to install some packages. Right away we are told by the documentation both about the development tools as well ass the part that needs to live with the app. Two development dependencies, one regular one:

yarn add @lingui/cli @lingui/macro @babel/core babel-core@bridge -D
yarn add @lingui/react

The first two are the CLI tool that helps us extract he translations, and the babel macro it uses to do so. The third is the react bindings.

Now, wee need to add som configuration (there’s never an end to this, configuration stuff, innit?), and we put it in a file called .linguirc in JSON format in the root folder:

//.linguirc
{
   "localeDir": "src/locales/",
   "srcPathDirs": ["src/"],
   "format": "minimal"
}

This file tells the extractor to look at the code in /src for things to extract, and put the extracted stuff into /src/locales/.

For sake of being able to run the commands, let’s add them to our NPM scripts:

//pakage.json
{
   "scripts": {
      "add-locale": "lingui add-locale",
      "extract": "lingui extract",
      "compile": "lingui compile",
   }
}

And now we can add our locales (already!) by using the command yarn add-locale en se es zh, and then test extracting with yarn extract – however, there’s currently nothing to extract, so I will just tell you that is extracted 0 for every locale. It also gives the status of translation, that is, if any message is currently lacking one.

Connecting our app

To connect, we use a provider, as all the other libraries do, so we’ll import it and wrap the App with it:

import React from "react";
import ReactDOM from "react-dom";
import { I18nProvider } from "@lingui/react";
import "./index.css";
import App from "./App";

ReactDOM.render(
  <I18nProvider language="en">
    <App />
  </I18nProvider>,
  document.getElementById("root")
);

And then we need to wrap all the messages in the app with the provided Trans component. We’ll import this in App.js and wrap our messages. You’ll notice that js-lingui provide some helpful components to make the plurality easier in the form of the Pluralcomponent.

import React, { Component } from "react";
import { Trans, Plural } from "@lingui/macro";
import "./App.css";
class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }
  addNumber = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };
  render() {
    const { count } = this.state;
    return (
      <>
        <header>
          <h1>
            <Trans>Testing i18next</Trans>
          </h1>
          <button>Other language</button>
        </header>
        <main>
          <p>
            <Trans>
              I want <Plural value={count} one="# puppy" other="# puppies" />
            </Trans>
          </p>
          <button onClick={this.addNumber}>
            <Trans>I want more puppies!</Trans>
          </button>
        </main>
      </>
    );
  }
}

export default App;

And now we’re ready to extract again! So yarn extract and generate the translation files! Currently all are missing, but we’ve got 3 messages to translate in each locale. They all look like this:

//en.json
{
  "I want more puppies!": "",
  "I want {count} puppies": "",
  "Testing i18next": ""
}

In the English one, we’ll just copy over the same text, as the messages themselves serve as keys. Here’s an example of the Swedish one’s, you can see the rest in the repo:

/se.json
  "I want more puppies!": "Jag vill ha fler valpar!",
  "I want {count, plural, one {# puppy} other {# puppies}}": "Jag vill ha {count, plural, one {# valp} other {# valpar}}",
  "Testing js-lingui": "Testar js-lingui"

To use the language files, they need to be compiled and we do that with yarn compile, and message.js is generated for each message.json. These should be imported into index.js and provided as a labeled object.

//index.js
import se from "./locales/se/messages.js";
import es from "./locales/es/messages.js";
import en from "./locales/en/messages.js";
import zh from "./locales/zh/messages.js";

const catalogs = { en: en, se: se, es: es, zh: zh };

The just give the provider component the props catalogs={catalogs}.

Toggle languages

As with react-intl, there’s no function to change language built in (however, there is a method for dynamic loading of language that would speed up the app!), so I wrote a simple wrapper in index.js, and rendered that instead of App in ReactDOM.render():

class Wrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = { language: "en" };
    console.log(this.state);
  }
  changeLang = lang => {
    this.setState(Object.assign({}, this.state, { language: lang }));
  };

  render() {
    return (
      <I18nProvider
        language={this.state.language}
        catalogs={catalogs}
        callback={this.changeLang}>
        <App callback={this.changeLang} />
      </I18nProvider>
    );
  }
}

export default Wrapper;

ReactDOM.render(<Wrapper />, document.getElementById("root"));

We can use our previously made locales.js, the version from the first example, and import it in App.js, and map over it to make the buttons, and on onClick we use the callback that is propped down from the original wrapper, so that we can change the language:

//App.js
import { locales } from "./locales";

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

And now we have it, three dead simple apps that have localisations for four languages!