Previous | Next | Trail Map | Internationalization | Message Formatting

Handling Plurals

In English, the plural and singular forms of a word are usually different. This can present a problem when you are constructing messages that refer to quantities. For example, if your message reports the number of files on a disk, the following variations are possible:
There are no files on XDisk.
There is one file on XDisk.
There are 2 files on XDisk.
The fastest way to solve this problem is to create a MessageFormat pattern like this:
There are {0,number} file(s) on {1}.
Unfortunately, the preceeding pattern results in incorrect grammar:
There are 1 file(s) on XDisk.
We can do better than that, provided that we use the ChoiceFormat(in the API reference documentation)class. In this section, we'll show you how to deal with plurals in a message by stepping through a sample program called ChoiceFormatDemo.java. This program also makes use of the MessageFormat class, which we discussed in the previous section, Dealing with Concatenated Messages.

1. Define the Message Pattern

First, let's identify the variables in our message:
There | are no files | on | XDisk | .
There | is one file  | on | XDisk | .
There | are 2 files  | on | XDisk | .
      |______________|    |_______|
            ^                 ^
            |                 |
         variable          variable
Next, we replace the variables in the message with arguments, creating a pattern that can be applied to a MessageFormat object:
There {0} on {1}.

The argument for the disk name, which is represented by {1}, is easy enough to deal with. We just treat it like any other String variable in a MessageFormat pattern. This argument matches the element at index 1 in the array of argument values. (See step 7.)

Dealing with argument {0} is more complex, for a couple of reasons:

We'll show you how all of this is done in the steps that follow.

2. Create a ResourceBundle

We'll isolate the message text in a ResourceBundle because it must be translated:
ResourceBundle bundle =
   ResourceBundle.getBundle("ChoiceBundle",currentLocale);
We've decided to back our ResourceBundle with properties files. The ChoiceBundle_en_US.properties file contains the following lines:
pattern = There {0} on {1}.
noFiles = are no files
oneFile = is one file
multipleFiles = are {2} files
The contents of this properties file shows how the message will be constructed and formatted. The first line contains the pattern for MessageFormat, which we discussed in the previous step. The other lines contain phrases that will replace argument {0} in the pattern. The phrase for the "multipleFiles" key contains the argument {2}, which will be replaced by number.

The French version of the properties file, ChoiceBundle_fr_FR.properties, is as follows:

pattern = Il {0} sur {1}.
noFiles = n' y a pas des fichiers
oneFile = y a un fichier
multipleFiles = y a {2} fichiers

3. Create a Message Formatter

In this step we instantiate MessageFormat and set its Locale:
MessageFormat messageForm = new MessageFormat("");
messageForm.setLocale(currentLocale);

4. Create a Choice Formatter

The ChoiceFormat object allows us to choose, based on a double number, a particular String. The range of double numbers, and the String objects to which they map, are specified in arrays:
double[] fileLimits = {0,1,2};

String [] fileStrings = {
   bundle.getString("noFiles"),
   bundle.getString("oneFile"),
   bundle.getString("multipleFiles")
};
ChoiceFormat maps each element in the double array to the element in the String array that has the same index. In our sample code, the 0 maps to the String returned by calling bundle.getString("noFiles"). By coincidence, in our example the index is the same as the value in the fileLimits array. If we had set fileLimits[0] to 7, then ChoiceFormat would map the number 7 to fileStrings[0].

We specify the double and String arrays when instantiating ChoiceFormat:

ChoiceFormat choiceForm = new ChoiceFormat(fileLimits, fileStrings);

5. Apply the Pattern

Remember the pattern we constructed in step 1? It's time for us to retrieve the pattern from the ResourceBundle and apply it to the MessageFormat object:
String pattern = bundle.getString("pattern");
messageForm.applyPattern(pattern);

6. Assign the Formats

In this step, we assign the ChoiceFormat object created in step 4 to the MessageFormat object:
Format[] formats = {choiceForm, null, NumberFormat.getInstance()};
messageForm.setFormats(formats);
The setFormats method assigns Format objects to the arguments in the message pattern. You must invoke the applyPattern method before you call the setFormats method. The following table shows how the Format array matches the arguments in the message pattern:

Array Element Pattern Argument
choiceForm {0}
null {1}
NumberFormat.getInstance() {2}

7. Set the Arguments and Format the Message

At runtime, we assign the variables to the array of arguments that we pass to the MessageFormat object. The elements in the array correspond to the arguments in the pattern. For example, messageArgument[1] maps to pattern argument {1}, which is a String containing the name of the disk. In the previous step, we assigned a ChoiceFormat object to argument {0} of the pattern. Therefore, the number assigned to messageArgument[0] determines which String the ChoiceFormat object selects. If messageArgument[0] is greater than or equal to 2, then the String "are {2} files" replaces argument {0} in the pattern. The number assigned to messageArgument[2] will be substitued in place of pattern argument {2}. We'll try this out with the following lines of code:
Object[] messageArguments = {null, "XDisk", null};

for (int numFiles = 0; numFiles < 4; numFiles++) {
   messageArguments[0] = new Integer(numFiles);
   messageArguments[2] = new Integer(numFiles);
   String result = messageForm.format(messageArguments);
   System.out.println(result);
}

8. Run the Demo Program

Let's run the program for the U.S. English Locale:
% java ChoiceFormatDemo  en US

currentLocale = en_US

There are no files on XDisk.
There is one file on XDisk.
There are 2 files on XDisk.
There are 3 files on XDisk.
Compare the messages displayed by the program with the phrases in the ResourceBundle of step 2. Notice that the ChoiceFormat object selects the correct phrase, which the MessageFormat object uses to construct the proper message.

The French version of the message looks okay, too:

% java ChoiceFormatDemo fr FR

currentLocale = fr_FR

Il n' y a pas des fichiers sur XDisk.
Il y a un fichier sur XDisk.
Il y a 2 fichiers sur XDisk.
Il y a 3 fichiers sur XDisk.


Previous | Next | Trail Map | Internationalization | Message Formatting