As I’ve been working on several React apps lately, the question of translations has come up multiple times. Translations have been an area of focus, how to do them well in React? We want a flexible solution that does not require us to do anything special in components requiring translated text.

I’ve made a demo project that demonstrates my solution succinctly. This blog post will go through and explain parts of it, and you are welcome to use it as a basis for your own solution to Translations.

The Solution

The solution is a store to keep all translations. There is an ActionCreator that handles the logic of fetching a JSON file from the server with all translations, and an ES6-style mixin for components that need to be localized that handles refreshing themselves (bypassing shouldUpdateComponent) to the new language when it changes.

The resultant app can be seen below:

Small demo of the translation component, try changing the language by clicking on the different buttons at the top.

To active translations for a component, you use the TranslatedComponent mixin [1].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import TranslatedComponent from '../utils/TranslatedComponent.js';

class WelcomeBanner extends React.Component {
  render() {
    return (
      <div className="banner">
        <h1>{this.translate('banner.header', this.props.who)}</h1>
        <p>{this.translate('banner.paragraph', this.props.who)}</p>
      </div>
    );
  }
}

WelcomeBanner.propTypes = {
  who: React.PropTypes.string.isRequired,
};


// Returns nothing because it mutates the class
TranslatedComponent(WelcomeBanner);
export default WelcomeBanner;

The magic happens on line 21. TranslatedComponent will modify the class so that it reloads itself when the language changes through a call to TranslationActionCreator.changeLanguage(languageCode).

It will also provide the class with the translate function, this functions takes a key into the language JSON dictionary, interpolates it with the arguments and then gives back the text.

The solution present in the repository is meant to be copied and act as a starting point for providing translations in your app. That is why it is not provided as a package. Copy-and-paste programming is a thing!

The most important files to take a look at are:

  • TranslationStore.js — The store that intermediates the translations. Here is also an example of how the moment.js locale is switched with the language.
  • WelcomeBanner.js — Demonstrates how you actually use the translations to modify your text.
  • TranslatedComponent.js — This is where the magic happens to configure your class so it updates dynamically. Might be interesting to look at, but is not necessary to understand the code.
  • en.json — The JSON data that acts as a base for the translation</li>

The Details

The way I’ve done it, is by defining a store with an associated action creator that holds all translations in the application. The translations are stored as a simple JSON file containing a single dictionary with all translations for a single language. One could imagine that in a truly huge application, you would need to split this into multiple files. But as long as you clock in under a thousand translations I doubt this will be a real problem.

The translation file is defined as follows:

{
  "banner.header": "Hello {0}",
  "banner.paragraph": "Click on the languages above to trigger a change of this text - you - {0} has the power!",

  "nameinput.label": "Your name: "
}

This file is then fed to the TranslationStore (via an action) that manages the translations. This store has a very simple interface:

  • getCurrentLanguage() Returns the language the app is currently displaying, or null if one is not loaded yet
  • getAvailableLanguages() Returns an array of all languages we support. Each object in the array contains the language code, and the localized name of the language.
  • getLanguage(languageCode) Given a language code 'en', returns the language associated with it (from the list given by getAvailableLanguages).
  • get(translationKey, arguments...) Returns the translation for a given key, will interpolate {0} arguments in the translation.
  • has(translationKey) Returns true if a given translation available.

As long as you use the TranslatedComponent mixin, you don’t need to think about what language the user has currently selected, and it can be reloaded at any moment. If you want to get the current language for some reason, you query it with TranslationStore.getCurrentLanguage().

You can find the entire code for the demo above on github: ReactTranslationStoreDemo

  1. Since ES6 does not support mixins, we simulate it through a wrapper function that replaces componentDidMount / componentWillUnmount with new versions, and then calls the original functions.
  2. Note that the solution makes no effort to handle number localization, for your needs, this might be necessary, and requires more extensive logic to handle correctly.