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.
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 forChoiceFormat
is 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.
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.