Eclipse Luna M6 – Localization Update

With Eclipse Luna M6 there are some interesting news about localization.

Locale changes at runtime

The application model is now able to react on Locale changes at runtime. In combination with the Eclipse Translation Pattern that comes with the New Message Extension, an Eclipse based application can be re-localized at runtime. For this the MLocalizable mixin interface was introduced, which is now implemented by every model class that contains localizable information, e.g. MUILabel. In this blog post I would like to show how to change the locale at runtime with the new extension, rather than explaining the internals that enables this.

In a previous blog post I described how to change the Locale directly via setting the value to the context. With Luna M6 you should use the newly introduced ILocaleChangeService. This is because the default implementation of that service is updating the Locale in the application context and notifies the model elements. Additionally the ILocaleChangeService posts an event for the topic org/eclipse/e4/core/NLS/LOCALE_CHANGE to the event broker.

A command handler for changing the Locale would now look like this:

/**
 * Item that can be added as a ToolControl to the TrimBar of a
 * Window. Shows an input field which allows to enter a
 * locale String and a Button to send the action to update
 * the Locale.
 */
public class LocaleChangeItem {

	Button button;

	@Inject
	ILocaleChangeService lcs;

	@Inject
	public LocaleChangeItem(Composite parent) {
		final Text input = new Text(parent, SWT.BORDER);
		input.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == SWT.CR
					|| e.keyCode == SWT.KEYPAD_CR) {
					lcs.changeApplicationLocale(
						input.getText());
				}
			}
		});

		button = new Button(parent, SWT.PUSH);
		button.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				lcs.changeApplicationLocale(
					input.getText());
			};
		});
	}

	@Inject
	public void translate(@Translation OsgiMessages messages) {
		//button localization via Eclipse Translation Pattern
		LocalizationHelper.updateButtonText(
			button, messages.button_change_locale);
	}
}

You can react on a Locale change in different ways:

  • by getting TranslationService.LOCALE injected (which is what the new message extension is doing)
    @Inject
    @Optional
    private void getNotified(
    	@Named(TranslationService.LOCALE) String s) {
    	System.out.println("Injected via context: " + s);
    }
  • by listening to the @UIEventTopic ILocaleChangeService.LOCALE_CHANGE via dependency injection
    @Inject
    @Optional
    private void getNotified(
    	@UIEventTopic(ILocaleChangeService.LOCALE_CHANGE) Locale s) {
    	System.out.println("Injected via event broker: " + s);
    }
  • by getting the newly created Message object instance injected
    @Inject
    public void translate(@Translation OsgiMessages messages) {
    	System.out.println("Messages injection");
    	//button localization via Eclipse Translation Pattern
    	LocalizationHelper.updateButtonText(
    		button, messages.button_change_locale);
    }
  • by listening to model updates directly (which is what the renderers need to do for example)

The example for the new message extension was updated to show the application model updates with Luna M6 aswell. You can find it at GitHub.

TranslationService Update

Another enhancement regarding localization is an update to the TranslationService. The TranslationService is responsible for localizing the application model. The default implementation BundleTranslationProvider is doing that job by using OSGi ResourceBundles. That means, it loads the file-based ResourceBundle typically located in OSGI-INF/l10n/bundle.properties. To change that, you could create a custom TranslationService implementation and put it to the context. But most of the time that meant to copy&paste some code. And it was not easily possible to configure a ResourceBundle that is located outside the OSGi context, which was asked at Stack Overflow. Two things were done to make it possible to exchange the ResourceBundle loading more easily:

  1. Modified the API of TranslationService.
  2. Introduced the new ResourceBundleProvider service, that is responsible for loading ResourceBundles.

The API modification of TranslationService is a quite simple one, as only the method getResourceString(String, ResourceBundle) was moved from BundleTranslationProvider to TranslationService with protected visibility. Therefore it is not necessary anymore to copy&paste that code for custom TranslationProvider implementations.

The BundleTranslationProvider was modified to use the newly introduced ResourceBundleProvider instead of having a direct dependency to BundleLocalization, which is loading the OSGi ResourceBundle. Of course the default implementation of ResourceBundleProvider is still using BundleLocalization. But with introducing ResourceBundleProvider, it is now possible to easily exchange the way the ResourceBundle is loaded, e.g. a ResourceBundle located on a different server can be loaded or a class-based ResourceBundle can be used. The following example is using a CustomResourceBundleControl, which simply returns a new class-based ResourceBundle instance.

public class CustomResourceBundleProvider
		implements ResourceBundleProvider {
	@Override
	public ResourceBundle getResourceBundle(
		Bundle bundle, String locale) {
		//we set the base name to be an empty String for
		//this simple example
		return ResourceBundle.getBundle("",
			ResourceBundleHelper.toLocale(locale),
			new CustomResourceBundleControl());
	}
}

class CustomResourceBundleControl extends Control
	@Override
	public ResourceBundle newBundle(String baseName, Locale locale,
		String format, ClassLoader loader, boolean reload)
		throws IllegalAccessException, InstantiationException,
			IOException {
		return new CustomListMockBundle();
	}
}

The CustomResourceBundleProvider needs to be set to the IEclipseContext before the model is passed to the renderer. This can be done either via a lifecycle manager in the @PostContextCreate method, or via model addon. In both cases the code gets executed before the model gets passed to the renderer, which is important as the renderer will request the values for the localized content.

public class LifecycleManager {
	@PostContextCreate
	public void postContextCreate(final IEclipseContext context) {
		//Use this to enable custom resource bundle loading,
		//e.g. using a custom ResourceBundle.Control
		context.set(ResourceBundleProvider.class,
			new CustomResourceBundleProvider());
	}
}

Note that the lifecycle manager needs to be registered via plugin.xml (see this vogella tutorial for more information), while the addon is registered via application model (see the Addons section in the Application Model Editor).

public class ResourceBundleAddon {
	@PostConstruct
	void postConstruct(final IEclipseContext context) {
		context.set(ResourceBundleProvider.class,
			new CustomResourceBundleProvider());
	}
}

Another advantage of the ResourceBundleProvider usage is that both, the application model and the new message extension, are using it in the last instance for searching the ResourceBundle to use for localizing the application. So it is only one step to exchange the ResourceBundle for application model and content.

The complete example can also be found at GitHub.

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

4 Responses to Eclipse Luna M6 – Localization Update

  1. Rico Scholz says:

    Thanks for the blogpost, it is very helpful and seems to be a really cool thing.

    In your first code-snippet you try to filter for the Enter-Key. In general I also include the keycode 16777296 for the keypad enter. Do you redirect this key to 13? I have in mind that for Swing it was possible, but didn’t see it on SWT yet.

  2. Dirk Fauth says:

    Well, it is just an example, so I didn’t check every possibility to commit a value in the text field. But of course you are right, if the Enter-Key is enabled to commit a value, also the keypad Enter-Key should do that.

    I updated the code snippet and the project sources on GitHub accordingly.

  3. Rico Scholz says:

    I know that it’s only an example and that it don’t have to be perfect. I was just asking because of the functionality in Swing that you can redirect the keypad enter to the “normal” enter and if something like that is also possible for SWT.

  4. Dirk Fauth says:

    As I’m not a Swing expert, I don’t know about such a feature. I would have thought that it needs to be an event listener that comes to play very early, before it gets further processed. As the events are mutable you should be able to change the event.keyCode at runtime then, so you are changing the keyCode that is fired at runtime. But I have never done such thing before.

Comments are closed.