CakePHP 1.2 i18n/l10n

A few month ago I started working with the CakePHP framework. I needed the i18n functionality that was in version 1.2 (late alpha). After a lot of research I was able to get what I needed. I wrote up my findings and submitted this to the CakePHP developers. They have accepted my submission on using i18n in 1.2 and it will appear in the CakePHP 1.2 Manual. This is my first contribution back to the open source community and while it is just a small bit, I am quite proud to have made even a small contribution back to what has given me so much. Since the 1.2 manual is not yet available I thought I’d post my contribution here:

Localization and Internationalization

What is the difference? Localization refers to the ADAPTATION of a product, application or document content to meet the language, cultural and other requirements of a specific target market (a “locale”). Internationalization is the DESIGN AND DEVELOPMENT of a product, application or document content that ENABLES easy localization for target audiences that vary in culture, region, or language.

How to Use Localization

  1. Include l10n Class
    uses('L10n');
    
    class OrderController extends AppController {
        ...
    }
  2. Create Language File Specific language translations are placed here:
    locale/eng/LC_MESSAGES/default.po (English)
    locale/fre/LC_MESSAGES/default.po (French)

    Language directories must be named with the official three-digit language code; you can find these here: http://www.loc.gov/standards/iso639-2/php/code_list.php

  3. Populate Language file Create entries in the appropriate default.po file in this form:
    msgid  "close_window"
    msgstr "Close"
    
    msgid  "where_pin"
    msgstr "Where is my PIN?"

    There is a limit of 1014 characters in a single msgstr.

  4. Using Use the underscore-underscore method, providing the msgid as the first parameter.
    __("closeWindow");
    __("closeWindow", true);

    If the msgid provided is not found, then the provided value id used, hence some people like to use the default text as the msgid:

    __("Close Window");

    With the second parameter as true the value is returns, without the second parameter is echo’d. If you see your msgid on your page instead of cooresponding msgstr, and the msgid/msgstr pair exists in the relevant default.po file, then you probably are missing dbouel quotes around the msgid or msgstr entry.

  5. Selecting Language To select the language you want:
    $this->L10n = new L10n();$this->L10n->get("en");
    $_SESSION['Config']['language'] = "en";

    Use the first two-digits of the language code (yes, I know it is weird to use two-digits here and three-digits as the language directory name). Configure::write(‘Config.language’, “en”) will work in place of $_SESSION['Config']['language'] = “en” in the future, but it does not work as of 1.2.0.4798alpha.

Real-Life Implementation Example

I created a language helper method:

public function setLang($language = 'en') {
    $this->L10n = new L10n();
    $this->L10n->get($language);
    //Configure::write('Config.language', $language);
    $_SESSION['Config']['language'] = $language;
}

Then I use it in my controller methods as needed.

function balance() {
    Utility::setLang(LOCALE);
    ...
}

And LOCALE is a constant = “en”.

Special Characters

Save the default.po files with an encoding of ISO-8859-1 htmlentities will not render correctly if when used in $form->error() unless you turn off the espcae option:

echo $form->error('Card.cardNumber', __("errorCardNumber", true), array('escape' => false));

32 comments


  • Tao...

    nicee, congratz :D
    what about how to use the i18n… ???
    :P

    January 31, 2008
  • This is the full functionality, unless something changed in the recent 1.2 beta. See the above section “How to Use Localization”. This is really translation. l10n includes things such as different currency and date/time formats, using appropriate colors/symbols that have meaning to the local audience, and hide/showing content with particular meaning to the target audience. Translation is only a piece of 18n/l10n, but the most visible piece. l10n and i18n are used interchangeably in common practice, even though they are technically different. i18n being the design of a l10n system, while l10n is the implementation.

    January 31, 2008
  • Lukas

    Damn, it doesn’t work for me, as i did everything like in this manual :(

    version Beta: 1.2.0.6311

    February 16, 2008
  • Can you email detailed samples of what you did that I can look at and offer some help?

    February 17, 2008
  • test

    Seems like msgstr needs to begin on a new line.

    February 17, 2008
  • You are correct. My mistake in formatting. The msgid and the companion msgstr each need to appear on a separate line. Thanks for noticing this.

    February 18, 2008
  • test

    Thank you very much for the tutorial :)

    February 20, 2008
  • Is it possible to pass variables inside __() somehow? I’ve tried something like without success

    February 20, 2008
  • I never tried before, but I was able to do this:

    $test = ‘Line’;

    __(‘tag’ . $test, true);

    This constructed ‘tagLine’, which is a valid msgid in my default.po file.

    February 20, 2008
  • Lukas

    :) Putting values to new line solved the problem
    Thanks :)

    February 23, 2008
  • cpe

    When writing the default.po file, remember to use double quotes around the text, when defining the msgid.
    Single does not work!
    It would be nice if both kind of quotes did work!

    March 15, 2008
  • Some useful comments from Daniel.S:

    … thought I should mention that with __(), it helps to use the full string text, not just a message ID. That way, if people need to create new translations, or you look at it a long time down the track – you won’t be wondering what ‘where_pin’ is actually meant to say.

    For example:
    __(‘Where is my PIN?’)

    and in the PO file, it will have

    msgid “Where is my PIN?”
    msgstr “”

    That way, when you create a translation, all you need to do is look at
    the POT file and translate away:

    msgid “Where is my PIN?”
    msgstr “Hvor finner jeg PIN-koden min?”

    This is the way i’ve seen it done in Drupal, and it makes sense.

    April 8, 2008
  • zabroc

    I would like to switch language within a component, like the page is in german but emails which are being sent via email component are in english. How can i switch the language (and switch back afterwards) .

    April 10, 2008
  • I have not tested but presumably German is the default language, and then you could use setLang(“en”), as defined above, before composing the email, and then setLang(“de”) after.

    April 10, 2008
  • zabroc

    I tried it your way but it is not working. There is a language found property ($found ) in l10n you can not change once it is set. (or I haven’t found a way yet ;)

    April 11, 2008
  • zabroc

    Ok, i’ve got it, but it is a bad hack:

    Configure::write(‘Config.language’, ‘de’);
    $curInst = I18n::getInstance();
    $curInst->__l10n->found = false;

    April 11, 2008
  • Nice post!
    Do you know how to do if I want to make a whole page “translateable”, msgstr’s has a limit at some charachters.

    June 30, 2008
  • I do not know what the character limit is, but I would break the page content into several smaller strings. The largest single string I have ever used is a single paragraph.

    June 30, 2008
  • Ok, that’s how I’ve done so far, thought perhaps it existed an easier solution

    June 30, 2008
  • thx for this article

    great thing to help you with your translation files are the gettext tools. for example it can create the .po files for you automatically by extracting all the strings defined in the __() function.

    http://www.gnu.org/software/gettext/#TOCdownloading

    August 26, 2008
  • Is it possible to pass wraiables into __() function.
    po files seem to support variables in trasnlations, cakephp not.

    Am i right?

    August 30, 2008
  • Thanks to the CakePHP group (http://groups.google.com/group/cake-php) the answer is yes.

    Just user sprintf() (or printf())…

    In your View:
    printf(__(“age_old”, true), 55);

    In your .po:
    msgid “age_old”
    msgstr “I am %d years old”

    Look at http://de.php.net/sprintf for further information.

    August 30, 2008
  • emnu

    how do i write something like this

    $html->link(__(‘Utama’), ‘/bills/index’);

    TQ

    November 5, 2008
  • @emnu:
    Try:
    __(“Utama”, true)

    __(“Utama”) will echo, if you use __(“Utama”, true) it will just return the value.

    November 5, 2008
  • emnu

    gustav,
    it works…
    TQ so much

    November 5, 2008
  • [...] CakePHP 1.2 i18n/l10n – Aaron Thies [...]

    December 26, 2008
  • Ive got a weird problem here, I have a default.po that works fine with some msgid/msgstr pair, but when I add new ones these new ones doesnt work, they show the msgid when called.

    For example:

    link(
    $html->image(‘delete.png’, array(‘alt’ => ‘Edit’)),
    array(‘action’ => ‘delete’, ‘id’ => $post['Post']['id']),
    null, __(‘post.delete.ask’, true), false
    ); ?>

    The msgid ‘post.delete.ask’ I just added to default.po, and it doesn’t show the corresponding msgstr. But if i change it to ‘comment.ask.delete’ it does show the correctly msgstr.

    Its happening for all new msgid/msgstr pair, no matter where I write then on default.po.
    I’m using the same editor I used to write all the pair (eclipse).

    msgid “post.delete.ask”
    msgstr “Deseja realmente deletar este post?”
    msgid “comment.delete.ask”
    msgstr “Deseja realmente deletar este comentário?”

    Any hints? :(

    January 8, 2009
  • uses(‘L10n’); <- why u using it before class?
    class OrderController extends AppController {

    }

    locale/eng/LC_MESSAGES/default.po (English) <- what is “locale”, and “LC_MESSAGES” ?

    where write this, and what this doing?
    __(“closeWindow”);
    __(“closeWindow”, true);

    at all, can u write normal tutorial?

    April 29, 2009
  • Since I made this blog post the official CakePHP manual has been upgraded to include a normal entry for l10n.

    http://book.cakephp.org/view/161/Localization-Internationalization

    April 29, 2009
  • Okay,

    This is my solution

    http://www.the-di-lab.com/?p=72

    August 18, 2009
  • Just a note, if you have Japanese correctly input into MySQL (see it correctly in phpMyAdmin) but the data is all question marks upon output, then run this query before your SELECT…

    // sets character set that is output from the database
    $this->query(‘SET CHARACTER SET utf8′);

    March 28, 2011
  • uses(‘L10n’); <- why u using it before class?
    class OrderController extends AppController {

    April 20, 2011

Leave a comment


Name*

Email(will not be published)*

Website

Your comment*

Submit Comment

© Copyright Aaron Thies - Designed by Pexeto