Localizing React app using React-Router with React-Intl

In the past, when we do our localization work we'll usually write our own library to do localization, and one of the example is node-webmaker-i18n.

Recently, I had to localize our React application for Webmaker and this time our team thought we might change our approach and let's not write our own, but use something that's already out there, so I did a research about different libraries for internationalization for React application and ended up using React-Intl by Yahoo.

Obviously, there are some good reason why we didn't write our own this time and why we ended up using React-Intl instead of other options. One of the reasons we didn't write our own this time simply because we don't want to reinvent the wheel and helping the community by contributing to the existing library is also a good idea. I find that React-Intl got a lot in common in term of their needs in the library that they've wrote, and they are very responsive and helpful when we had problem using their library.

Now, let's get started on how to integrate React-Intl with React app that's using React-Router for handling routes.

NOTE: We're using Webpack.js to handle our modules bundling.

entry.js

import React from 'react';  
import Router from 'react-router';  
import routes from './routes.jsx';  
import messages from './messages';

var locale = navigator.language.split('-')  
locale = locale[1] ? `${locale[0]}-${locale[1].toUpperCase()}` : navigator.language

var strings = messages[locale] ? messages[locale] : messages['en-US']  
strings = Object.assign(messages['en-US'], strings);

var intlData = {  
    locales : ['en-US'],
    messages: strings
};

Router.run(routes, Router.HistoryLocation, function (Handler, state) {  
  React.render(<Handler {...intlData} />, document.querySelector("#my-app"));
});

routes.jsx

import React from 'react';  
import { Route } from 'react-router';


var routes = (  
  <Route>
    <Route name="home" path="/" handler={require('./home.jsx')} />
  </Route>
);

module.exports = routes;  

messages.js

// This is essentially bulk require
var req = require.context('../locales', true, /\.json.*$/);  
var exports = {};

req.keys().forEach(function (file) {  
  var locale = file.replace('./', '').replace('.json', '');
  exports[locale] = req(file);
});

module.exports = exports;

Just to explain a bit what's going on here in each file.

  1. messages.js is basically just to pre-load all the locale files, so that you don't have compile time error with webpack.

  2. routes.jsx this file is pretty straightforward since it's just the normal way of declaring your routes in react-router.

  3. entry.jsx this is where it gets a bit complicated. First thing first let's talk about this line

var locale = navigator.language.split('-')  
locale = locale[1] ? `${locale[0]}-${locale[1].toUpperCase()}` : navigator.language  

This basically just extracting the language code from the browser using navigator.language then we rewrite the string to match what we have in our dictionary that was stored in messages.js file. The reason I have to do toUpperCase() here because Safari will return en-us where as Firefox and Chrome will return en-US.

var strings = messages[locale] ? messages[locale] : messages['en-US'];  

This one is pretty simple since we are just trying to retrieve the strings from our dictionary and if we can't find that locale then just fallback to en-US.

strings = Object.assign(messages['en-US'], strings);  

Sometimes we will include a language with partial translation
and we need to make sure the object that we pass to intlData
contains all keys based on the en-US messages otherwise React-intl will throw.

Now, let's look at home.jsx where we will use React-Intl to dynamically change the string based on our language.

home.jsx

import React from 'react';

export class render extends React.Component {  
  mixins: [require('react-intl').IntlMixin],
  render() {
    return (
      <div>
        <h1>{this.getIntlMessage('hello_world')}</h1>
      </div>
    );
  }
}

en-US.json

{
  'hello_world': 'Hello World'
}

th-TH.json

{
  'hello_world': 'สวัสดีชาวโลก'
}

So, I think that's it! We are now fully localized our React app using React-Intl :)
If you find any problem in my code or have any question don't forget to leave a comment down below or tweet me @alicoding and I'm happy to answer :)