translate
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 / MUSIC / RSS
Blog

jQuery i18n plugin proposal

h3  ~  17 Aug 2008, 16:30  –  Post a comment

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);

jQuery experimental plugin: jPath

h3  ~  21 Jul 2008, 14:21  –  Post a comment

intro

Recently my coworker (Gnuvince) gave me the nice idea of using a path based approach like xPath to get properties of JSON objects.

My first thought was to replicate xPath's syntax, but it quickly came apparent that beside of being way to heavy, it didn't fit at all.

On the other hand, jQuery's selector approach turned out to be the perfect syntax. I wrote a working and extensible prototype, which I <sarcasm>creatively</sarcasm> decided to name jPath (source code).

how it works

var employee: {
        name:      'John Doe',
        phone:     '(555) 555-1234',
        bookmarks: ['google.com', ...],
        prefs: {
            color: 'blue',
            page:  'http://www.google.com/'
        }
    }    
};

$(employee).jpath('name');         // John  Doe
$(employee).jpath('bookmarks');    // ['google.com', 'reddit.com', 'xkcd.com']
$(employee).jpath('prefs.color');  // blue

I find it syntactically sexy. But more importantly, it takes cares of checking if the property is defined before returning it. Which means that when requesting an property of an undefined property it will consistently return false:

// plain javascript
alert(enployee.rights); // 'undefined'
alert(employee.rights.constitutional); // reference error exception

// leads to ugly and redundant code like this:
if (employee && employee.rights.constitutional && employee.rights.constitutional) { ... }

// jPath
$(employee).jpath('rights'); // false
$(employee).jpath('rights.constitutional'); // false

Until now it's all cute, but I though I could spice it up and add expressions to it, again like jQuery' selectors.

$(employee).jpath('name:match(John)');      // return true
$(employee).jpath('bookmarks:eq(2)');       // return 'xkcd.com'
$(employee).jpath('color:is(blue)');        // return true

Expressions

exprdescription
:contains(value) Returns true if the object's contains value
:first Returns the first element of an object
:last Returns the last element of an object
:eq(N) Returns the Nth element of an array or the N property of an array
:is(str) Returns true if the object's value == str
:match(str) Returns true if the object's value match str

Extending expressions

It's quite easy to add custom expressions, here's two examples:

(function($){
    $.extend(jpath.expr, { // s: scope, k: key, p: param
            'eq': function(s, k, p) {
                return s[k] && s[k][p] || false;
            },
            'first': function(s, k) {
                return s[k] && s[k][0] || false;

            }
    }); 
 })(jQuery);

Extending axis

Axis too are extensible. Until now I only considered the "." (dot) to access child property, but there's probably more I could do. So I let room for expansion here too.

(function($){
        axis: { // scope, property
            '.': function(s, k) {
                return $.jpath.getObj(s, k);
            }
        }
 })(jQuery);

Conclusion

It's an experimental plugin, I still haven't tested really it so use it on a production site at your own risks.

Feedbacks are more than welcome. If you have any improvements idea or suggestions, please don't hesitate.

The most annoying feature of VLC player

h3  ~  21 Jul 2008, 14:09  –  1 comments

VLC is without a doubt my favorite video player, in my opinion it's simply the best. It's really cross platform, intuitive, fast and light. Oh, and the keyboard shortcuts are great too.

But there is one oddly head banging feature which drives me crazy in term of usability: fullscreen mode on a multi display setup.

I don't know if Windows users are afflicted by this (I use only Ubuntu), but here's what I have to do just in order to choose the monitor in which I want fullscreen mode:

  • click settings
  • click Preference (or CTRL+s)
  • double click Video
  • double click Output modules
  • click XVideo
  • select Advanced options
  • set Screen for fullscreen mode to 0 or one 1
  • click save
  • return to player
  • go to fullscreen mode (double click)

That's freaking insane. The normal and expected behavior for a end user is simply to:

  • get the player in the desired monitor
  • go to fullscreen mode (double click)

The Screen for fullscreen mode option could simply be changed to Force screen for fullscreen mode. -1 could mean auto and higher than -1, the screen number.

But then again VLC beat most other players, because it's an Open Source project. Unfortunately, I have no C programming skill so I can't implement this improvement myself, but at least I can suggest it.

I really hope this improvement idea will makes its way into a future release and kudos to the dev team for their great work.

Copyrighted stuff .. u know.