Login Register

Internationalization (i18n)

Up until this point, we haven't had to use JavaScript beyond the dojo.require's. That's about to change. But as you'll see, a small amount of JavaScript goes a long way ... when hooked to Dijit and Dojo.

We're going to make the tax form accessible to readers of both languages: English and Swedish Chef. But first here's the wrong way to do it: make two pages with the same controls, the same styles, and the same wiring, but with different text on each. Very bad. When one page changes the other needs to change too. To add more languages, the job becomes even worse.

Since only the text changes, it be nice to have one copy of the page with textual placeholders. Dojo i18n is perfect for that.

Bundles and Resources

If you've used i18n in other languages like Java, the concepts are pretty similar here. Your application users are partitioned into "locales" - a language and a location and/or dialect. The ISO defines standard identifiers for locales, so that en-us is English in the U.S. and en-au is English in Australia. In our example, we'll be using a functional locale called "sw-chef". All other locales will map to the default locale, which in our case is just plain ol' English.

A bundle is a directory named after a locale, and a Dojo i18n resource is a JavaScript file underneath. As is common in other i18n setups, all of our bundles go under the directory "nls". For our i18n setup, we'll use the resource name "taxform". Here's the way it'll look:

taxform5.png

The taxform.js underneath nls is the default resource, which looks like this:

({
first: "First Name",
last: "Last Name",
email: "Email Address",
filingDate: "Filing Date",
grossincome: "Please Enter Your 2007 Gross Income",
deductibles: "Total Deductions",
netincome: "Taxable Income",
taxpaid: "Total Witholding",
refund: "Your Refund",
owed: "Amount You Owe",
campaign: "Would you like to contribute an extra $3 to the Presidential Campaign Fund?"
})

The names before the : are called keys and they are one-word abbreviations for each of the text pieces. The sw-chef resource sw-chef/taxform.js is similar:

({
first: "Furst Neme-a",
last: "Lest Neme-a ",
email: "Emeeel Eddress",
filingDate: "Feeling Dete-a",
grossincome: "Pleese-a Inter Yuoor 2007 Gruss Incume-a",
deductibles: "Tutel Dedoocshuns",
netincome: "Texeble-a Incume-a",
taxpaid: "Tutel Veethulding",
refund: "Yuoor Reffoond",
owed: "Emuoont Yuoo Oove-a",
campaign: "Vuoold yuoo leeke-a tu cuntreeboote-a un ixtra $3 tu zee Preseedentiel Cempeeegn Foond? Bork Bork Bork!"
})

Now we somehow need to pull these names into the right places on the form. For that, we'll build our own custom widget.

An i18n Field Label Widget

Widgets are a lot like macros. You're basically substituting one "fake" tag with a dojoType - called the widget class - for a set of "real" tags. To transform the fake tag to the real tag, you use a template. For example, suppose our goal is to get this tag:

<label for="first" id="first_label">First Name</label>

To construct the Dijit template, we insert place holders for the parts that will vary:

<label for="${fieldid}" id="${fieldid}_label"></label>

Then nestle it inside a dijit.Declaration tag. We can place this tag and template anywhere in our HTML file, in theory, but most people place them under the BODY tag.

<span dojoType="dijit.Declaration" widgetClass="TaxformI18n"
      defaults="{ fieldid: 'none' }">

    <label for="${fieldid}" id="${fieldid}_label"></label>
</span>

The widgetClass parameter gives the name TaxformI18n to our widget class. This will match the dojoType in our actual widgets. The defaults parameter defines all the placeholders and their default values. Any placeholder you use in the template must have a default here.

Having declared this, now the tag:

<div dojoType="TaxformI18n" fieldid="first"></div>

Will be replaced with

<label for="first" id="first_label"></label>

Well, that's helpful but we still need our translated text. That's where we'll call Dojo.

The Dojo i18n API

The fieldid in our widget template can be used as a key into our resource. Ah ha! So you've got a label tag connected to first, which will be filled in with the text tied to "first" ... e.g. "First Name" or "Furst Neme-a". Given that the key is in "fieldid", here's the JavaScript to get the translation:

var labelNode = dojo.byId(this.fieldid + "_label");
var taxFormBundle = dojo.i18n.getLocalization("taxformI18n","taxform");
labelNode.innerHTML = taxFormBundle[this.fieldid];

The third line puts the translated text into the tag marked "fieldid_label".

So taxform is the resource name, but what is taxformI18n? That's a package name, which we define outside of the widget:

// This is necessary to i18n routines look in this directory for the nls folder
dojo.registerModulePath("taxformI18n","/online-book/taxform");
dojo.requireLocalization("taxformI18n", "taxform");

These two statements say "my resources are in http://yourserver/online-book/taxform/nls".

Now for the wiring. The dojo.Declaration must have a template, but it can also contain JavaScript code. You might be tempted to just plunk it in there with a <script type="text/javascript"> but that won't work. Instead you use the type "dojo/connect" and the event "startup." like this:

<span dojoType="dijit.Declaration" widgetClass="TaxformI18n"
    defaults="{ fieldid: 'none' }">

    <label for="${fieldid}" id="${fieldid}_label"></label>
       
    <script type='dojo/connect' event='startup'>
        var labelNode = dojo.byId(this.fieldid + "_label");
        var taxFormBundle = dojo.i18n.getLocalization("taxformI18n","taxform");
        labelNode.innerHTML = taxFormBundle[this.fieldid];
    </script>
</span>

And we're ready to go!

i18n in Action

So here's the complete listing:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Googolica Tax Form</title>
<style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
</style>
<script type="text/javascript" src="/dojoroot/dojo/dojo.js"
        djConfig="parseOnLoad: true">
</script>
<script type="text/javascript">
        dojo.require("dojo.parser");
        dojo.require("dijit.form.TextBox");
        dojo.require("dijit.form.CheckBox");
        dojo.require("dijit.form.DateTextBox");
        dojo.require("dijit.form.CurrencyTextBox");
        dojo.require("dijit.Declaration");
        dojo.require("dijit.TitlePane");
        dojo.require("dijit.Tooltip");
               
        // This is necessary to i18n routines look in this directory for the nls folder
        dojo.registerModulePath("taxformI18n","/online-book/taxform");
        dojo.requireLocalization("taxformI18n", "taxform");
    </script>
</head>
<body class="tundra">
<span dojoType="dijit.Declaration" widgetClass="TaxformI18n"
        defaults="{ fieldid: 'none' }">

        <label for="${fieldid}" id="${fieldid}_label"></label>
       
        <script type='dojo/connect'     event='startup'>
        var labelNode = dojo.byId(this.fieldid + "_label");
        var taxFormBundle = dojo.i18n.getLocalization("taxformI18n","taxform");
        labelNode.innerHTML = taxFormBundle[this.fieldid];
    </script>
</span>
<form>
<div dojoType="TaxformI18n" fieldid="first"></div>
<input type="text" size="20" name="first" dojoType="dijit.form.TextBox"
        trim="true" propercase="true" />
<br>
<div dojoType="TaxformI18n" fieldid="last"></div>
<input type="text" size="20" name="last" dojoType="dijit.form.TextBox"
        trim="true" propercase="true" />
<br>
<span dojoType="TaxformI18n" fieldid="email"></span>: <input type="text"
        length="20" name="email" dojoType="dijit.form.TextBox" lowercase="true" />

<div dojoType="TaxformI18n" fieldid="filingDate"></div>
<input type="text" length="10" id="filingDate" name="filingDate"
        dojoType="dijit.form.DateTextBox">

<hr />
<ol>
        <li>
        <div dojoType="TaxformI18n" fieldid="grossincome"></div>
        <input type="text" maxlength="12" id="grossincome" name="grossincome"
                value="0" dojoType="dijit.form.CurrencyTextBox" required="true"
                currency="USD" />
<img src="symbol_help.gif" id="helpIncome" />
        <div dojoType="dijit.Tooltip" connectId="helpIncome">That's how
        much <b>money</b> you make.</div>
        </li>
        <li>
        <div dojoType="TaxformI18n" fieldid="deductibles"></div>
        <input type="text" dojoType="dijit.form.CurrencyTextBox"
                id="deductibles" name="deductibles" value="0" required="false"
                currency="USD" />
</li>
        <li>
        <div dojoType="TaxformI18n" fieldid="netincome"></div>
        <input type="text" value="0" dojoType="dijit.form.CurrencyTextBox"
                required="false" id="netincome" name="netincome" currency="USD" />
</li>
        <li>
        <div dojoType="TaxformI18n" fieldid="taxpaid"></div>
        <input type="text" value="0" class="fillwidth currency"
                dojoType="dijit.form.CurrencyTextBox" required="false" readonly="true"
                id="taxpaid" name="taxpaid" currency="USD" />
</li>
        <li>
        <div dojoType="TaxformI18n" fieldid="refund"></div>
        <input type="text" value="0" dojoType="dijit.form.CurrencyTextBox"
                required="false" readonly="true" id="refund" name="refund"
                currency="USD" />
</li>
        <div dojoType="TaxformI18n" fieldid="owed"></div>
        <input type="text" value="0" dojoType="dijit.form.CurrencyTextBox"
                required="false" readonly="true" id="owed" name="owed" currency="USD" />

        </li>
        <li>
        <div dojoType="TaxformI18n" fieldid="campaign"></div>
        <input type="checkbox" name="campaign" value="Y"
                dojoType="dijit.form.CheckBox">
</li>
</ol>
<div dojoType="dijit.TitlePane" open="false"
        title="Directions (click to Expand)" style="width:400px;height:300px">

Proin risus. Nullam rhoncus purus id turpis. Praesent aliquam adipiscing
ligula. Aenean lorem ante, accumsan quis, elementum id, cursus eu,
lorem. Fusce viverra. Ut tempor nisi at ipsum. Etiam sed nibh.</div>
</body>
</html>

The browser knows which locale to use based on the web browser installation, and it feeds this directly to Dojo, by default. So unless your locale is "sw-chef", your page will look exactly like the previous examples. But you can easily override this by changing the configuration when loading dojo.js:

And run the result.

taxform6.png

It makes you want to throw kitchen utensils in the air with glee! OK, one more design run, and our form will be finished.

AttachmentSize
step4.html4.07 KB
taxform.js_.txt374 bytes
taxform.js_.txt for nls/sw-chef folder430 bytes

Tooltips

I noticed the tooltip was still in English. It would be good if the example showed how to create localised tooltips to go along with the localised input fields. On the other hand it does leave an interesting exercise for the reader (I think I might give it a go).

Wy not the complete code against a defined local library?

For me the example worked only, after I referenced all resources localy on the current release. A look into an up to date and checked complete source relying on a local dojo would have saved a lot of time and the tutorial could remained unchanged when minor changes in the dojo basis happen.

registerModulePath Problem

I am new to dojo, and I having trouble understanding exactly what is going on in regards to the bundle. When I try to run the example, I am getting an error "Error: Bundle not found: taxform in taxformI18n, locale=en-us". Where (in relation to the main html file) should the taxform directory be? Also, the directory picture above shows several files as children to the taxform directory (besides the two "taxform.js" that were explained); do I need them? Any help will be greatly appreciated!

client side i18n

I believe this should really be done on the server side. Adding illegal properties to your HTML or using big loads of Javascript to generate a userinterface is quite error-prone. What if a user doesn't have Javascript turned on? Suddenly no (or no translated) UI?

"But you can easily override

"But you can easily override this by changing the configuration when loading dojo.js:"
Whatever comes after this is blank.

Also, it would be very convenient if there were a hosted, finished product that shows the result of this work. Using an image instead seems like kind of a hack.