Previous | Next | Trail Map | Writing Global Programs | Locale-Sensitive Data

How to Format Messages

A message is any textual information presented to the user. A message could be an error message, or the instructions in an alert box, or the label on a button or other GUI item. Typically, messages are short but they don't have to be.

A lot of messages are straightforward text which can simply be stored in a resource bundle and used as-is by the program. Examples of these types of messages are the Locale and Today's Date labels in the AroundTheWorld applet. These messages are simple text messages used directly from a resource bundle. Other messages are constructed at runtime and cannot simply be used verbatim from a resource bundle.

For example, consider this message from the AroundTheWorld program: Current Time (in San Francisco). This message has two parts: the base message and the city. Each part of the message is dependent on the user's locale but in different ways. The base message depends on the locale's language--that is, the base message changes when the language changes but not when the country changes. So, locales with the same language such as en_GB and the en_US can share the base message. However, the city depends on the locale's country--that is, the city changes when the country changes so en_GB and en_US cannot share the city portion of the message. Thus the message must be constructed at runtime and cannot be used directly from a resource bundle.

Other messages must also be constructed at runtime but for reasons other than differences in locale. For example, the italic parts of the following message are dependent on the user's runtime:

You are running JDK 1.1 on Solaris.
You might be tempted to construct messages like the two described above using String concatenation and partial text items from resource bundles:
String version = getVersion();
String system = getSystem();
String msg = myBundle.getString("Running") + version + 
		myBundle.getString("On") + system ;
However, this approach introduces language dependencies into your program because the word order in different languages is different. So while this may work in English, it probably won't work in French, and German, and certainly won't work in Japanese, Russian and other languages that use a completely different writing system. Instead you should format messages using the JDK 1.1's MessageFormat(in the API reference documentation) class.

Use MessageFormat to construct sequences of strings, numbers, dates, and other formats to create messages. This class facilitates localization because it prevents both hard-coding of message strings, and hard-coding of the concatenation sequence for portions of message strings. This means localizers can change the content, format, and order of any text as appropriate for any language.

Here's how the AroundTheWorld applet uses MessageFormat to format the Current Time label:

Object[] args = { labels.getString("RepCity") };
String result = MessageFormat.format(labels.getString("TimeLabel"), args);
The first line creates an array of objects and puts one item in the array: the string containing the city portion of the message. It gets the city name from the labels resource bundle. This array is used as an argument list to MessageFormat's format method.

The second line of code formats the message with MessageFormat.format. The format method requires two arguments: a pattern, and an array of arguments. AroundTheWorld stores the pattern for this message in a resource bundle and retrieves it with labels.getString("TimeLabel"). If you look at the pattern in each of the resource bundles, you will notice the construct {0} where the city name ought to be. This is a placeholder for the argument that will be filled in with a value from the args argument array. The number 0 is the argument number and is the index in the array where the value for this argument is stored.

MessageFormat.format( pattern , args) formats the message and fills in the missing parts using the objects in the args array matching up the argument numbers and the array indices.

And finally, the third line of code, timeLabel.setText(result), sets the label in the LinguaPanel with the formatted message.

You will notice that AroundTheWorld did not use a factory method to create a MessageFormat object like it did to create the number, and date and time formatters. Instead the program used MessageFormat's class method format. This is because messages are all different and there is no notion of "the message that most programmers want". So MessageFormat cannot come up with a reasonble default. Thus to format messages, you either instantiate a MessageFormat using its constructors and set it up, or you use the class method MessageFormat.format.

The above example is fairly straightforward: The message contains one argument, {0}, which is filled in by the one object contained in a single-element array. MessageFormat can handle complex messages that have multiple arguments and even recursive arguments. Also, the argument in the above example is a String that requires no special formatting itself. MessageFormat can handle arguments of any type (numbers, dates) and use other formatters (NumberFormat, DateFormat) to format them.

To get a better understanding of what MessageFormat can do, and how it does it, bring up the following demo applet provided by Taligent and try some of the suggestions in their guide.

Since you can't run the applet, here's a picture of it:

The Result text box at the top of the demo program's window shows the result after MessageFormat formats the Pattern using the values in the Arguments list in place of the argument placeholders in the pattern.

The following code segment is the Java code that produces the same results as the demo program when it is first invoked:

Calendar calendar = Calendar.getInstance();
Date datearg = calendar.getTime();
Object[] formatargs = { new Integer(3), "MyDisk", datearg };

String pattern = "The disk {1} contained {0,choice, 0#no files|1#one file|1< {0,number,integer} files} on {2,date}.";
String result = MessageFormat.format(pattern, formatargs);
The first four lines create a Date object with March 3, 1996 as the date, and create the arguments array with the three argument values. Next, the code snippet sets up the pattern, formats the message and displays the result.

The most interesting part of this example is the message pattern. This pattern uses three different types of arguments: {0} is a choice, {1} is a string, and {2} is a date. Additionally, the choice argument is recursive, that is, it contains another argument that is a number.

Let's look at the pattern more closely and locate all of the argument placeholders. The first thing that you will notice is that the arguments do not appear in the pattern in the same order that they are listed in the argument list. This is why they are numbered based on their position in the argument array.

The following table shows the relationship between the argument value, the argument placeholder and the result. The table lists the arguments from simplest to most complex.

Argument Placeholder Result
{1} MyDisk
{2,date} 03-Mar-96
{0,choice, 0#no files|1#one file|1< {0,number,integer} files} 3 files

Argument {1} is simply a string. MessageFormat formats this item itself and just replaces the argument placeholder with the argument value.

Argument {2} is a date and MessageFormat uses a DateFormat object to format this argument. MessageFormat replaces the argument placeholder with the String that results from calling DateFormat's format method. As specified in the demo program, MessageFormat uses the default DateFormat object that you get when you call DateFormat.getInstance. However, you can change this by specifying a date pattern in the argument placeholder.

Try this: Change {2,date} to {2,date,yy.MMM.dd}. Notice that the format of the date changes. The syntax for the pattern in the date placeholder is specified by the SimpleDateFormat class.

Argument {0} is a choice and has the most complicated looking pattern. Argument {0} is also recursive, it contains another argument placeholder: {0,number,integer}. MessageFormat uses a ChoiceFormat(in the API reference documentation) object to format this argument. A ChoiceFormat allows you to attach a format to a range of numbers. When the argument value is in a certain range, ChoiceFormat chooses the corresponding format as its result.

The syntax for specifying the choices is defined by the ChoiceFormat class and requires that you specify a list of limits and a list of formats. The limits and formats in the pattern in the demo program are:

Pattern Meaning
0#no files Result is no files if value is equal to 0
1#one file Result is one file if value is equal to 1
1< {0,number,integer} files Result is {0,number,integer} files if value is greater than 1. Note that {0,number,integer} is an argument that gets formatted by NumberFormat

You can change the results by modifying the limit or the format specifier.

Try this:

Generally speaking, argument placeholders look like this:

{ number, type, format }
Curly brackets delimit the placeholder from the rest of the pattern. number is the argument number. The curly brackets and the argument number are required. type is optional and indicates the data type of the argument. The data type can be one of: time, date, number, or choice. format is also optional and is a string describing format of the argument. You can only specify a format if you specify a type because the format depends on the type.

MessageFormat often uses other format classes to format these items: it uses a ChoiceFormat(in the API reference documentation) to format choices, a DateFormat(in the API reference documentation)to format dates, and a NumberFormat(in the API reference documentation) to format numbers. Check these classes on how to specify the format pattern in the argument placeholder.

This page incorporates material or code copyrighted by Taligent, Inc. For more information on international resources, see their International Fact Sheet.

Previous | Next | Trail Map | Writing Global Programs | Locale-Sensitive Data