You are on page 1of 9

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
String
String
String

none = resourceBundle.getString("none");
one = resourceBundle.getString("one");
many = resourceBundle.getString("many");
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:
i:
i:
i:
i:

0
1
2
3
4

/
/
/
/
/

I
I
I
I
I

have no cars in
just bought one
won the lottery
won the lottery
won the lottery

the garage.
car.
and bought 2 cars.
and bought 3 cars.
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<I won the lottery and bought {0} cars.
This translates to: for the number 0, use "I have no cars in the
garage." For the number 1, use "I just bought one car." For
everything greater than 1, use "I won the lottery and bought {0}
cars."
Let's look at one more example. This example doesn't use a
MessageFormat. Instead, it simply uses ChoiceFormat to map an
array of limits to a specific string.
The limits are from 0-5, 5-10, and beyond 10:
double limits[] = {0, 5, 10};
The strings are Low, Medium, and High:
String formats[] = {"Low", "Medium", "High"};
Taking these two arrays, you again create the ChoiceFormat:
ChoiceFormat cf = new ChoiceFormat(limits, formats);
Here's the complete example. It takes a number from the command
line, and use the ChoiceFormat to display the appropriate message.
import java.text.*;
public class Choice2 {
public static void main(String args[]) {
if (args.length == 0) {
System.err.println(
"Please provide a number when starting");
System.exit(-1);
}

double limits[] = {0, 5, 10};


String formats[] = {"Low", "Medium", "High"};
ChoiceFormat cf =
new ChoiceFormat(limits, formats);
System.out.println(cf.format(
Integer.parseInt(args[0])));
}
}
Run the example program with an argument of 3 to produce an
output of Low, with 5 to display Medium, and an argument above 10
to display High.
java Choice2 3
Low
java Choice2 5
Medium
java Choice2 13
High
ResourceBundle, MessageFormat, and ChoiceFormat give you the
capabilities you need to properly localize both variable and
quantified text.
For more information on internationalizing your applications, see
the Internationalization trail in the Java Tutorial
(http://java.sun.com/tutorial/i18n)/.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - COMPONENTORIENTATION IN SWING USER INTERFACES
One of the often-overlooked aspects of internationalization is
component orientation. While English and the Western European
languages have letters and words that go left-to-right and
top-to-bottom, not all do. For instance, many Middle Eastern
languages such as Hebrew and Arabic are right-to-left,
top-to-bottom. Others such as Mongolian are top-to-bottom first,
then right-to-left. And Far Eastern languages such as Japanese,
Chinese, and Korean can be either like the Western European ones
or more appropriately top-to-bottom, right-to-left.
Here's an aid to visualize the differences. For a 3x3 grid of
characters:
Left-to-right, top-to-bottom
A B C
D E F
G H I
Right-to-left, top-to-bottom
C B A
F E D
I H G
Top-to-bottom, left-to-right

A D G
B E H
C F I
Top-to-bottom, right-to-left
G D A
H E B
I F C
What does this have to do with Swing and graphical interfaces?
When targeting an international market for your programs, you
should design considering their ComponentOrientation property.
Found in the java.awt package, the ComponentOrientation class
(http://java.sun.com/j2se/1.4.2/docs/api/java/awt/ComponentOrientation.html)
allows you to discover the current orientation for a Locale, and
design screens accordingly. You ask for the ComponentOrientation
of a Locale with the getOrientation(Locale) method. Then, you can
ask if that orientation is horizontal with the isHorizontal method
(horizontal corresponds to both the Left-to-right, top-to-bottom,
and Right-to-left, top-to-bottom grids shown above). You can also
ask if the orientation is left-to-right with the isLeftToRight
method (left-to-right corresponds to both the Left-to-right and
Top-to-bottom, left-to-right grids above).
Why do you need to know the direction? One common reason is for
building screens. The BorderLayout class is commonly used to
create screens. Using the NORTH, SOUTH, EAST, and WEST constants,
you can place components on the top, bottom, left, or right sides.
And, you can place a component in the center with CENTER. But,
many people don't know, or at least don't bother using, another
set of constants: PAGE_START, PAGE_END, LINE_START, and LINE_END.
(Note that these names were simplified in J2SE 1.4 from the
earlier names that were added in the J2SE 1.2: BEFORE_FIRST_LINE
(PAGE_START), AFTER_LAST_LINE (PAGE_END), BEFORE_LINE_BEGINS
(LINE_START), and AFTER_LINE_ENDS (LINE_END).) Although EAST and
WEST might make perfect sense for a Roman language, they don't
necessarily make sense for other languages. For example, if you
need to place a component at the beginning of the line, that
component would need to be on the WEST side for a right-to-left
language, but the EAST side for a left-to-right language. Instead
of adding that component with an orientation of EAST or WEST, you
should add it with a constraint of LINE_START. This will ensure
the correct internationalized behavior.
To illustrate the point, run the following program,
BorderOrientation. It will display two screens, one for a
US-English locale and another for an Israel-Hebrew locale.
import java.awt.*;
import javax.swing.*;
import java.util.*;
public class BorderOrientation {
public static void main(String args[]) {
JFrame frame1 = new JFrame("US");
frame1.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
ComponentOrientation usOrientation =

ComponentOrientation.getOrientation(
Locale.US);
Container contentPane1 =
frame1.getContentPane();
contentPane1.setComponentOrientation(
usOrientation);
contentPane1.add(new JButton(
"LineEnd"), BorderLayout.LINE_END);
contentPane1.add(new JButton(
"LineStart"), BorderLayout.LINE_START);
contentPane1.add(new JButton(
"PageStart"), BorderLayout.PAGE_START);
contentPane1.add(new JButton(
"PageEnd"), BorderLayout.PAGE_END);
frame1.pack();
frame1.setLocation(100, 100);
frame1.show();
JFrame frame2 = new JFrame("Israel");
frame2.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
Locale israel = new Locale("he", "IL");
ComponentOrientation hebrewOrientation =
ComponentOrientation.getOrientation(israel);
Container contentPane2 =
frame2.getContentPane();
contentPane2.setComponentOrientation(
hebrewOrientation);
contentPane2.add(new JButton(
"LineEnd"), BorderLayout.LINE_END);
contentPane2.add(new JButton(
"LineStart"), BorderLayout.LINE_START);
contentPane2.add(new JButton(
"PageStart"), BorderLayout.PAGE_START);
contentPane2.add(new JButton(
"PageEnd"), BorderLayout.PAGE_END);
frame2.pack();
frame2.setLocation(200, 200);
frame2.show();
}
}
For simplicity, the previous example used hard-coded strings for
button labels. Remember that in "real" internationalized programs,
button labels should come from resource bundles.
You don't always have to build screens differently based on the
locale and its associated component orientation. Containers using
FlowLayout, for instance, will draw their components in the
reverse order when the orientation is right-to-left. Some
components, such as the text components, will lay out their
contents accordingly, based on their orientation setting. Other
components, such as JTree, reorient the tree to the other side of
the display when the orientation is right-to-left. For example,
run the following program, TreeSample. Notice that for the
right-to-left component orientation, the JTree displays a
"flipped" tree.
import java.awt.*;
import javax.swing.*;

public class TreeSample {


public static void main (String args[]) {
JFrame f = new JFrame("Flipped");
f.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
Container content = f.getContentPane();
JTree tree = new JTree();
tree.setComponentOrientation(
ComponentOrientation.RIGHT_TO_LEFT);
JScrollPane scrollPane = new JScrollPane(tree);
content.add(scrollPane, BorderLayout.CENTER);
f.setSize (300, 200);
f.show();
}
}
Other AWT and Swing components also reorient their content based
on the current component orientation setting. For a JTable,
column order is reversed when the orientation is right-to-left.
With a JMenuBar, the menus are reversed. For instance, the
"File" menu, typically found on the far left, is on the other
side when reversed. As a developer, you don't have to do anything
differently to manipulate these components, you just need to be
aware that not everything will appear the way you expect it to.
JOptionPane is probably one of the exceptions -- its definition
states the location of its various elements.
For more information on ComponentOrientation in Swing see the
article "Component Orientation in Swing: How JFC Components
Support 'BIDI' Text"
(http://java.sun.com/products/jfc/tsc/articles/bidi/).
. . . . . . . . . . . . . . . . . . . . . . .
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing
policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
* FEEDBACK
Comments? Please enter your feedback on the Tech Tips at:
http://developers.sun.com/contact/feedback.jsp?category=sdn
* SUBSCRIBE/UNSUBSCRIBE
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using
enterprise Java technologies and APIs, such as those in the
Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless
Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page,

(http://developer.java.sun.com/subscription/),
choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions page,
(http://developer.java.sun.com/subscription/),
uncheck the appropriate checkbox, and click "Update".
- To use our one-click unsubscribe facility, see the link at
the end of this email:
- ARCHIVES
You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, California 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
Core Java Technologies Tech Tips
September 26, 2003
Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks
or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries.

You might also like