The Best Libraries for React I18n

React is a popular front-end development library that helps you build scalable UI interfaces. When it comes to internationalizing your apps though, it does not offer a built-in solution. Fortunately for us, it is not hard to do that, especially with the help from some open source libraries like react-intl, i18next or react-intl-universal.

In this article, we will take a look at the different libraries we have at disposal for React i18n. We will also understand the pros and cons for each option in terms of flexibility, scalability and most of all, developer productivity as its very important to have a consistent view of the app.

Understanding the pros and cons of each library takes time and effort. The main reason is that almost every language has different rules and conventions and adapting to them in the scope of those libraries, is tricky to do it right.

At the time of this writing, I’m using just the latest versions of React v16.11.0 and React Router v5.1.2. I’ve also added the code examples in this GitHub repo.

The Base Project

Let’s start by layering a base project that we would like to localize. It would be a typical, unopinionated React project with a minimal boilerplate, useful for starting out fresh. Create a new app using Create React App and update the App.js with the following content:

import React from 'react';
import {Switch, Route, HashRouter as Router} from 'react-router-dom';
import Home from "./Home";

import './App.css';

const App = () => (
  <Router>
    <Switch>
      <Route exact path="/" component={Home} />
    </Switch>
  </Router>
);

export default App;
We defined the Home.js component as:
import React, {Component} from 'react';
import logo from './logo.svg'
import './Home.css';

class Home extends Component {
  render() {
    return (
      <div className="Home">
        <div className="Home-header">
          <img src={logo} className="Home-logo" alt="logo"/>
          <h2>Welcome to React.js</h2>
        </div>
        <div className="Home-container">
          <div className="Home-items">
            <div className="Home-item">
              <h3 className="focus">Declarative</h3>
              <div><p>React makes it painless to create interactive UIs. Design simple views for each state in your
                application, and React will efficiently update and render just the right components when your data
                changes.</p>
                <p>Declarative views make your code more predictable and easier to debug.</p>
              </div>
            </div>
            <div className="Home-item">
              <h3 className="focus">Component-Based</h3>
              <div>
                <p>Build encapsulated components that manage their own state, then compose them to make complex UIs.</p>
                <p>Since component logic is written in JavaScript instead of templates, you can easily pass rich data
                  through your app and keep state out of the&nbsp;DOM.</p>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default Home;

And the  Home.css component as:

.Home {
  text-align: center;
}

.Home-logo {
  height: 40vmin;
}

.Home-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.Home-link {
  color: #09d3ac;
}
When you run yarn start or npm start you will see the following screen:
Initial Screen

React-intl

This library is one of the most popular solutions for i18n in React and it’s now part of Formatjs. Offered by Yahoo is bundled with common locale data like dates, currencies, numbers, and support for over  150+ languages. It builds upon the concepts of the ECMAScript’s ECMAScript’s Internationalization API namespace.

In order to use it, we need to configure a few things first.

First we need to install it:

yarn add react-intl

Then on the root of the application just before we render the initial component, we need to add the following bootstrapping code:

/* react-intl imports */
import { IntlProvider } from 'react-intl';

/* Define your translations */
let i18nConfig = {
  locale: 'el',
  messages: {
    "home.welcome": "Καλώς 'Ηρθατε στο {name}!",
    "home.declarative": "Δηλωτικό"
  }
};

Notice that the i18nConfig contains all the specific language translations for the current locale. We could have loaded this config before the application starts using a JSON Webpack loader or an Ajax call.

When we have loaded our supported locale and config messages, then we only need to wrap the application with the that will apply the i18n context available for our internal React Components:

ReactDOM.render(
  <IntlProvider
    locale={i18nConfig.locale}
    defaultLocale={i18nConfig.locale}
    messages={i18nConfig.messages}
  >
    <App />
  </IntlProvider>, document.getElementById('root'));

Finally, we are ready to provide our translated components. We need to convert our existing text in the Home component into a series of FormattedMessage components:

import React, {Component} from 'react';
import {FormattedMessage} from 'react-intl';
import logo from './logo.svg'
import './Home.css';

class Home extends Component {
  render() {
    return (
      <div className="Home">
        <div className="Home-header">
          <img src={logo} className="Home-logo" alt="logo"/>
          <h2><FormattedMessage id="home.welcome" values={{name: 'React.js'}}/></h2>

        </div>
        <div className="Home-container">
          <div className="Home-items">
            <div className="Home-item">
              <h3 className="focus">
                <FormattedMessage id="home.declarative"/></h3>
              <div><p><FormattedMessage id="home.declarative.p1" values={{name: <i>React</i>}}/></p>
                <p><FormattedMessage id="home.declarative.p2"/></p>
              </div>
            </div>
            <div className="Home-item">
              <h3 className="focus"><FormattedMessage id="home.component-based"/></h3>
              <div>
                <p><FormattedMessage id="home.component-based.p1"/></p>
                <p><FormattedMessage id="home.component-based.p2"/></p>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default Home;

Then, we need to update our translations to match the keys for each one of the messages:

let i18nConfig = {
  locale: 'el',
  messages: {
    "home.welcome": "Καλώς 'Ηρθατε στο {name}!",
    "home.declarative": "Δηλωτικό",
    "home.declarative.p1": "To {name} καθιστά ανώφελη τη δημιουργία διαδραστικών διεπαφών χρήστη. Σχεδιάστε απλές προβολές για κάθε κράτος στο δικό σας\n" +
    "                εφαρμογή και το React θα ενημερώσει αποτελεσματικά και θα αποδώσει τα σωστά στοιχεία όταν τα δεδομένα σας\n" +
    "                αλλαγές.",
    "home.declarative.p2": "Οι δηλωτικές προβολές καθιστούν τον κώδικα πιο προβλέψιμο και πιο εύκολο στον εντοπισμό σφαλμάτων.",
    "home.component-based": "Βασισμένο σε στοιχεία",
    "home.component-based.p1": "Δημιουργήστε ενσωματωμένα στοιχεία που διαχειρίζονται τη δική τους κατάσταση, και στη συνέχεια συνθέστε τα για να δημιουργήσετε σύνθετα UI.",
    "home.component-based.p2": "Δεδομένου ότι η λογική συνιστωσών είναι γραμμένη σε JavaScript αντί για πρότυπα, μπορείτε εύκολα να περάσετε πλούσια δεδομένα\n" +
    "                  μέσω της εφαρμογής σας και να κρατήσετε την κατάσταση εκτός του & nbsp; DOM.",
  }
};

Now, if we notice the app will load with the Greek translations. Notice also that the FormattedMessage can return also React components and not just text.

Translated View

Among other things, React-intl offers some extra components:

  • FormattedDate: Formats locale-specific dates.
  • ForrmattedTime: Formats locale-specific times.
  • FormattedRelative: Formats locale-specific relative durations.
  • FormattedNumber: Formats locale-specific numbers and decimals.
  • FormattedPlural: Formats locale-specific plurals and quantities.

There is also support for React Hooks via the useIntl hook. To use it in our Functional Components, instead of importing the above components, we just call the hook and it will return the imperative formatting API as an object for us:

const HomeHeader = () => {
  const intl = useIntl();
  return (
    <div className="Home-header">
      <img src={logo} className="Home-logo" alt="logo"/>
      <h2>{intl.formatMessage({id: 'home.welcome'}, {name: 'React.js'})}</h2>
    </div>
  )
};

class Home extends Component {
  render() {
    return (
      <div className="Home">
        <HomeHeader />
        ...

React-intl offers a good overall package for internationalizing your React Application. However, it is not without its disadvantages. You cannot use it for non-react components as it requires the top level component to inject the context to the children. We will see how the next library helps with providing a holistic solution to this problem.

React-intl-universal

react-intl-universal is a React internationalization package developed by Alibaba Group. It builds on top of React-intl by allowing non-React components use the library by providing a singleton object that can be used to load the current locale. Let’s see how we can use this library in our project.

Install it first:

yarn add react-intl-universal

Using the i18nConfig  we need to load the translations and pass them through the intl.init  method call:

import React, {Component} from "react";

import intl from 'react-intl-universal';
// common locale data
require('intl/locale-data/jsonp/el.js');

export default class App extends Component {

  state = {initDone: false};

  componentDidMount() {
    this.loadLocales();
  }

  loadLocales() {
    intl.init({
      currentLocale: i18nConfig.locale,
      locales,
    })
      .then(() => {
        // After loading CLDR locale data, start to render
        this.setState({initDone: true});
      });
  }
...

Once we update the state we can render the App component:

render() {
    return (
      this.state.initDone &&
      <Router>
        <Switch>
          <Route exact path="/" component={Home} />
        </Switch>
      </Router>
    );
import intl from 'react-intl-universal';
 
intl.get('home.declarative'); // Simple text message
intl.getHtml('home.declarative.p1') // HTML message
intl.get('not-exist-key').defaultMessage('Μύνημα που δεν υπάρχει') // Default message
intl.get('home.welcome', {name:'React.js'}) // Message with variables.

Now the API for accessing those translations is simple:

import intl from 'react-intl-universal';
 
intl.get('home.declarative'); // Simple text message
intl.getHtml('home.declarative.p1') // HTML message
intl.get('not-exist-key').defaultMessage('Μύνημα που δεν υπάρχει') // Default message
intl.get('home.welcome', {name:'React.js'}) // Message with variables.

It can also handle locale-specific messages for Times, Dates, Decimals and Plurals.

LinguiJS

LinguiJS is considered a newcomer in this list, offering an impressive array of powerful features for i18n. It’s clean, simple and with low overhead. Not to mention it works with React too!

Let’s see an example usage. First we need to install a bunch of libraries:

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

Next we need to add a configuration file for the CLI tool that will extract and compile the messages. Create the following file with this config:

.jslingui

{
   "localeDir": "src/locales/",
   "srcPathDirs": ["src/"],
   "format": "minimal",
   "fallbackLocale": "el",
   "sourceLocale": "en"
}

We specify that we need a JSON format (using minimal) for our message formats and they will be saved in the src/locales folder.

Now let’s create the Home.js Component:

import React from 'react';
import {Trans} from "@lingui/macro"
import logo from './logo.svg'
import './Home.css';

const Home = ({name}) => {
  return (
    <div className="Home">
      <div className="Home-header">
        <img src={logo} className="Home-logo" alt="logo"/>
        <Trans render="h2" id="home.welcome">Welcome to {name}!</Trans>
      </div>
      <div className="Home-container">
        <div className="Home-items">
          <div className="Home-item">
            <h3 className="focus">
              <Trans id="home.declarative">Declarative</Trans>
            </h3>
            <div>
              <p>
                <Trans id="home.declarative.p1">
                  {name} makes it painless to create interactive UIs.
                  Design simple views for each state in your application, and
                  React will efficiently update and render just the right components when your data changes.
                </Trans>
              </p>
              <p>
                <Trans id="home.declarative.p2">
                  Declarative views make your code more predictable and easier to debug.
                </Trans>
              </p>
            </div>
          </div>
          <div className="Home-item">
            <h3 className="focus">
              <Trans id="home.component-based">Component-Based</Trans>
            </h3>
            <div>
              <Trans render="p" id="home.component-based.p1">
                Build encapsulated components that manage their own state, then compose them to make complex UIs.
              </Trans>
              <Trans render="p" id="home.component-based.p2">
                Since component logic is written in JavaScript instead of templates, you can easily pass rich data
                through your app and keep state out of the DOM.
              </Trans>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Home;

Then we need to extract and compile the messages that we are going to translate:

$ npm run extract                          

> react-i18n-libraries@0.1.0 extract /Users/itspare/WebstormProjects/react-i18n-libraries
> lingui extract

Catalog statistics:
┌─────────────┬─────────────┬─────────┐
│ Language    │ Total count │ Missing │
├─────────────┼─────────────┼─────────┤
│ el          │      7      │    7    │
│ en (source) │      7      │    -    │
└─────────────┴─────────────┴─────────┘

(use "npm run add-locale <locale>" to add more locales)
(use "npm run extract" to update catalogs with new messages)
(use "npm run compile" to compile catalogs for production)

$ npm run compile
> lingui compile

Compiling message catalogs…
Done!

Note that, when you run the app the first time you will see only the English translations and not the Greek. That’s because the Greek translations are missing. We will have to add them to the src/locales/el/messages.json before we run the compile command:

{
  "home.component-based": "Βασισμένο σε στοιχεία",
  "home.component-based.p1": "Δημιουργήστε ενσωματωμένα στοιχεία που διαχειρίζονται τη δική τους κατάσταση, και στη συνέχεια συνθέστε τα για να δημιουργήσετε σύνθετα UI.",
  "home.component-based.p2": "Δεδομένου ότι η λογική συνιστωσών είναι γραμμένη σε JavaScript αντί για πρότυπα, μπορείτε εύκολα να περάσετε πλούσια δεδομένα μέσω της εφαρμογής σας και να κρατήσετε την κατάσταση εκτός του & nbsp; DOM.",
  "home.declarative": "Δηλωτικό",
  "home.declarative.p1": "To {name} καθιστά ανώφελη τη δημιουργία διαδραστικών διεπαφών χρήστη. Σχεδιάστε απλές προβολές για κάθε κράτος στο δικό σας εφαρμογή και το React θα ενημερώσει αποτελεσματικά και θα αποδώσει τα σωστά στοιχεία όταν τα δεδομένα σας αλλαγές.",
  "home.declarative.p2": "Οι δηλωτικές προβολές καθιστούν τον κώδικα πιο προβλέψιμο και πιο εύκολο στον εντοπισμό σφαλμάτων.",
  "home.welcome": "Καλώς 'Ηρθατε στο {name}!"
}

Once we’ve completed those step’s, we only have to load those messages into the I18nProvider component:

import { I18nProvider } from '@lingui/react';
import catalogEl from './locales/el/messages.js';
import catalogEn from './locales/en/messages.js';
const catalogs = { en: catalogEn, el: catalogEl };

ReactDOM.render(
  <I18nProvider
    language={i18nConfig.locale}
    catalogs={catalogs}
  >
    <Home name={'React.js'}/>
  </IntlProvider>, document.getElementById('root'));

Now if we run the app we will see the same screen translated as before. LinguiJS offers a lot of cool features so I urge you to consider it for your next project.

Having explored those three powerful libraries for localization, let’s see one more option that competes equally with the latter.

i18next

i18next aims to target high as it promises to give you a complete solution to localize your product from web to mobile and desktop. i18next is a plugin-based framework and provides you with plugins to:

  • detect the user language
  • load the translations
  • optionally cache the translations
  • extension, by using post-processing – e.g. to enable sprintf support.

It works well with small projects and scales when the project has many translation files to load.

Let’s see how we can use it in our React App.

Install it first:

yarn add i18next react-i18next i18next-browser-languagedetector

Next, we need to create a file that will load our i18next configuration and any plugins we need to use. Let’s create a file named i18n.js and put our translations there:

import i18n from 'i18next';
import { initReactI18next } from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(initReactI18next)
  .use(LanguageDetector)
  .init({
    // we init with resources
    resources: {
      en: {
        translations: {
          "Welcome to React.js": "Welcome to React.js",
          "Declarative": "Declarative",
          "React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.": "React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.",
          "Declarative views make your code more predictable and easier to debug.": "Declarative views make your code more predictable and easier to debug."
        }
      },
      el: {
        translations: {
          "Welcome to React.js": "Καλώς 'Ηρθατε στο React.js!",
          "Declarative": "Δηλωτικό",
          "React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.": "To {{name}} καθιστά ανώφελη τη δημιουργία διαδραστικών διεπαφών χρήστη. Σχεδιάστε απλές προβολές για κάθε κράτος στο δικό σας\\n\" +\n" +
            "                    εφαρμογή και το React θα ενημερώσει αποτελεσματικά και θα αποδώσει τα σωστά στοιχεία όταν τα δεδομένα σας " +
            "                    αλλαγές.",
          "Declarative views make your code more predictable and easier to debug.": "Οι δηλωτικές προβολές καθιστούν τον κώδικα πιο προβλέψιμο και πιο εύκολο στον εντοπισμό σφαλμάτων."
        }
      }
    },
    fallbackLng: 'en',
    debug: true,

    // have a common namespace used around the full app
    ns: ['translations'],
    defaultNS: 'translations',

    keySeparator: false, // we use content as keys

    interpolation: {
      escapeValue: false, // not needed for react!!
      formatSeparator: ','
    },

    react: {
      wait: true
    }
  });

export default i18n;

We needed to put some fallback translations so that we have at least English displayed. Also, we used a LanguageDetector  plugin that will use a variety of method to detect the clients supported languages.

Next, we need to update our root component to use this i18n config inside the index.js:

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

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

So now we are ready to go. Let’s see how we can use it in our main Home.js component:

import React, {Component} from 'react';
import { withTranslation, Trans } from 'react-i18next';
import logo from './logo.svg';
import './Home.css';

class Home extends Component {

  render() {
    const { t, i18n } = this.props;

    const changeLanguage = (lng) => {
      i18n.changeLanguage(lng);
    };

    return (
      <div className="Home">
        <div className="Home-header">
          <img src={logo} className="Home-logo" alt="logo"/>
          <h2>{t('Welcome to React.js')}</h2>
          <button onClick={() => changeLanguage('el')}>el</button>
          <button onClick={() => changeLanguage('en')}>en</button>

        </div>
        <div className="Home-container">
          <div className="Home-items">
            <div className="Home-item">
              <h3 className="focus">
                {t('Declarative')}
              </h3>
              <div><p>
                <Trans name={'React.js'}>
                  React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.
                </Trans>
              </p>
                <p><Trans>Declarative views make your code more predictable and easier to debug.</Trans></p>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

// extended main view with translate hoc
export default withTranslation('translations')(Home);

We need to wrap our component with the withTranslation HOC passing our namespace. Then we have access to our translations. The Trans component enables you to nest any react content to be translated as one string. Supports both plural and interpolation.

We also added a little language switcher there. If you try to use it you may notice that the switch happens almost instantly and it does not reload the page.

i18next is a huge framework and it supports all the major javascript frameworks over there. I suggest you give it a try and see what a complete solution it is.

Conclusion

When it comes to internationalizing your React apps, it’s important to provide a holistic and future-proof solution that will not inhibit your development efforts. At the end of the day, it’s not just updating some JSON files. Fortunately, Phrase can make your life as a developer easier! To learn more about Phrase, please refer to the Getting Started guide.

If you found these libraries helpful, stay tuned on this blog for more articles related to this subject, because we are not there yet. I18n has a lot of factors you need to consider and great care has to be given in order to provide the best locale experience for your global community of users.

4.7 (93.3%) 182 votes
Comments