ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

I18N Messages and Logging

by John Mazzitelli
12/06/2006

For many a software developer, the mere mention of a requirement to support internationalization (aka i18n) is sure to elicit groans. Writing code that is targeted towards an international audience does require some forethought, because it is not something very easily introduced into an existing piece of software. If you feel there is even a small possibility that you will need your software to support different locales and languages, it is always more prudent to internationalize your project at the start, rather than attempting to retrofit i18n into it after it has begun.

That said, what does "internationalizing" mean? It is more than just providing translations of your user interface messages into different languages. It involves dealing with different character encodings, localizing date, time, and currency formats, and other things that differ across multiple regions around the world.

Introducing i18nlog

While this article will not attempt to discuss all the facets of internationalization, it will cover how to more easily perform some of the tasks necessary to introduce i18n functionality by examining a new open source project called I18N Messages and Logging, or i18nlog for short.

i18nlog allows you to incorporate internationalized messages into your Java applications by providing an API to:

  • Annotate Java classes to identify your i18n messages.
  • Obtain i18n messages from resource bundles in any supported locale.
  • Create localized exceptions whose messages are internationalized.
  • Log i18n messages using any logging framework.
  • Automatically generate resource bundles in any supported locale.
  • Automatically generate help/reference documentation.

Defining i18n Messages

One of the more tedious tasks involved when internationalizing your software is maintaining resource bundles. Resource bundles are properties files that contain "name=value" pairs where "name" is the resource bundle key string and "value" is the localized message string itself. It is customary to store messages in resource bundles such that there is one resource bundle per language and each bundle has an identical set of keys with their associated messages each translated into their locale's language. The name of the resource bundle file dictates which locale it is for; for example, mybundle_en.properties denotes the messages in that file are written in English, where mybundle_de.properties contain German messages.

i18nlog provides annotations to define your resource bundle messages and their key strings--used in conjunction with i18nlog's custom Ant task, you can automatically generate your resource bundles and not have to do much to ensure consistency between your properties files and the Java code that accesses them.

The @I18NMessage annotation is placed on constants that you use in place of your resource bundle key strings. Using these constants inherently forces compile-time checks; typos introduced in the code (i.e., misspelling a constant name) and usage of obsolete/deleted messages are detected at compile time. An example usage of this annotation is:

@I18NMessage( "Hello, {0}. You last visited on {1,date}" )
public static final String MSG_WELCOME = "example.welcome-msg";

The above defines one i18n message. The value of the constant defines the resource bundle key string. The value of the annotation is a translation of the actual message. You can place these annotated constants in any class or interface within your application. You can put all of them in a single class or interface (to centralize all of your message definitions in a single location), or you can place these constants in the classes where they are used.

The @I18NResourceBundle annotation defines the resource bundle properties file where your messages are to be placed. This can annotate an entire class or interface, or it can annotate a specific field. If you annotate a class or interface, all @I18NMessage annotations found in that class or interface will be stored, by default, in that resource bundle. If the annotation is on a particular constant field, it will be the bundle for that constant only. An example usage of this annotation would be:

@I18NResourceBundle( baseName = "messages",
                     defaultLocale = "en" )

which indicates that all associated @I18NMessage-annotated messages will be placed in a resource bundle file named messages_en.properties. A more complete example that illustrates an interface that contains a set of i18n messages is shown below:

@I18NResourceBundle( baseName = "messages",
                     defaultLocale = "en" )
public interface Messages {
   @I18NMessage( "Hello, {0}. You last visited on {1,date}" )
   String MSG_WELCOME = "welcome-msg";

   @I18NMessage( "An error occurred, please try again" )
   String MSG_ERR = "error-occurred";

   @I18NMessage( "The value is {0}" )
   String MSG_VALUE = "value";
}

Retrieving i18n Messages

Now that you have defined your i18n constants, you can use the API provided by the core class in i18nlog: mazz.i18n.Msg. It allows you to load messages from resource bundle properties files. It will also replace the messages' placeholders (e.g. {0}, {1,date}) with the values passed as variable argument parameters. Although the API frees you from having to work directly with some JDK classes, you might want to read the Javadocs on java.text.MessageFormat to get a feel for how i18nlog does what it does, specifically how it replaces the placeholders with localized data.

Example usages of the Msg class are as follows:

// using a static factory method to create a Msg object
// this assumes the default bundle base name of "messages"
System.out.println( Msg.createMsg( Messages.MSG_WELCOME,
                                   name,
                                   date ) );

and

// using a constructor to create a Msg object
Msg msg = new Msg( new Msg.BundleBaseName("messages") );
try {
   String hello = msg.getMsg(Messages.MSG_WELCOME, name, date );
   ... do something ...
}
catch (Exception e) {
   throw new RuntimeException( msg.getMsg( Messages.MSG_ERR ) );
}

The name and date arguments are an example of passing an arbitrary variable arguments list--each object represents the value to replace the placeholder in its respective position ({0} and {1,date} respectively, based on the Messages.MSG_WELCOME definition given earlier).

The Msg object will know which locale's resource bundle to use based on its "bundle base name" and its current locale setting. The "bundle base name" is the name of the bundle file minus the locale specifier and extension (in the example above, the bundle base name is messages). You can define the bundle base name the Msg object will use by passing it into the Msg constructor or one of its static factory methods (if left unspecified, the default is messages). While the bundle base name is fixed for the lifetime of the Msg object, you can switch the locale used by the Msg object by calling its setLocale() method (its default is the JVM's default locale). This is useful if you pool Msg objects and have to switch its locale based on the user to which the object is currently assigned.

Pages: 1, 2

Next Pagearrow