ResourceBundle
has always been an important part of these internationalization facilities.This approach is well developed, and lots of tools are available to help using it, and still there is an issue about it. Java code is linked to string resources via symbolic names that are mere strings, and it is pretty easy to get them out of sync in the course of changes. And as these links are not checked by compiler, a programmer is almost certain to feel happy until a surprise at run time...
A colleague of mine has once mentioned that it would be more robust and convenient to have text resources linked to Java code via class member names. Then these links would be checked by the compiler, and a programmer could also benefit from autocompletion facilities provided by modern IDE's.
It took some time to get the idea crystallized, and here is the result. It is all about a class named
I18nItem
that extends HashMap
(that might be any other Map
) and has an overridden toString()
method. Every instance of such a class would wrap one text message in different languages, indexed by language. One more task was to implement a convenient way to construct such collections as members of some class, so to allow easy referring to them from other parts of code.The new approach in action
Let's have a look at what a standard use case might look like.
First an application would typically have
I18nItem
subclassed:... import usn.util.i18n.I18nItem; public class AppSpecificI18nItem extends I18nItem { private static final long serialVersionUID = 1L; // define a simple subclass constructor to be used AppSpecificI18nItem (I18nItem.LanguagePair... data) { super (data); } // let's assume the default application language is going to be // French, so override the appropriate method @Override protected String getDefaultLanguage () { return "fr"; } // then the application needs to define the way to obtain current // user's language preferences as string array, most preferred // ones coming first @Override protected String [] getUserLanguages () { // normally we are expected to do something more interesting // here... return super.getUserLanguages (); } } // class AppSpecificI18nItem
Next we need to construct our international string resources without using
ResourceBundle
☺ – in any Java class you may find appropriate:
... import usn.util.i18n.I18nItem; // get 'p()' available as shorthand method without being prefixed by // 'I18nItem' import static usn.util.i18n.I18nItem.p; public class MyMessages { public static I18nItem MESSAGE_1 = new AppSpecificI18nItem (p ("en", "That's cool!"), p ("fr", "C'est le pied!"), p ("ru", "Круто!")); public static I18nItem MESSAGE_2 = new AppSpecificI18nItem (p ("en", "Just what we need!"), p ("fr", "Exactement ce qu'il nous faut!"), p ("ru", "То, что нужно!")); // ... } // class MyMessages
And finally we are going to use the messages we have defined:
... // in some cases implicit conversion to String is available, and the // overridden 'getUserLanguages()' method combined with // 'I18nItem#toString()' takes care of everything... System.out.println (MyMessages.MESSAGE_1); // ... and in other cases the 's()' shorthand method is at our // disposal... logger.info (MyMessages.MESSAGE_2.s ());
And in every case like these your IDE is likely to assist you with right selection of the message to use...
Downloads
Both jar-with-source and demo downloads are available. Please feel free to use and modify. The license is BSD.
Acknowledgements
Special thanks to my colleague Sergey Petrov for the idea that access to a resource via some class member might be preferable against string alias ☺.