Localize Your Client-Side JavaScript Applications

Display localized strings in a JavaScript-intensive web page, with help from the jQuery Globalize plug-in

Dino Esposito

July 16, 2012

10 Min Read
ethernet cable sitting on map

For a long time, the notion of localizing JavaScript intensive pages was a non-issue for the simple reason that there weren't enough JavaScript-intensive pages to localize. Localization of web pages was simply a matter of using the most appropriate tools available on the server. In past installments of this column, I covered the foundation of multi-lingual applications -- from using .resx files for localized strings to the building of an engine that can pick up the correct resource (master page, layout, user control, style sheet, or image) for the current locale. (See the Learning Path for a list of relevant articles.) However, all this work is controlled and orchestrated on the server side using a managed language and the native tools of the specific ASP.NET framework in use. For example, in an ASP.NET MVC solution you'd add extension methods to the UrlHelper class; in ASP.NET Web Forms, you'd leverage the ResolveUrl method on the Page class. For more information on JavaScript, see "How to Structure Your JavaScript Code: Part 1" and "Structuring JavaScript Code in HTML5 Applications: Part 2."

The bold advent of HTML5 and the Single-Page Interface pattern increased the average amount of JavaScript code that comes with each web page. More often than not, this JavaScript code goes beyond basic event-handling and user interface adjustments and contains bits and pieces of presentation logic. Because of this, it's reasonable to expect that you'll need to localize some UI elements and messages that the client-side presentation logic will display. This article presents an approach to building a custom engine to mimic server-side .resx resources on the client side.

Principles of JavaScript Development

Because modern web pages tend to be highly dynamic and contain a lot of JavaScript code, if you don't use a disciplined approach in your coding, you'll likely end up with kludges and spaghetti code. The code base becomes messy, and bugs spring up from everywhere. As a countermeasure, I suggest you split the JavaScript code into four main areas: presentation, localization, application state, and application behavior.

The presentation layer can be architected using a Model-View-Controller pattern (including the popular variation of the pattern, known as Model-View-ViewModel). There are quite a few libraries available that can help you in this regard. The implementation of application (client) state is essentially all about having one JavaScript dictionary class to gather the state information that needs be persisted in a cookie or, more likely, in some form of client storage. The implementation of the application behavior -- that is, handlers for UI events and pieces of business logic you hold on the client —might be partially mandated by the library you use for the presentation. If not, it's about organizing related functions in the same structure, ideally on a per-use-case basis.

And what about localization: How do you handle this area in your JavaScript code? A localized application will display text in a specific language and also use date, time, and currency formats for the specific culture. Available jQuery plug-ins and facilities can help you handle aspects of localization, but for the most part it's up to you to determine the best localization approach to use in your application. Let's look at some examples that will give you a feel for how to handle localization in JavaScript code.

Taming Globalization on the Client Side

In software, globalization is about displaying dates, times, numbers, and currencies in the format and with the symbols that members of a given culture expect. All you need is a framework that can transparently convert user-entered strings into the correct numbers, dates, and currency values. Likewise, the framework should be able to accept raw numbers and date objects and render them into culture-specific strings.

A popular library that provides such a framework for dealing with globalization issues is Globalize—a jQuery plug-in. The Globalize library offers a few powerful methods to parse numbers and dates and format raw values into strings. Figure 1 summarizes several of these functions.

Function

Figure 1: Basic globalization functions in the Globalize jQuery plug-in

Globalize.format

Globalize.parseDate

Globalize.parseFloat

Globalize.parseInt

Overall, the Globalize library offers formatting facilities similar to those you find on the server side embedded in the DateTime .NET Framework type. Armed with these functions, you can easily manipulate the formatting of special types sensitive to culture changes. But what about detecting the current culture on the client side?

Detecting the Current Culture

Depending on how your application is laid out for internationalization, the information about the current culture could be a parameter that the user explicitly sets from a menu (and that the application saves as a cookie), or it could be something you detect programmatically. In the former case, you have an explicit culture string to pass to the functions in Figure 1 to parse numbers and dates. If culture is detected programmatically, you can find two distinct values: the culture of the device (smartphone, tablet, or laptop) and the culture of the specific browser making the request. The two values could easily be different. Which one should you pick up?

I tend to use a fairly simple rule: I go with the device culture in a mobile scenario and try to figure out the browser's language preferences in a desktop scenario. The following code works well for detecting the device language on most browsers, including mobile browsers:

var language = navigator.language;if (typeof navigator.language == "undefined")     language = navigator.systemLanguage; // Works for IE only

The navigator.language expression returns the system's language on most browsers. If the browser is Internet Explorer (IE), you need an additional if branch that checks another property. Note that this code might fail in detecting the language that the user explicitly set as the browser's preferred language. Figure 2 shows the browser's language dialog box for IE; similar windows exist for other browsers.

Figure 2: The Language Preference property window in Internet Explorer


The languages you set through the window in Figure 2 are rendered as a weighted string that becomes the value for the Accept-Language HTTP header sent with any requests. Here's an example:

en;q=0.4, es;q=0.5, it

The value of the header contains multiple language identifiers optionally associated with a quality factor. The quality factor -- the q parameter -- represents an estimate of the user's preference for the language. The default quality value is 1. Interpreting the value of the header is up to the application, and the analysis should look into the priorities set with the q parameter. In the previous example, English, Spanish, and Italian are all accepted languages, but Italian (default q=1) is the most preferred. The Globalize plug-in has a built-in method that sets the current culture analyzing a string, as shown in the following example:

Globalize.culture( "en;q=0.4, es;q=0.5, it" );

The problem is that reading the Accept-Language header from JavaScript might not be easy. Common workarounds consist of having a server endpoint return that header value. My favorite trick to use in reading the Accept-Language header, however, is another approach: When I serve the HTML client page, I programmatically add a global variable with a fixed name set to the value of the HTTP header as detected on the server. Here's a short example that shows how to do this in a Razor-based ASP.NET MVC page:

Various other options exist to convey to the client side the value of the Accept-Language HTTP header. For example, you can copy the value to a meta tag when serving the page, then use jQuery facilities to read the meta tag back from JavaScript.

Accessing More Cultures to Globalize

The Globalize library is a valid solution for globalization and also for localizing text. In particular, the library supports a long list of predefined cultures but also lets you add a culture not on the list. Adding an unsupported culture is as easy as creating a new entry in the cultures dictionary, as follows:

Globalize.cultures[ "..." ] = {    name: "...",    :}

Likewise, you can add to an existing culture new, unique properties that aren't natively supported. Here's how you can customize an existing culture by adding new properties:

Globalize.addCultureInfo( "it", {    numberFormat: {    newProperty: value    }});

Processing the new property appropriately is up to your code and isn't necessarily the responsibility of the library. (For more information, see the Globalize web page.)

Creating Your Own Solution for Localized Text

The Globalize library also provides a way to return a string of text as localized to a given (or current) culture. In this case, you use the localize function, as follows:

var textToDisplay = Globalize.localize(Key_AppTitle, "it"));

The first parameter for the localize function is a key that identifies the text to translate. The key in this context is analogous to the keys you use in a .resx resource file in a server-side environment. To define keys, you can use the code in Figure 3.

Globalize.addCultureInfo( "en", {    messages: {    Key_AppTitle: "My Cool Application"    }});Globalize.addCultureInfo( "it", {    messages: {    Key_AppTitle: "La mia applicazione"    }});

If you don't feel comfortable using a general-purpose framework for text localization, you can instead use the pattern shown in Figure 4.

GLOBALS.Literals_En = {    Separator: ",",    AppName: "My cool application",    ...}GLOBALS.Literals_It = {    Separator: ".",    AppName: "La mia applicazione",    ...}

Here, you define various dictionaries, one per supported language. Next, you define a container of language dictionaries:

GLOBALS.AvailableLiterals = {    EN: { value: GLOBALS.Literals_En },    IT: { value: GLOBALS.Literals_It }}

Finally, you select one of these dictionaries based on the currently selected language. Here's the relevant code:

GLOBALS.loadLanguage = function (lang) {    GLOBALS.Literals = GLOBALS.AvailableLiterals[lang].value;}

The lang parameter is any string that identifies a language that you obtained in some way -- for example, from the navigator or a server endpoint. In this example, it's assumed that the lang parameter is EN or IT, as the entries in the AvailableLiterals dictionary. The following example shows how you'd use localized text in your code:

alert(GLOBALS.Literals.AppName);

Localize on the Client Side

Until recently, the client side of computing was merely a way to hide and display pieces of the UI. With the increasing use of JavaScript on the client and HTML5, the type of code we write and run within the browser is changing to include more and more presentation logic code. Because of this, client-side localization is fast becoming a necessity for displaying messages, text, and resources localized for the user's culture. With the help of the Globalize jQuery plug-in and the techniques I've covered here, you can start incorporating localization in your client-side JavaScript code.

Learn more about application localization:

Learn more about JavaScript:

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like