front and back-end web development, Leeds, UK


Richard's Blog - Design, coding and life in Japan

Richard

Practical Internationalization in Lithium

Just thought I would write some jottings about internationalization as it is something I seem to do quite a lot of these days. (I have lived in Japan for 15 years with most of my websites being in Japanese so I get asked this a lot!).

Luckily Lithium has had translations in mind from the beginning which means it has great support for it. Although there were a few holes and guesswork in the beginning which I will try to help you out with in this blog post.

Much of this is covered in the Lithium g11n documentation so please use this together with the official documentation. This is just the process I have found to be the easiest/smoothest.

Setting Locales

config/bootstrap/g11n.php

$locale = 'en';
$locales = array('en' => 'English', 'ja' => 'Japanese', 'it' => 'Italian');

Environment::set('production', compact('locale', 'locales'));
Environment::set('development', compact('locale', 'locales'));
Environment::set('test', array('locale' => 'en', 'locales' => array('en' => 'English')));

Here is the place to set initial locale and default locale - which will however be later overwritten.

Setting Catalogs

config/bootstrap/g11n.php

Catalog::config(array(
	'runtime' => array(
		'adapter' => 'Memory'
	),
	'default' => array(
		'adapter' => 'Gettext',
		'path' => LITHIUM_APP_PATH . '/resources/g11n'
	),
	'lithium' => array(
		'adapter' => 'Php',
		'path' => LITHIUM_LIBRARY_PATH . '/lithium/g11n/resources/php'
	),
	'code' => array(
		'adapter' => 'Code',
		'path' => LITHIUM_APP_PATH
	)
) + Catalog::config());

This is important to differentiate the different catalog approaches Lithium takes. We will essentially be extracting text from the 'Code' of your application and then saving all of our translations in 'Gettext' which is a standardized translation package.

Extraction Preparation

Extraction across the framework can easily be done by framework. Even though 'lithium\g11n\Message::translate()' can translate our strings for us this is not a possible method for extraction. For this we need to extract this method into an anonymous function by using 'extract(lithium\g11n\Message::aliases())', this now allows for translation and extraction to be available through "$t('My string flagged for translation')". If you have strings that have wildcards you can do so like "$t('I think {:wildcard} are cool!', array('wildcard' => $t('Pink Socks')))".

Where do you do this?

In your templates and layout files the '$t()' command and '$tn()' command are already available to ready to go so you don't need to do anything. But in any other method across the website will need the 'lithium\g11n\Message::aliases()' extracted.

What about validations? 

Because of PHP scoping issues using the $t() extracted anonymous function in the $validations property are not practically possible. Because of this I have placed my validation translations in a method like the following example.

public static function validationRules(){
	extract(Message::aliases());
	return array('name' => array(
		array('notEmpty', 
			'message' => $t('{:item} should not be empty.', array(
				'item' => $t('Name')
		)))) 
	);
}

Then in a base model class you simply add those values to the '$validates' property in the initializer.

public static function __init() {
	$class = get_called_class();
	if (method_exists($class,'validationRules')) {
		$self = static::_object();
		$self->validates = static::validationRules();
	}
	parent::__init();
}

Now for the extraction and language file creation

Because so many have found this step confusing using the current extractor I have made my own string translation lithium command class to simplify this.

app/extensions/command/Translation.php

namespace app\extensions\command;

use lithium\g11n\Catalog;

class Translation extends \lithium\console\Command {

	protected $_languages = array('ja', 'it');

	/**
	 * Generate a language files for each translation
	 */
	public function create() {
		foreach ($this->_languages as $locale) {
			$command  = "msgmerge -U resources/g11n/{$locale}/LC_MESSAGES/default.po ";
			$command .= "resources/g11n/message_ default.pot";
			passthru($command);
		}
	}

	/**
	 * Compile a binary for each translation file
	 */
	public function compile() {
		foreach ($this->_languages as $locale) {
			$command  = "msgfmt -D resources/g11n/{$locale}/LC_MESSAGES ";
			$command .= "-o resources/g11n/{$locale}/LC_MESSAGES/default.pot default.po";
			passthru($command);
		}
	}

	/**
	 * Extract all translated strings from the codebase
	 */
	public function extract() {
		$data = Catalog::read('code', 'messageTemplate', 'root', array(
			'lossy' => false
		));
		$scope = 'default';
		return Catalog::write('default', 'messageTemplate', 'root', $data, compact('scope'));
	}
}

Using this command all you have to do is type the following from the app directory:

'li3 translation extract'

This will now save your pot file to 'resources/g11n/message_default.pot'. From here your .po files for each locale need to be created. That can be done by typing:

'li3 translation create'

Now you can translate the '.po' files that exist at 'resources/g11n/{$locale}/LC_MESSAGES/default.po', the next step is to turn these into compiled '.pot' files:

'li3 translation compile'

Now you are good to start using translations in your lithium app.

Routes

You can set your routes up to use locales which should get the desired locale strings to be processed. The localized routes method has documentation but is likely to not be supported and depricated in the very near future. A simple example of a working route is as follows:

$persistLocale = array('persist' => array('locale'));
Router::connect('/{:locale}', 'Pages::view', $persistLocale);

Translation Strings

As you can see this is only about translation of strings in your apps. The translation of DB stored items is also important. I have written a plugin named li3_translate which deals with this and I will write more about it at another date. 

Tags:

Recent Blog Posts