i18njs : Internationalize Your JavaScript With A Little Help From JSON And The Server

Schalk Neethling

8

Couple of weeks ago I spoke to a friend over at Mozilla about internationalization in JavaScript. This is an area that’s lacking in JavaScript and during this discussion I got the idea of implementing a solution based on the methods used be server side languages. Today I am happy to announce the first release of i18njs to fill this gap.

i18njs is made up of a couple of parts, the main script, json files and a small dependency on the server side. Why this server side dependency? Well basically, because their is no reliable way to get hold of the user’s language and/or locale using client side JavaScript. There are a couple of language properties exposed on the navigator Object such as .language, .userLanguage etc. but none of these are reliable and, for the most part, are not effected by changes made by the user with regards language preferences.

The one place that has this information and is most reliable is the ‘Accept-Language’ HTTP header. Unfortunately we cannot get at this using JavaScript on the client but, we can make a quick call to the server for this information, store it on the client and then we are good to go. I am not going to discuss the implementation of this, there are currently a demos using Java as well as Rails in the repo on Github you can check-out.

Let’s start by looking at the usage of i18njs:

Pretty straight forward. You create the options object and then populate two properties. The first is the URL that will return the sub-string from the Accept-Language header and the second indicates whether you wish to support locales or not. Let’s look at the second property a little more. When a user sets their language in their browser they have, for some languages, more than one option. For example for French, you can select French(France), French(Canada) etc. For these language selections the result returned from the server will include the language as well as the locale as follows: fr-FR or fr-CA

If you need to differentiate between locales in your application, then you want to set the supportLocale property to true. So why does this property exist and why is it important? The best way to explain is to discuss how i18njs works after the call to the server has completed.

In server side languages internationalization is usually handled by loading some form of properties file that contains the strings, formats etc. localized for the current language and/or locale. These normally take the form of a text based properties file or in some cases a ‘standardized’ XML format, i18njs works in the same manner.

After the language has been determined, the appropriate localization file get’s loaded. These files are in the JSON format and the names of the files are named according to the language and, optionally, the locale so, if supportLocale is set to true and the current user language is set to French/Canadia it will try to load a file named fr-CA.json. If the supportLocale is false, it will substring the retunt value and use the first 2 characters only, which means it will now attempt to load a file simply names fr.json.

Your localization files should all reside in a folder called locale, the content of the file is up to you, as long as it is in a valid JSON format or parse error will result. Let’s look at a small sample. Below is thee contents of a en-US.json file:

And the following is then the French localized version:

As you can see from the above, the keys always remain the same irrespective of the language of the values, this is standard practice and ensures that your code does not have to change if the language changes. Say you were doing some form validation, you will use i18njs as follows in this scenario:

So, now that you know why you would want to set the supportLocale property and some of the inner workings of i18njs there is a couple of small details to cover.

This has been tested in IE6+, Firefox, Chrome, Safari and Opera and works everywhere. There is one aspect of i18njs that works slightly differently in IE7/6 and modern browsers and that is the way the localization data is stored on the client. In all modern browsers, and this includes IE8, once the data has been loaded the first time, the result is stored using localStorage part of the WebStorage API and therefore THE Ajax call to the server never happens again.

In IE6/7 on the other hand it will make a call to the server on each page load. Now, I could polyfil this but two things, the size of the localization files are generally not going to be large, generally smaller then the polyfil itself, and the performance hit users with the older versions are going to suffer will not be great. I also wanted to avoid yet another dependency on a polyfil as well as third party plugins.

One last thing to mention is the userSelected function. Even though Accept-Language is going to be accurate in what it returns, we do not want to lock our users down and not give them the option to switch to another language should they wish to, this then is where userSelected is used.

Say we some links at the top of our site or application:

We can hook userSelected to these links as follows:

Now when a user clicks any of those links, the language will be overridden and set to the user’s selected choice. The code and demos are available on Github, a live demo is available on the project page and I would love to hear your feedback. On a side note, you are more than welcome to use http://i18njs.espressive.org/get-lang if you do not feel like implementing the server side or are in a scenario where there is no real back-end code.

8 responses

  1. Jigar Shah wrote on ::

    When we are using struts application, where i18n is part of framework, we use kist a jsp with js variables, something like

    var error =
    ….

    Is this right or there is another alternative to this ?

  2. Schalk Neethling wrote on ::

    Hey Jigar,

    I am not pushing i18njs as ‘the’ solution for internationalization but, what often happens when people mix JSP, or PHP or whatever with JS to provide internationalization is that their JS are embedded in the page and they are not using an external JS files of a more modular nature that can be reused and better maintained.

    The idea behind i18njs is to provide a solution within JavaScript that is completely server agnostic. If however you have a solution that allows you to mixin JSP tags within an external JS fiile, great, use it. If not, consider i18njs and let me know your thoughts.

    Thank you for your comment,
    Schalk

  3. Billy Hoffman wrote on ::

    Cool library. I have two ways to make it better.

    First, you can replace the server-side component. Instead, use an XHR and set the HTTP method to “TRACE.” TRACE tells the web server to return the HTTP request you made (including the Accept-Language header) to you as the response. Now you can parse the Accept-Language header in JavaScript. Most web servers have this turned on by default, and its a trivial configuration change if its not. (Also, there were some security concerns with enabling TRACE, mainly around XSS attacks, but all modern web servers have patched this issue).

    Second, you just want to temporary cache the JSON data with localized messaging. And a polyfill for this is too heavy as you said. Why not replace localStorage with using window.name? It’s a bit of a hack, but will give you dozens of kilobytes to cache the JSON string, and it works in all browsers. This makes you logic very simple: if in window.name cache, parse the JSON, if not, fetch it and cache it.

    http://www.sitepoint.com/cookieless-javascript-session-variables/

  4. Simon wrote on :

    @Billy – no! Don’t stuff around with the window.name property. As you say, it’s a bit of a hack, and it’s also quite likely to conflict with other libraries doing similar tricks – e.g I’ve seen some session-management code using window.name to link a window to server-side sessions…

  5. Billy Hoffman wrote on ::

    That makes sense. On second thought, keep using localStorage. Worse case is only two older browsers get a slower implementation.

  6. gandalf wrote on ::

    Hey Schalk,

    I think you’re not talking about i18n library, but l10n library. I18n JS library is being developed by TC39 EcmaScript comitee – https://docs.google.com/document/pub?id=1rsUxJQ03Ql6o3bh6RN7J81dtYZXE7OVsdQBw_h5ASnM&ndplr=1

    For L10n needs, I’m afraid that your lib is not different from gettext in the sense that it supports plain key-value pairs without any flexibility for placeables, genders, declensions, plural forms etc.
    For Jetpack JS l10n I’m working on the library called Common Pool – http://diary.braniecki.net/search/Common+Pool and a more powerful but complex library called “l20n” – http://wiki.mozilla.org/L20n (JS bindings even have some initial patches for Gecko js service).

    If you want to chat about it, join us at #l10n-tools IRC channel! I’m going to start a l10n technologies community at Mozilla very soon and projects like yours are welcomed! :) I’d love more people to think and work on various approaches to localizability of our technologies :)

  7. Schalk Neethling wrote on ::

    Hey @gandalf, you are right l10n is definitely much more accurate. The projects looks seriously interesting, I will definitely see everyone on IRC.

    @Billy, I agree with @Simon and will avoid window.name and stick with localStorage and then Cookies for IE7- I will look into TRACE but, as their are some security concerns and the fact that not all servers will be configured for this, one will need to test and see whether TRACE is configured and if not, fall back to the ‘service’ url.

  8. mobile application development wrote on ::

    its really nice article.. thanks .. :)