Problems with Java Internationalization and Eclipse
During development on uDig, I have had plenty of time to examine and use the internationalization tools that come with Eclipse. The Externalize Strings wizard is quite powerful and we have been using it very successfully, but we have run into a couple of hiccups.
I present the problems here, but I believe the solutions are fairly obvious. Perhaps I will detail them in a later post.
1. The Externalize Strings wizard retains no history
For the uDig project, we follow Eclipse's recommendation to use one Policy object per plug-in that tracks its own ResourceBundles. We also only had one ResourceBundle (messages.properties) file for all of the source code in a plug-in. The advantage of this Policy object is that it loads and unloads itself when the plug-in is loaded and unloaded.
The main problem with the Externalize Strings wizard is that every time you run it, you must enter in the different values again. We use a nonstandard class (Policy, vs the default Messages), a nonstandard method call (bind() vs getString()), and because we only use one ResourceBundle for all the source code in a plug-in, there is only one messages.properties file, which will often reside in a different package than the current Class being edited. It would be nice if the wizard would at least remember the previous values entered.
An ideal solution would be the ability to set project-level configuration options for internationalization. If you have multiple projects in your Eclipse workspace, they might each do internationalization differently.
2. The Java way of Internationalization is problematic
A bit of background first. Scroll down if you are familiar with how Java does internationalization. The standard way to indicated that your String is internationalized is to insert a comment at the end of the line:
label.setText(Policy.bind("WizardPage.label.text")); //$NON-NLS-1$This will tell the Policy class to look up the internationalized String with the key "WizardPage.label.text" and return the proper String according to the current locale. The //$NON-NLS1$ indicates that this String has been properly "externalized."
The comment is also used to indicate that a String should not be externalized:
private static final String RECENT = "RECENT"; //$NON-NLS-1$This String is used for saving of recently entered values for a text widget. It is a constant that is not intended ever to be displayed to the user, therefore it should not be externalized.
The problem with this is that the indicators are separated from the Strings they reference. If someone deletes the String parameter to label.setText() and points it to something else, the marker comment remains behind, often forgotten about it, because it is a pain to remove it. Later, if someone comes back and changes the code to label.setText("some string that the user will read"), the marker comment will still be there, and it will falsely indicate that this String should not be externalized.
I can't imagine what would happen if we ran Jalopy on the uDig source code. It wouldn't surprise me if long lines containing these comments were split in half, causing the Externalize Strings wizard to run over all of these Strings again.
3. Developers are lazy
When Eclipse is complaining about something, the easiest way to fix it is to hit Ctrl-1 and then enter to have it perform auto-fix. In the case of Strings that need to be externalized, that means adding //$NON-NLSn$ to the end of the line. Yes, the Externalize Strings option appears when Ctrl-1 is pressed, but, as explained earlier, it is a pain to use, especially if one is in a hurry.
Most developers are actually quite good about this, and I have only encountered it three times so far (and I admit to doing it myself). But any falsely marked Strings will prevent a user interface from ever being completely internationalized.
4. What does that key mean?
When reading source code that contains externalized strings, the reader see something like:
label.setText(Policy.bind("WizardPage.label.text")); //$NON-NLS-1$What does that mean? Well, looking at the key here, we can presume that it is the text value for a label. That doesn't really help indicate what the text is being used for. If I want to look up the value of an externalized string, I have to hunt down the ResourceBundle that contains the value, and that might not always be easy to find (I have a hard time remembering which package I put it in when there are over 10 packages in a plug-in - The solution here would be to standardize where the bundles lives. It's on my todo list.). Once I have found the file, I have to search the file for the key. Not an easy task if the file is not sorted and contains hundreds of keys (although a search will often do the trick quickly).
It would be nice if there was a seamless way for the developer to determine what the value of a key is.
These are all issues that I would love to address in my spare time. I have given some thought to solutions to all of these problems, and will likely present them in another post sometime.
Links:
uDig has some quick and dirty documentation on how to externalize strings:
Plugin Internationalization (plugin.properties)
Plugin Internationalization with ResourceBundles (Java source)
Java internationalization tutorial

2 Comments:
My comment is a little late, but here it goes. On jLibrary I use a different approach. I use fragments and resource bundles. So, I only need to put the resource bundles on internationalized fragments, and that's is.
Cheers.
Right. We do the same with uDig as well. Each plug-in has a fragment that contains the translated bundles. I would be interested in learning more about how jLibrary does other aspects of internationalization.
Post a Comment
<< Home