Localization

TeamBeam is available in multiple languages. The GNU Gettext system is used to extract and manage translation strings, and to merge the messages for individual languages into the code base.

skp-server

The Java-based application server uses the gettext-commons library to provide localized message strings. The standard gettext tools are used to scan the source code and extract translatable strings. During the build process, PO-files are compiled into Java resource bundles by maven.

The resource bundles are identified as com.skalio.skp.i18n.Messages.

process flow

Java source code

In order to use localized messages, an instance of I18nFactory must be obtained. Ideally specify the target locale at that time:

Locale locale = Locale.getDefault();
I18n i18n = I18nFactory.getI18n(
        this.getClass(),
        "com.skalio.skp.i18n.Messages",
    locale
);

Alternatively the locale can be switched later. Be aware that this triggers a reload of the resource bundle.

i18n.setLocale(locale);

To get a localized string, call i18n.tr() with the translatable string as first parameter. Strings are message-formatted, additional parameters will be inserted into placeholders.

String hello = i18n.tr("Hello, world!");

UserDAO userDAO = userRepository.getByEmail('bob@example.org');
String greeting = i18n.tr("Hello {0}!", userDAO.getRealname());

Plural-forms of translatable strings are also supported. In this case, the method trn() expects at least three parameters.

  • singular version of the string
  • plural version of the string
  • number, whose value determines if the the singular or plural version is to be used
  • following this is an optional number of parameters for message-format substitution
int fileCount = transferDAO.getTransferObjects().size();
String text = i18n.trn(
        "The file has been transferred.",
        "All {0} files have been transferred.",
        fileCount,
        fileCount
);

Handlebars templates

Within Handlebars templates, localization is triggered with Handlebars helpers tr and trn. Their functionality is identical to the Java code.

<p>
    {{tr "Hello, {0}" transfer.sender.realname}}
</p>
<p>
    {{trn "The file has been transferred." "All files have been transferred." fileCount}}
</p>

The desired locale is exported into the template context:

  • env.locale, an instance of java.util.Locale, e.g. Locale.GERMANY
  • env.language, String, e.g. de
  • env.lang, String, e.g. de_DE

Extraction of keys

Translatable strings are not extracted automatically, instead this process must be done manually. A build-script that facilitates this process is available at build/gettext-scan.sh. The script depends on:

  • gettext utilities, available in the users search path
  • Node.js, npm
  • npm module xgettext-template

The scan-script searches the Java source code and the Handlebars templates for translatable strings, and the message catalogs in src/po are updated.

$ build/gettext-scan.sh
Preparing i18n files for skp-server 1.4.0
Supported locales: de_DE fr_FR ru_RU ko_KR ja_JP zh_CN zh_TW

Scanning Java files ... done!
Scanning Handlebars files ... done!
Merging catalogs into language files ...
 de_DE: ... done.
 fr_FR: ... done.
 ru_RU: ... done.
 ko_KR: ... done.
 ja_JP: ... done.
 zh_CN: ... done.
 zh_TW: ... done.
done!

PO-files and the index file src/po/keys.pot are under version control.

To quickly scan the ratio of translated strings, use the script build/gettext-check.sh:

$ build/gettext-check.sh
Checking translated i18n files for skp-server 1.4.0
Supported locales: de_DE fr_FR ru_RU ko_KR ja_JP zh_CN zh_TW

Checking status of translations:
 de_DE: 26 translated messages.
 fr_FR: 6 translated messages, 20 untranslated messages.
 ru_RU: 0 translated messages, 26 untranslated messages.
 ko_KR: 0 translated messages, 26 untranslated messages.
 ja_JP: 0 translated messages, 26 untranslated messages.
 zh_CN: 0 translated messages, 26 untranslated messages.
 zh_TW: 0 translated messages, 26 untranslated messages.
done!

Resource bundle compilation

While the resource bundles are created automatically at build time, this process can be triggered manually as well:

$ mvn com.googlecode.gettext-commons:gettext-maven-plugin:dist
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Skp Restful Server Gateway 1.4.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- gettext-maven-plugin:1.2.4:dist (default-cli) @ skp-server ---
[INFO] Processing de_DE.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_de_DE.class
[INFO] Processing fr_FR.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_fr_FR.class
[INFO] Processing ja_JP.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_ja_JP.class
[INFO] Processing ko_KR.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_ko_KR.class
[INFO] Processing ru_RU.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_ru_RU.class
[INFO] Processing zh_CN.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_zh_CN.class
[INFO] Processing zh_TW.po
[INFO] Not compiling, target is up-to-date: skp-server/target/classes/com/skalio/skp/i18n/Messages_zh_TW.class
[INFO] Creating resource bundle for source locale
[INFO] Creating default resource bundle
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.391 s
[INFO] Finished at: 2017-02-24T09:29:15+01:00
[INFO] Final Memory: 7M/245M
[INFO] ------------------------------------------------------------------------

Tips and tricks

Installing gettext via homebrew on macOS requires the tools to be linked into /usr/local/bin:

brew install gettext
brew link --force gettext

When maven is executed within NetBeans during the Build Project action, it may not find the gettext binaries. In order to fix this, export an extended PATH in the NetBeans config file netbeans.conf. Unfortunately, on macOS the config file is found within the application at /Applications/NetBeans/NetBeans 8.1.app/Contents/Resources/NetBeans/etc/netbeans.conf. Most likely, changes to this file will need to be repeated after each upgrade.

Add the following line to the bottom of the file:

export PATH=$PATH:/usr/local/bin