I18n with React and i18next

Danny Hurlburt

More and more apps are being designed for the global market which means your app will need to work for an audience using various languages and dialects. React does not have internationalization (i18n) built-in, but it is not hard to internationalize an app, especially with the help from i18next.

i18next is an i18n framework written in and for JavaScript. It provides the standard i18n features of interpolation, formatting, and handling plurals and context.

A 30,000 foot view of i18next would be that it provides a function that takes a key, some options, and returns the value for the current language.

The following is a simple example of using the aforementioned function with a simple key and no options.

i18n.t('error') // 'An error occurred' in English and
                // 'Ocurrió un error' in Spanish.

Because the function will likely be called several times in an app, the creators of i18next have chosen a shortname: t – which is short for translate. It is common to see the t function in the i18next documentation and you will see it used here in this post.

Although i18next is designed to work with many frameworks, this post will focus on how to internationalize a React app using i18next.

In order to use i18next with React, the t function needs to be made available to the components that need to be internationalized. This can be done by various means. We’ll demonstrate a couple below.

Manual Integration

Before i18next can be used, it needs to be configured. Here is the configuration used by the examples in this post. It turns off value-escaping since React already takes care of that for us, it sets the current language to English, and it hardcodes translations for two languages (English and Spanish). See the i18next configuration for more options.

Module: i18n.js

import i18next from 'i18next';

i18next
  .init({
    interpolation: {
      // React already does escaping
      escapeValue: false,
    },
    lng: 'en', // 'en' | 'es'
    // Using simple hardcoded resources for simple example
    resources: {
      en: {
        translation: {
          age: { label: 'Age', },
          home: { label: 'Home', },
          name: { label: 'Name', },
        },
      },
      es: {
        translation: {
          age: { label: 'Años', },
          home: { label: 'Casa', },
          name: { label: 'Nombre', },
        },
      },
    },
  })

export default i18next

Manual integration begins with importing our pre-configured instance of i18next which will provide the t function.

import React from 'react';

// Import a pre-configured instance of i18next
import i18n from './i18n';

function Gator({ gator }) {
  return (
    <div className="Gator">
      <label>i18n.t('name.label')</label>
      <span>{ gator.name } 🐊</span>
      <label>i18n.t('age.label')</label>
      <span>{ gator.age }</span>
      <label>i18n.t('home.label')</label>
      <span>{ gator.home }</span>
    </div>
  )
}

Although the example above will render the labels using the current language (English), the labels won’t be updated after the language changes since React does not yet know how to determine when the language has changed. So, let’s tweak the code to re-render the component when the language changes.

import React from 'react';

// Import a pre-configured instance of i18next
import i18n from './i18n';

class Gator extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      lng: 'en'
    }
    this.onLanguageChanged = this.onLanguageChanged.bind(this)
  }

  componentDidMount() {
    i18n.on('languageChanged', this.onLanguageChanged)
  }

  componentWillUnmount() {
    i18n.off('languageChanged', this.onLanguageChanged)
  }

  onLanguageChanged(lng) {
    this.setState({
      lng: lng
    })
  }

  render() {
    let gator = this.props.gator,
        lng = this.state.lng

    return (
      <div>
        <label>{ i18n.t('name.label', { lng }) }</label>
        <span>{ gator.name } 🐊</span>
        <label>{ i18n.t('age.label', { lng }) }</label>
        <span>{ gator.age }</span>
        <label>{ i18n.t('home.label', { lng }) }</label>
        <span>{ gator.home }</span>
      </div>
    )
  }
}

See a working example of the above code.

Although we fixed the problem of the component not re-rendering after the language changes, we introduced a lot of boilerplate code:

  1. Added two lifecycle methods (componentDidMount and componentWillUnmount). (Which means we had to switch from a functional component to a class component.)

  2. Added an event listener so we can notify React that the language changed.

  3. Introduced a mechanism to cause the component to re-render. In the example above, state is being used. Another alternative is to call forceUpdate after the language changes.

Is there an easier way? Yes, there is!

The react-i18next Binding

Fortunately, there is an easier way with a binding: react-i18next.

import React from 'react';
import { I18n } from 'react-i18next';

// Import a pre-configured instance of i18next
import i18n_unused from './i18n';

function Gator({ gator }) {
  return (
    <I18n>
      {
        (t) => {
          return (
            <div>
              <label>{ t('name.label') }</label>
              <span>{ gator.name } 🐊</span>
              <label>{ t('age.label') }</label>
              <span>{ gator.age }</span>
              <label>{ t('home.label') }</label>
              <span>{ gator.home }</span>
            </div>
          )
        }
      }
    </I18n>
  )
}

The React binding for i18next provides the I18n component. It expects a single function expression as its children. This function will be passed the t function. (The use of a function expression as children of a component is known as the function-as-children pattern.)

A lot easier! See a working example of the above code.

So, take a look at i18next and react-i18next for more details on configuration, more advanced translation, and other binding options.

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...