Chapter 10. Internationalization

When you build a web application that grows to or near the saturation point in one country or culture, the easiest way to grow your user base is to expand to other cultures.

Internationalization or localization is the process of converting text strings, numbers, dates, and currency into localized languages and representations. One of the biggest hurdles to cross is obviously the translation of your app, but before you can even start down that road, you need to have some mechanisms in place to make that possible.

So when is the right time to get started? Right now. The biggest obstacle to translation is that you must plan for it or budget a whole lot more time and energy to painstakingly go through the app, find all the strings that need translating, and then add support for it after the fact. By far, the easiest way to support internationalization is to plan for it up front, as soon as you start building templates. Any string that gets displayed to the user should be run through a translation function first.

Instead of rolling your own translation solution, I recommend that you find a library to handle it for you. Some good ones include Moment.js for dates, and i18next for strings.

Microsoft produced a very thorough solution that works with strings, numbers, currency, percentages, and dates. It's called Globalize, and it was generously donated to the jQuery project, but it has since been made standalone and converted to work with AMD and node-style modules. You don't need to use jQuery to use Globalize.

One advantage of Globalize is that it uses a fairly comprehensive database of locale information that is hard to match in JavaScript. It uses the Unicode Common Locale Data Repository (CLDR). CLDR is used by many of the top software development companies, including Apple, Google, IBM, and Adobe. Anybody can contribute to it. You could think of it is the Wikipedia of globalization data.

Once you've chosen a solution, getting your translation data where it needs to be is your next challenge. It doesn't make sense to ship the whole catalog of translation data to the client if they only need a single locale set.

If you render all your templates on the client side, you can inject a default set of translations into the page data at page-load time, or make an asynchronous call to fetch translations, or both.

There have been apps where the page renders, and then an alternate translation loads, and the page re-renders. Those cases can present an awkward user experience, so make sure that you resolve the preferred locale before you invoke your initial page render.

Once it has been determined that the user has a locale preference different from the default, it's important to store that information so that you can present him with his preferred locale experience at every subsequent page load. It's a good idea to set a cookie that contains this information. It's probably not a good idea to store that information in a server-side session, because if the user is not logged in, or the session is expired, the user might have trouble logging in and getting his preferred locale experience back.

Unfortunately, there is no easy way to get a user's true locale preference on the client side without asking. However, the browser's request header can tell you the user's preference. There's a locale module for express that makes the best match easy to get:

var locale = require('locale'),
  // ... other requires ...
  // Tell locale which locales you support.
  supportedLocales = ['en', 'en_US', 'ja'];

// locale() returns a middleware function that pulls the
// req.headers["accept-language"] attribute and runs the value
// through locales.best(supported) to determine the closest
// matching locale.
app.use( locale(supportedLocales) );

Once you get the user's preference, it's probably a good idea to bundle any required locale data with the same response payload so that you don't incur the latency hit of an extra request. Most libraries will let you load translations from a JSON data object embedded in the page. For example, Globalize includes a .loadTranslation() method:

Globalize.loadTranslation('pt_BR', {
  greetings: {
    hello: "Olá",
    bye: "Tchau"
  }
});

To translate some text:

// Set the locale
  Globalize.locale('pt_BR');

  // Select the text you want to translate.
  // Organized by path:
  Globalize.translate('greetings/bye');

Conclusion

I hope you're thinking of how you can incorporate translations into whatever app you're currently working on. Remember, it's really hard to add translation capabilities to your app after the fact. Get in the habit of running all of your text through a translate function as soon as you start writing your first lines of code. When your app smashes its way through cultural divides, you'll be handsomely rewarded for the little bit of extra effort.

If you're a small developer and you think that translations might be out of your reach, you may be surprised. Services like One Hour Translation and Tethras charge less than $0.20 per word with no minimums. At that rate, you could translate a fairly complex application for just a few hundred dollars.