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.

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));

    @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 = ResourceBundle.getBundle(BUNDLE_PREFIX, 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;
    }
}

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()));
    }
}

Supporting Right-to-Left Mode

Vaadin components have support for RTL languages. The components will work out-of-the-box on this mode, but to make your application support both LTR and RTL modes, a few changes are needed.

On top of the last examples, let’s say that your application is now also translated into a RTL Language, such as Arabic. Besides providing the sample for the Arabic translation, at your main layout you can add a code like such:

public class MainLayout extends VerticalLayout {

    public MainLayout() {
        // ...
        final UI ui = UI.getCurrent();
        if (ui.getLocale().getLanguage() == "ar") {
            ui.setDirection(Direction.RIGHT_TO_LEFT);
        }
    }
}

This will work if changing of locale is only based on the Accept-Language coming from the client, but if user can define their language, for instance, at your application’s setting page, then you can make your main layout implement the LocaleChangeObserver interface, so it will receive the changes of locale and then you can set the direction based on the locale set:

public class MainLayout extends VerticalLayout implements LocaleChangeObserver {

    @Override
    public void localeChange(LocaleChangeEvent event) {
        if (event.getLocale().getLanguage() == "ar") {
            event.getUI().setDirection(Direction.RIGHT_TO_LEFT);
        } else {
            event.getUI().setDirection(Direction.LEFT_TO_RIGHT);
        }
    }
}

Frontend Projects

For frontend applications only, to set the RTL mode you can call document.dir = 'rtl'.

Adding RTL Support to Your Custom Elements or Application

If you have your own custom elements or your application has custom styles, there are a few steps needed in order to add RTL support to them:

  1. If your element extends Vaadin’s ElementMixin, make sure that it depends on the version starting from 2.3.0. If not, you can make the element extends it or DirMixin only (DirMixin is part of the @vaadin/vaadin-element-mixin package).

    import { PolymerElement } from '@polymer/polymer/polymer-element.js';
    import { DirMixin } from '@vaadin/vaadin-element-mixin/vaadin-dir-mixin.js';
    
    class MyElement extends DirMixin(PolymerElement) {}

    The DirMixin registers the element to changes on the dir attribute at the document level and keeps it in sync with the element’s dir attribute. That’s helpful to easily check the RTL status in both CSS and JS code.

  2. Make sure your styles are adjusted properly for the RTL mode.

    For example, if you define values for the padding on the :host, like:

    :host {
        padding-right: 1em;
        padding-left: 2em;
    }

    You may want to define the proper style for the RTL, like:

    :host([dir="rtl"]) {
        padding-right: 2em;
        padding-left: 1em;
    }

    You may want to pay attention to declarations such as padding, margin, text-align, float and transform on your styles. In case your custom element doesn’t have to support old browsers (such as IE11), you can replace some properties with CSS Logical Properties. The MDN web docs has a full list of CSS Logical Properties and Values available along with the browsers support for each property. Flex and Grid containers usually are handled well by the browser and don’t require extra work. More information can be found at this extensive RTL styling guide.

    To help adjusting the styles for the RTL mode, you can go to the RTL CSS page. There you can paste your original styles and it will generate the code that you can take into usage for your element.

  3. If your element uses icons or unicode symbols to define direction (for instance ⬅ for back button) you may need to use the right icons/symbols for RTL.

  4. If keyboard interaction are used, such as to navigate between items with arrow keys, make sure to check if dir is rtl and use it to define the direction of the movement.

    // somewhere in your code
    const dirIncrement = this.getAttribute('dir') === 'rtl' ? -1 : 1;
    
    switch (event.key) {
        // ...
        case 'ArrowLeft':
            idx = currentIdx - dirIncrement;
            break;
        case 'ArrowRight':
            idx = currentIdx + dirIncrement;
            break;
        // ...
    }
  5. If your custom element relies on some Javascript calculation for sizing, position and/or horizontal scroll, check if it needs some adjustments for RTL.

  6. If you have visual tests, you may want to add or update the current ones to also run in RTL.