CakePHP 1.2 i18n/l10n
- At August 3, 2007
- By Aaron
- In CakePHP
32
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.
- http://www.w3.org/International/questions/qa-i18n
- “internationalization” and “localization” are often abbreviated as i18n and l10n respectively; 18 and 10 are the number of characters between the first and last character.
How to Use Localization
- Include l10n Class
uses('L10n'); class OrderController extends AppController { ... } - 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
- 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.
- 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.
- 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));


Tao...
nicee, congratz :D
what about how to use the i18n… ???
:P
admin
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.
Lukas
Damn, it doesn’t work for me, as i did everything like in this manual :(
version Beta: 1.2.0.6311
admin
Can you email detailed samples of what you did that I can look at and offer some help?
test
Seems like msgstr needs to begin on a new line.
admin
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.
test
Thank you very much for the tutorial :)
zabroc
Is it possible to pass variables inside __() somehow? I’ve tried something like without success
admin
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.
Lukas
:) Putting values to new line solved the problem
Thanks :)
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!
admin
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.
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) .
admin
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.
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 ;)
zabroc
Ok, i’ve got it, but it is a bad hack:
Configure::write(‘Config.language’, ‘de’);
$curInst = I18n::getInstance();
$curInst->__l10n->found = false;
Gustav Ernberg
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.
admin
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.
Gustav Ernberg
Ok, that’s how I’ve done so far, thought perhaps it existed an easier solution
icko
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
Avo
Is it possible to pass wraiables into __() function.
po files seem to support variables in trasnlations, cakephp not.
Am i right?
admin
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.
emnu
how do i write something like this
$html->link(__(‘Utama’), ‘/bills/index’);
TQ
Gustav
@emnu:
Try:
__(“Utama”, true)
__(“Utama”) will echo, if you use __(“Utama”, true) it will just return the value.
emnu
gustav,
it works…
TQ so much
CakePHP - i18n and l10n - development
[...] CakePHP 1.2 i18n/l10n – Aaron Thies [...]
Kym
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? :(
Gediminas
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?
Aaron
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
The Di Lab
Okay,
This is my solution
http://www.the-di-lab.com/?p=72
Aaron
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′);
Yousaf
uses(‘L10n’); <- why u using it before class?
class OrderController extends AppController {