======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: - Start the staff client and log in - Click **Admin -> For developers -> about:config**. The **about::config** window opens. - Type ''general'' in the **Filter** field. The list of configuration entries is limited to those that include the word ''general''. - Double-click the **general.useragent.locale** entry. The **Enter string value** dialog box opens. - Enter the name of the locale you want to use; for example, ''hy-AM'' or ''fr-CA''. - Type ''intl'' in the **Filter** field. The list of configuration entries is limited to those that include the word ''intl''. - Double-click the **intl.accept_languages** entry. The **Enter string value** dialog box opens. - Enter the name of the locale you want to use; for example, ''hy-AM'' or ''fr-CA''. - 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//'' 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: So to create a translation of the catalog interface in a new language, you could simply: - Copy ''locale/en-US/opac.dtd'' to ''locale/new-locale/opac.dtd'' - Translate the strings contained within the quoutation marks ("User name" in the preceding example) - Add a new ''SetEnvIf'' statement to ''eg_vhost.conf'' as above for your new locale - (Optional): Send your new translation to the [[http://open-ils.org/listserv.php|open-ils-dev]] mailing list. However, this would be a fragile way of translating the catalog interface. We have opted instead to use the [[http://www.gnu.org/software/gettext/manual/gettext.html|GNU gettext]] translation framework of POT and [[http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files|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:problem_children|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: ]> ''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 [[http://developer.mozilla.org/en/docs/Localizing_an_extension|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: We should be able to reuse code from the Firefox [[https://addons.mozilla.org/en-US/firefox/addon/1333|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: - 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 - 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: /common.properties"/> /circ.properties"/> - In the corresponding JavaScript file, declare the variables that will hold the strings in a global scope: var commonStrings; var circStrings; - 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'); - 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]); [[evergreen-admin:customizations:i18n:Staff client progress|Staff client i18n progress]]