Since ZK 2.2.0 is possible to add i18n support by using* i3-label.properties files to store localized strings and use resource class Labels to retrieve them. This approach is the most extended in ZK for localizing ZUL files, and it was introduced by Minjie Zha in his smalltalk “I18N Implementation in ZK”.
However, this approach has its drawbacks. It requires developers to think first of a key for the text they are going to localize, store it in a i3-label.properties *file together with the localized text, an enable the mechanisms necessary to retrieve keys from a ZUL file. In addition, ZUL files are flooded with keys which sometimes have an obscure meaning.
GNU Gettext utilities follow a similar approach, but with the clear advantage of saving developers the burden of thinking of a key. Developers simply write down text in their source files as they would normally do, together with Gettext utilities, that very same text will serve as a key for all localized texts.
On my previous posts I gave a brief introduction to GNU gettext, how to settup Getttext Commons in a Java enviroment, and how to use Gettext from a Java project. Before going ahead with this post, I recommend you to take a look at those articles in case you haven’t done that yet, as from here on I will refer to some of the concepts and examples explained on them.
First of all, check you have sucessfully installed and configured Gettext Commons in your project.
Now what we are going to modify I18nHelper.java class explained on my last post. I am not going to get very much in detail here, what I am basically going to do is to modify getI18n() method so it knows through ZK engine which Locales end-user is asking for, loading it in case it exists, or using a default one (Click to download final version of I18nHelper.java).
After that we are going to create a taglib which is simply a facade for I18nHelper.
java.lang.String _(java.lang.String name)
Name it i18n.tld and save it under your /WEB-INF/tld/ directory (Click to download i18n.tld).
After that, you are ready to include i18n.tld taglib from a ZUL file. Include it and use the i18n prefix accordingly. For instance,
Load it on your favorite browser and see what happens. Basically what we are doing here is to rely on I18nHelper, which exposes a _ function for localizing strings passed as parameter. I18nHelper works against a specific resource bundle, a binary file compiled as the result of a gettext process. A resource bundle contains localized strings and knows how to convert msgids, or keys, to locale strings.
As you may have seen in Using Gettext Commons from Java files, localizing strings in Java files was an easy task. We used GNU Gettext utilities to parse down Java files, searching for strings marked with the _ function. The text within this function served as a key for generating a keys.pot file. Later that file was localized to a locale.po, es.po (Spanish localized file, for instance), and with the help of msgfmt command was possible to generate a resource bundle that could be used later from Java files for localizing texts dinamically. This same philllosophy applies to the process of localizing ZUL files.
The first obstacle that we need to surpass to generate a resource bundle successfully is parsing ZUL files searching for texts wrapped by ${i18n:_(‘%s’)}. GNU gettext utilities support a myriad of programming languages but unfortunately gettext does not support XML files. However, this is not a problem as parsing a XML file is much easier than parsing source code of a programming language. I wrote a small Perl script: gettext_zul.pl, which does exactly that. Run it like this:
gettext_zul.pl --dir path_to_zul_files --keys existing_keys.pot_file
You can ommit the –keys parameter, so a new keys.pot will be created in your current directory. This option can be useful in case you have an existing keys.pot file, generated prior as the result of processing a set of Java files for example.
Once your keys.pot file is up-to-date, create a localized version out of it (See I18n with GNU Gettext utilities). Lastly, run msgfmt to generate a locale resource bundle, Messages_XX.class, out of locale.po.
Supporting arguments
Generally text messages need extra parameters. For example, message “Confirm deleting element?” should be localized as ‘”Confirm deleting {0}?”, item.name’. To sort out this problem, we can extend I18nHelper and overload _ method with extra arguments.
public static String _(String str) { return getI18n().tr(str); } public static String _(String text, Object o1) { return getI18n().tr(text, o1); } public static String _(String text, Object o1, Object o2) { return getI18n().tr(text, o1, o2); } public static String _(String text, Object o1, Object o2, Object o3) { return getI18n().tr(text, o1, o2, o3); } public static String _(String text, Object o1, Object o2, Object o3, Object o4) { return getI18n().tr(text, o1, o2, o3, o4); } public static String _(String text, Object[] objects) { return getI18n().tr(text, objects); }
To make use of these new functions, we need to exposed them in i18n.tld tag-lib. Tag libs do not support function overloading, so we need to provide different names for each function. For instance, we may add a new function, __, that receives one extra parameter.
java.lang.String _(java.lang.String name, java.lang.Object arg0)
I18n as a Macrocomponent
Another way of supporting arguments from ZUL files is to encapsulate I18nHelper funcionality into a Macrocomponent.
First of all, create a HTMLMacroComponent, save it as i18n.zul at webapp/common/components/ (Click to download i18n.zul)
Create its corresponding Java file (Click to download I18n.java) and save it as I18n.java at webapp/common/components/
And finally add this new macrocomponent to your lang-addon.xml file.
Now you are ready to use it from a ZUL page.
Macrocomponent Vs Taglib
Most part of the time we may just need to localize values inside attributes, in this case using a tag-lib is the right way to go, for example:
Tag-libs are OK for static texts. Static texts are evaluated only once when the ZUL page is rendered for the first time. When showing texts with data bindings, we must use i18n macrocomponent. In most cases, strings with arguments have their arguments bound to dynamic data. As rule of thumb, whenever there are arguments, i18n macrocomponent is the right choice.
However, enabling i18n to support a different number of arguments can be convenient if we need to substitute an argument inside a literal value. Consider the following example:
This is a very particular case but it still can happen in your code. Adding an extra __ function in your taglib (see above) can solve this problem.
NOTE: Do not interpolate requestScope variable as it is already inside another interpolation (${i18n:…})
In this last chapter I have reviewed a new approach to internationalize texts in ZUL files. This approach is based on GNU gettext, probably the most wide-spread way of localizing texts in the FOSS world. This method provides clear advantages to developers as they do not need to spend time thinking of meaningless keys and keeping track of them manually across different localized files, which in the long-run means saving time and automatizing the whole process of translating and localizing.
Download all files on this post: i18n.tar.gz.