Eclipse Internationalization Part 1/4 – Current Situation by Dirk Fauth

If you are developing Eclipse applications and plugins for users around the world, you should prepare them to be translated into several languages. While in other frameworks this process is quite straight forward (think of any webframework you know), in Eclipse you have to deal with several facts. In the upcoming series of blog entries I want to show you the following about internationalization with Eclipse 4:

  1. why the current situation is not satisfying
  2. what is possible (and already available) using OSGi services
  3. how to migrate your current localization code to the new solution
  4. what cool stuff is possible with the new solution you always waited for

If you are not yet familiar on how to internationalize your Eclipse application, you should start with reading this tutorial by Lars Vogel: Eclipse RCP and Plugin Internationalization

To localize an Eclipse 4 application you need to prepare several files:

  • plugin.xml
  • application model (*.e4xmi)
  • source code

Translating plugin.xml / application model

The plugin.xml and the application model are both using OSGi resource bundles for translation. For any label or value that needs to be translated you enter a value that starts with % and corresponds to a key in your resource bundle. The location where to find the resource bundle can be configured in the MANIFEST.MF by setting a value for the Bundle-Localization attribute.

Bundle-Localization: OSGI-INF/l10n/bundle

OSGI-INF/l10n/bundle is the default bundle and doesn’t need to be set explicitly. But you can modify this to point to a different location which we will use in a second.

For the plugin.xml there is no alternative to this. You are limited to using resource bundles based on properties files at the specified location.

The application model instead uses the TranslationService and you are able to change the mechanism that should be used, e.g. translating by using a database or Google translate which was shown by Tom Schindl at several conferences. More on the TranslationService will be showed later in this blog.

Translating the Sources

While you are limited in the way you translate the plugin.xml, there are several ways to translate your application within the source code. Talking about an Eclipse application the following options are available at the moment:

  • String based translation
  • Constant based translation
  • Eclipse 4 TranslationService

Using the Eclipse 4 TranslationService also allows you to modify the way the application model is translated. Using the default TranslationService also limitates you to use the resource bundle at the specified location.

String based translation

Your first option is to translate the source code based on Strings. This is a solution that works for every Java project and does not rely on Eclipse. For convenience you typically create a class that looks like this:

public class Messages {
	private static final String BUNDLE_NAME = 
		"org.eclipse.nebula.widgets.nattable.messages"; //$NON-NLS-1$

	private static final ResourceBundle RESOURCE_BUNDLE = 
		ResourceBundle.getBundle(BUNDLE_NAME);

	private Messages() {
	}

        //use this method to simply retrieve the value for the key
	public static String getString(String key) {
		try {
			return RESOURCE_BUNDLE.getString(key);
		} catch (MissingResourceException e) {
			return '!' + key + '!';
		}
	}

        //use this method to retrieve the value with parameters
	public static String getString(String key, Object[] args) { 
		return MessageFormat.format(RESOURCE_BUNDLE.getString(key), args); 
	}
}

Setting the localized text to a label in your code you would look like this

myLabel.setText(Messages.getString("label_message"));

Using the String based translation has the advantage that you are using plain Java ResourceBundle mechanisms and are able to use all features that are provided by Java. But it also has several disadvantages:

  • You need to know the keys that are available in the resource bundle
  • If a key changes you need to search for the String in your code
  • You need to copy the class for every plugin because of the RESOURCE_BUNDLE constant
  • The resource bundle files need to be in the same classpath as the Messages class. Well, as you are able to use all ResourceBundle capabilities you could also specify a different classloader, but understanding classloading in an OSGi context is something advanced, so typically you end up in having the resource bundle and the Messages class in the same package.

Constant based translation

The constant based solution, which can be found in the org.eclipse.core.runtime plugin, solves most of the mentioned issues for String based translation. Using this approach the Messages class would look like this:

public class Messages extends NLS {
   private static final String BUNDLE_NAME = 
		"org.eclipse.nebula.widgets.nattable.messages"; //$NON-NLS-1$

   public static String label_message;

   static {
       // initialize resource bundle
       NLS.initializeMessages(BUNDLE_NAME, Messages.class);
   }

   private Messages() {}
}

To set the localized text to a label in your code you would write code like this

myLabel.setText(Messages.label_message);

Using this approach you don’t need to know the keys exactly, as you can use code completion to see which translations are available. If a key changes you can use the refactoring mechanism in Eclipse to update your code and you can search for occurrences of used constants in your code. As a constant based resource bundle is tightly coupled to the plugin it belongs to, it is obvious that there need to be one for each plugin. But as the containing code differs because of different constants, this isn’t an issue.

But the constant based solution also has some disadvantages:

  • The resource bundle files need to be in the same classpath as the Messages class. As you are not using the Java ResourceBundle directly, there is no other option to that.
  • The translations are stored in constants. So they can not be removed by the garbage collection and they can’t be changed at runtime.
  • You can not use the “.” as separator for hierarchies in your properties because they are not allowed Java to be part of a constant/member name.

The String based solution aswell as the constant based solution for translating source code share one common disadvantage. Additionally to the OSGi resource bundle for translating the application model and the plugin.xml, you need to create a resource bundle within your source code package structure. This is because the resource bundle is resolved via the same classloader as the Messages class and therefore does not know about the plugin structure. As I personally prefer to manage translations only in one resource bundle rather than splitting them, the only way to solve this is to set the OSGi resource bundle to point into the source package:

Bundle-Localization: src/org/eclipse/nebula/widgets/nattable/messages

But this way you are moving resources into the source tree, and not in the default OSGi container location anymore. IMHO this is also not a good design for managing resources.

Eclipse 4 TranslationService

With using the Eclipse Application Platform you get access to OSGi services. For translation there is the TranslationService whose default implementation is the BundleTranslationProvider. You can inject the TranslationService into your classes and use it for translation like shown here:

@Inject
public TranslationTest(Composite parent, TranslationService service) {
       myLabel = new Label(parent, SWT.NONE);
       myLabel.setText(service.translate(
              "%label_message", "platform:/plugin/translation")); //$NON-NLS-1$ //$NON-NLS-2$
}

Looking at the usage of the TranslationService it looks like an advanced String based solution with the following enhancements:

  • There is no need to copy code for several plugins as it is an OSGi service
  • It uses the OSGi resource bundle rather than resource bundle in the source tree
  • You could exchange it to get the translations from anywhere you want, even a database

So it is really a flexible alternative for translating your source code. But it still has several disadvantages which makes the TranslationService not a good solution in the develoment process:

  • If you are using the default implementation, than you are forced to use the OSGi resource bundles
  • You need to know the keys that are available in the resource bundle
  • If a key changes you need to search for the String in your code
  • Additionally to know the key to use for translation, you need to know the plugin URI where the OSGi resource bundle should be retrieved. Of course you could also point out that this is an advantage because of the flexibility to choose in which bundle the resource bundle should be searched. But usually you don’t mix.

Conclusion

So what is the conclusion of my blog post until now? To perform localization in your source code in Eclipse you have several options. But they all have several disadvantages. While the Eclipse Platform tries to add some convenience for translating over using the plain String based Java solution, those solutions doesn’t feel comfortable in the end.

The question that remains is, why isn’t there a solution that combines the advantages of the shown solutions and avoids the disadvantages to have a clean and straight forward solution. Well, there is such a solution, otherwise my blog post series would be nonsense. And I will show you this solution in my next post. So stay tuned for cool stuff coming!

Links:

About Dirk Fauth

Dirk Fauth is a Software Architect for Rich Client Systems working for the Robert Bosch GmbH in Stuttgart and a lecturer in Java basics for the Baden-Wuerttemberg Cooperative State University (DHBW). He is active in developing, teaching and talking about OSGi, Eclipse RCP applications and Eclipse related technologies. He is project lead of the Nebula NatTable project, Eclipse Platform committer and also a committer and contributor to several other Eclipse projects.
(Twitter: @fipro78)

This entry was posted in Dirk Fauth, Eclipse, Java, OSGi. Bookmark the permalink.