User Tools

Site Tools


backend-devel:i18n

Localization: technical details

For developers, the goal is to make it easy to incorporate localized strings in their code by using native string localization methods (such as defining entities in DTDs, or defining properties in JavaScript property files, or wrapping translatable text in database strings in a special translation function).

For translators, the goal is to make it easy to translate by extracting translatable strings from the various native formats and presenting the translatable strings in a standard GNU gettext format.

Bundling a new localization in the staff client

Assuming that you have already installed a new localization in the source tree, you can now tell XULRunner that another translation is available. When you run make LOCALE=foo install, the installer adds another set of locale entries to Open-ILS/xul/staff_client/chrome/chrome.manifest. In the following example, fr-CA has been added to chrome.manifest:

content open_ils_staff_client content/
locale open_ils_staff_client en-US locale/en-US/
locale open_ils_staff_client fr-CA locale/fr-CA/
skin open_ils_staff_client open_ils_staff_client skin/
locale branding en-US branding/locale/en-US/
content venkman jar:venkman.jar!/content/venkman/
locale venkman en-US jar:venkman.jar!/locale/en-US/venkman/
skin   venkman modern/1.0 jar:venkman.jar!/skin/modern/venkman/

You then have to build the staff client to include the new localization and the appropriate chrome.manifest file.

Using a different locale in the staff client

When you select a different locale in the staff client, two configuration settings are modified that control the client's behavior. The intl.accept_languages setting changes the language of the documents requested from the server via the Accept-Language header. The general.useragent.locale setting changes the language of the local staff client files. The staff client locale switcher handles this for you, but you could also use the 'about:config' dialog:

  1. Start the staff client and log in
  2. Click Admin -> For developers -> about:config. The about::config window opens.
  3. Type general in the Filter field. The list of configuration entries is limited to those that include the word general.
  4. Double-click the general.useragent.locale entry. The Enter string value dialog box opens.
  5. Enter the name of the locale you want to use; for example, hy-AM or fr-CA.
  6. Type intl in the Filter field. The list of configuration entries is limited to those that include the word intl.
  7. Double-click the intl.accept_languages entry. The Enter string value dialog box opens.
  8. Enter the name of the locale you want to use; for example, hy-AM or fr-CA.
  9. Close the staff client and start it up again. The interface will use the specified locale, if it has been specified in the chrome.manifest and if the relevant files exist in the build/locale/<locale>/ subdirectory.

Translating the catalog interface

The Evergreen developers have made it possible to load different languages for the catalog. Laurentian University has contributed a French (Canadian) translation of the catalog interface to the Evergreen project as a proof-of-concept. Currently this relies on the Apache configuration file eg_vhost.conf to detect the locale in the URL (ll-LL in /opac/ll-LL/blah/) as follows:

SetEnvIf Request_URI "/en-US/" locale=en-US
SetEnvIf Request_URI "/fr-CA/" locale=fr-CA

The internationalization of the catalog depends on the use of entities within the XML files that provide the catalog interface. These entities act like variables, and the Open-ILS/web/opac/locale/ll-LL/lang.dtd file is where the values of the entities are defined for the given locale ll-LL. When used within an XML file, an entity starts with an ampersand (&) character and ends with a semi-colon (;) character – for example, &common.username;. The entity definitions within the DTD look like:

<!ENTITY common.username "User name">

So to create a translation of the catalog interface in a new language, you could simply:

  1. Copy locale/en-US/opac.dtd to locale/new-locale/opac.dtd
  2. Translate the strings contained within the quoutation marks ("User name" in the preceding example)
  3. Add a new SetEnvIf statement to eg_vhost.conf as above for your new locale
  4. (Optional): Send your new translation to the open-ils-dev mailing list.

However, this would be a fragile way of translating the catalog interface. We have opted instead to use the GNU gettext translation framework of POT and PO files to store the translatable strings for all of EG; we will then generate the output files (such as opac.dtd) from the PO files for each language. Some of the benefits of the GNU gettext format are that there are many tools that support POT and PO files that help identify fuzzy or untranslated strings. This way, if a new string is added to locale/en-US/opac.dtd, we can automatically update the translated versions of the files with the English version of the string until a translator has the opportunity to translate the string. We hope to have a release management process that will ensure that translators have ample opportunity to update their translations to 100% completion, and we will be able to use the GNU gettext tools to display the status of all translations.

I18N challenges

How it works

To use the definitions from the correct DTD, the XML files that make up the catalog contain the following statement:

<!-- Loads the environment -->
<!--#include virtual="setenv.xml"-->

<!DOCTYPE html PUBLIC
        "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
        <!--#include virtual="/opac/locale/${locale}/opac.dtd"-->
]>

setenv.xml defines default values for a number of environment variables on the server side, including the locale variable (which defaults to en-US). The previously mentioned Apache configuration statements will override the value of the locale variable to fr-CA if the URL includes the string "/fr-CA/". Finally, the locale variable is passed to the #include statement for opac.dtd where it replaces the ${locale} placeholder and determines which DTD the entity definitions will be loaded from.

However, there is one more piece to this puzzle. Web browsers do not normally support the remote retrieval of DTD files for parsing XML files; therefore, Evergreen runs the mod_xmlent Apache module to replace the entities in-place on the server before sending the HTML output to the client's browser. The following line of the Apache configuration file causes mod_xmlent to pre-process XML files for any includes and any entity replacements:

AddOutputFilter INCLUDES;XMLENT .xml

Translating the Staff Client

The staff client does not currently support dynamically switching between languages, but it is based on a Mozilla XML-based User Interface Language (XUL) framework. As XUL offers good support for dynamic language packs, we can enable language-switching fairly easily. However, our translation task is slightly more difficult than with the catalog interface:

  • In addition to strings in XUL files (XML files that rely on entities defined in Open-ILS/web/opac/locale/ll-LL/lang.dtd), the Staff Client makes heavy use of strings in JavaScript files. JavaScript strings must be localized using a property file that provides message catalogs on a per-language basis. JavaScript tends to be harder to convert to a cleanly localized file because it often concatenates strings with variables and requires the use of getFormattedString() to enable translators to reorder the variables within the string.
  • The Staff Client supports an off-line mode, meaning that all entities must be able to be resolved without relying on the Apache server to translate the content in-place via mod_xmlent. To support this mode, the lang.dtd file is packaged as part of the chrome and included in every XUL file as follows:
    <!-- LOCALIZATION -->
    <!DOCTYPE window SYSTEM "chrome://open_ils_staff_client/locale/lang.dtd">

We should be able to reuse code from the Firefox Quick Locale Switcher extension to enable dynamic switching of interface languages without requiring a restart of the Staff Client.

JavaScript message catalogs

Evergreen now sports its own message catalog implementation for JavaScript files. To make use of the messageCatalog interface, perform the following steps:

  1. Create a JavaScript properties file with key=value notation for defining strings; for example, to hold the strings for all JavaScript within the Open-ILS/xul/staff_client/chrome/content/circ/ directory, you could create the file Open-ILS/xul/staff_client/chrome/locale/en-US/circ.properties containing string definitions like:
    circ.standalone=Offline circulation
  2. In the XUL file that will embed the JavaScript file that needs to be localized, initialize a variable with the contents of a given messageCatalog – typically within the /window/ XPath for the XUL file:
    <!-- For client-side XUL - everything under Open-ILS/xul/staff_client/chrome/content/ -->
    <!-- The chrome protocol is supposed to automagically supply the locale -->
    <messagecatalog id="offlineStrings" src="chrome://open_ils_staff_client/locale/offline.properties"/>
     
    <!-- For server-side XUL  - everything under Open-ILS/xul/staff_client/server/ -->
    <!-- We use server-side includes to insert the locale variable -->
    <messagecatalog id="commonStrings" src="/xul/server/locale/<!-- #echo var="locale"-->/common.properties"/>
    <messagecatalog id="circStrings" src="/xul/server/locale/<!-- #echo var="locale"-->/circ.properties"/>
  3. In the corresponding JavaScript file, declare the variables that will hold the strings in a global scope:
    var commonStrings;
    var circStrings;
  4. Within the myInit() function of the JavaScript file, initialize the string variables to the contents of the XML element with the corresponding ID in the corresponding XUL file. $('id') is a convenient alias for "retrieve the contents of the XML element with the id attribute value of id:
    function my_init() {
    	try {
     
     
    		commonStrings = $('commonStrings');
    		circStrings = $('circStrings');
  5. Anywhere that you would normally use a hardcoded string in JavaScript, you can now use the getFormattedMessage() function to call the string from the message catalog; for example, rather than:
     window.xulG.set_tab_name('Offline circulation');

    you would use:

     window.xulG.set_tab_name(circStrings.getString('circ.standalone'));

    If the properties file has been translated into the currently selected staff client locale, it will automatically be retrieved and substituted for the name of the tab; otherwise, the en-US version will be substituted.

Hardcoded JavaScript strings that currently interpolate variables can be written in message catalog format to take multiple parameters using positional notation. getFormattedString() can accept a second parameter of an array that holds the parameters for a given string. An entry in the message catalog looks like:

common.barcode.status.warning=Warning: As of %1$s, this barcode (%2$s) was flagged %3.

The call to getFormattedString() that supplies the required values for those parameters looks like:

var msg = commonStrings.getFormattedString('common.barcode.status.warning', 
    [g.data.bad_patrons_date.substr(0,15), barcode, code]);

Staff client i18n progress

backend-devel/i18n.txt · Last modified: 2009/03/13 22:37 by dbs

© 2008-2017 GPLS and others. Evergreen is open source software, freely licensed under GNU GPLv2 or later.
The Evergreen Project is a member of Software Freedom Conservancy.