Getting started

In order to get an impression how this component works, we will start with an example showing the capabilities of this package.

Defining the messages in an XML file

First of all we need to define some localized messages. There are two default message providers included that can be used to provide messages either in an XML-based format or in the well known ResourceBundle format.

Using XML based files has some advantages:

  • You can use an XML-editor of your choice to get assistance while typing the messages
  • All entries that belong together are logically grouped into a single XML element
  • All provided languages reside in a single file, so it is easy to add a new language (matter of taste?)
  • As the whole file gets parsed at initialization time, the localization is very fast
<?xml version="1.0" encoding="UTF-8" ?>
<messages>
  <message id="welcome">
    <locale language="en"> 
	  <entry key="text">Welcome</entry>
    </locale> 
    <locale language="de"> 
      <entry key="text">Willkommen</entry>
    </locale> 
  </message>
  <message id="usage">
    <locale language="en"> 
      <entry key="title">Usage</entry>
      <entry key="text">The application requires the following parameters:</entry>
      </locale> 
    <locale language="de"> 
      <entry key="title">Benutzung</entry>
      <entry key="text">Die folgenden Parameter werden erwartet:</entry>
    </locale> 
  </message>
  <message id="validationFailed">
    <locale language="en">
      <entry key="title">Parameter {0} invalid</entry>
      <entry key="text">The given value of the parameter {0} is invalid</entry>
      <entry key="summary">Value of parameter {0} invalid</entry>
      <entry key="details">The given value {1} of parameter {0} is invalid.</entry>
    </locale>
    <locale language="de">
      <entry key="title">Parametervalidierung fehlgeschlagen.</entry>
      <entry key="text">Die Validierung des Parameters {0} ist fehlgeschlagen.</entry>
      <entry key="summary">Validierung des Parameters {0} fehlgeschlagen.</entry>
      <entry key="details">Der Wert {1} des Parameters {0} ist ungültig.</entry>
    </locale>
  </message>	
</messages>
	

This is an example that shows how to create localized bundles. As you can see each message is identified by a message id and contains the bundled messages for the defined locales. The language identifiers are well known from the Locale class and support language variants and the appropriate fallback mechanism.

Each bundle can consist of a number of message entries that belong to this bundle. You are free to add as many entries to each bundle as you like. The I18n component contains a number of classes that simplify the access to entries of frequently used bundles.

After defining the messages we have to add them to the message provider dealing with xml-based messages by providing an input stream giving access to the xml document containing the localized messages.

ResourceBundle based message provider

The ResourceBundleMessageProvider enables you to keep your property files that may contain localized messages.

You can group message entries by adding the key at the end of the existing message key. The following example shows how a property file should look like to provide the same messages as the previous XML based example.

As you know you'll need two files, each containing the messages for a specific locale. This one might be the default one calld myMessages.properties:

welcome.text=Welcome
usage.title=Usage
usage.text=The application requires the following parameters:
validationFailed.title=Parameter {0} invalid
validationFailed.text=The given value of the parameter {0} is invalid
validationFailed.summary=Value of parameter {0} invalid
validationFailed.details=The given value {1} of parameter {0} is invalid.

The following one would contain the corresponding german translations in a file called myMessages_de.properties:

welcome.text=Willkommen
usage.title=Benutzung
usage.text=Die folgenden Parameter werden erwartet:
validationFailed.title=Parametervalidierung fehlgeschlagen.
validationFailed.text=Die Validierung des Parameters {0} ist fehlgeschlagen.
validationFailed.summary=Validierung des Parameters {0} fehlgeschlagen.
validationFailed.details=Der Wert {1} des Parameters {0} ist ungültig.

Initializing the messages

Now that we created the desired messages, we want to make use of them. To do so we have to initialize the appropriate MessageProvider with these messages.

Initializing messages depends on the MessageProvider that you are using. In case of an XMLMessageProvider initialization looks like this:

...
try {
	FileInputStream inputStream = new FileInputStream("myMessages.xml");
	XMLMessageProvider.install("myMessages", inputStream);
} catch ( FileNotFoundException e ) {
	// handle exception
}
...

As you can see it is very easy to install new messages. All you need is to provide a unique identifier and an input stream to access the xml messages.

Why is the manager initialized with an input stream and not using a file name? You might want to use the i18n component within web applications where you want probably load messages from you .war archive. So an input stream is much more flexible, even if it is a little bit more unconvenient than using a file name in our use case.

When installing messages we can specify an identifier that enables us to uninstall or update these messages later on.

In case of the brand new ResourceBundleMessageProvider initialization looks even simpler:

...
ResourceBundleMessageProvider.install("myMessages");
...

It's this simple, because the ResourceBundleMessageProvider uses the build-in features of Java to locate and load the appropriate property files or resource bundle classes.

Using message bundles

Now we are ready to go! First of all we want to print out a simple localized welcome message to the user. There are different way to do so: We can call the MessageManager directly by asking for a specific entry of a message:

...
System.out.println(MessageManager.getText("welcome", "text", new Object[0], Locale.getDefault()));
...

If you are familiar with text formatting in Java you will have guessed correctly that you have the ability to pass arguments to the localized text. In our case we don't pass any arguments but just an empty object array.

The previous example might be useful if you want to print out some localized message quick and dirty, but the recommended way is the following:

...
TextBundle welcome = new TextBundle("welcome");
// Using the default locale
System.out.println(welcome.getText(Locale.getDefault()));
// Using some other specific locale
System.out.println(welcome.getText(Locale.GERMAN));
...

In this example we make use of the predefined message bundle called TextBundle that just consists of a single text element. The advantage of this approach is, that you avoid misspelling as you have getter-methods for each entry. You also have the ability to pass some default text that will be used if the message was not found:

...
TextBundle welcome = new TextBundle("welcome");
System.out.println(welcome.getText(Locale.GERMAN, "No welcome message found!"));
...

As the MessageManager can not find the message with id welcome, a MessageNotFoundeException is thrown. This one is a RuntimeException as this avoids bloating up the code with exception handling.

The TextBundle handles this exception and returns the given default text instead.

Using localized exceptions

The concept of message bundles is very useful when it comes to exception handling. You can simply create a LocalizedException that will be constructed with an ErrorBundle object containing title, text, summary and details of the exception. In addition you can specify the causing exception:

...
try {
	doSomething();
} catch ( SomeException exception ) {
	throw new LocalizedException(
		new ErrorBundle("somethingFailed", new Object[] { agrument1 }),
		exception);
}
...

The big advantage of this approach is that you can create localized exceptions with all arguments that are describing the error in detail and print out localized details including this arguments later on. Have a look at the examples to see how this can simplify your life ;-)

Pluggable message providers

You can add your own custom message providers.

This is a big plus if you already have your localized messages in a database for example. You do not have to convert them into the supported XML or property-based format, but you can write a simple MessageProvider by implementing a the MessageProvider interface and plug it in.