Internationalization in RAP

Internationalization (i18n) is the process of adapting applications to the user's language and region, including things like number formats, date and time, etc. The Java class library has support for internationalization, most notably by locales and resource bundles. RAP applications can use these features just like other applications, but since they are accessed by multiple users simultaneously, they must be designed to work with user-specific locales instead of a single, system-wide locale.

Getting and Setting the Locale

In RAP, every UISession has its own locale that can be obtained using the method getLocale. By default, this locale is based on the user's preferred locale provided by the client. If the client does not provide a locale, the system-wide default locale is taken as a fallback. The default locale can be set by adding the system property user.language to the launch configuration. The UISession locale can also be changed programmatically using setLocale, for example when the user selects a preferred locale in the application's UI.

Note: The static methods RWT.getLocale() and RWT.setLocale() are shortcuts for RWT.getUISession().getLocale() and RWT.getUISession().getLocale(...), respectively.

In addition to this, a client provides the user's preferred locales in the ClientInfo service. This information is based on the list of locales passed in the HTTP header Accept-Language, which usually reflects the user's language preferences in the Web browser. Alternative RAP clients may provide a different ClientInfo implementation.

Localization

Localization is the process of translating an internationalizable application for a certain language and region. The locale-specific strings are usually kept in a Java properties files, accessed by a ResourceBundle. Translated versions of these property files must have a suffix that indicates their language, optionally also the country and a variant (for the details, please refer to Locale and ResourceBundle. For example, to create a German translation for a resource property file named MyResources.properties, you would create a copy of the file and name it MyResources_de.properties. Then you would translate the strings in this file and put it into the same package as the original file. The translated files and other localized resources can go into a separate .jar file or, if your application uses OSGi, in a fragment bundle.

Be aware that the property files are expected to be in ISO-8859-1 (Latin 1) encoding. If the translated properties files contain accented characters that are not included in the Latin-1 encoding, they have to be represented by Unicode Escapes. Property files in other encodings can be converted using the native2ascii utility, that is shipped with the Java SDK.

Note: The ResourceBundle fallback mechanism can sometimes lead to confusion. You could expect that the system falls back to the default properties file if no translated properties file can be found that matches the user's locale. That's not the case. If a translation is not found for the locale in question, the ResourceBundle lookup will first try the default locale, i.e. Locale.getDefault(). For example, if your VM is set to German and there is a MyResources_de.properties file, the translation will fallback to this file instead of the default MyResources.properties. If you want to have English as your fallback, you have to set your VM to English.

Localizing RAP Messages

In case of session timeout, a network error, and other kinds of exception, the web client will display a message box. In an internationalized application these messages should be translated as well. The default texts reside in the properties file org/eclipse/rap/rwt/internal/RWTMessages.properties in the bundle org.eclise.rap.rwt. To localize the application, include the translated versions (RWTMessages_xx.properties) in the deployed application, e.g. in an OSGi fragment bundle. At the moment, we don't provide any translated versions, so you'll have to create them on your own.

Internationalization in JFace

The texts for JFace dialog buttons are traditionally set directly to one of the constants in IDialogConstants. This method is not suitable for multiple users because the strings contained in these constants are translated only once. For multi-user applications, IDialogLabelKeys also provides keys that can be resolved to localized strings dynamically using JFaceResources.getString( key ). In a RAP application, label texts should be obtained as in this example:

protected void createButtonsForButtonBar( Composite parent ) {
  createButton( parent,
                IDialogConstants.OK_ID,
                JFaceResources.getString( IDialogLabelKeys.OK_LABEL_KEY ),
                true );
  createButton( parent,
                IDialogConstants.CANCEL_ID,
                JFaceResources.getString( IDialogLabelKeys.CANCEL_LABEL_KEY ),
                false );
}

Internationalization in RCP

RCP established its own internationalization mechanism that is explained in [1]. This approach is based on resource bundle accessor classes that keep translated strings in static fields, which is not suitable for a multi-user system. To solve this problem, the RAP project has developed a method to adjust existing RCP applications for use in multi-user environments with minimal modifications.

Here's an example snippet of RCP code that reads a localized string from a static field of an accessor class (commonly named “Messages”).

label.setText( Messages.ExampleView_Message );

For multiple users, we need different translations. Therefore we suggest to use instance fields instead of static fields and to create a different instance of the class for every language. This allows for re-using the existing code with only a minimal change, which is to insert a get() method that returns an instance of the resource bundle accessor class for the current UISession locale:

label.setText( Messages.get().ExampleView_Message );

To allow for this pattern, the static fields in the Messages class must be changed to instance fields. Moreover, the get() method must be added to this class. This method must return an instance of the class with all fields translated to the current UISession locale. The RAP framework provides a helper class RWT.NLS to do this. Here's an example of a modified accessor class:

public class Messages {

  private static final String BUNDLE_NAME = "org.example.views.messages"; //$NON-NLS-1$

  public String ExampleView_Title;
  public String ExampleView_Message;

  public static Messages get() {
    Class clazz = Messages.class;
    return ( Messages )RWT.NLS.getISO8859_1Encoded( BUNDLE_NAME, clazz );
  }

  private Messages() {
    // prevent instantiation
  }

}

Note that in contrast to RCP, the class does not extend org.eclipse.osgi.util.NLS. The constant BUNDLE_NAME contains the name (without extension) of the properties file that contains the mapping from keys to localized strings. In the example above, a properties file messages.properties is expected in the package org/example/views. This property file must contain the default strings for every key:

ExampleView_Title = Example
ExampleView_Message = Hello World

Note: to simplify the translation to non-Latin languages, the RAP NLS utility also accepts UTF-8 encoded property files.

Internationalization of Plug-ins (plugin.xml)

Plug-in manifest files (plugin.xml) may also contain translatable strings, e.g. view names or menu labels. Therefore, a plugin.xml may contain placeholder strings that are prefixed with a % sign. These placeholder keys are then resolved in a properties file, which is by convention named plugin.properties and resides in the root directory of the plug-in. Here's an example of a view extension that contains a placeholder for the name attribute:

<extension
      point="org.eclipse.ui.views">
   <view
         id="org.example.views.exampleView"
         class="org.example.views.ExampleView"
         name="%exampleView_name">
   </view>
</extension>

The bundle would include a file plugin.properties that contains the translatable text for this placeholder in a line like the following:

exampleView_name = Example

To make this work, the OSGi manifest (MANIFEST.MF) must contain a Bundle-Localization header that points to the name of the properties file (without extension):

Bundle-Localization: plugin

In a multi-user environment, the Equinox extension registry must be set to multi-locale mode to be able to serve strings in multiple locales simultaneously. This is done by setting the system property eclipse.registry.MultiLanguage to true or by setting the framework property registryMultiLanguage.

If you're deploying your application as a WAR, make sure to include the framework property in the web.xml like shown below:

<init-param>
  <param-name>commandline</param-name>
  <param-value>-registryMultiLanguage</param-value>
</init-param>

Tool Support

The Java tools in the Eclipse IDE provide a tool to easily extract strings into message bundles, the Externalize Strings wizard (Source > Externalize String…). This wizard has an option to “use Eclipse's string externalization mechanism” (the mechanism that is described in [1]), which is enabled by default for Plug-in Projects. As explained above, this mechanism is not suitable for multi-user applications.

References