Continuing my series on Kohana 3 (see Auth here and Validation here), I’m tackling Kohana 3 internationalization in this post.
How Kohana 3 I18n works
Kohana 3′s i18n functionality is implemented in two files:
- /system/classes/i18n.php – Implements the i18n class, which provides all the functionality.
- /system/base.php – Implements the __() function, which is most commonly used for translating strings.
The Kohana_I18n class provides the following functions:
- I18n::lang($lang = NULL). Gets or sets the target language for translation. Call without parameters to get the current target language to which strings will be translated to.
- I18n::get($string, $lang = NULL). Returns the translation of the source string, optionally specifying what the target language should be. You should not usually use this function, however it has some uses (discussed later).
- I18n::load($lang). Loads the translation table for a given language, caches it and returns it.
The base file provides one translation function, the double underscore:
- __($string, $values, $lang = ‘en-us’). When the current target language is the same as the param $lang, returns the same string. When the current target language is something else, returns the translated string if it exists, or the same string if it does not.The $values array determines replacements made in the string.
__() is the main function you should be using. Just wrap all your strings into it.
Storing and loading the translation strings
As you notice if you look at the source, there are no translation strings here. They are stored under /application/i18n/languagename.php, where languagename is the name of the language you want to use. Kohana uses the string “en-us” as the default target language.
Note how the default language string has two parts: one specifying the language (en for English) and another for the region (us for the US).
Loading is done automatically on-demand when you call __(). The loading function I18n::load() works in a way that it can flexibly search for a file. It explodes the string on the “-” character, so the default target language “en-us” results in a search for the following files:
- /application/i18n/en.php
- /application/i18n/en/us.php
Note that the order means that you can have a single language file for English, and then override some parts of the file – for example with the target language string “en-gb”, Kohana would first load the en.php file and then the /en/gb.php file; this makes it easy to have regional variants for strings (“color” vs. “colour”).
What should I store in the i18n language files?
This is an important question, and Kohana does not make it for you. There are basically two approaches:
Option 1: Storing identifiers
// in View
echo __
('about.description');
// in /i18n/latin.php
return array(
'about.description' => 'Lorem ipsum dolor amet...',
);
Option 2: Storing the translated string itself
// in View
echo __
('This is a sample text...');
// in /i18n/latin.php
return array(
'This is a sample text...' => 'Lorem ipsum dolor amet...',
);
Which one is better? This is a matter of preference, and I strongly advise that you store the translated string itself. I absolutely HATE storing identifiers, having done that and worked with some applications that store identifiers. shadowhand (Kohana’s benevolent dictator) agrees, see this discussion and this discussion.
The problem with identifiers is that they make you remember a million different strings and hunt around for the right string to change in your file. Your translators have to remember what “about.description” meant when they translate your files, and you have to remember what is behind “about.description” when you make code changes. This is a maintenance nightmare, and if you forget to translate a string, the user will see a cryptic identifier.
The pros of identifiers are that the translation files are slightly smaller and use slightly less memory, and that you can change the translation files without changing the code. However, these are in my opionion rather meager advantages compared to the maintenance problems that are created.
Storing the string itself is better, because it allows you to see the text in your files and allows you to edit the translation in-place. This 1) allows for direct editing, and 2) makes it obvious that a new translation is needed since the English text is shown, and 3) if you forget to translate a string, the user will see something in English rather than just a identifier.
The nice thing is that if you write your application in the default target language, no I18n calls are performed when you use __()! So you don’t even pay the cost of a lookup. You do not need to create a translation file for the default language, because strings in the default language will be returned as-is by __().
TL;DR: Store the translated string itself.
How do I tell Kohana I want to translate a string?
Use __(‘This is my string’). If the string is in a language other than en-us, then use __(‘String’, null, ‘language’); you will then need to have a /application/i18n/en.php file for the English equivalent.
If you need to replace items in the string, do something like:
echo __
('Dear :firstname, your username is: :user', array(
':firstname' => $user->name,
':user' => $user->username,
));
How do I start collecting the i18n strings?
Put a file like this as /application/languagename.php:
<?php defined('SYSPATH') or
die('No direct script access.');
return array(
'Hello World' => 'Terve maailma',
);
and start writing the English-to-your-language strings.
Now that you know how to do that – don’t do it manually! Instead take a look at my automatic I18n string collector, which extends I18n and automatically detects missing translations (for whatever language is currently set as the target), and keeps updating your translation files. It saves some serious amounts of time.
How do I allow the user to switch the language dynamically?
Use a cookie to store the user language, then load the value in /application/bootstrap.php:
// default value for the cookie is 'fi' for Finnish
$lang = Cookie
::get('lang', 'fi');
if(!in_array($lang, array('fi', 'sv', 'en-us'))) {
// check the allowed languages, and force the default
$lang = 'fi';
}
// set the target language
i18n
::lang($lang);
Then provide some sort of interface to changing the cookie in a Controller:
function action_change_language
($lang) {
if(!in_array($lang, array('fi', 'sv', 'en-us'))) {
$lang = 'fi';
}
Cookie
::set('lang', $lang);
I18n
::lang($lang);
Request
::instance()->redirect('page/index');
}
The link would look something like Html::anchor(‘/controller/change_language/fi’, ‘Finnish’) to change the language.
How do I get a string in a different language than the one currently in use?
This is the use case for I18n::get($string, $lang). It will get you the string in a different language than the one currently in use. An example would be when you want the user to select the language an invitation email is sent to a friend (who may speak a different language), and you already have the invitation email translated in multiple languages.
Remember, in __($string, $values, $lang) you set the source language (what language is the string in) while in I18n::get($string, $lang) you set the target language (what language you want the string to be returned in).
Update: An important thing to note about newline characters in multi-line strings
I noticed that there is one area which causes problems with translations: newline characters.
Since lookups are character-sensitive, if your newline characters change in the translation file, you may not be able to look up the correct string!
I strongly recommend using UNIX line endings in your translation files and explicitly using “\n” when performing multi-line string lookups (e.g. __(‘”String\nNewline\nSecond newline”‘). Otherwise, there is the risk that you accidentally save with Windows file endings, causing translation lookups to fail for multi-line strings. If you need different line endings (e.g. emails), do a strtr afterwards.
Update: Remember to save as UTF-8
The default output from Kohana is UTF-8 encoded, so make sure your translation files are saved as UTF-8.
How do I make it easier for someone to translate my strings?
Updated: I’ve now released the Kohana-translate module, which is an application for collecting translations for KO3 applications. Read about & download Kohana-translate here.
Questions, comments, thanks, contributions?
Leave a comment below… or ask on the Kohana forums.