Pressure is nothing more than the shadow of great opportunity. - Michael Johnson

Managing Translation Strings

2 years, 11 months ago

Library updated to include improved translation key/value pair parsing, named substitution variables and translation file inheritance.

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.

»Download the library

Using The Library

Here's a quick example of using the library:

  1. <?php
  2. require('translations.inc.php');
  3. $oTranslation = new Translations('en');
  4. echo $oTranslation->Get('site.title');
  5. echo $oTranslation->Get('site.heading');
  6. ?>

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:

  1. # global site information
  2. site.title = Website Performance | CSS Sprite Generator
  3. site.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:

  1. <?php
  2. require('translations.inc.php');
  3. require('template-engine.inc.php');
  4. // do business logic here
  5. // set up the libraries
  6. $oTranslation = new Translation('en');
  7. $oTemplate = new Template('home.php');
  8. // pass the instance of the translation library into the scope of the template
  9. $oTemplate->Set('translation', $oTranslation);
  10. // assign data to template
  11. $oTemplate->Set('data', $aData);
  12. ?>

The corresponding template (home.php) might look something like this:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  5. <title><?php echo $translation->Get('site.title'); ?></title>
  6. </head>
  7. <body>
  8. <h1><?php echo $translation->Get('site.heading'); ?></h1>
  9. <!-- display data and rest of template code here -->
  10. </body>
  11. </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:

  1. results.currentPage = Showing page {0} of {1}.

The corresponding code for calling this key might look like this:

  1. <?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.

Comments

  • 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.

    Martin Fjordvald - 3rd October 2007 #

  • 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 - 3rd October 2007 #

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

    Ed Eliot - 3rd October 2007 #

  • Massively late to the game, but I'll speak anyway:

    I prefer named arguments to ordered arguments in translations. Instead of:

    results.currentPage = Showing page {0} of {1}.

    I'd use:

    results.currentPage = Showing page {start} of {end}.

    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 Wright - 9th October 2007 #

  • 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.

    Ed Eliot - 10th October 2007 #

  • How about:

    <?php echo $translation->Get('results.currentPage', array('{start}'=>$pageNum, '{end}'=>$numPages)); ?>

    ? Then it would just be a wrapped for

    strtr

    , with the "fetch from translation file" functionality built into it.

    Brad Wright - 10th October 2007 #

  • 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.

    Ed Eliot - 10th October 2007 #

  • hi, I just had to modify the Translation class' constructor to load in files relative from the location of the library

    define('TRANSLATIONS_PATH', 'translations/'); define('TRANSLATIONS_CACHE', .'translations/cache/');

    to

    define('TRANSLATIONS_PATH', dirname(__FILE__).'/translations/'); define('TRANSLATIONS_CACHE', dirname(__FILE__).'/translations/cache/');

    as running PHP which invoked the library from outside of my "application directly" failed with errors indicating the library could not find the translation file.

    Steven Sproat - 29th July 2010 #

  • # wasn't paying enough attention to them (a classic case of not RTFM). I wish all projects did it this well. # Being able to query models from the python command line shell. It also doubles as a fairly quick (and I guess scriptable) way to add test data to your database.

    sohbet siteleri - 19th August 2010 #

  • Aşk aşk thanks admin onayla pls

    izmir chat - 20th August 2010 #

Help make this post better

Notes: Standard BBCode for links, bold, italic and code are supported. rel="nofollow" is added to all links. Your email address will never be displayed on the site.

Back to index