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.
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);
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
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.
Love the idea. I will give it a spin
Hello Nice site!
Bye
permalink ArtitaRahRich ~ October 1, 2008 at 8:42 a.m.