X

Geertjan's Blog

  • December 1, 2010

One Big Bundle File For Internationalization

Geertjan Wielenga
Product Manager
I visited Monaco yesterday, after doing NetBeans presentations at the Riviera JUG in Nice, France, the day before. Monaco is only a short train ride from Nice. A very large application on the NetBeans Platform (over 300 modules) is created in Monaco (as well as in England and Malaysia) by Experian, in the credit risk management sector. Read about that project here.

So, in Monaco, I talked about the latest NetBeans Platform features to a group of developers from Experian. One question they asked was: "Is there a way to make use of one single bundle file for the whole application and in such a way that it is read from outside the JARs in the application?"

The scenario is easy to imagine: your application is complete and now you hire an external organization to translate it. That means you have two requirements. Firstly, it would be convenient to provide the external organization with a single file that contains all your keys, rather than lots of small bundle files. Secondly, once the translation is complete, you'd like to simply replace the original file with the new file, in a simple way, without needing to recompile the whole application, i.e., the application is already delivered and therefore you can't change the JARs anymore.

Well, here's a solution, not sure if it is perfect and hoping anyone reading this with experience in this area will leave comments with alternative solutions:

FileObject fo = FileUtil.getConfigFile("bundle/Bundle.properties");
if (fo != null && fo.isValid()) {
Properties bundle = new Properties();
try {
bundle.load(fo.getInputStream());
//Example usage, where "greeting" is the name of a key:
setName(bundle.getProperty("greeting"));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}

Above, the first statement points to a "Bundle.properties" file that is found in the user directory of the application, within a folder named "bundle". Line 5 above takes the input stream of the Bundle.properties file and feeds a new Properties file with it. Then you can use your keys to retrieve values set in the Bundle.properties file.

So, the above code is somewhere within your application. Probably you have a Utilities module somewhere and you would have the code in that module, then refer to it from all the places in your application where you need to refer to bundle keys.

How to distribute the properties file? Either via a module that you somehow get the user to install (e.g., if you use a Plugin Manager, you'd have a list of various languages in the Plugin Manager, with a module containing the properties file, plus code to write it to the user directory, for each language) or simply send them the properties file, just in an e-mail or something, and tell them where to stick it in their user directory.

Join the discussion

Comments ( 12 )
  • Carlos Hoces Wednesday, December 1, 2010

    I see a potential problem with this approach: Matisse code.

    I have found no way to tell Matisse to look for bundles outside Module scope. And the solution involving "use own class" renders the forms mostly unreadable.

    Anyway, for non-GUI bundles seems a reasonable solution, although I would prefer to pack them all in single module, which could be edited and updated quite easily. Unfortunately, I have tried it before... to hit Matisse restrictions, or ugly forms rendering.


  • Jesse Glick Wednesday, December 1, 2010

    Putting all strings in one file is contrary to modularization and is not supported by the NB Platform. What you are talking about sounds more like a workflow issue for localizers. App developers can just write a little script which takes an untranslated app and creates a properties file with lines like

    platform/modules/whatever.jar//some/pkg/Bundle.properties//some_key=Original Value!

    After translating to another language, a reverse script could take

    platform/modules/whatever.jar//some/pkg/Bundle.properties//some_key=Nový Parametr!

    and produce platform/modules/locale/whatever_cs.jar with an entry some/pkg/Bundle_cs.properties with the line

    some_key=Nový Parametr!

    No special code is then needed at runtime, yet localizers get to work with one big file if they are more comfortable that way.


  • Carlos Hoces Wednesday, December 1, 2010

    This is what I would like to be available in the Platform, regarding translation bundle files:

    The capability to pack all I18N related files into a single Module, and get them available to any module setting a dependency on it, specially Matisse related modules.

    I don't see IMHO this approach would break any modularity, and at the same time would make translation much easier.

    We use PRBEditor https://prbeditor.dev.java.net/ which makes possible to directly edit the bundle files, so it could be used by translators, w/o any need for scripts.

    But by having by force all I18N files shattered across many modules, makes both development and translation tasks unnecessary complex.

    I'm talking about specific app development bundles, not Platform ones. I agree Platform bundles should be as they are.


  • Simon Wednesday, December 1, 2010

    I agree with Jesse's comment about modularisation.

    Where I work, we put all our resource bundles into a single file. We have an Excel spreadsheet with the first column defining the keys and subsequent columns for each locale. Then the resource bundles are then generated via some VB script.

    The convenience of a single bundle is not worth it and I would discourage this approach because developers tend to be lazy and look for existing entries rather than create duplicates. A word or phrase in one language in a certain context may not make sense in another language in another context. Also, when it comes to version control and with a single bundle, there is more likely to be conflicts - especially with the Excel spreadsheet solution we have.

    Jesse's suggestion for some script to grab all the bundles and put them into a single file to give to a translator and then a script to write out back to individual bundles is something I have been thinking about doing.


  • Thiago Bonfante Thursday, December 2, 2010

    Funny, this week I was thinking in a way to make a better methodology to internationalization, but some other issues with JBoss Isolation are consuming all my time. But, I'm wondering if a inner database, like derby can do the job and how it impacts at app weigth.


  • Tim Dudgeon Thursday, December 2, 2010

    We have worked things so that we can generate an Excel file will all the strings in that we can generate, send for translation, and then re-incorporate the translations back into the codebase.

    The code is quite involved, but we'd be happy to share if there is interest.


  • Geertjan Wielenga Thursday, December 2, 2010

    That would be very useful, Tim!


  • Thiago Bonfante Thursday, December 2, 2010

    Hi Tim. If you can share, I'm really interested. My email is thiagobonfante at gmail dot com. In my project I've implemented some nice things too, then we can share some knowledge.


  • Jesse Glick Thursday, December 2, 2010

    @Carlos: you can already do this if that's what you want. There's no special trick; just define a module containing just bundles (or just one big bundle), and use NbBundle.getBundle(name) to find it. (You can optionally pass a ClassLoader, which should preferably be that of the bundle module; or just define a trivial API class in the bundle module with methods like getString(key) which delegate to the usual NbBundle.getMessage(ThisAPI.class, ...).)

    I'm pretty sure the form editor can be configured to use a specific bundle as well, rather than the default of Bundle.properties in the same package, though you may need to set this up for each form (could very likely be automated with a little script or plugin).

    This approach is "nonmodular" because the master bundle must contain all keys used by any modules in the app, even optional extensions that don't happen to be enabled at the moment; and local feature changes to a module's GUI will require matching changes in the bundle module (implying version number changes, if you are using an update center). For an application written all by one team, released as a unit, this does not matter so much (just like modularity in general), but it is not an option for an app like the IDE that really exposes plugins to the user.


  • Steve G Thursday, December 2, 2010

    For our company we extended ResourceBundle to pull from a database. Instead of a flat structure, the entries were in a tree structure with no limits on the depth, and the client would traverse up the tree if the translation was not found in a lower leaf. The motivation for us was for site-specific translation moving up to corporate translation if there was commonality, if that makes sense. Not sure that is what you are looking for.


  • Carlos Hoces Thursday, December 2, 2010

    @Jess: I'm already in the process of doing such a thing. But in order to place bundle files in a single module, and get them available app wide, the packages containing the bundle files must be set as public, and it cannot be done (as far as I know), unless there is some plain java class file in each package.

    Even when Matisse may find them at design time, being private, at runtime a trick like that one is needed to get them visible.

    I'm using the ServiceProvider for those in-package classes to make bundle files visible and available, while it allows the package itself to get marked as public. It's a bit tricky, but seems to work so far ;-D

    I never though this would be a reasonable approach for NetBeans IDE, but for stand alone applications.


  • Jesse Glick Thursday, December 2, 2010

    @Carlos: it is possible to make a package with only resources public, if you edit project.xml directly rather than using the Properties dialog.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services