ResourceBundle
to do the job. Collections of localized messages may be arranged to be referenced as static class members rather than string aliases for properties in a ResourceBundle
. The main benefit is gained from getting references to messages compiler verifiable, with programmer's IDE autocompletion aids available for localized messages as a bonus ☺.This approach was described and implemented previously at initial level, being still more of a concept than of a tool for real life applications. The main feature missing was an ability to supply arguments to localized messages, similar to
MessageFormat
.Changes
Getting message arguments into the game resulted in certain refactoring, particularly in substantial separation of code from data and coining a concept of
I18nHandler
. Another change in concept was due to the fact that it is not possible to get a string value of an object via its toString()
method if you are to supply formatting arguments. Hence, only methods like s(TArg1 arg1, TArg2 arg2)
are available in these cases. Some less important change is that I18nItem
no more extends HashMap
but rather has more Map
instances as members ☺. And the former p(String language, String message)
method for instantiating multi-language messages was refactored to lm(String localeTag, String message)
in a separate class. One more notable change is that differentiation of messages by language turned into differentiation by locale language tag. And finally, the package was renamed to usn.i18n.nobundle
.New design
Data
LocalizedMessage
is the basic container for localized messages. Internally it is a tuple of a locale language tag and a corresponding localized message. In case you do not care about locale-specific formatting of dates and numbers, feel free to reduce the full language tag to just the language subtag like "en". The localized message may be a mere string or a formatting pattern as per MessageFormat
convention. The standard way to construct a LocalizedMessage
instance is via its static factory method lm(String localeTag, String message)
. Normally there is no need to construct an isolated LocalizedMessage
instance unless as a component of an I18nItem
.I18nItem
is the basic class to represent an internationalized message. Internally is has a collection of LocalizedMessage
instances for a necessary set of locales. I18nItem
is not intended for direct use by applications but rather serves as the base class for an hierarchy of more specific subclasses.Special care was taken to reduce the risk of wrong use of arguments for messages, e.g. providing arguments of wrong type or in wrong number. With this in mind the following
I18nItem
subclasses are offered for use:I18nItem0
– the class to use for messages without arguments;I18nItem1<TArg1>
– a generic class to use for messages with exactly one argument;I18nItem2<TArg1,TArg2>
– a generic class to use for messages with exactly two arguments;I18nItem2<TArg1,TArg2,TArg3>
– a generic class to use for messages with exactly three arguments;I18nItemAny
– a class to use for messages with arbitrary number of arguments, as the last resort for cases that did not fit into previous subclasses;
... import usn.i18n.nobundle.I18nItem; // get 'lm()' available as shorthand method without being prefixed by // 'LocalizedMessage' import static usn.i18n.nobundle.LocalizedMessage.lm; ... static I18nItem0 MESSAGE_1 = new I18nItem0 (lm ("en", "That's cool!"), lm ("fr", "C'est le pied!"), lm ("ru", "Круто!")); static I18nItem1<Integer> MESSAGE_2 = new I18nItem1<Integer> (lm ("en", "I know {0,number,integer} guys seeking something" + " like this..."), lm ("fr", "Je sais que {0,number,integer} gars à la recherche" + " de quelque chose comme ceci..."), lm ("ru", "Я знаю ещё {0,number,integer} ребятишек, которые" + " хотели чего-то подобного..."));
Handlers
The next important topic to take care of is an approach to obtaining user language / locale preferences during run time. In anticipation of vast variety of application architectures, this topic is resolved in the most general way with the help of the
I18nHandler
class. This class has absorbed almost all the code that is responsible for handling of methods like s(TArg1 arg1, TArg2 arg2)
, and meanwhile it allows applications to achieve necessary behavior by its subclassing and overriding getDefaultLocaleTag()
and most importantly getUserLocaleTags()
methods. The only thing an application needs to do is:1) subclass
I18nHandler
or select from its existing subclasses as necessary;2) create an instance of the selected
I18nHandler
subclass; this instance will establish itself as a singleton and take care of all the rest.I18nHandlerForSingleUser
is an almost ready to use I18nHandler
subclass that is intended for use by applications like desktop ones, with single user per application instance, when it is possible to define all user preferences upon application startup. You may only still wish to override the getDefaultLocaleTag()
method.Context based internationalization
There are also cases to be taken care of specially, when user preferences are conveniently available from some execution context rather than from the application directly. A standard example is a web application, where some primitive user preferences may be retrieved from a
ServletRequest
instance. Or maybe even better from an associated HttpSession
instance. These cases are taken care of by I18nHandlerInContext<TContext>
and I18nHandlerForServletRequest
subclasses, to be subclassed further as necessary.And
I18nHandlerInContext<TContext>
subclasses require a special family of I18nItem
subclasses, that take a TContext
instance as the first argument for all their methods:I18nItemInContext0<TContext>
;I18nItemInContext1<TContext,TArg1>
;I18nItemInContext2<TContext,TArg1,TArg2>
;I18nItemInContext3<TContext,TArg1,TArg2,TArg3>
;I18nItemInContextAny<TContext>
.
Internationalization for logging
Finally, to bring the renovated package closer to real world application needs, two more classes are added that get this internationalization technology available for logging:
I15dLogger
and I15dLoggerFactory
. These two bridge I18nItem
with SLF4J in hope that is going to be sufficient these days ☺.Behind the scenes
So what happens when an
I18nItem
instance's s()
or similar method is called?- The
I18nHandler
singleton is located. - Its
getUserLocaleTags()
method is called. This method is expected to be overridden by a subclass to do something meaningful ☺. An array of user preferred locale language tags is the result. - A negotiation between user preferences and locales available for given
I18nItem
instance takes place in the handler'sfindBestLocaleTag(I18nItem,String[])
method. For every user's preferred locale two attempts are made: for exact match and for approximate match via language subtag. If no luck, then the system default locale is attempted. If again no luck, then plain English is attempted. And if not successful again, the first locale available for givenI18nItem
instance is selected. - The localized message (message pattern) is retrieved from given
I18nItem
instance for the locale language tag selected on the previous step. - Message formatting arguments are applied if required.
Downloads
Both jar and source downloads are available. And the project is now hosted at Github. Please feel free to use and modify. The license is BSD.
Usage – HOW-TO
- Download the .jar file ☺.
- Add it to your application's class path.
- Make a decision on the
I18nHandler
subclass you need: whether it should depend on some context for obtaining user's locale preferences or should it get these preferences from the application directly; hence select an existingI18nHandler
subclass or make a subclass of your own. - Create an instance of the chosen
I18nHandler
subclass in your application; no need to care about its further fate; feel free to mark it with@SuppressWarnings ("unused")
. - Create as many of
I18nItem
sublasses' instances as you need for all the messages that require internationalization.
- Use your messages via corresponding
s(TArg1 arg1)
and similar methods. - Refer to the modest example on the package summary javadocs page and to API docs in general when necessary.
- Enjoy ☺.
Package name
I personally feel not very comfortable having a negative word like "nobundle" in the package name. But got no better idea so far. So positive ideas on naming are welcome! ☺
Comments welcome
Though the library is already tested in a real life application, it is probably undercooked ☺ and may contain bugs. It may also ask for improvement here and there. So any comments are welcome too!.. ☺