I18N localization

To use localization and translation strings the application only needs to implement I18NProvider and define the fully qualified class name in the property i18n.provider.

Defining the i18n provider property

The i18n.provider property can be set from the command line as a system property, as a Servlet init parameter in the web.xml or using the @WebServlet annotation.

As a system property the parameter needs the vaadin prefix e.g.:

mvn jetty:run -Dvaadin.i18n.provider=com.vaadin.example.ui.TranslationProvider

When using the annotation you could have the servlet class as:

@WebServlet(urlPatterns = "/*", name = "slot", asyncSupported = true, initParams = {
        @WebInitParam(name = Constants.I18N_PROVIDER, value = "com.vaadin.example.ui.TranslationProvider") })
public class ApplicationServlet extends VaadinServlet {
}

Or when using the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
  id="WebApp_ID" version="3.0"
  xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

  <servlet>
    <servlet-name>myservlet</servlet-name>
    <servlet-class>
        com.vaadin.server.VaadinServlet
    </servlet-class>

    <init-param>
      <param-name>i18n.provider</param-name>
      <param-value>com.vaadin.example.ui.TranslationProvider</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>myservlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

You may provide a I18NProvider as a bean in case you are using Spring. All you need in this case it’s just annotate your implementation with @Component so it becomes available as a Spring bean. Spring add-on will automatically use it in case if it’s available. See the class SimpleI18NProvider.java implemented in the tutorial project as an example.

Locale selection for new session

The initial locale is decided by matching the locales provided by the I18NProvider against the Accept-Language header in the initial response from the client.

If an exact match (language + country) is found that will then be used, else we will try to match on only language. If neither is found the locale will be set to the first 'supported' locale from I18NProvider.getProvidedLocales() and if that is empty Locale.getDefault() will be used.

Provider sample for translation

For this example we enable Finnish and English to be used with Finnish being the "default" that is used if the user client doesn’t specify english as an accepted language.

In this sample the language .properties files start with "translate" e.g. translate.properties (for default), translate_fi_FI.properties and translate_en_GB.properties

The translation properties files are in the example loaded using the class loader so they should be located on the classpath for example in the resources folder e.g. src/main/resources for a default maven setup.

The LoadingCache used in the implementation is from the Google Guava package.

public class TranslationProvider implements I18NProvider {

    public static final String BUNDLE_PREFIX = "translate";

    public final Locale LOCALE_FI = new Locale("fi", "FI");
    public final Locale LOCALE_EN = new Locale("en", "GB");

    private List<Locale> locales = Collections
            .unmodifiableList(Arrays.asList(LOCALE_FI, LOCALE_EN));

    private static final LoadingCache<Locale, ResourceBundle> bundleCache = CacheBuilder
            .newBuilder().expireAfterWrite(1, TimeUnit.DAYS)
            .build(new CacheLoader<Locale, ResourceBundle>() {

                @Override
                public ResourceBundle load(final Locale key) throws Exception {
                    return initializeBundle(key);
                }
            });

    @Override
    public List<Locale> getProvidedLocales() {
        return locales;
    }

    @Override
    public String getTranslation(String key, Locale locale, Object... params) {
        if (key == null) {
            LoggerFactory.getLogger(TranslationProvider.class.getName())
                    .warn("Got lang request for key with null value!");
            return "";
        }

        final ResourceBundle bundle = bundleCache.getUnchecked(locale);

        String value;
        try {
            value = bundle.getString(key);
        } catch (final MissingResourceException e) {
            LoggerFactory.getLogger(TranslationProvider.class.getName())
                    .warn("Missing resource", e);
            return "!" + locale.getLanguage() + ": " + key;
        }
        if (params.length > 0) {
            value = MessageFormat.format(value, params);
        }
        return value;
    }

    private static ResourceBundle initializeBundle(final Locale locale) {
        return readProperties(locale);
    }

    protected static ResourceBundle readProperties(final Locale locale) {
        final ClassLoader cl = TranslationProvider.class.getClassLoader();

        ResourceBundle propertiesBundle = null;
        try {
            propertiesBundle = ResourceBundle.getBundle(BUNDLE_PREFIX, locale,
                    cl);
        } catch (final MissingResourceException e) {
            LoggerFactory.getLogger(TranslationProvider.class.getName())
                    .warn("Missing resource", e);
        }
        return propertiesBundle;
    }
}

Using localization in the application

Using the internationalization in the application is a combination of using the I18NProvider and updating the translations on locale change.

To make this simple the application classes that control the captions and texts that are localized can implement the LocaleChangeObserver to receive events for locale change.

This observer will also be notified on navigation in the attach phase of before navigation after any url parameters are set, so that the state from a url parameter can be used.

public class LocaleObserver extends Div implements LocaleChangeObserver {

    @Override
    public void localeChange(LocaleChangeEvent event) {
        setText(getTranslation("my.translation", getUserId()));
    }
}

Using localization without using LocaleChangeObserver

public class MyLocale extends Div {

    public MyLocale() {
        setText(getTranslation("my.translation", getUserId()));
    }
}