i18n for Vaadin Applications Using CustomLayouts and Apache Velocity

Writing internationalized web applications with Vaadin is quite easy and works the same as with any other framework. You write your message property files and load them through a ResourceBundle. Using this resource bundle, you can then have your texts translated into the language defined by a user’s current locale. These translated texts can then be used for instance in captions or labels.

The internationalization (i18n) of a web application does not stop at translating label and caption texts. If you are working with Vaadin’s CustomLayouts these layouts may contain static text which will also need to be translated. Vaadin does not provide any mechanism to support i18n for CustomLayouts out of the box. But this can be remedied when we make use of the Apache Velocity templating engine.

In this article we’re going to investigate why and when you should prefer static texts to using labels and how such text can be internationalized when it appears in a HTML template for CustomLayouts. We’ll make use of Apache Velocity to translate static text in our CustomLayout templates. The mechanism described in this article can be used with Vaadin 7 and with older versions of the framework.

Why com.vaadin.ui.Label should be avoided for static text

A typical web application contains two types of text: dynamic text and static text. Dynamic text will be changed during the user’s interaction with an application, e.g. labels showing the currently logged-in user or some calculation’s result. Static texts on the other hand will typically only be changed when the user switches her current locale, such as headlines or help texts.

From a coder’s perspective the quickest thing to do when writing a Vaadin application is to put all those texts into instances of Vaadin’s Label compontent, regardless of whether they are dynamic or static. This is ok for small applications or when you only expect a restricted user base so that performance and memory consumption are only minor concerns.

If you have to meet tight requirements regarding performance and session size one measure to achieve that is to reduce the total number of components you’re using on your UIs. There is a nice article about performance-tuning Vaadin applications to be found in the Vaadin wiki which states exactly this recommendation among others. The less components and layouts managers you add to your UI the leaner your component tree will become and thus the smaller your session memory footprint will be and the less rendering the browser has to do.

To reduce the number of components in your UI it is recommended to prefer CustomLayouts or CSSLayouts instead of assembling complex layouts with other available layout managers. By that, layout calculations can be done by the browser through the use of CSS. Furthermore, CustomLayouts can contain static text which does not need to be rendered by Label components.

If static text is put into a Label, you’re virtually wasting heap memory and processor time. With class com.vaadin.ui.Label being derived from com.vaadin.ui.AbstractComponent each label inherits a lot of internal state and methods to manage this state. For each used label this state unnecessarily adds up in the user’s session. To avoid this, I therefore recommend to put all static texts into HTML templates for CustomLayouts. This cuts down on the total number of active components in your UI with respect to both the number of labels and the number of layout managers.

I18n for CustomLayouts

Let us now see how we can localize such static text used in CustomLayouts. Since this text is embedded in a HTML template, Vaadin has no direct access to it, and you cannot simply change the translated text with some convenient setter method. This can be achieved when you use a template engine as an intermediate step to preprocess the HTML templates before you hand them over to the CustomLayout. In our example, we will use the Apache Velocity templating engine which is widely used in a lot of production scenarios.

To be able to localize CustomLayout templates with Velocity, each static text in a template to be translated needs to be referenced by a translation key. For example, consider the following template:

<div>
    <h1>Hello World!<h1>
    Welcome to my fancy web application.
    <div location="myComponent"></div>
</div>

To be able to localize such a template, each of the static texts has to be replaced with a translation ID and moved to a resource bundle. The example template will be changed like so:

<div>
    <h1>${msg.hello.world}<h1>
    ${msg.welcomeMessage}
    <div location="myComponent"></div>
</div>

The corresponding translation properties file messages.properties has the following contents:

hello.world=Hello World!
welcomeMessage=Welcome to my fancy web application.

Here it is important to note that while each translation key in the template is referenced with the common prefix msg, this prefix is omitted in the resource bundle. The reason for this is that the keys used in the HTML template are in fact Velocity variables sharing the same namespace msg. All translation IDs from the resource bundle are subsumed under this namespace. In consequence, all translation keys referenced from your HTML templates have to be accessed through this namespace.

The Apache Velocity template engine

We need to add two dependencies to our project when we want to do i18n with Velocity. First, we need Apache Velocity itself. Second, we need the Velocity Tools project which contains a class ResourceTool. This tool will transform our resource bundles into a hierarchy of Velocity variables enabling us to reference e.g. translation key hello.world with the Velocity variable $msg.hello.world.

Using Maven’s dependency notation, you need to add the following two dependencies to your project:

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-tools</artifactId>
    <version>2.0</version>
</dependency>

The next thing to do in our code is to configure an instance of the Velocity engine which we will use later for the template transformations.

import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.config.EasyFactoryConfiguration;
import org.apache.velocity.tools.generic.ResourceTool;
// [...]

@Theme("mytheme")
public class MainApplicationUI extends UI {

  private Locale currentLocale;
  private VelocityEngine velocityEngine;

  @Override
  protected void init(VaadinRequest request) {
    currentLocale = Locale.ENGLISH;

    createVelocityEngine();
    createVelocityContexts();
    buildMainLayout();
  }

  private void createVelocityEngine() {
    velocityEngine = new VelocityEngine();

    Properties velocityProperties = new Properties();
    velocityProperties.put("resource.loader", "url");
    velocityProperties
        .put("url.resource.loader.class",
             "org.apache.velocity.runtime.resource.loader.URLResourceLoader");
    velocityProperties.put("url.resource.loader.root",
             "http://localhost:8080"
             + VaadinService.getCurrentRequest().getContextPath()
             + "/VAADIN/themes/" + getTheme() + "/layouts/");
    velocityEngine.init(velocityProperties);
  }
// [...]
}

In method createVelocityEngine() we’re configuring our Velocity engine object such that it will load its resources from a URL whose root is configured to be the base directory of our CustomLayout template files in VAADIN/themes/myTheme/layouts. The resources loaded by the engine are the template files needed by Velocity for the transformation process. This means that our CustomLayout templates are used as plain Velocity templates at the same time.

The next step is to create a Velocity context for each locale supported by our application.

private Map<locale, Context> localizedVelocityContexts;

private void createVelocityContexts() {
  localizedVelocityContexts = new HashMap<locale, Context>();
  localizedVelocityContexts.put(Locale.GERMAN,
      createContextForLocale(Locale.GERMAN));
  localizedVelocityContexts.put(Locale.ENGLISH,
      createContextForLocale(Locale.ENGLISH));
  localizedVelocityContexts.put(new Locale("sv"),
      createContextForLocale(new Locale("sv")));
}

private Context createContextForLocale(Locale locale) {
  EasyFactoryConfiguration config = new EasyFactoryConfiguration();
  config.toolbox(Scope.APPLICATION)
      .tool("msg", ResourceTool.class)
      .property("bundles", "messages")
      .property("locale", locale);

  ToolManager manager = new ToolManager(false, false);
  manager.configure(config);

  return manager.createContext();
}

Here we create a org.apache.velocity.context.Context object for each of the supported locales German, English and Swedish and put that into a map for later reference. In short, a Velocity context serves as a container for the set of variables used by the Velocity engine for template processing. The code above uses the ResourceTool to load one or more message bundles (a bundle named “messages” in our case) into the context for the given locale. Thus, each of the contexts contains the translations for one particular language. Note in method createContextForLocale() that the ResourceTool is stored in the context with key “msg“. This is where we define the namespace for our translation variables as described above.

Now we have completed all necessary configuration tasks for our engine. We can now use Velocity for preprocessing our templates.

private void buildMainLayout() {
  try {
    // initialize a CustomLayout with the preprocessed
    // template from Velocity
    CustomLayout layout =
        new CustomLayout(getLayoutTemplate("main"));
    // add components to the CustomLayout
    layout.addComponent(createMyComponent(), "myComponent");
    setContent(layout);
  } catch (IOException e) {
    // [...]
  }
}

private InputStream getLayoutTemplate(String templateName) {
  // let Velocity load the template through its URLResourceLoader
  Template template =
      velocityEngine.getTemplate(templateName + ".html");
  StringWriter writer = new StringWriter();

  // get the Velocity context for the current locale
  Context ctx = localizedVelocityContexts.get(currentLocale);
  if (ctx == null) {
    // use English as the default language
    ctx = localizedVelocityContexts.get(Locale.ENGLISH);
  }
  // let Velocity do its template processing
  template.merge(ctx, writer);

  // return the result as an InputStream which can be
  // used to initialize a CustomLayout
  return new ByteArrayInputStream(
      writer.toString().getBytes(Charset.forName("UTF-8")));
}

Since a Vaadin CustomLayout can be initialized by an InputStream, we can provide the template data through such a stream. We use a StringWriter as data buffer for the preprocessed template data.

Method getLayoutTemplate() will do the template transformation with Velocity. In this method, we fetch the Velocity context for the user’s current locale and use that for the templating process. During that process, Velocity will replace all Velocity variables in the template with their respective translations from the used context. The result of this is passed to the constructor of CustomLayout.

We now have a working solution for localizing CustomLayout templates. A useful enhancement to this setup would be to put the preprocessed templates into a cache so that each template only has to be prepared once per available locale. This could be achieved for instance with the Ehcache implementation. Caching the preprocessed templates will minimize the total time spent with running the Velocity engine.

Example code

You can find a demo project for this proposed solution on GitHub.

Short URL for this post: https://wp.me/p4nxik-1pq
Roland Krüger

About Roland Krüger

Software Engineer at Trivadis / Orientation in Objects GmbH. Follow me on Twitter.
This entry was posted in Java Web Frameworks and tagged , , , . Bookmark the permalink.

Leave a Reply