Chapter 6. Internationalization (i18n)

This chapter provides a brief synopsis of the tools Dojo provides for internationalizing a module. The key topics are defining bundles on a locale basis and using Core's facilities for formatting and parsing dates, currency, and numbers. In case it wasn't quite obvious, internationalization is usually abbreviated as i18n simply because it is such a long word to type; thus, the shorthand is the first and last letters, with the number 18 between them to take place of the 18 letters in between.

Introduction

If you have the good fortune of developing an application that becomes even mildly popular, you will certainly want to consider supporting more than one language. Although Dijit, which you'll learn about in Part II of this book, is already internationalized with several common locales, custom modules and widgets of your own devising will require some special attention. Fortunately, the toolkit provides techniques for supporting more than one language in a highly uniform way, saving you the headache of inventing your own system of mapping tokens back and forth; because Dojo manages how the loading takes place, you are also freed from thinking of ways to optimize the loading. Additional utilities also support common operations involving numeric formatting, currency, and more.

It's worth pointing out that the i18n facilities are technically part of Core, not Base. XDomain builds; however, include the dojo.i18n module as part of dojo.xd.js at the expense of an extra 2KB to workaround some tricky loading issues involving i18n bundles. Regardless, you should still dojo.require("dojo.i18n") into your page to be explicit about your intention to use these facilities as a matter of good form.

Internationalizing a module you've developed is simple because the details of your string table stay compartmentalized inside of a special nls directory that appears in your module directory with your JavaScript source files; nls stands for native language support. The nls directory itself breaks down all of the various translations by abbreviations for locales as defined in RFC 3066 (Tags for the Identification of Languages).[14]

For example, the abbreviation for generic English is en, the abbreviation for the dialect of English as it is spoken in the United States is en-us, and the abbreviation for generic Spanish is es. During the bootstrap process, Dojo queries your browser for your particular locale, and stores it internally. You can check it by typing dojo.locale in Firebug. The value of dojo.locale is what Dojo uses to determine the most appropriate translation when loading a module that has been internationalized.

Internationalizing a Module

Let's assume that you've gotten with the times and expanded on your magic genie module from the "Building a Magic Genie Example Module" in Chapter 2 to produce a psychic module. Let's assume that your default language is English, and you've determined that the first additional language you should support is Spanish.

Layout on Disk

Like any other module, your psychic readings module should simply be a source file contained in a typical directory structure:

dtdg/
  psychic/
    Psychic.js /* Lots of useful stuff in here */

Not surprisingly, an incredible utility provided by your psychic module is the ability to predict the future. As such, users of your module might stick it in a page and use it like so:

<script type="text/javascript">
    dojo.require("dtdg.psychic");
    dojo.addOnLoad(function(  ) {
      dtdg.psychic.predictFuture(  );
    });
</script>

Although there's an awful lot of real magic that happens in the predictFuture function, the part that we're interested in at the moment is where a String value actually gets written to the screen because that's where the internationalization work happens. As it turns out, the output from your module gets written out with the following logic:

dojo.byId("reading").innerHTML = predictFuture( /* magic */ );

As a first stab at internationalization, start out with plain old English and plain old Spanish, ignoring any particular dialects. Given this decision, the nls directory might look something like the following:

dtdg/
  psychic/
    Psychic.js
    nls/
        readings.js /* The default English translation bundle */
      es/
        readings.js /* The Spanish translation bundle */
      en/
        /* The default English translation folder is empty, so
           Dojo looks one level up for it in the nls/ directory */

By convention, each of the .js files containing translation information is called a bundle. The convention used is that the default translation bundle appears in the top level of the nls directory, but not in a language-specific directory. The basic rationale for this convention is that you always want a default translation to be available in the nls directory, which is the most logical place for it, and there's no value in including an exact copy of the default translation bundle in its own directory, (en in this case) because that would just be one more thing to keep up with.

Defining String Tables

Here's an excerpt from each of the readings.js files that shows some of the strings that are translated as part of the final reading.

First, the default readings.js file:

{
/* ... */
reading101 : "You're a Libra, aren't ya darling?",
reading102: "Can you please tell me your first name only, and your birthday please?",
reading103: "Yep, that's the Daddy."
/* ... */
}

And now, the es/readings.js file:

{
/* ... */
reading101 : "¿Eres un Libra, no, mi corazón?",
reading102: "¿Me puedes dar el nombre y tu cumpleaños por favor?",
reading103: "Sí, el es papá"
/* ... */
}

One of the beautiful things about localizing your application with Dojo is the simple manner in which you provide the listing of tokens.

Putting It All Together

It's time to put it all together and show just how easy it is to support multiple languages, but first, have a look at the relevant functions, listed in Table 6-1, that are involved in the process.

Table 6-1. Localization functions

Name

Comment

dojo.i18n.getLocalization(/*String*/moduleName, /*String*/bundleName, /*String?*/locale)

Returns an Object containing the localization for a given resource bundle in a package. By default, locale defaults to dojo.locale ; however, providing an explicit value allows you to look up a specific translation.

dojo.i18n.normalizeLocale(/*String?*/locale)

Returns the canonical form of a locale.

dojo.requireLocalization(/*String*/moduleName, /*String*/bundleName, /*String?*/locale)

Loads translated resources in the same manner as dojo.require would load modules. Note that this function is a Base function, not part of Core's i18n module.

Whereas you previously might have looked up reading102 value from a hash value like psychic.reading102, you now do it with help from the toolkit. If you've provided a translation for a particular user's locale, everything "just works." Looking up symbols for your various translations is as simple as the following generic piece of logic:

/* Require in Dojo's i18n utilities first... */
dojo.require("dojo.i18n");

/* Then, require in your various translations */
dojo.requireLocalization("psychic", "readings");

function predictFuture(  ) {

  /* Deep inside of your predictFuture function somewhere... */
  var future= dojo.i18n.getLocalization("psychic", "readings").reading597;
  return future;
}

Note that you can change your value of dojo.locale if you'd like to test out various translations. A good place to change this value is in djConfig block. Here's an example of how you might test out your Spanish translation from a local installation:

<head>
    <script type="text/javascript" src="your/path/to/dojo.js"
    djConfig="dojo.locale:'es'">
    </script>
</head>

<!--
  All of your internationalized modules now use the Spanish translation
-->

Just like any other module or resource, don't call dojo.i18n.getLocalization as part of an object property definition; instead, call dojo.i18n.getLocalization in a dojo.addOnLoad block:

dojo.addOnLoad(function(  ) {
     //Returns a localized Object
     var foo = {bar : dojo.i18n.getLocalization( /* ...*/)}
});

A nuance you may want to be aware of is that if your default locale is a variant of English and you are testing the Spanish localization, both the nls/es/readings.js and the nls/readings.js bundles are loaded. In fact, the default bundle that is contained in the nls/ directory will always be loaded. You can use Firebug's Net to verify this behavior for yourself.

Although this particular example didn't involve any dialects of either language, note that dialects are most certainly taken into account when loading localized bundles. For example, if your locale was en-us and there had been an en-us bundle provided, Dojo would have attempted to load both the en-us bundle and the en bundles, flattening them into a single collection for you to query via your various dojo.i18n.getLocalization calls. The working assumption is that when defining locale specific symbols for English, you want to provide as much general information as possible in the en bundle and then override or fill in gaps inside of the dialect specific bundles such as en-us.

Use build tools for snappy performance

As a final yet very important observation about internationalization, note that the Dojo build tools provided in Util can automatically take care of the myriad details associated with minimizing the number of synchronous calls and data redundancy when you perform a custom build of your module. It may not seem like much at first, but the build tools combine what could be lots of small resource files together and avoid all of the lookups and the latency that goes along with them. In terms of a snappy page load, it can really make all the difference. Util and the build tools are discussed in Chapter 16.

Dates, Numbers, and Currency

Additional Core facilities provide additional support for manipulating and supporting internationalization of dates, numbers, and currency via the dojo.date, dojo.number, and dojo.currency modules, respectively. In Part II, you'll learn that Dijit makes extensive use of these modules to provide advanced support for commonly used widgets. This section provides a quick inventory of these features.

Dates

Table 6-2 shows a quick overview of the dojo.date module. As you'll see, there are some real gems in here if you ever need to perform any routine processing of the built-in Date object.

Table 6-2. Summary of the date module

Name

Return type

Comment

dojo.date.getDaysInMonth (/*Date*/date)

Integer

Returns the number of days in date's month.

dojo.date.isLeapYear (/*Date*/date)

Boolean

Returns true if date is a leap year.

dojo.date.getTimezoneName (/*Date*/date)

String

Returns time zone information as defined by the browser. A Date object is needed because the time zone may vary according to issues such as daylight savings time.

dojo.date.compare(/*Date*/ date1, /*Date*/ date2, /*String?*/ portion)

Integer

Returns 0 if the two parameters are equal; returns a positive number if date1 > date 2 ; returns a negative number if date1 < date2. By default, both date and time are compared, although providing "date" or "time" for a portion produces a comparison strictly by date or time, respectively.

dojo.date.add(/*Date*/date, /*String*/ interval, /*Integer*/ amount)

Date

Provides a convenient way to add an incremental amount to a Date object by providing a numeric amount and the type of units. Units may be "year", "month", "day", "hour", "minute", "second", "millisecond", "quarter", "week", or "weekday".

dojo.date.difference (/*Date*/date1, /*Date*/ date2, /*String*/ interval)

Integer

Provides a convenient way to calculate the difference between two Date objects in terms of a specific type of unit, which may be "year", "month", "day", "hour", "minute", "second", "millisecond", "quarter", "week", or "weekday".

As of version 1.1 of the toolkit, getTimezoneName is not localized.

Numbers

The dojo.number module provides some handy functions, shown in Table 6-3 and Table 6-4, for parsing String values into numbers, formatting a Number in accordance with a specific pattern template, or rounding to a specific number of decimal places.

Table 6-3. Formatting options for the number module that are used in the dojo.number.format and dojo.number.parse functions provided in Table 6-4

dojo.number.format options

Type

Comment

pattern

String

Can be used to override the formatting pattern.

type

String

A format type based on the locale. Valid values include "decimal", "scientific", "percent", "currency", and "decimal". "decimal" is the default.

places

Number

Provides a fixed number of places to show, which overrides any information provided by pattern.

round

Number

Specifies rounding properties based on a multiple. For example, 5 would round to the nearest 0.5 and 0 would round to the nearest whole number.

currency

String

A currency code that meets the ISO4217 standard. For example, "USD" would signify U.S. Dollars.

symbol

String

A localized currency symbol.

locale

String

Allows a specific locale to be provided which drives formatting rules.

Table 6-4. Summary of the number module

Name

Return type

Comment

dojo.number.format (/*Number*/value, /*Object*/options)

String

Formats a Number as a String using locale-specific settings. Options may take on the values from Table 6-3.

dojo.number.round (/*Number*/value, /*Number*/places)

Number

Rounds a number to a given number of places after the decimal.

dojo.number.parse (/*String*/value, /*Object*/options)

Number

Converts a properly formatted String to a Number using locale-specific settings. Valid options include the following values from Table 6-3: pattern, type, locale, strict, and currency.

Currency

The dojo.currency module, described in Table 6-5 and Table 6-6, is similar to dojo.number in that it provides a means of formatting numeric values, only this time it is currency codes as defined in ISO427.[15]

Table 6-5. Formatting options for the currency module as used by the dojo.currency.format and dojo.currency.parse functions

Name

Type

Comment

currency

String

A three-letter currency code as defined in ISO4217 such as "USD".

symbol

String

A value that may be used to override the default currency symbol.

pattern

String

Used to override the default currency pattern.

round

Number

Used to provide rudimentary rounding: -1 means don't round at all, 0 means round to the nearest whole number, and 5 means round to the nearest one-half.

locale

String

Override the default locale, which determines the formatting rules.

places

Number

The number of decimal places to accept (default is defined by currency.)

Table 6-6. Summary of the currency module

Name

Return type

Comment

dojo.currency.format (/*Number*/value, /*Object?*/options)

String

Formats a Number as a String, using locale-specific settings. Values for options are given in Table 6-5.

dojo.currency.parse (/*String*/ expression, /*Object?*/ options)

Number

Converts a properly formatted String to a Number. Values for options are given in Table 6-5.

Some of Dojo's build tools can be used to generate support for arbitrary locales and currencies since a lot of this work simply entails building lookup tables of information. See the file located at util/buildscripts/cldr/README for more details.

Summary

After reading this chapter, you should be able to:

  • Internationalize a module for more than one locale

  • Be aware that Core provides utilities for handling currency, numbers, and dates—all of which may be helpful in various internationalization efforts

Next, we'll discuss drag-and-drop.