Tuesday, January 15, 2008

Gettext for PHP and Javascript

I feel like a spy returning from the cold. I haven't visited this blog in quite a few months, mostly because writing blogs is difficult. I must say after writing my first 2 posts I have a whole new appreciation for bloggers our there, who I mocked and dissed over years. But as a web programmer I bump into weird problems almost every week, and after I solve them I always tell myself "someone should write about this." So I finally picked up the courage to return here again. But enough of that lets get to business.

Recently we have been internationalizing (aka i18n, l10n) our website Traxtuff. Like most programmers we began by producing a detailed design of our dream translation framework for PHP, but a quick search in google made us choose gettext. There are many good articles about using gettext with PHP(Here's one and here's another, Pablo Hoch also posted a benchmark of gettext). Even though it's far from perfect, it's widely used in many projects, so we decided to use it. We added some nice helper functions to wrap _() (aka gettext()):


function _L($str)
{
echo _($str);
}

function _S()
{
if (func_num_args() == 0) {return;}
if (func_num_args() == 1) {_L(func_get_arg(0));return;}
$args = func_get_args();
$args[0] = _(func_get_arg(0));
echo call_user_func_array('sprintf', $args);
}

function _T()
{
if (func_num_args() == 0) {return false;}
if (func_num_args() == 1) {return _(func_get_arg(0));}
$args = func_get_args();
$args[0] = _(func_get_arg(0));
return call_user_func_array('sprintf', $args);
}


Don't ask what L, S & T stand for, I just chose letters randomly :) .

We then had to decide what to do with internationalized strings inside our javascript files. I bumped (completely by accident while researching something else) upon a cool implementation of gettext for Javascript. Here is a link to it. I admit it's not very well documented (major understatement), but it's pretty simple to use. To get the code you need to go into the code repository here. This code depends upon the ubiquitous prototype javascript framework. Basically what you need to do is add in your HTML a link to your po (POT) file:

<link rel="gettext" lang="de" href="[path to your po file]"></link>

And after it in your scripts:
Gettext.lang = 'de';
Gettext.gettext('hello %s','world');
_('hello %s','world');


Yonatan couldn't make this work, so he added before this the code:

Gettext.include.apply(Gettext, Gettext.links[Gettext.linksPointer]);
Gettext.linksPointer++;

Hope this works for you. Let us know if you find something more elegant.

Everything was working peachy, until we upgraded to prototype 1.6. I spent a few hours today trying to figure out why gettext.js stopped working. Finally it turned out that the prototype function Array.indexOf doesn't work in the same way (hope I'm not saying something stupid). Previously it supported finding elements in an array like this:

var arr = [["a"], ["b"]];
arr.indexOf("a");


But now it only supported flat arrays (I think this is because prototype.js first checks if the browser already has an implementation of Array.indexOf). So I fixed gettext.js by adding flatten() call before calling indexOf for the message arrays. It's just a small change in 2 places, so it's not too scary :).

That's all, happy hacking for all of you.

p.s. We recommend creating a separate po files for your javascripts, so it will be as small as possible. After all this gettext implementation isn't very efficient.

4 comments:

m0n5t3r said...

... and if one happens to use jQuery instead of Prototype, like me, there is also gettext for jQuery; slightly different method (I initially contrmplated porting this one to jQuery, chose to write it from scratch and use JSON instead of parsing the po file in the end), same results :)

Unknown said...

One of the oldest jewelers in the world,
http://www.watchestoo.com
replica watches
Men’s watches
Fake watches
watches
IWC
IWC Pilot's watches Classics Spitfire Double

Chronograph IW3718

IWC Pilot's watches Classics Vintage Pilot IW325401
IWC Pilot's watches Classics Watch Chronograph

Antoine de Saint

Pilot's watches SPITFIRE
IWC Pilot's watches SPITFIRE CHRONO AUTOMATIC IW370613
IWC Pilot's watches SPITFIRE CHRONOGRAPH AUTOMATIC

IW370618

IWC Pilot's watches SPITFIRE CHRONOGRAPH AUTOMATIC

IW370623

IWC Pilot's watches SPITFIRE CHRONOGRAPH AUTOMATIC

IW370628

IWC Pilot's watches SPITFIRE CHRONOGRAPH AUTOMATIC

IW371702

IWC Pilot's watches SPITFIRE CHRONOGRAPH AUTOMATIC

IW371705

IWC Pilot's watches SPITFIRE Chronograph Laureus

IW371712

Unknown said...

The popularity of Christian Shoes from is increasing at such a fast pace that even people, who can afford to buy the Christian Louboutin Pumps are opting for these. The main reason for this being that they can buy a variety of Christian Louboutin Boots for the money that would buy Christian Louboutin Sandals only one original.

Unknown said...

Hi! If you need to localize your website or any other piece of software, I would recommend evaulating a collaborative translation management platform like POEditor that can improve your workflow.