symfony advent calendar day twenty-three: Internationalization ============================================================== Previously on symfony --------------------- Now that you learned how to transfer a symfony application to a production host, the askeet application can run anywhere. But what if someone decided to use it in a non-English speaking country like, say, France? Askeet being an open-source project, we hope that people from all over the world will use it shortly. Not only does that mean that all the files of the project have to be encoded in [utf-8](http://en.wikipedia.org/wiki/UTF-8), the application also has to propose a multilingual interface and content localization. Think about the multinational companies that are going to install askeet on their Intranet to have a knowledge management base. They will definitely require that users can switch interface language or content rather than install one askeet per language... Fortunately, the choices made during the [eighteenth day](18.txt) to implement universes will ease our task a lot, and symfony has native support for internationalized interfaces. Localization ------------ What if the call to an address like: http://fr.askeet.com/ ...displayed only the French questions? Well, this is quite easy, because since the [eighteenth day](18.txt), such an URI is understood as a universe. ### Content Creating a question in a language universe will have it tagged automatically with the language tag (here: 'fr'). And, if you browse the 'fr' universe, only the questions with the 'fr' tag will appear. So the universe filter already takes care of content localization. That was an easy move. ### Look and feel The universes can have their own stylesheet. This means that the look and feel of a localized askeet can be easily adapted as well, with the same mechanism. Next, please. ### Language-dependent functions The database indexing system built during the [twenty-first day](http://www.symfony-project.com/askeet/22) relies on a stemming algorithm which is language-dependant. In a localized version, it has to be adapted. For now, there is no available stemming library for other languages than English in PHP, but what if there was one, or what if someone decided to port one of the [Perl stemming libraries](http://search.cpan.org/search?query=stem&mode=all) to PHP? Then, in the `myTools::stemPhrase()` method, we should call a [factory method](http://en.wikipedia.org/wiki/Factory_method_pattern) instead of a simple PorterStemmer (left as an exercise for now). ### Database content Imagine an international website proposing a list of hotels around the world. Each hotel is shown with a text description of the rooms, the service and the opening hours. There are thousands of hotels, so this content is to be stored in a database. The problem is that there must be as many versions of the descriptions as there are translations of the site. Symfony provides a way to structure data in order to handle such cases. As for the example above, there would be a `Hotel` class for the fares, address and not-to-be-translated content, and a `HotelI18n` class for the localized content. As the Propel accessors abstract this separation, even if the `description` was located in the `HotelI18n` table, you would still access it with a simple: [php] $description = $hotel->getDescription(); To understand how this works, refer to the [i18n chapter](http://www.symfony-project.com/book/1_0/13-I18n-and-L10n) of the symfony book. Fortunately, the filter system of the askeet universes replaces the need for content adaptation, so we won't use it here.. Internationalization -------------------- As it is a long word, developers often refer to [internationalization](http://en.wikipedia.org/wiki/Internationalization_and_localization) as 'i18n'. For those who don't know why, just count the letters in the word 'internationalization', and you will also understand why 'localization' is referred to as 'l10n'. In web application development, i18n mostly concerns the translation of text content and the use of local formats for the interface. ### Set the culture A lot of built-in i18n features in symfony are based on a parameter of the user session called the **culture**. The culture is the combination of the country and the language of the user, and it determines how the text and culture-dependant information will be displayed. When the askeet application recognizes a universe as a localization, it has to set the corresponding culture. When should a permanent tag be recognized as a localization? We choose to allow only the ones for which the interface is translated (see below), so the fact that a universe is a localization is determined by the existence of an XML translation file in the project `i18n/` directory. The universes are discovered in the `askeet/apps/frontend/lib/myTagFilter.class.php` filter, so we just need to modify it a little bit: [php] public function execute ($filterChain) { ... // is there a tag in the hostname? $request = $this->getContext()->getRequest(); $hostname = $request->getHost(); if (!preg_match($this->getParameter('host_exclude_regex'), $hostname) && $pos = strpos($hostname, '.')) { $tag = Tag::normalize(substr($hostname, 0, $pos)); // add a permanent tag constant sfConfig::set('app_permanent_tag', $tag); // add a custom stylesheet $request->setAttribute('app/tag_filter', $tag, 'helper/asset/auto/stylesheet'); // is the tag a culture? if (is_readable(sfConfig::get('sf_app_i18n_dir').'/global/messages.'.strtolower($tag).'.xml')) { $this->getContext()->getUser()->setCulture(strtolower($tag)); } else { $this->getContext()->getUser()->setCulture('en'); } } ... } >**Note**: The language tags that will be recognized are to be coded in two lower-case characters, as described in the [ISO 639-1 norm](http://www.w3.org/WAI/ER/IG/ert/iso639.htm) (for instance `fr` for French). When dealing with internationalization, always prefer ISO codes for countries and languages, so that your code can comply with international standards and be understood by foreign developers. You will find more information about internationalization and cultures in the [i18n chapter](http://www.symfony-project.com/book/1_0/13-I18n-and-L10n) of the symfony book. ### Dates, Times, Numbers, Currency, Measurements The way to display a date in France is not the same as in the US. What an American would write: December 16, 2005 9:26 PM ...is written by a French 16 décembre 2005 21:26 If you remember well, each time we had to display a date in an askeet template, we used the `format_date()` helper. This helper formats the date given as parameter according to the user culture. As the culture is set in the `myTagFilter.class.php` filter, the date formatting will be done automatically.  This is a another good practice for international projects: always use the i18n helpers when you have to output a date, a time, a number, a currency or a measurement. Symfony provides helpers for most of them (see the [i18 helpers chapter](http://www.symfony-project.com/book/1_0/13-I18n-and-L10n) of the symfony book for more information). ### Interface translation The interface of the askeet project contains text. In a localized version, the text of the interface should be displayed in the language of the user culture. To enable interface translation, all the texts of the askeet templates have to be enclosed in a special i18n helper, `__()`. In addition, the helper must be declared at the top of the template. For instance, to enable interface translation in the home page, open the `askeet/apps/frontend/modules/question/templates/listSuccess.php` template and change it to: [php]
$question_pager)) ?> >**Note**: Instead of having to add the `i18n` helper on top of each template, you can just add it once to the application `settings.yml` in `askeet/apps/frontend.config/`: > > all: > .settings: > > standard_helpers: Partial,Cache,Form,I18N > For each language in which the interface is translated, a `messages.xx.xml` file must be created in the `askeet/apps/frontend/i18n/` directory, where `xx` is the language of the translation. This XML file is a [XLIFF](http://www.xliff.org/) dictionary, showing the translated version of the text from the source language (English for askeet). For instance, to enable a French translation, you must create a `messages.fr.xml` with the following content: [xml]