Getting started with internationalization (i18n) in React

Getting started with internationalization (i18n) in React

The i18n is widely known between the developers for being a hard and largely unsolved problem. The internationalization of your application is a primordial step to make it succeed in other regions or planets where they don't speak the same language as you, it should support virtually any language or local.

In this article, you will learn how to localize your React application.

1. Install the i18n module

To handle the internationalization in your application, we are going to use the i18n-react module. This module offers easily handling of text internationalization and externalizing. Install the module using the following command in your command line:

npm install i18n-react --save

The module works pretty well, is really easy to understand and to use. For more information and documentation, please visit the official Github repository here.

2. Using the module

The logic to make this module works is simple:

  1. Require or import the module at the top of your app (Main component).
  2. Add the texts of your app at the top of your app (Main component). This action doesn't need to be necessarily executed from the main component but child components too.
  3. Use the module and translate labels in other components.

The translation system works with an object that contains some properties and strings as values (these strings contain the labels in one language). 

Provide text

You need to provide as first some text that will be translated with the setTexts method. Obviously the module needs to be imported or required according to the EcmaScript version that you use:

/* ES6 & TS */
import T from 'i18n-react';
/* commonJS */
var T = require('i18n-react').default;
/* when using UMD version w/o modules */
var T = window['i18n-react'].default;


// Set some texts
T.setTexts({
    welcome: "Bienvenido {username}!",
    buttons: {
        exit: "Salir",
        start: "Iniciar"
    }
});

Note that i18n supports interpolation to modify some value in the translation dinamically (as not every language follow the same structure). One of the fundamental features with the implementation of an internationalization module is that it shouldn't provide support only for simple string-to-string translations but support for pluralization. i18n-react supports other localization, pluralization, support for formal and informal, please read more about these features here.

This method will be probably initialized once and it will probably executed in your application entry point.

Translating

Once there are some available texts, they can be used to translate the labels in your app with the same module. You can use the single translate method to retrieve the label with the key or render directly different type of elements like a, p or span elements: 

import React from 'react';
import {render} from 'react-dom';

// Our custom react component with some elements inside
import Main from './Main';

// Import the translation module
import T from 'i18n-react';


// link
<T.a text="buttons.exit" href="a's href"/>
// Text
<T.text tag='h1' text="buttons.start" context: "context-if-any"/>
// Paragraph
<T.p text={{ key: "path.to.string", var1: "string", var2: 2}} anyValidHtmlAttribute="p.will.have.it"/>
// Span
<T.span text={{ key: "path.to.string", context: "context-if-any", var1: "string", var2: 2, var3: <span className="c">X</span>}}/>

/* Or translate without using an Element with the T.translate method */
<h1>
    {T.translate("welcome", { username: "Bruce Wayne" })}
</h1>

By default if translation for the specified key is not present the key itself is returned to help you find the missing translation. This behaviour can be augmented by providing notFound property in the options or MDText object.

Example

We have the following app.js that initializes a React application. Here we are going to use the i18n module an we'll provide an object that contains the text of some buttons in Spanish. They will be declared at the initialization of your app:

import React from 'react';
import {render} from 'react-dom';

// Our custom react component with some elements inside
import Main from './Main';

// Import the translation module
import T from 'i18n-react';

// Add the texts for your APP
T.setTexts({
    labels: {
        accept: "Aceptar",
        decline: "Declinar"
    }
});

// Render the Main app react component into the app div.
// For more details see: https://facebook.github.io/react/docs/top-level-api.html#react.render
render(<Main />, document.getElementById('app'));

Now, that we have some labels, we can use it from our components. In our Main component we will just add 2 buttons that triggers an alert according to the clicked item, the labels of these buttons we'll be obtained through the i18n module:

import React from 'react';
// Some button component example
import FlatButton from 'material-ui/FlatButton';

// Import Internationalization
import T from 'i18n-react';

export default class Main extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            
        };
    }

    actionAccepted = () => {
        alert("You declined this");
    }

    actionDeclined = () => {
        alert("You declined this");
    }

    render() {
        return (
            <div>
                <FlatButton
                    label={<T.span text="labels.accept" />}
                    onTouchTap={this.actionAccepted}
                />
                <FlatButton
                    label={T.translate("labels.decline")}
                    onTouchTap={this.actionDeclined}
                />
            </div>
        );
    }
}

Note that the text parameter or the first argument of the translate function expects the "Javascript style" name of the object (Structure of the object).The previous example would unsurprisingly render:

Example React Buttons Spanish

As you can see, for the only thing you need to worry about is how to provide the labels. Note that we are doing this only for spanish, therefore if you have another object for different languages then you needs to execute the setTexts method with the labels of the new language as first argument.

Is recommendable to store the translations in external files (instead of mix them directly within your code) like JSON or YAML and then require them in your code again in object format.

3. Loading locale data from files

As previously mentioned, it is preferable to store the locales in special files like JSON that makes easy the administration of the labels for different languages in your app. This step is totally up to you, the way in you retrieve the locale from the browser, the way in you store the locales in files etc. However we are going to show you a way that works and it's easy to implement.

As first we need to know how we are going to store the locales in external files, we are going to use JSON files. For example the labels in spanish will be stored in a JSON file (es.json) with the following content:

{
    "labels": {
        "accept": "Aceptar",
        "decline": "Declinar"
    }
}

Then, once you have some labels for any language, you need to determine in the browser which language is using currently the user. This vary a lot with your project, you may store this language in a database or something, however to explain it we are going to use the language of the browser. This will be retrieven with the following code:

var language = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage;

// language would be something like es-ES or es_ES
// However we store our files with format es.json or en.json
// therefore retrieve only the first 2 digits
if (language.length > 2) {
    language = language.split("-")[0];
    language = language.split("_")[0];
}

// language would be now only "es"

As the translation files of our project will be very simple, they will have the name structure (only 2 digits): es,de,en,ru,jp,zh etc. We are going to use it to request the correct translation files.

According to the way you're working and the size (and quantity) of locales of your app, you can load the locales from static JSON files.

A. Request locale files

As you surely know, you are free to use any 3rd party library to request files in React. You can use the native browser implementation of fetch (recommendable also to include a polyfill like whatwg-fetch for outdated browsers) or use special libraries like axios (or jQuery ajax, nobody judges nobody here).

The logic to make it work is pretty simple, as you know the setTexts method expects an object, therefore that's the only thing you need to worry about, provide an object with the structure that you need for your internationalization and you're ready to go. The following example (we are going to use the getJSON method of jQuery ajax that probably every developer knows) downloads a JSON file and converts it to a Javascript Object that is automatically delivered as first argument to the setTexts method of the internationalization library that we've just installed:

$.getJSON("path-to/locales/"+ language +".json", function( data ) {
    T.setTexts(data);
});

Note that you will need to update the component after the update of the locale. This function can be triggered from any form element that changes the language of your app e.g a Dropdown, a Select etc. You can store locally the already used locales (using localStorage or webStorage) to prevent the execution from an innecesary request to the server.

B. With a webpack loader

In case your application uses webpack and your locale files aren't so big (just 3 languages and they don't increment the filesize of your app.min.js drastically) or it works offline (no worry about the app.min.js filesize) e.g a React application that runs under Electron or Cordova, you can add the locale files in json format to your application using a special loader for json files (json-loader).

Note

Since webpack V2, JSON files will work by default. You might still want to use this if you use a custom file extension.

Install the json-loader module with the following command:

npm install --save-dev json-loader

Once the module is installed, you need to modify your webpack-<env>.config.js (production and development) files and add the custom loader: 

{
    test: /\.json$/, 
    loader: "json", 
    include: "path/to/locales/json-files"
}

Once made this, you'll be able to require locales and include them in your build file.

Note

Even if your app uses a lot of languages, is recommended to use this method at least with the default locale (e.g english). In this way, your first page will already have the basic messages it requires, avoiding a GET request with the default labels until the user explicitly changes the locale:

T.setTexts(require(`!json!./path-to-locales/${language}.json`));

Note that with webpack is necessary to prepend the !json! string to the URL to make it work properly. You can create a custom method that wraps all the previous steps

Updating labels dinamically

If you've already implemented the localization with this tutorial in your project, you may've noticed that if you update the texts with T.setTexts apparently nothing happens (although it really happens, the labels in your project aren't updated, they still with the same content of the first time that you executed setTexts). Let me tell you that this behaviour isn't a bug. Currently, there are no modules that automatically updates the elements dinamically on every component. Therefore you will need to figure out (according to the way your work with React, as normally when someone changes the language of an application it should be restarted or re-rendered in the case of React) how to update them dinamically e.g setting the locales in the state of a component.

If you don't want to spend time on it, there are other simple solutions that may work according to the architecture of your project, for example simply re-render the component may do the trick for some projects:

// The app starts normally with english locale ...

// Then set it to spanish
T.setTexts(require('!json!./locales/es.json'));
// Render component again ... voila in Spanish
this.forceUpdate();

// Wait 5 seconds ...

// Now change it to German
T.setTexts(require('!json!./locales/de.json'));
// Render component again ... voila in german
this.forceUpdate();

If you choice the easy way, read more about how to re-render a React component, because there's a discussion about how a component should be re-rendered. Apparently forceUpdate should be avoided because it deviates from a React mindset, so you can use another way like resetting the state from the component:

// The app starts normally with english locale ...

// Then set it to spanish
T.setTexts(require('!json!./locales/es.json'));
// Render component again ... voila in Spanish
this.setState(this.state);

// Wait 5 seconds ...

// Now change it to German
T.setTexts(require('!json!./locales/de.json'));
// Render component again ... voila in german
this.setState(this.state);

Note that the update would affect the single component (useful only when you use an app with react router or something like that).

Happy coding !

Become a more social person