Twitter Logo Follow us on Twitter
Project Information About this project

How to Internationalize a RAP application

Internationalization in RAP follows the same approach as RCP. However, due to the server-side, multi-user nature of RAP, a few adaptations are necessary. In the following, you will get a step-by-step guide to internationalize the simple Hello World application created in the getting-started chapter. For a more general introduction to internationalization in RCP, see [1].

Why does RAP internationalization differ from RCP?

In RAP we have to deal with different languages for different user sessions. Indeed, the language can also change between requests within the same session. Therefore, we cannot store language related information statically in Message classes as this is done in RCP. Instead, we must use a different instance of the Message class for every language.

Move translatable strings into *.properties files

In RCP, it's quite simple to externalize strings using the Externalize Strings wizard provided by JDT. Unfortunately, this wizard is not yet prepared to externalize strings the RAP way. You can probably benefit from the assistance of the Externalize Strings wizard anyway, but only partly. Until now, you are on your own here. We will now explain how to do it from scratch.

Let's start with the preparations. You probably know the resource bundle accessor classes (usually called Messages). We also use such a class, but instead of accessing a resource bundle, we use the RAP NLS facility to access nationalized strings. We create a class Messages in the package org.eclipse.rap.helloworld with the following initial content:


  public class Messages {
  
    private static final String BUNDLE_NAME
      = "org.eclipse.rap.helloworld.messages"; //$NON-NLS-1$
    
    private Messages() {
      // prevent instantiation
    }
    
    public static Messages get() {
      Class clazz = Messages.class;
      return ( Messages )RWT.NLS.getISO8859_1Encoded( BUNDLE_NAME, clazz );
    }
  }
  

The constant BUNDLE_NAME contains the name (without extension) of a properties file, that contains the mapping from keys to real strings. Note that in contrast to RCP, the class does not extend org.eclipse.osgi.util.NLS. Instances, which can be acquired through the factory method get(), contain fields that hold the translated strings.

In the next step, we create an empty properties file messages.properties in the same package. This properties file follows the conventions of standard ResourceBundle properties files. For each externalized string, there has to be a key entry in the properties file.

Now we are prepared to externalize strings. Let's start with the class HelloWorldView from the hello world example. The class contains one string we'd like to externalize in order to make it translatable:


  public void createPartControl( Composite parent ) {
    Label label = new Label ( parent, SWT.NONE );
    label.setText( "Hello RAP World" );
    label.setSize( 80, 20 );
  }
  

We change the string into the following code:


  public void createPartControl( Composite parent ) {
    Label label = new Label ( parent, SWT.NONE );
    label.setText( Messages.get().HelloWorldView_Message );
    label.setSize( 80, 20 );
  }
  

The key HelloWorldView_Message can be freely chosen, however, the RCP convention is to prefix it with the name of the class that uses it. Now we have to add that key to the Messages class:


  public class Messages {  
    ...
    public String HelloWorldView_Message;
    ...
  

and add a definition to the messages.properties file:


  HelloWorldView_Message = Hello RAP World
  

Note that in contrast to RCP, you must use fields instead of constants in the Messages class, as they are not shared over all user session and thus cannot be accessed in a static way in RAP.

Translate plug-in manifest

Also the plug-in manifest file (plugin.xml) may contain translatable strings. Like in RCP, those stings are replaced by unique keys, prefixed with a % sign. The keys are then resolved in a plugin.properties file that resides in the root directory of the plug-in. For example, the internationalized version of the HelloWorld plug-in manifest file contains placeholders for the names of the view and the perspective.


   ...
   <extension
         point="org.eclipse.ui.views">
      <view
            id="org.eclipse.rap.helloworld.helloWorldView"
            class="org.eclipse.rap.helloworld.HelloWorldView"
            name="%helloWorldView_name">
      </view>
   </extension>
   
   <extension
         point="org.eclipse.ui.perspectives">
      <perspective
            id="org.eclipse.rap.helloworld.perspective"
            class="org.eclipse.rap.helloworld.Perspective"
            icon="icons/icon.gif"
            name="%perspective_name">
      </perspective>
   </extension>
  

And here's the plugin.properties:


  helloWorldView_name = Hello World View
  perspective_name = Hello World Perspective
  

To make this work, the OSGi manifest file (MANIFEST.MF) must contain the line:


  Bundle-Localization: plugin
  

In the end the Equinox extension registry must be made aware that it has 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 are 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>
  

Create translations of your *.properties files

The last step of the internationalization is to actually translate. The translated strings are contained in localization properties files. These files may also reside in a fragment of its own, together with other localized resources. Localization properties files have a suffix that determines the language, optionally also the country, and a variant (refer to the java.util.Locale JavaDoc for these concepts), all preceded by an underscore character. For example, to create a translation to Swiss German, create a copy of the messages.properties file and name it messages_de_CH.properties. Then you can start to translate the contained stings. Be aware that the translated properties files will very likely contain accented characters that are not included in the Latin-1 encoding (UTF-8), which is expected by the RAP NLS support (as well as by the Java ResourceBundle mechanism). Those files can be converted using the native2ascii conversion utility, included with the Java SDK. Alternatively, RAP also allows for UTF-8 encoded properties files to ease the translation into non-latin languages. In this case, you have to change the call to RWT.NLS.getISO8859_1Encoded into RWT.NLS.getUTF8Encoded in the Messages class.

How does RAP select the language for a user session?

Web browsers allow users to set their preferred languages and they pass those preferences in an Accept-Language HTTP header with every request. RAP tries to select one of the requested languages in the preferred order and if this is not possible, it uses the default locale as fallback. The default locale can be set by adding the system property user.language to the launch configuration. If no matching properties file can be found, the default one (messages.properties) takes precedence.

References