i18n
i18n copied to clipboard
Gettext based framework for i18n in Java and JSF applications
A gettext based framework for i18n in Java and JSF applications
What is gettext?
Gettext is an internationalization and localization (i18n) library which is commonly used for writing multilingual programs. Its most popular implementation is that of the GNU project.
I18n with gettext works by marking up translatable strings in the source code, usually by wrapping them with a function call. The xgettext tool extracts these strings and creates a text file listing them. This file is called the template and its name usually ends in ".pot".
The msginit tool creates a new text file mapping the extracted strings to their translation in a given locale, having the ".po" extension. Finally the msgfmt tool creates an optimized representation of the translation mappings that is then used at runtime. For most programs this is a binary file ending in ".mo", but it is also possible to create other formats, for example a java ResourceBundle.
There are specialized editors for editing the ".mo" files, which can remember already translated strings and contain databases of repeatedly used words.
Why use gettext instead of property bundles?
Translation in java is usually done using property files, which map some artificial key to the corresponding translation. In my opinion this approach has several drawbacks.
-
The need to invent a key for each message interrupts the programmers flow. Sometimes it is already pretty hard to think of meaningful variable names, having to think about something totally unrelated to the code, like translation further distracts the programmer.
-
The plural handling in gettext is superior for some languages having more than two plural forms. For simple cases pluralization can be done using
ChoiceFormat, but languages like Polish require more complicated rules. Additionally, the syntax forChoiceFormatis probably to complicated for non-developers. -
There are specialized editors for editing gettext files, which are also usable by non-programmers. These editors have extensive features to support the translator, like remembering already translated string or containing a terminology database.
How to use this framework in java?
The first step when doing translation is actually to determine the correct
locale and ResourceBundle to look up messages. For a desktop program this is
simple since it can just use the default locale of the jvm, for a multi user
web application its more complicated. Futhermore you usually do not want to
pass around the locale to any method that might need to output messages,
neither do you want to tightly couple the java logic to the view, for example
by calling FacesContext.getCurrentInstance.
To solve these problems, this framework uses a ServiceLoader based solution
to lookup the locale and ResourceBundle, with implementations for the default
locale and JSF. The implementation classes can be specified in two files in
META-INF/services, net.jhorstmann.i18n.LocaleProviderFactory and
net.jhorstmann.i18n.ResourceBundleProviderFactory. To use the JSF
implementation it is enough to depend on the i18n-jsf library since it
contains these files in its classpath.
Translatable string in java code should be marked up by calls to the static
functions in net.jhorstmann.i18n.I18N. The trc functions take an additional
context parameter that is supposed to help translate words with different
meaning when used in different contexts, trn takes an additional parameter
for plural handling and trnc uses both.
In JSF views there are two ways to markup strings. The first one is to use EL
functions from the http://jhorstmann.net/taglib/i18n namespace. These work
just like the java functions described above. The second and preferred way is
to use the tr JSF component. Examples how to use this can be found in the
index.xhtml of the i18n-sample-webapp project.
Extraction of messages is supported by a maven plugin. The i18n:gettext goal
extracts messages from xhtml files and java class files, meaning it should
be invoked in the process-classes phase or manually after the project is
compiled.
The i18n:init goal creates a new ".po" file for the locale given as
-Dlocale=xyz. When messages are added or changed in the code, the
i18n:gettext and i18n:merge goals should be invoked to update all ".po"
files.
The i18n:dist goal generates the java ResourceBundle that is used at runtime
and should be bound to the generate-resources phase. The other goals should
be executed manually.
<plugin>
<groupId>net.jhorstmann.i18n</groupId>
<artifactId>i18n-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<targetBundle>net.jhorstmann.i18n.sample.Messages</targetBundle>
<outputFormat>class</outputFormat>
</configuration>
<executions>
<execution>
<goals>
<goal>dist</goal>
</goals>
<phase>generate-resources</phase>
</execution>
</executions>
</plugin>
A JSF application has to declare its supported locale and the ResourceBundle to
use in faces-config.xml:
<application>
<locale-config>
<default-locale>de</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>net.jhorstmann.i18n.sample.Messages</base-name>
<var>i18n</var>
</resource-bundle>
</application>
The ResourceBundle should be named "i18n" by default, this name can be
overridden in web.xml using the net.jhorstmann.i18n.ResourceBundleVar
context parameter. Alternatively, the ResourceBundle can also be specified in
web.xml instead of faces-config.xml by setting the
net.jhorstmann.i18n.ResourceBundle context parameter.