Notice This is a beta feature offered by Google. Also this is automatic translation, which means the results are often inacurate and/or hilarious. Enjoy.

ARCHIVES /  RSS
Blog

jQuery i18n plugin proposal

h3  ~  17 Aug 2008, 16:30  –  4 comments

I'm working on a quite big jQuery UI plugin right now. I think it will be a great open source project, so I thought using a translation system. The only plugin I found using such system is the ui.datepicker.js and it does the job, I think it's somewhat primitive.

Here's how it works. When a translation file is included it extends the plugin's regional property with a object containg translation key/value pairs and sets itself to the default language.

Here an example of a (stripped) translation file taken from ui.datepicker.js:

// French initialisation for the jQuery UI date picker plugin.
// Written by Keith Wood (kbwood@virginbroadband.com.au) and Stéphane Nahmani (sholby@sholby.net).
jQuery(function($){
	$.datepicker.regional['fr'] = {
		clearText: 'Effacer',
		closeText: 'Fermer',
		prevText: '<Préc',
		prevBigText: '<<',
		nextText: 'Suiv>',
		nextBigText: '>>',
		currentText: 'Courant',
		dateFormat: 'dd/mm/yy', 
        firstDay: 0, 
		initStatus: 'Choisir la date', 
        isRTL: false};
	$.datepicker.setDefaults($.datepicker.regional['fr']);
});

By now it's quite simple, but when we look at the plugin code it gets a bit uglier. For the plugin to work without translation file, the author is forced to create a "default" fall back language object within his plugin:

	this.regional[''] = { // Default regional settings
		clearText: 'Clear', // Display text for clear link
		closeText: 'Close', // Display text for close link
		prevText: '<Prev', // Display text for previous month link
		prevBigText: '<<', // Display text for previous year link
		nextText: 'Next>', // Display text for next month link
		nextBigText: '>>', // Display text for next year link
		currentText: 'Today', // Display text for current month link
        firstDay: 0, 
		dateFormat: 'mm/dd/yy', // See format options on parseDate
		initStatus: 'Select a date' // Initial Status text on opening
        isRTL: false
    }

Now while this approach work fine, it's far from optimal for many reasons.

  1. When you look at the code you see nothing but JavaScript code, no strings. And since JavaScript is weakly typed, you just hope that what you think is a string, really is (see firstDay or isRTL). Personnaly I like being able to distinguish strings from other types in my code, without having to remember each property of a translation object. Which leads me to point two..
  2. You have to think about relevant keywords that preserve the semantic meaning of the text to use as translation key, which is not always obvious. I think it's important for the translators to have quick access to the "default" full translation in order to grasp the context of the word or sentence.
  3. I saw no mechanism for advanced string formating. That's not the end of the word, but being able to give translation strings like "comment posted %d days ago" is always handy.

So here I am with my i18n plugin proposal, it's really my first working draft and I hope I'll get some feedbacks or improvement ideas on it.

The first thing I designed was the translation processe, just because I though it would be the easy part.. fool I was. But after some trials and errors I found what I think is a better solution.

$.i18n('fr.datepicker', {
    'Clear': 'Effacer',
	'Close': 'Fermer',
	'<Prev':   '<Préc',
	'<<'  '<<',
	'Next>':   'Suiv>',
	'>>': '>>',
	'Today': 'Courant',

	'{m:d}/{d:d}/{y:d}': 
	'{d:d}/{m:d}/{y:d}',

	'Select a date': 
	'Choisir la date'
});

The first argument is the language code, it can be namespaced with a dot. By default "jQuery" namespace is used (so you can only give the language code).

Secondly, the original string is in the translation file, like with gettext. I think it's a must for several reasons. Among them, it helps the translator and you don't have to create a meaningful key.

The only downside I can see is the key length, I don't know what's the maximum string length you can use as object key or if there is performances costs, but on the other and I wouldn't recommend JavaScript for storing long texts. I see this plugin more to translate short texts for UI components, like the datepicker.

Another advantage is more legible code, take this simple example taken from the datepicker plugin:

var controls = '<div class="ui-datepicker-control">' + (isRTL ? '' : clear) +
	'<div class="ui-datepicker-close"><a onclick="jQuery.datepicker._hideDatepicker();">' +
	this._get(inst, 'closeText') + '</a></div>' + (isRTL ? clear : '')  + '</div>';

Now with my proposed system:

var controls = '<div class="ui-datepicker-control">' + (isRTL ? '' : clear) +
	'<div class="ui-datepicker-close"><a onclick="jQuery.datepicker._hideDatepicker();">' +
	$.i18n('datepicker', 'Close') + '</a></div>' + (isRTL ? clear : '')  + '</div>';

Not bad, but not a really great improvement neither. Here's the sugar:

// I enclose the plugin
(function($){	
	// defined a "_" method like gettext, which will specify 
	// the plugin and make the call
	function _(str, args) {
	    	return $.i18n('imgTools', str, args);
	}

	var controls = '<div class="ui-datepicker-control">' + (isRTL ? '' : clear) +
		'<div class="ui-datepicker-close"><a onclick="jQuery.datepicker._hideDatepicker();">' +
		_('Close') + '</a></div>' + (isRTL ? clear : '')  + '</div>';

Bonus: Now js files can be scanned for _(*) and base translation files generated automatically.

But where to specify the language ? Simple, it's global. You set it once and all your plugins will look for the translated string or use the original if no translation is available.

// switching language
$.i18n('fr');

Here's the complete code, my jquery.strings.js plugin is required for the string formating.

(function($){
    $._i18n = { trans: {}, default:  'en', language: 'en' };
    $.i18n = function() {
        var getTrans = function(ns, str) {
            var trans = false;
            // check if string exists in translation
            if ($._i18n.trans[$._i18n.language] 
                && $._i18n.trans[$._i18n.language][ns]
                && $._i18n.trans[$._i18n.language][ns][str]) {
                trans = $._i18n.trans[$._i18n.language][ns][str];
            }
            // or exists in default
            else if ($._i18n.trans[$._i18n.default] 
                     && $._i18n.trans[$._i18n.default][ns]
                     && $._i18n.trans[$._i18n.default][ns][str]) {
                trans = $._i18n.trans[$._i18n.default][ns][str];
            }
            // return trans or original string
            return trans || str;
        };
        // Set language
        if (arguments.length < 2 && arguments[0].length == 2) {
            return $._i18n.language = arguments[0];
        }
        else {
            // get translation
            if (typeof(arguments[1]) == 'string') {
                var trans = getTrans(arguments[0], arguments[1]);
                // has variables for string formating
                if (arguments[2] && typeof(arguments[2]) == 'object') {
                    return $.format(trans, arguments[2]);
                }
                else {
                    return trans;
                }
            }
            // set translation
            else {
                var tmp  = arguments[0].split('.');
                var lang = tmp[0];
                var ns   = tmp[1] || 'jQuery';
                if (!$._i18n.trans[lang]) {
                    $._i18n.trans[lang] = {};
                    $._i18n.trans[lang][ns] = arguments[1];
                }
                else {
                    $.extend($._i18n.trans[lang][ns], arguments[1]);
                }
            }
        }
    };
})(jQuery);

post a comment Comments

Hello Nice site!

Bye

ArtitaRahRich ~ October 1, 2008 at 8:42 a.m.

Awesome plugin idea! Have you pinged the jQuery list with this? This should go into core IMHO as that would then promote localisation of all plugins, not just jUI stuff.

Would it be worth using jQuery's .data() method to store and retrieve info within your plugin? Would make the code much cleaner :)

Also, what about using method overloading to make the code more readable:

http://ejohn.org/blog/javascript-meth...

If I change the language at runtime, how do all the plugins know to update their display? Maybe a new templating engine is needed that has this i18n plugin built in to it - so plugins define and render their templates using new templating plugin then when language changes the templating plugin knows which bits of the page are localised, re-renders them and triggers an event on them in case the parent plugin wants to redraw to accommodate changed text lengths?

Guy Fraser ~ October 8, 2008 at 5:38 a.m.

@Guy Fraser

Yeah I've announced it on the official jquery mailling list, but I did not really have any kind of useful feedback..

Excellent observations, thanks for the feedback.

h3 ~ October 9, 2008 at 8:31 a.m.

Love the idea. I will give it a spin

Rogers ~ October 16, 2008 at 10:35 p.m.
Copyrighted stuff .. u know.