Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips, September, 26, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE). This issue covers: * Using ChoiceFormat for Handling Plural Messages * Component Orientation in Swing User Interfaces These tips were developed using Java 2 SDK, Standard Edition, v 1.4. This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2003/tt0926.html. See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING CHOICEFORMAT FOR HANDLING PLURAL MESSAGES The August 19, 2003 Tech Tip titled "Formatting Messages With Variable Content" (http://java.sun.com/jdc/JDCTechTips/2003/tt0819.html#1) described how to use the MessageFormat class of the java.text package to create internationalized error messages that contain variable content. Using the MessageFormat class is a good approach when most of the message stays the same, and you can simply "fill in in the blanks" to complete the variable part of the message. But the approach doesn't work particularly well in situations where part of the error message is a quantity, and you need to follow it with a noun that matches the quantity. Matching nouns to quantities is tricky in English. Case in point: when the count of something is zero, the word it modifies is plural, as in "I have no cars in the garage." (Cars is plural here, even though you have zero of them.) When the count is one, the word it modifies is singular, as in "I just bought one car." And with more than one, the modified word is plural again: "I won the lottery and bought twelve cars." This suggests that matching nouns to quantities is also tricky when you display localized error messages, especially when you want to display slightly different messages based on a quantity. However the java.text package includes a class, ChoiceFormat (http://java.sun.com/j2se/1.4.2/docs/api/java/text/ChoiceFormat.html), that handles the mapping of quantities to messages. You use the results produced by ChoiceFormat in conjunction with a MessageFormat object to generate the appropriate quantified message text. Let's look at an example. Here, you'll use a ChoiceFormat (together with MessageFormat) to display the correct message for a given quantity. Specifically, you'll display an English message that contains the correct noun plural for the quantities zero, one, and more than one. Start by creating a resource bundle. To do this, create a file named SampleResources.properties, and place the necessary resources in it: none=I have no cars in the garage. one=I just bought one car. many=I won the lottery and bought {0} cars. Remember that the {0} in the last line is a placeholder. It means replace this with a number used as an argument. This example is for English only. You need to provide a similar file for every language you want to support. See the May 21, 1998 Tech Tip titled "Resource Bundles" (http://java.sun.com/jdc/TechTips/1998/tt0521.html#tip2) for additional information on using a ResourceBundle. Next, create a Choice Format. A ChoiceFormat allows you to attach a format to a range of numbers. To specify the range, you create an array of ranges for the formats. In this example, you want one format for 0, another format for 1, and yet another format for 2 and beyond. You can specify these "ranges" in the following array: double limits[] = {0, 1, 2}; Although the array doesn't look as though it specifies ranges, it actually does. Technically, the array specification means: for ranges from 0 to just below 1, use the first format; for 1 to just below 2 use the next; and for 2 and above use the last. However, because the example uses only integer values for the value to print, you don't have to consider this technicality yet. Next, specify the formats for each MessageFormat string to use: String none = resourceBundle.getString("none"); String one = resourceBundle.getString("one"); String many = resourceBundle.getString("many"); String formats[] = {none, one, many}; Notice that the formats are the strings in the resource bundle. Now you can create the ChoiceFormat: ChoiceFormat cf = new ChoiceFormat(limits, formats); The ChoiceFormat class has a format method that you can use to go directly from something like an integer to a formatted string. However, because the "many" formatting string has a placeholder (the {0}), you need to go from ChoiceFormat to MessageFormat. You do this by creating a MessageFormat and telling the object to use the ChoiceFormat as its formats: MessageFormat mf = new MessageFormat("{0}"); mf.setFormats(new Format[]{cf}); The first line says create a MessageFormat object, where the entire content is provided by the formats. The second argument is the array of formatting objects, that is, the single ChoiceFormat object. The {0} in the MessageFormat maps into the ChoiceFormat. The {0} in the specific ChoiceFormat selected is the argument provided to the MessageFormat's format method. Next, add a for loop, and loop from 0 to 4: for (int i=0; i<5; i++) { Object messageArgs[] = {new Integer(i)}; System.out.println("i: " + i + " / " + mf.format(messageArgs)); } For each pass through the loop, create an Integer for the loop index, and tell the MessageFormat to format the output string. The MessageFormat then determines which ChoiceFormat object to use, and passes its argument to the ChoiceFormat for formatting of the message. Here's what the complete example looks like: import java.text.*; import java.util.*; public class ChoiceFormatExample { public static void main (String args[]) { ResourceBundle resourceBundle = ResourceBundle.getBundle( "SampleResources", Locale.US); double limits[] = {0, 1, 2}; String none = resourceBundle.getString("none"); String one = resourceBundle.getString("one"); String many = resourceBundle.getString("many"); String formats[] = {none, one, many}; ChoiceFormat cf = new ChoiceFormat(limits, formats); MessageFormat mf = new MessageFormat("{0}"); mf.setFormats(new Format[]{cf}); for (int i=0; i<5; i++) { Object messageArgs[] = {new Integer(i)}; System.out.println("i: " + i + " / " + mf.format(messageArgs)); } } } When you run the example program, you should see the following displayed: i: 0 / I have no cars in the garage. i: 1 / I just bought one car. i: 2 / I won the lottery and bought 2 cars. i: 3 / I won the lottery and bought 3 cars. i: 4 / I won the lottery and bought 4 cars. There's more to ChoiceFormat than what's presented through this simple example. First, recall the technicality that was previously mentioned about the ranges in the limits array. Instead of specifically saying 2 for the third argument in the array, you could specify the next double after the value 1. You might think this is something like 1.00000000001, however there is a simpler way to specify the next double value. The ChoiceFormat class offers a nextDouble method for this exact purpose. Using nextDouble, you can specify the next double value as follows: {0, 1, ChoiceFormat.nextDouble(1)} There is even a previousDouble method for specifying the previous double value. In addition, the ChoiceFormat constructor allows you to specify all the formats in one string -- the string is parsed into its component pieces. Here, each limit is separated by the pipe (|) character, and each limit itself is specified by either a #, <, or > character. In other words, you could specify the previous set of resource strings in the following single line (for readability, the formats are shown below on two lines): 0#I have no cars in the garage.|1#I just bought one car.|1