GWT UiBinder and i18n, you said nightmare ?

GWT with nuiton-i18n (and Maven) and it's easy

The way i18n works in GWT

If you got screens you made using UiBider and that you want to localize, you will go to the ad-hoc page of the GWT documentation (http://code.google.com/intl/fr/webtoolkit/doc/latest/DevGuideUiBinderI18n.html). In this documentation, you learn tha tyou must use, for localization, the following syntax :

  <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
    ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
    ui:generateLocales="default">
    <div><ui:msg description="Greeting">Hello, world.</ui:msg></div>
  </ui:UiBinder>

This will give you a nice property file that looks like that :

  # Generated from my.app.HelloWorldMyBinderImplGenMessages
  # for locale default
  # Description: Greeting
  022A824F26735ED0582324BE34F3CAE1=Hello, world.

Nice, but this property file, if you give it to a translator, he will look at you with big eyes, smash the door and you will never see him again. And you will got a property file per UiBinder file. Yes you read well.

Let's admit that, try to improve the property file then. Changing the generator, we succeed in getting nice files. Ok. But we still get lots of property files, that are generated in different directories. A nightmare.

If you search on the net, we see that if we put all our translated strings in a file called LocalizableResources.properties, we do not need all the other files anymore. You can find a python script that puts everything in a single place, but you see yourself using python script whereas all repetitive tasks are automatized. A python script, but you are coding in Java !

So if you get there, you found out that localization in GWT when using UiBinder can be quite a pain.

How we want it to work

So let's ask the right questions. What all people involved in the localization process want ? Developers ? Translators ?

Developers want to write the minimum code. Repeat generation documentation on top of each file can be quite repetitive and useless. Applying a python script is not optimum, if we could process this with our build manager (like Maven or Ant) that could be great. The developer must only have to code, all the files manipulations must be left to the build manager.

The translators want files easy to deal with. And one file per language maximum. We must gather the translation sof all files in one. The translator should not have to search for translation keys in the code. Everything must be in the property file, only translation must be done by the translator.

So to summarize, we want the strings to be extracted in an automatized way and placed in a unique property file (one per language). We must keep the already translated strings from a version to the other. And the generation configuration must be centralised and the generation dealt with by the build manager.

How we do that ?

The build process can already be managed by Maven (http://mojo.codehaus.org/gwt-maven-plugin/), so we will be based on Maven, and nuiton-i18n got a Maven plugin managing translation keys and extraction quite well.

We will consider here that you can compile/run your GWT project with Maven to go to the interesting part, the translation of UiBinder files. So we will use the xml parser with our own rules.

Creating our own parsing rule

Nuiton-i18n got a XML parser that is quite powerful and can be configured with external rules. So we will create a rule to parse GWT UiBinder files. Our rule will be named gwt.rules and will be placed in *src/main/i18n* directory. It's content will be :

  //ui:msg/@key

The value is jxpath indicating that we look all the key attributes of the ui:msg elements. So far no problem. You can update this rule if you got other stuff to translate, but this should cover most of the cases.

Plugin configuration

We will configure the nuiton-i18n plugin to be able to do what we want.

In a first execution, we will tell him to parse our UiBinder files (*.ui.xml) located in our src/main/java directory using the rule previously created. Do not forget to indicate the namespaces so the plugin can deal with paths.

In a second execution, we generate the property files located in src/main/resources/i18n.

The last execution generate the Bundle. We give the name of the file and its package : LocalizableResource (language and .properties are added by the plugin) and com/google/gwt/i18n/client.

<plugin>
  <groupId>org.nuiton.i18n</groupId>
  <artifactId>i18n-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>scan-gwt-sources</id>
      <goals>
        <goal>parserXml</goal>
      </goals>
      <configuration>
        <basedir>${project.basedir}/src/main/java</basedir>
        <includes>**/*.ui.xml</includes>
        <userRulesFiles>
          <file>${basedir}/src/main/i18n/gwt.rules</file>
        </userRulesFiles>
        <namespaces>
          <gwt>urn:import:com.google.gwt.user.client.ui</gwt>
          <ui>urn:ui:com.google.gwt.uibinder</ui>
        </namespaces>
      </configuration>
    </execution>
    <execution>
      <id>gen</id>
      <goals>
        <goal>gen</goal>
      </goals>
    </execution>
    <execution>
      <id>make-bundle</id>
      <goals>
        <goal>bundle</goal>
      </goals>
      <configuration>
        <generateDefinitionFile>false</generateDefinitionFile>
        <addBundleOuputDirParent>false</addBundleOuputDirParent>
        <bundleOutputPackage>com/google/gwt/i18n/client</bundleOutputPackage>
        <bundleOutputName>LocalizableResource</bundleOutputName>
      </configuration>
    </execution>
  </executions>
</plugin>

First build

Launch a first build, your empty (not translated) property files will be created and in your src/main/resources/i18n. Fill them (translate) (think about escaping special caracters by replacing them with their unicode code).

Second build, said difficult ?

Launch a second build, launch your app. Your languages are available ? Ok... still getting nightmares ?