I18n & l10n

Internationalization & localization

Internationalization of an application requires to identify all language dependent resources and make them exchangeable for the actual localization into a specific language. This includes static and dynamic texts as well as images. Since images are referenced by their filename or represented as text (e.g. base64 encoded), it is sufficient to focus on text.

Static text

Static text refers to text used in application templates or messages generated while the application is executed. To keep the application code language agnostic only so called text identifiers are used in these places and the actual translation is retrieved by calling the Message::getText method:

$textId = 'An error occured';
// retrieve the translation
$message = ObjectFactory::getInstance('Message');
$errorText = $message->getText($textId);
// use the translation
$ex = new Exception($errorText);
Note
Implementations of Message must return the text identifier, if no translation is found or the translation is empty. This helps to simplify the translation process, if the default language is used in text identifiers. In this case, no translation is required for the default language.

If no language parameter is passed to the Message::getText method, the returned translation will be in the application's default user interface language. This language is determined in the following way:

  1. Use the value of the language parameter of the Message configuration section
  2. If not defined use the value of the global variable $_SERVER['HTTP_ACCEPT_LANGUAGE']

If parts of the text vary depending on the application state, it is necessary to put variables into the text identifier. Variable names are %0%, %1%, ... and they are replaced in the order of the values passed into the Message::getText method:

$textId = 'Logged in as %0% since %1%';
$parameters = [$username, $date];
// retrieve the translation
$message = ObjectFactory::getInstance('Message');
$loginMessage = $message->getText($textId, $parameters);
// use the translation
$view->setValue('loginMessage', $loginMessage);

Template text

wCMF provides the translate plugin for embedding language dependent text into Smarty templates. The plugin is automatically included and used in the following way:

{translate text="An error occured"}
{* with variables *}
{translate text="Logged in as %0% since %1%" r0=$login r1=$logindate}

Localization

Since Message defines the interface, it needs concrete implementations to actually get the translations. The FileMessage class retrieves translations from the file system. It expects one file per language defining a language dependent array that maps text identifiers to translations. The language code (e.g. en) is used to identify the language of the translation. The file must be named like messages_code.php, the variable like $messages_code.

The following examples show definitions for English (en) and German (de) texts, where the array keys are the text identifiers used in the application and the values are the translations in the appropriate language.

messages_en.php

$messages_en = [];
$messages_en['%0% selected'] = '';
$messages_en['Author'] = '';
$messages_en['Book'] = '';

messages_de.php

$messages_de = [];
$messages_de['%0% selected'] = '%0% ausgewählt';
$messages_de['Author'] = 'Autor';
$messages_de['Book'] = 'Buch';
Note
Note that the text identifiers are in the application's default language (en) and therefor no translations need to be provided.

The default configuration of Message is as follows:

[Message]
__class = wcmf\lib\i18n\impl\FileMessage
localeDir = app/locale/
language = en

The parameter localeDir defines the directory where FileMessage searchs for translation files and the language parameter defines the application's default language.

Tools

The default application ships with a tool for finding translatable text and generating localization files from it. After installing the application it is available under http://localhost/wcmf-default-app/tools/locale/.

The tool uses I18nUtil internally to search for occurrences of

  • ->getText('Text to translate', ...) as used in $message->getText('Text to translate', ...) in PHP code
  • {translate:"Text to translate" ...} as used in Smarty templates
  • Dict.translate("Text to translate", ...) as used in the Javascript code of the default application

Text to translate is supposed to be the message to be localized. The tool generates one localization file for each language puts it into the appropriate directory. It's capable of merging already existing translations into the newly generated files.

The tool's configuration is defined in the file config.ini in the tool's directory and defaults to:

[Application]
localeDir = app/locale/
[I18n]
searchDirs = {vendor/wcmf/wcmf/src/wcmf, app/}
exclude = {vendor, .git, .svn, test}
languages = {de, en}

Model content

Model content is initially created in the application's default model language, which might differ from the default user interface language (see Static text). Localization of the model content is done by using implementations of Localization. The interface defines methods for loading and saving localized model content. The following code demonstrates how to use it to get translated content:

$oid = new ObjectId('Book', 1);
// retrieve a localized entity instance
$localization = ObjectFactory::getInstance('localization');
$bookDe = $localization->loadTranslatedObject($oid, 'de');

Localization

There are several strategies for storing localized versions of the content, all having advantages and disadvantages regarding extensibility and performance. The idea behind the DefaultLocalization implementation is to keep the domain model clear of localization related attributes by storing translations in a separate entity type. Using one entity type for all content leads to more flexibility when extending the domain model.

The translation entity type must have the following attributes:

  • objectid object id of the translated entity (e.g. Book:1)
  • attribute translated attribute (e.g. title)
  • language the language code of the translation (e.g. en)
  • translation the actual translation

The supported content languages are either defined in the Languages configuration section (see below) or in the language entity type that is used by DefaultLocalization, if the configuration section does not exist.

The language entity type must have the following attributes:

  • code language code (e.g. en)
  • name language name (e.g. English)

The following example demonstrates the creation of localized content using the Localization interface:

$oid = new ObjectId('Book', 1);
// retrieve the original entity instance
$persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
$book = $persistenceFacade->load($oid);
// localize the instance
$book->setValue('title', 'Übersetzter Titel');
// save the translation
$localization = ObjectFactory::getInstance('localization');
$localization->saveTranslation($book, 'de');

The localization configuration of the default application is the following:

[Localization]
__class = wcmf\lib\i18n\impl\DefaultLocalization
defaultLanguage = en
languageType = app.src.model.wcmf.Language
translationType = app.src.model.wcmf.Translation
[Languages]
de = Deutsch
en = English

In this example the default model language is set to English and the language entity type is ignored, because the available languages are defined in the configuration.