If you stand any chance of localising your web site good translation string support should be at the heart of your internationalisation strategy. Even if you've no plans to launch your web site outside your local market then translation strings can help you to reduce maintenance overheads by centralising all static text used on your site. Need to rebrand, need to update legal text, titles or meta data - no problem, with a translation mechanism in place these can all be stored in one easily accessible place, free from the business and template logic of your site.
Stuart and I recently launched a CSS Sprite Generation tool. We were keen to offer the interface in multiple languages so I knocked together a quick translation string library to cater for this. It's far from comprehensive, no doubt has a few bugs and hasn't been tested with multibyte languages (yet) but I'm throwing it out there to show that there isn't really much work required to meaningfully provide translation string support in your site.
Using The Library
Here's a quick example of using the library:
<?phprequire('translations.inc.php');$oTranslation = new Translations('en');echo $oTranslation->Get('site.title');echo $oTranslation->Get('site.heading');?>
Keys are arbritrary. Make them whatever makes sense to you but avoid making them the default (usually english) language text. If the text in the default language changes and you've used it as your key you'll have to update all your translation files to reflect. I tend to add full stops (periods) or hyphens between parts to effectively namespace keys under specific pages or features. It helps me to more quickly understand where on the site particular translations will appear.
Creating a Translation File
The translation files are simply text files containing key / value pairs separated by an equals (=) character. A translation file for the code displayed above might look like:
# global site informationsite.title = Website Performance | CSS Sprite Generatorsite.heading = CSS Sprite Generator
You can insert comments in the file to help explain the keys or give further context. Comments start with a hash (#) and can appear on a line of their own or at end of translation lines. Each translation key / value pair should be entered on one line only - line breaks denote the start and end of each so a single entry can't span multiple lines.
If you need to insert literal hash (#) or equals (=) characters you can do so by prefixing them with a backslash.
Combining with a templating library
Ordinarily you'd combine the library with a templating library to separate your business logic and presentational logic. I wrote one short time ago which you can read more about or download the latest copy. You may also require my caching library which you can also download. Here's a simple example of implementing the templating library with translation strings:
<?phprequire('translations.inc.php');require('template-engine.inc.php');// do business logic here// set up the libraries$oTranslation = new Translation('en');$oTemplate = new Template('home.php');// pass the instance of the translation library into the scope of the template$oTemplate->Set('translation', $oTranslation);// assign data to template$oTemplate->Set('data', $aData);?>
The corresponding template (home.php) might look something like this:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title><?php echo $translation->Get('site.title'); ?></title></head><body><h1><?php echo $translation->Get('site.heading'); ?></h1><!-- display data and rest of template code here --></body></html>
Substitution Variables
When fetching a key you also have the option to pass an arbitrary number of subsitution variables which will replace placeholder markers within the translation key value. Take the following text for example:
Showing page 1 of 30.
It's a mixture of text and dynamic data. One could split it up into two keys, "Showing page" and "of" but this wouldn't be very flexible as it would provide no way to honour differing word orders in other languages. A better approach would be to define a translation key / value pair which contains substitution markers as shown:
results.currentPage = Showing page {0} of {1}.
The corresponding code for calling this key might look like this:
<?php echo $translation->Get('results.currentPage', $pageNum, $numPages); ?>
When parsed {0} will be replaced by the current page number and {1} by the number of pages. Taking this approach you can obviously see that {0} and {1} can be moved around to match the word order of the particular language in question.
Caching
It wouldn't be very efficient to parse the translation files on each request so the library writes them to a serialised PHP array which is written to a file and used for subsequent requests. The library checks a timestamp stored in the cache with the corresponding translation file and rewrites the cache whenever it changes which prevents the cache from ever containing stale data.
Other Options
You may also want to take a look at the Gettext functions included in PHP which can be used to help you internationalise your applications.
If you'd prefer to handle your translations offline and also need complex template localisation functionality take a look at Yahoo! R3.
Seems like reinventing the wheel to be honest. Personally I'm more in favour of using the PHP gettext module. Simply stuff it in a wrapper class which falls back to the PHP implementation of gettext should the module or required locale not be installed.
Besides the fact that you deal with C code which is obviously faster than a PHP implementation you also get slightly different syntax.
For example instead of:
You get to use the entire string.
In my opinion, this yields a far better overview of exactly what goes where and you don't have that. "hmm, what did I call this string variable again?"
Apart from that it also handles localizing of images, not that I'd recommend using images with text.
Ah it would seem the comment system decided that the opening PHP tag is HTML tag and strip it instead of using entities. *cough* :P
Martin - fair enough. I mentioned that it's worth looking at gettext(). Personally I'm not a fan and I think it's safe to assume it wouldn't be installed / available in a lot of situations but I would encourage people to look at all available options and see which works best for them.
Apologies for the comment system. I've been meaning to fix that for a while but there never seems to be enough hours in the day. ;-)
Massively late to the game, but I'll speak anyway:
I prefer named arguments to ordered arguments in translations. Instead of:
I'd use:
Of course this depends on a few things:
- Documentation of translation variables; and - Who the people translating for you are. Clearly if they're an unknown quantity, named variables are vastly superior in usability and for portability.
Brad - never too late. ;-) I'd tend to agree with that named variables would be better although one could solve the problem by adding comments in the translation file to explain the variables.
The one issue with using named variables is that it makes the implementation more complex in the template. Instead of just passing parameters one after the other to the $translation->Get method one has to instead work out a way to assign values explicitly to {start} and {end}. Of course this doesn't make it a reason not to consider implementing it this way but I think one does have weigh up the upsides of this approach in the translation files with the corresponding downside in the templates.
How about:
? Then it would just be a wrapped for
, with the "fetch from translation file" functionality built into it.
Brad - yup, that'd work well but of course it does make the calls in the template more complicated. I could implement both mechanisms so people could choose which they preferred.
This might be slightly more preferable:
There's probably no reason to include the curly braces in the call itself.