Skip to main content

Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Babel / Runtime Translation Editor

Babel Runtime Translation Editor

One of the aims of the Babel project is to make it is easy as possible for end users to contribute translations.

The Babel Runtime Plug-in

The Babel runtime plug-in (org.eclipse.babel.runtime) should be included in an Eclipse RCP application to allow users of that application to provide translations. Note that this is quite different from the Babel Resource Bundle Editor plug-in. The Message Editor plug-in runs in the IDE only, and is used by the developer to aid in producing language files for the program under development.

The runtime plug-in, in comparison, is included in the RCP application (the target configuration), and allows the end-user to provide translations. While end-users of the Eclipse IDE typically have a good understanding of plug-ins, Java resource bundles, and the localization architecture in general, end-users of RCP applications typically will have no such understanding. The Babel runtime plug-in therefore is oriented towards allowing the user to translate what the user sees on the screen at that moment, and hides from the user details such as the key and the plug-in that contributed the message.

Getting Started

Because readers will more likely be familiar with the Eclipse IDE than with any other single RCP application, the Eclipse IDE is used as the example target application in these steps. However, these steps can be applied to any RCP application.

1. Copy org.eclipse.babel.runtime_1.0.0.jar into your Eclipse 3.3 plugins directory. This plug-in is currently available only by checking out the project source from CVS, so you will have to create the jar yourself. The source can be found at dev.eclipse.org:/cvsroot/technology,org.eclipse.babel/plugins/org.eclipse.babel.runtimehttp://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.babel/plugins/org.eclipse.babel.runtime/?root=Technology_Project. In order to compile, your CLASSPATH must include several of the jars in your Program Files\Eclipse\plugins directory (specifically org.eclipse.core.commands, org.eclipse.core.runtime, org.eclipse.equinox.common, org.eclipse.equinox.registry, org.eclipse.jface, org.eclipse.osgi, org.eclipse.ui.forms, org.eclipse.ui.workbench, and org.eclipse.swt).

2. For a more interesting demo, install at least one language pack. Language packs can be installed from the Babel update site at http://download.eclipse.org/technology/babel/update-sitehttp://www.eclipse.org/babel/downloads.php. Then start the target platform with the "-nl" parameter specified in the program arguments (this may not be necessary, as Eclipse uses the current system locale localization automatically). So, for example, to run with Swiss German, start Eclipse from the command line using "eclipse.exe -nl de_CH" or from the IDE by including "-nl de_CH" in the program arguments box.

The Babel runtime plug-in allows users to translate only to the language to which the system locale is set. If their locale has both a language and a country code then they can provide both country specific translations and general translations for that language. For example, if the locale is fr_CA then the user can provide both translations specific to French Canadians and general French translations. The reason for restricting the user in this way is to make the UI as simple as possible for non-technical users. The intent is that the user is translating/correcting what he sees on the screen. There is of course nothing to stop a user from changing the system locale and restarting the platform.

The plug-in does allow a way for the user to view and translate messages based on the contributing plug-in. However, as end-users are not likely to be aware of a particular message's contributing plug-in, the user can also view messages based on the part (editor or view), the action bar (menu, toolbar, or status bar), or the dialog.

Having started the Eclipse IDE, open the "Translate Text..." menu under "Help". Select the "Menu" tab and you should see a list of menu items. This list should match the actual menu (the only known difference being that items will appear in the list even if they have been excluded from the menu based on disabled activities). A few items may have 'lock' icons, which means the Babel runtime plug-in was not able to obtain sufficient information about the source of the message to allow translation. On any of the other items, you may provide translations by in-place editing. If you are interested, the icons have tooltips that show the source of the message. However, this information has been relegated to the tooltip because end-users have no need for this information and it probably would not mean anything to them.

Expand the "File" element in the tree and you should see a row for each of the menu items under the "File" menu. Try entering a translation for, say, the "Open" menu. Press OK. Now select the "File" menu and you should see your new translation taking immediate effect. Quit the workbench and re-start. You should see that your new translation is still active. This demonstrates that the plug-in allows the user to provide translations that are both hot-swappable and persistent across sessions.

The Babel runtime plug-in fully takes care of the menu. It could also fully take care of the toolbar and status bar though this has not been implemented so currently there is no support for providing translations of toolbar and status bar text (other than knowing the contributing plug-in, resource bundle, and key).

To allow translation of views, editors, and dialogs, changes must be made to the source code for those parts. However, none of the parts and dialogs in the IDE or, likely, in your RCP application have yet been modified to work with the Babel runtime. You will see a tab in the translation dialog with a title that matches the editor or view part that was active when you opened the dialog. If you select that tab, though, you will see only a message indicating that the part does not support dynamic text translation. However, there is one dialog that has been so modified. The dialog boxes used in Babel itself supports dynamic translation.

Workbench actions cannot be used while a dialog is open. Translatable dialog boxes therefore have a button in the lower left corner next to the help button. Press that to translate messages in the dialog box. You should then get another dialog box but with a tab titled 'Dialog - Text Translations'. Select that and you will see something like:

File:Babel-dialogTranslation.jpeg

Enter your translations in the table. When you press 'OK', you should see the new text in the dialog.

You will notice one message that has the lock icon to indicate it is not translatable. That is because the message is in fact formatted from other component messages. Expand it and you will see the format string which you can edit and a parameter text which you cannot edit. The top level messages are what the user actually sees in the application. By doing this, instead of showing only the translatable parts, it is easier for the user to see the message that the user wants to translate.

As mentioned earlier, to allow translation of views, editors, and dialogs, changes must be made to the source code for those parts. We now describe the changes that the developer of a part must make to the code so that end-users can provide translations.

Editors and Views

To allow translation of editors and views, the following steps must be taken by the developer.

1. If you currently use the NLS class to load your messages, then use instead TranslatableNLS.

A new class has been provided that replaces NLS. To provide translatable messages, extend from TranslatableNLS. Change all the messages from String objects to ITranslatableText objects. Note that the bind methods also accept and return ITranslatableText objects.

The initialization method is slightly different. Your class would typically contain the following code:

  static {
     // A little risky using the plugin activator in a static initializer.
     // Let's hope it is in a good enough state.
     initializeMessages(BUNDLE_NAME, Messages.class, Activator.getDefault());
  }

You will see an extra parameter. The plug-in activator is required because

  • getStateLocation is called to get the location in which the language delta files are maintained.</LI>
  • The OSGi bundle symbolic name is displayed in the tooltip giving the origins of each text.</LI>
  • The OSGi bundle symbolic name is used to uniquely identify the message on the Babel server.

If you currently use the Java ResourceBundle objects to load your messages then it is recommended that you upgrade to use TranslatableNLS. However, if you want to support the Babel runtime with the minimum of changes, you can use the TranslatableResourceBundle class provided by the Babel runtime. This class is derived from ResourceBundle so can be used in it's place. An instance of this class can be obtained by calling the static get method:

  resourceBundle = TranslatableResourceBundle.get(getBundle(), getClass().getClassLoader(), "com.acme.myApplication.Language");

This is typically called when the plug-in is started.

2. Define a TranslatableSet object in the part.

  private TranslatableSet fTranslatableSet = new TranslatableSet();

3. All text used in the part should be first wrapped in an object that implements the ITranslatableText interface. These objects contain information about the source of the text such as the contributing plug-in, the resource bundle, and the key. The ITranslatableText implementation is then 'associated' with a control.

The TranslatableText object can be used to provide an implementation of ITranslatableText where the text comes directly from a resource bundle. The constructor needs the TranslatableResourceBundle and the key:

  ITranslatableText title = new TranslatableText(resourceBundle, "Title") //$NON-NLS-1$

Note that if you use the TranslatableNLS class, you do not need to call the above constructor because the TranslatableText objects are created for you and are the values of the message fields in your derived class.

This translatable text object is then 'associated' with a control. This is done by creating an object derived from the abstract TranslatableTextInput class and providing an implementation of the updateControl method. The updateControl method should set the text into the appropriate control. So, for the title of a part, the following code may be used:

  fTranslatableSet.associate(
     new TranslatableTextInput(title) {
        @Override
        public void updateControl(String text) {
           ViewPart.this.setText(text);
        }
     }
  );

The updateControl method is called initially when the TranslatableTextInput object is constructed and also whenever the user provides a different translation of the text. Thus the user will see the changed text immediately. This immediate feedback right into the user's running application is important to encourage contributions and also to ensure users know what they are changing.

Now suppose there are two possible titles to the view, depending, perhaps, on the state of a checkbox in the view. Suppose both possible titles are taken from the resource bundle, and the title flips between them as the checkbox is checked. The application, when changing the title, could make another called to 'associate'. The problem is, there would then be two updateControl implementations, and the original would be called also. To solve this issue, the 'associate' method has another form in which an object is passed as the first parameter. You can pass any object, but it must be the same object each time 'associate' is called to set the title. This object is used as the key is a map, and if the key matches a previous key then the entry is replaced. In the above example of setting the title, you could use, say, a String object with a value of "title" as the key, or you could use the ViewPart object itself.

It is a little long winded to provide an implementation of updateControl for every text message. Other forms of the 'associate' method are provided in the TranslatableSet class that do this for you. For example, to associate text with a Label, simply call:

  associate(Label labelControl, ITranslatableText text) 

The first parameter is used both as the key in the map and also it the control into which the text is set.

Most methods are overloaded versions of 'associate'. However, some have different names to avoid ambiguity. For example, to associate text with the tooltip of a label control, you would use:

  associateTooltip(Label labelControl, ITranslatableText tooltip) 

When a new translation is set into an active control, the container layout may need to be re-calculated. If a dialog box, this may even cause the size of the dialog box to change. This could be done in the updateControl implementations. However, that would mean the implementations supplied by the TranslatableSet object cannot be used. An alternative method is to implement the Layout method in the TranslatableSet object. By implementing the layout in this method, the layout will be re-calculated only once even if the user changed multiple text values.

Persistence

If you restart Eclipse, you will see your text changes are still there. The messages are stored in a properties file in the runtime workspace. You can find these delta files in the plug-in's state directory at ".metadata\.plugins\<Bundle-SymbolicName>\.translations". The delta files contain the differences between the resource bundles embedded in the plug-in or fragment and the actual text to be used. The differences being changes either made by the user or obtained from a server running Aptana's software.

The message changes should be communicated to the servers running Aptana's code contribution. This work has not been done. Before this can be done, a programmatic interface is required.

Although the language fragments are built nightly on the Babel server, users may not be downloading these on a regular basis. Non-technical users may not even know about the Babel server or how to install the fragments. If users open the dialog to enter translations then it is desirable for the user to see the latest translations from the server. Therefore the current values should be fetched from the server every time the translation dialog is opened. This work has not been done.

Another problem to consider is how we handle the installation of a later language pack. Ideally the delta files should modified to be deltas from that later version. This work has not been done, which means there may be older translations left in the delta files that are overriding more recent translations in the language packs.

Implementation

The code to build the menu is one of the trickier parts of this plug-in.

1. It is possible to traverse the menu, but that gains us little as we have no means of determining where each text originated. We solve this by traversing the menu bar contribution tree. This is rather trickier because the tree does not map simply to the actual menu but needs some knowledge of the various contribution classes in order to get the menu tree to match the menu.

2. Having access to the contribution objects still does not get us to the origins of each piece of text. The problem is that the code to parse the plugin.xml files will replace any keys with the actual localized text. The way we get back to the key is to re-parse plugin.xml ourselves. This is obviously not the most efficient. Basically we know what elements to look for in the plugin.xml file based on the type of the menu contribution object.

3. The last problem concerns updating the menu dynamically after the user has edited a message. Sometimes this is as easy as calling 'setText' on the contribution item followed by a call to 'update'. Sometimes 'setText' is private, for some reason. It would be possible to update the underlying menu items, but that runs the risk that the change could be overwritten.

The above could all be made rather simpler if we were able to make changes to the core eclipse code. It will probably to a lot easier to get the changes into the core if the Babel runtime code is already working and the concept proven.

Back to the top