The Harmoni Language Localizer provides the following functionality to a Harmoni-based application:
- Acts as a streamlined wrapper for the gettext setup functions
- Maintains the language and character-set throughout the session
- Provides listings of the languages availible in the application
The articles listed below are recommended reading. This documentation summerizes some aspects of them and documents the use of Harmoni's Language Localization service to aid in the managing of languages in an application. This documentation should be sufficient to bring a new developer up to speed on the use of Language Localization in a Harmoni-based application. If you are creating a new Harmoni-based application and wish to use Language localization, it is recommended that you be familier with the material below.
- Internationalization Using PHP and GetText by Luis Argerich, the basis for much of this documentation.
- PHP gettext doc [on PHP.net]
The gettext functions provide NLS (Native Language Support) to PHP applications via the GNU gettext package. Instead of outputting strings directly, the gettext() or its alias, _(), function is used:
print _("Hello World!");
Command line programs such as xgettext are used are used to extract the gettext strings from the source code into a .po translation file. After the strings in the .po file are translated, a program such as msgfmt, "message format", is used to generate the message object files that will be referenced when the gettext() or _() function is executed in the application.
Below is an example of some application code that is using gettext. The _() alias is used to save typing.
<?php // create.act.php
...
// Instantiate the wizard, then add our steps.
$wizard =& new Wizard(_("Create a Collection"));
$_SESSION['create_collection_wizard'] =& $wizard;
// :: Step One ::
$stepOne =& $wizard->createStep(_("Name & Description"));
// Create the properties.
$displayNameProp =& $stepOne->createProperty("display_name", "Regex");
$displayNameProp->setExpression(".*");
$displayNameProp->setDefaultValue(_("Default Collection Name"));
$descriptionProp =& $stepOne->createProperty("description", "Regex");
$descriptionProp->setExpression(".*");
$descriptionProp->setDefaultValue(_("Default Collection description."));
// Create the step text
$stepOneText = "\n<h2>"._("Name")."</h2>";
$stepOneText .= "\n"._("The Name for this <em>Collection</em>: ");
$stepOneText .= "\n<br><input type='text' name='display_name' value=\"[[display_name]]\">";
$stepOneText .= "\n<h2>"._("Description")."</h2>";
$stepOneText .= "\n"._("The Description for this <em>Collection</em>: ");
$stepOneText .= "\n<br><textarea name='description'>[[description]]</textarea>";
$stepOneText .= "\n<div style='width: 400px'> </div>";
$stepOne->setText($stepOneText);
...
?>
To generte a .po translation file for the create.act.php file shown above, one would use the following command:
$ xgettext -C -o myAppName.po --keyword=_ create.act.php
To generate a .po file from many files, one could run:
$ xgettext -C -o myAppName.po --keyword=_ *.php
If you wish to generate a .po with all the strings scattered throughout an application source-hierarchy, it is necessary to pipe the filenames to xgettext from the "find" command, as xgettext doesn't do recursive searching through directories. The "-j" option joins the messages together with the already existing file. This is necessary to prevent xgettext from overwriting the contents of the .po each time it is called with a find result. This means however, that the output file must exist already. Running the command below without the -j first will create the file so that it can be joined to later. The following command is being run from the application root directory, where all sources are in the "main" directory:
$ find main/ -iname "*.php" -exec xgettext -C -j -o main/languages/en_US/LC_MESSAGES/myAppName.po --keyword=_ {} \;
As mentioned in the Argerich article, the message files should be placed in LC_MESSAGES directories inside directories for each language:
/languages/en_US/LC_MESSAGES/myAppName.mo
myAppName.po
/es_ES/LC_MESSAGES/myAppName.mo
myAppName.po
After a translated copy of the .po file has been placed in the LC_MESSAGES directory, a .mo message object file needs to be generated. This file is the binary has of the messages that PHP's gettext implementation will use to pull out the appropriate strings. To create the .mo file, cd to the directory that the .po file is in and run the following:
$ msgfmt myAppName.po -o myAppName.mo
Assuming that you have your .mo files in a languages directory inside your application like the following,
/languages/en_US/LC_MESSAGES/myAppName.mo
myAppName.po
/es_ES/LC_MESSAGES/myAppName.mo
myAppName.po
the following are all that are needed to set up the Language Localizer.
<?
...
// Start the service
Services::startService('Lang');
// Set the application language directory
$langLoc =& Services::getService ('Lang');
$langLoc->addApplication('myAppName', '/languages');
...
?>
If you wish to change the language from the default of en_US, pass that when starting the service.
// Start the service with Spanish as the default language
Services::startService('Lang', 'es_ES');
Changing the via the Language Localizer instead of directly via setlocate() allows the Language Localizer to store the current language settings to the session. To change the current language, just use the following function:
// Set the current language
$langLoc =& Services::getService ('Lang');
$langLoc->setLanguage('de_DE');
Changing the character set from the default of UTF-8 is done in a similar fashion:
// Set the current code set
$langLoc =& Services::getService ('Lang');
$langLoc->setCodeset('ISO-8859-1');
Often, you may wish to have plugins or additional libraries with their own source-trees and message object files. The LanguageLocalizer can aid with the managing of these.
To add another translation source, one first needs to register it with the Language Localizer.
<?
...
// Start the service
Services::startService('Lang');
// Set the application language directory
$langLoc =& Services::getService ('Lang');
$langLoc->addApplication('myAppName', '/languages');
$langLoc->addApplication('myOtherAppName', '../../myOtherAppName/languages');
...
?>
In all applications trees except for the first, the textdomain will need to be manually specified to ensure that the proper location is accessed for message object searching. This is done with the textdomain() function as shown in the example below.
<?
...
// make sure we have the right textdomain
$defaultTextDomain = textdomain();
textdomain('myOtherAppName');
// execute our code in this textdomain
print _("Hello World.");
...
// go back to the default textdomain
textdomain($defaultTextDomain);
...
?>