Saturday, December 13, 2008

Towards a better integration of i18n in Dojo widgets

Maybe this is old hat to some, but I've been doing some research into i18n and come up with what I think is an interesting way to get better integration with internationalization in layout containers.

To recap, Dojo has, and uses internally, an internationalization system which makes sure that the Cancel button button read 'Anulla' if your browser says that your locale is 'it', and so on.

I have been thinking about various ways of making the use of i18n more dynamic. My first stab at this was a small widget, which could be embedded in a page (at any amount of places) which treated its innerHTML as a key string to be looked up in the i18n system and exchanged for the actual message du jour.

This was a rather heavyweight solution (and I also wrongly used dojo.requireLocalization inside the widget, instead of by the dojo.require statements where it should be. It was just insane to do that every time an instance of the widget was created, but that's how I roll :), and was unable to handle translations of html properties, like the title="" inside an anchor element, for example.

I then thought of extending dijit._Templated (the superclass of all Dojo widgets whichuses 'normal templates) to do roughly the same for all all widget templates. But somehow it felt not really spot on for my problem.

What I had, was a number of pages, which I had been translating to a number of hmtl 'snippet' files, which only contained the markup for what they wanted to do - no body, header or stuff like that. They are used inside ContentPanes which are managed in turn by a StackContainer, which simulates page transitions but within preloaded html snippet, getting you a mighty quick site where all parts are already loaded in the browser,but only one shown at a time.

Each of these 'pages' have a lot of text, which should be translated, as well as a number of title="" and related element properties which also should be translated, so having a solution which only worked for widget templates would only solve part of the problem.

SP what Idid was to create a widget which sublassed dijit.ContentPane, and which overrode the function called when the pane is done loading remote content (the file containing the html snippet), onDownloadEnd.



dojo.declare("layout.TranslationPane", dijit.layout.ContentPane,
onDownloadEnd: function(pane)
console.log("layout.TranslationPane callback onDownloadEnd called.");
var html = this._getContentAttr();
var res = dojo.i18n.getLocalization("layout", "salutations");
html = dojo.string.substitute(html, res);
this._isDownloaded = true;

Not much code? No, that's because I solve my problems a) at the machine they appear, b) in JavaScript, and c) on the shoulders of Dojo. So there.

That leave me with snippet files which contain any number of ${title_of_stuff} anywhere at all, since the 'page' is treated as a string.

I think that this would be a great thing to add to ContentPane permanently, with a couple of extra properties to it, which would point out locale overide and (not optional) the name of the package and translation file to use for any ${keywords} appearing in the markup loaded.




Sean said...

Hey Peter,

I've an incredibly similar situation where the application I'm working on uses a StackContainer with multiple ContentPanes to simulate different 'pages' in the application. I'm using PHP (with gettext) in each of these sub 'pages'. If I had know about your ContentPane workaround, I could have skipped the gettext stuff and just done all my internationalization work in dojo! At the moment, I have resource bundles for PHP and dojo - would be nice to consolidate these into one resource bundle. Managing resource bundles based on technology choices is a bit of a pain.

Speaking of resource bundles, do you have any ideas on how best to only download the resources which are absolutely necessary? I've one monster i18n.js file which contains ALL my dojo internationalized strings. When the user logs into the application, they have to download all these strings, even though they might never see any of them being presented on the page. I'm gonna take a look into the 'resourceName' option in the dojo build system and see if that offers me any way to split the monster js file.

One place where I've found gettext useful is with declarative widgets such as the Dialog. For example, I use gettext to display the Dialog's title based on the user's language. Not sure whether there's a way to accomplish this with regular dojo other than creating the dialogs programmatically.

Great post - always cool to see people working on dojo.i18n.


Anonymous said...

I recently came across your blog and have been reading along. I thought I would leave my first comment. I don't know what to say except that I have enjoyed reading. Nice blog. I will keep visiting this blog very often.


Peter Svensson said...

Hi Betty,
Thanks a lot for your comment ^-^

Is there any area in particular that interest you?


Peter Svensson said...

@Sean Ouch! I'll have to get back to you on that. On the top of my head I'm not sure ehwther the extraLocal property in djCOnfig actually read in the required bundle or not. Possibly not.

You should be able to do several requireLocalization calls at some further point in time than in the document head, but I haven't tried it out yet.

Nice to hear someone working on i18n as well :)


Sean said...

Hey Peter,

Think I need to upgrade my dojo instance before I look into how resourceName is used. I'm running with 1.0.2 still and this could prove painful:

That's an good point though about using different requireLocalization calls. The application I work on is broken up into around 5 php pages (all with StackContainers and multiple ContentPanes). I could use different requireLocalization calls on each of these pages and split my dojo resources based on that.


Shane said...

Hi Peter,

A nice solution I've come up with is to add an "i18n" attribute to any tag that needs to be updated with a localized piece of text, with the value being the key for the resource bundle. e.g.

<a href="blah" i18n="Hello.World"></a>

Then if the user wants to switch locales, a simple dojo.query call can find all the tags and update them, e.g.

var text = dojo.i18n.getLocalization("pkg", "text", "en");
dojo.query("[i18n]", node).forEach(function(node){
var a = node.getAttribute("i18n");
node.innerHTML = text[a];

This works quite well, is nice and quick, and is obviously extremely lightweight - it can be used on any page, no Dijits required or anything like that.

Peter Svensson said...

@Sean: OK, that's true. My first translation widget was a bit along those lines, in that you just added the dojoType="" for the widget on any element, and it treated the innerHTML as the translation string.

However, just using a property is much more elegant. I should have thought of that :)

But (there's always a bit, isn't there?) this approach also suffer from the same problem mine did (which you might not be affected by), in that element properties becomes impossible to translate in this manner.

For example <a href="" title="${company_name}">

So to be as general as possible, a translation feature would have to move into pure string space, it seems.