Language code URL in React-Intl

Recently we've changed our URL structure for one of the Mozilla's projects to make it readable and the most make sense for both developers and users.

What it used to be:

http://localhost:3000/page-name-en-us

What we want the end result to be:

http://localhost:3000/en-US/page-name

Our approach in solving this since we are using React, Webpack and React-Router for handling our routes we can simply add some codes to handle the routes in our entry file in my case we call this client.jsx which is defined in webpack.config.js

client.jsx

import React from 'react';  
import Router from 'react-router';  
import routes from './routes.jsx';  
import i18n from '../locales/i18n.js';  
import assign from 'react/lib/Object.assign';


Router.run(routes, Router.HistoryLocation, function (Handler, state) {  
  var lang = i18n.isSupportedLanguage(i18n.currentLanguage) ? i18n.currentLanguage : i18n.defaultLang;
  var queryString = state.query;

  // checking if language code is part of the URL e.g. /en-US/thank-you
  if(i18n.urlOverrideLang(queryString).test) {
    // but is the language code supported in our app?
    if(i18n.isSupportedLanguage(i18n.urlOverrideLang().lang)) {
      var messages = i18n.intlDataFor(i18n.urlOverrideLang().lang);
      values = assign(values, messages);
    } else {
      pathname = pathname.split('/')[2] ? pathname.split('/')[2] : '';
      return this.replaceWith(pathname, {}, queryString);
    }
    // if not we will hijack the URL and insert the language code in the URL
  } else if(!i18n.urlOverrideLang(queryString).test) {
    return this.replaceWith("/" + lang + pathname, {}, queryString);
  }
  React.render(<Handler {...values} />, document.querySelector("#my-app"));
});

I have created a little helper functions to do a lot of the work above and they are very useful client side functions:

i18n.js

import assign from 'react/lib/Object.assign';  
var locales = ['en-US', 'de', 'fr'];

// This is essentially bulk require for our translation files which is in JSON format
var req = require.context('./', true, /\.json.*$/);  
var dictionaries = getAllMessages(req);

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

// we need to make sure we transform the given locale to the right format first
// so we can access the right locale in our dictionaries for example: pt-br should be transformed to pt-BR
function formatLocale(lang) {  
  lang = lang.split('-');
  return lang[1] ? `${lang[0]}-${lang[1].toUpperCase()}` : lang[0];
}

function getMessages(locale) {  
  var messages = dictionaries[locale] ? dictionaries[locale] : dictionaries['en-US'];
  return assign({}, dictionaries['en-US'], messages);
}


var locale = formatLocale(navigator.language);  
module.exports = {  
  intlData: {
    locales : ['en-US'],
    // 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.
    messages: getMessages(locale)
  },
  defaultLang: 'en-US',
  currentLanguage: locale,
  isSupportedLanguage: function(lang) {
    return !!dictionaries[lang];
  },
  // This method will check if we have language code in the URL
  // by extracting the pathname from `window.location` and split
  // them to find language code. The expected language code is in the
  // first parameter e.g. /en-US/thank-you. If we don't find the language code
  // we will assume that the URL does not contain language code and return false for `test`
  urlOverrideLang: function(path) {
    var localPath = path || location.pathname;
    var localeCode = localPath.split('/')[1];
    var pathname = localPath.split('/')[2];
    return {
      test: locales.indexOf(localeCode) !== -1,
      pathname: pathname,
      lang: locales.indexOf(localeCode) !== -1 ? localeCode : null
    };
  },
  intlDataFor: function(lang) {
    var locale = formatLocale(lang);
    return {locales: [locale], messages: getMessages(locale)};
  }
};

Now in our app we can define routes like this

import { Route } from 'react-router';

module.exports = (  
  <Route>
    <Route name="home" path="/:locale/?" handler={yourHandler}/>
    <Route name="home" path="/:locale/page-name/?" handler={yourHandler2}/>
  </Route>
);

And that's it folks! Now you have language code in the URL! If you are confused or have any question please feel free to tweet @alicoding :)