| ## Java 1.1 Unleashed # - 52 - Java Internationalization by Glenn Vanderburg IN THIS CHAPTER About Internationalization An Overview of Java's Internationalization Features Locales Resource Bundles Manipulating Data Input and Output Internationalizing Graphical Interfaces As the world grows smaller--because of the continued success of the Internet as well as other developments--it is becoming more important to write software for the world rather than for one just language or culture. Writing internationalized applications has been an extremely challenging task, partly because the problems were not well understood, and partly because good tools and facilities to help with the task have been hard to come by. Java is intended to be the programming language for the Internet; for it to succeed in that role, it has to provide some of those tools for programmers. It should be easy to write internationalized programs in Java. Java 1.1 is a big step ahead of other programming languages. Java now has unprecedented support for internationalization: a full array of APIs and facilities to assist programmers in developing for a global market. In this chapter, you learn about the problems of internationalization and how to use Java's internationalization features to solve those problems in your programs. ## About Internationalization Internationalization is a large topic--and a complex one. There are facets to internationalization that have never occurred to most programmers. In fact, the size of the topic is one of the reasons that good internationalization facilities are so rare: Very few programmers or groups of programmers have the understanding necessary to design and build tools and libraries to support the task. Internationalization is the process of writing a program (or modifying an existing program) so that it is ready to support other languages and cultures. Note that internationalization does not include actually translating messages and other tasks specific to supporting a particular language; that job is called localization. A more concise definition of internationalization is this: preparing a program to be localized. "But," you ask, "if that's all internationalization is, what's the big deal? It seems that localization is the hard part." Localization certainly is difficult, but internationalization has its own challenges. The typical program, before being internationalized, has messages, prompts, and textual labels scattered throughout the code. That's bad enough, but nonlocalized data isn't the worst problem. Most programs contain hidden assumptions about the meaning and proper format of various constructs, such as numbers, currency values, times, dates, sort order, and capitalization rules. Programs like that are hard to localize because finding all the locale-dependent data is difficult--and finding the hidden assumptions about language and culture is even more difficult. Furthermore, because all the locale dependencies are intertwined with the application code, localizing such programs frequently involves creating entirely new versions for each new locale. Those versions must be supported and maintained in parallel, which is--to say the least--a support nightmare. It is far better to internationalize programs first, before localizing them. Internationalized programs can usually be switched easily between any of the supported locales, whether at run time or when the program is shipped to customers. An internationalized program has locale- sensitive data grouped together in just a few parts of the program or in external data files. Additionally, code that depends on locale-specific assumptions is replaced with calls to a few special routines that encapsulate those assumptions. Thus, internationalization depends on two types of techniques: program structuring techniques and disciplined use of library facilities. In addition, an internationalized program uses three kinds of data: Internal data. Messages, labels, icons, and other "resources" that can be considered part of the program itself. System dat Information obtained from the runtime platform, such as date and time information or system properties. (System data is usually a tiny portion of the information manipulated by a program, but it is no less important for purposes of internationalization.) User dat Text, images, or other information supplied at run time at the direction of the user of the program (this kind of data is also called "documents"). Internationalization isn't an absolute quality. Some programs are more thoroughly internationalized than others. Many programs can support multiple European languages, but are prohibitively difficult to localize for a right-to-left or vertical writing system. Some internationalized programs can be switched between locales when they are run; others require recompilation. Finally, some programs require a particularly strong form of internationalization so that they can actually support multiple languages and locales simultaneously. As an example of such a program, consider a word processor to be used by a linguist. That program would have to allow the use of multiple languages and alphabets in a single document. Such programs are called multilingual (or, in some cases, multilocale) programs. Although this extreme variety of internationalization has its own name, it is still a form of internationalization. ## An Overview of Java's Internationalization Features Most of Java's internationalization facilities fall into the category of library routines that encapsulate rules and assumptions about dealing with different types of locale-sensitive data. There are classes for representing and comparing times, dates, numbers, and currency values. Other classes assist with sorting and classifying text and characters. Additionally, Java provides facilities for formatting and parsing those objects for display, either alone or embedded in other localized text (such as GUI labels or error messages). Java also provides some help with the program structure issues that are an important part of internationalization. The mechanism for managing internal data related to localization is the `ResourceBundle` class. This class is versatile enough to support many different ways of storing and representing the data, and the Java 1.1 library contains two simple but useful implementations that are very useful for internationalization. Underneath nearly every part of Java's internationalization support is the Unicode character set. With the release of Java 1.1, Unicode support in the Java language and its libraries is extremely thorough. Having Unicode as the native character set for strings and characters in the language simplifies the rest of the internationalization task immensely. All these Java facilities are designed to support dynamic localization, even to the point of supporting multilingual programs. There is no global state that affects the entire program, no "system locale" that must be changed. Instances of the `Locale` class represent particular locales. All the locale-sensitive methods in the Java libraries can take a `Locale` object as an argument so that locales can be easily changed from one operation to the next. There are "default locales" that make certain operations easier in programs that use only one locale at a time, but they are implemented so that they do not impact full multilingual support when it is needed. In describing the goals for the Java internationalization facilities, JavaSoft's design specification document contains this statement: Java programs should be internationalized by default. This implies that it should be easier than not to write internationalized Java code. That goal was perhaps a little unrealistic, and it certainly wasn't realized in every respect. It still takes discipline and a little extra work to make proper use of the library facilities and to structure things properly. But it's notable that little or none of the extra work is particularly cumbersome; in some respects, it actually is easier to do it the right way. Certainly, internationalization is easier in Java 1.1 than it has ever been before. ## Locales `Locale` objects are tokens that represent locales. In Java, locales are defined as geographic, political, or cultural regions. For practical purposes, a locale is usually the combination of a language and a set of cultural rules and assumptions (regardless of the geographical designation). In most respects, although they are represented as objects rather than strings, `Locale` objects function simply as names: they don't really have any behavior or semantics apart from identifying locales. In fact, there is a standard form for representing a locale name as a string. It is relatively easy to convert between the string representation and a `Locale` object and back again. To construct a `Locale`, you must supply a language name because language is the single-most important characteristic of a locale. You can also specify a country name, which helps to distinguish between different cultural conventions used by speakers of the same language. For example, English speakers in the United States do many things differently than those in the United Kingdom; Brazilians have some customs that would be unfamiliar to people in Portugal, even though Portuguese is the dominant language in both countries. Here are some examples of how to construct `Locale` objects for various situations:
    new Locale("en", "")        // Default English/American locale
    new Locale("en", "GB")      // English locale for Great Britain
    new Locale("fr", "CH")      // French locale for Switzerland
The names used for the languages and countries are the two-letter international standard language and country codes. The API documentation for the `Locale` class contains pointers to Web pages with complete lists. Confusingly, language codes are always two lowercase letters, but country codes are two uppercase letters. The `Locale` constructors aren't picky about case, but they do convert their parameters to the appropriate case when they are called. When you call the `toString()` method, the result is the two codes concatenated with an intervening underscore, or just the language code if no country is specified. There is also a constructor that takes three parameters--all strings--to provide a way to specify additional variant locales, including cultural or linguistic enclaves within a single country and language group. For example, among the locales supported by Java 1.1 are two locales for the country of Norway. Norwegian is the language for both locales, but there is a minor difference: they use different names for some of the days of the week. The default Norwegian locale, Bokmål, has a string representation `no_NO_B`, but because it is the default, it is the locale you get if you omit the variant token or specify only the language token. The other Norwegian locale, Nynorsk, must be specified explicitly; its string representation is `no_NO_NY`. Here are some examples:
    new Locale("no", "")            // Bokmål, no_NO_B
    new Locale("no", "NO")          // Bokmål, no_NO_B
    new Locale("no", "NO", "B")     // Bokmål, no_NO_B
    new Locale("no", "NO", "NY")    // Nynorsk, no_NO_NY
Although a `Locale` is just a form of name, if it is to actually be useful, it must refer to something that really exists. Many internationalization support classes use the locale name to find resources or rules of various kinds for a particular locale. Java 1.1 includes support for the locales listed in Table 52.1. #### Table 52.1. Locales supported in Java 1.1.
Minimal Name | Full Name | Language | Country
`ar` | `ar_EG` | Arabic | Egypt
`be` | `be_BY` | Belorussian | Belarus
`bg` | `bg_BG` | Bulgarian | Bulgaria
`ca` | `ca_ES` | Catalan | Spain
`cs` | `cs_CZ` | Czech | Czech Republic
`da` | `da_DK` | Danish | Denmark
`de` | `de_DE` | German | Germany
`de_AT` | `de_AT` | German | Austria
`de_CH` | `de_CH` | German | Switzerland
`el` | `el_GR` | Greek | Greece
`en_CA` | `en_CA` | English | Canada
`en_GB` | `en_GB` | English | United Kingdom
`en_IE` | `en_IE` | English | Ireland
(none) | `en_US` | English | United States
`es` | `es_ES` | Spanish | Spain
`et` | `et_EE` | Estonian | Estonia
`fi` | `fi_FI` | Finnish | Finland
`fr` | `fr_FR` | French | France
`fr_BE` | `fr_BE` | French | Belgium
`fr_CA` | `fr_CA` | French | Canada
`fr_CH` | `fr_CH` | French | Switzerland
`hr` | `hr_HR` | Croatian | Croatia
`hu` | `hu_HU` | Hungarian | Hungary
`is` | `is_IS` | Icelandic | Iceland
`it` | `it_IT` | Italian | Italy
`it_CH` | `it_CH` | Italian | Switzerland
`iw` | `iw_IL` | Hebrew | Israel
`ja` | `ja_JP` | Japanese | Japan
`ko` | `ko_KR` | Korean | Korea
`lt` | `lt_LT` | Lithuanian | Lithuania
`lv` | `lv_LV` | Latvian | Latvia
`mk` | `mk_MK` | Macedonian | Macedonia
`nl` | `nl_NL` | Dutch | Netherlands
`nl_BE` | `nl_BE` | Dutch | Belgium
`no` | `no_NO_B` | Norwegian (Bokmål) | Norway
`no_NO_NY` | `no_NO_NY` | Norwegian (Nynorsk) | Norway
`pl` | `pl_PL` | Polish | Poland
`pt` | `pt_PT` | Portuguese | Portugal
`ro` | `ro_RO` | Romanian | Romania
`ru` | `ru_RU` | Russian | Russia
`sh` | `sh_SP` | Serbian (Latin) | Serbia
`sk` | `sk_SK` | Slovak | Slovakia
`sl` | `sl_SI` | Slovene | Slovenia
`sq` | `sq_AL` | Albanian | Albania
`sr` | `sr_SP` | Serbian (Cyrillic) | Serbia
`sv` | `sv_SE` | Swedish | Sweden
`tr` | `tr_TR` | Turkish | Turkey
`uk` | `uk_UA` | Ukrainian | Ukraine
`zh` | `zh_CN` | Chinese | China
`zh_TW` | `zh_TW` | Chinese | Taiwan
The `Locale` class is found in the `java.util` package. ## Resource Bundles Resource bundles can be used to solve the structuring problem associated with internationalizing internal data. Using resource bundles, locale-sensitive data can be grouped together in one place--or just a few places--so that it can be localized easily. Resource bundles implement special support for locales. In particular, the `java.util.ResourceBundle` class provides methods that search for bundles associated with a particular locale. The search process tries several variations, starting with the specified locale, falling back to the default locale, and finally settling for a nonlocalized bundle. The search does not fail unless there simply isn't a resource bundle by the requested name at all. That's convenient, and it is usually what you want: If the program hasn't been localized for a particular locale, it is probably better to continue running without localization than simply to fail. As an example of how the search process works, assume that your program makes use of a resource bundle called `MessageBundle`. Also assume that the default locale is `fr_CA`, and the bundle is requested for the `no_NO_NY` (a situation that might occur if the user is a Norwegian living in Quebec). Now suppose that the application makes the following call:
    ResourceBundle.getBundle("MessageBundle", new Locale("no", "NO", "NY"))
In response, the search tests for the existence of these bundles, in the following order: 1. `MessageBundle_no_NO_NY` 2. `MessageBundle_no_NO` 3. `MessageBundle_no` 4. `MessageBundle_fr_CA` 5. `MessageBundle_fr` 6. `MessageBundle` In other words, the search starts with the specified locale and looks for progressively less- specific alternatives; if none are found, the search starts over again with the default locale; finally, if none of the bundles associated with the default locale is found, the default bundle is tried. If no bundle is found with any of those names, the `MissingResourceException` exception is thrown. Resource bundles can be used for any type of data, and the `ResourceBundle` class can be extended to support any kind of storage mechanism. That's a useful flexibility for internationalization because any kind of data used by a program may have to be localized, including messages, prompts, labels, icons, sound files, and images. ## Manipulating Data Programmers have developed many tricks for quickly dealing with various kinds of data. But it's surprising how often those tricks incorporate cultural or language assumptions. For example, how does one sort a list of words in a language that uses accents and diacritical marks? The problem usually comes as a surprise to English-speaking programmers who are unaccustomed to using accented letters. An even bigger surprise is the revelation that there is no one answer to the question! Different languages and cultures have different rules about how to alphabetize `a`, `á`, and `â`, for example. There are many such issues to be considered for text, dates and times, currency values, and other kinds of data. Fortunately, the Java libraries provide assistance for handling dates, times, and textual information in a locale-independent way. The next two sections explain the relevant facilities. ### Dates and Times The world doesn't have as many calendar systems as it does languages, but it has enough to be troublesome for the programmer writing international applications. In addition to the standard Gregorian calendar used by most of the world, Chinese, Hebrew, and Islamic calendars are in wide use. In Java 1.1, dates are represented by instances of the `java.util.Date` class. Times are represented that way, too; after all, a time is nothing more than a very precise date. To represent any date, some calendar system must be chosen. The `Date` class implements one particular calendar system, in which times are represented as the number of milliseconds since the first instant of January 1, 1970, GMT (Greenwich mean time). The number can be negative, to indicate times before that demarcation point. That may not seem like a particularly useful calendar to you; in fact, it is not meant for human consumption. The purpose of this calendar system is to be a simple, uniform calendar for representing times within the Java library. As such, `Date` objects are really useful only for comparing with other `Date` objects. The class provides comparison methods so that it is easy to tell how one `Date` relates to another. More complicated operations, such as learning about months, years, and days of the week, are the job of the `Calendar` classes. `Calendar` provides a generalized view of different calendar systems; particular calendar systems are supported by subclasses of `Calendar`. The primary purpose of `Calendar` objects is to convert between `Date` objects and integer values for year, month, day of month, day of week, hour, minute, second, and so on. (The documentation for the various calendar classes calls these values fields.) You can change the time a `Calendar` object represents by providing a new `Date` object, or by changing the integer values that represent the portions of the calendrical date specification. For example, given a `Date` object `d1`, you can calculate another date one week later by doing the following:
    Calendar cal = Calendar.getInstance();    // acquire a localized Calendar object
    cal.setTime(d1);                          // set the Calendar's time value

    cal.set(Calendar.WEEK_OF_MONTH,           // set the "week-of-month" field to
       cal.get(Calendar.WEEK_OF_MONTH) + 1);  //   the current value plus one

    Date d2 = cal.getTime();                  // now retrieve a new Date object
There are some interesting details in this code fragment, so I'll discuss the code step by step. First, note that I didn't just create a new instance of `Calendar` with a constructor. Instead, I used the "factory method" `getInstance()` to get a new `Calendar` object. The `getInstance()` method creates an object appropriate for the default locale. There is also a version of the method that takes a `Locale` object as a parameter and creates an object appropriate for the specified locale. This is a pattern common to most of the internationalization classes discussed in the rest of this chapter. After creating the `Calendar` object, I had to set the time it represented, using my `d1` object. That's a bit cumbersome; hopefully, some future version of `Calendar` will allow a `Date` object to be specified as a parameter to the `getInstance()` method for automatic initialization. Next, I advanced the week by 1. First I queried the current week of the month, added 1, and reset the same field. The `Calendar` class can represent dates in terms of the following different fields, denoted by named constants:
Field Name | Description
`AM_PM` | Before or after noon
`DATE` | The day of the month
`DAY_OF_MONTH` | The day of the month (synonym for `DATE`)
`DAY_OF_WEEK` | The day of the week
`DAY_OF_WEEK_IN_MONTH` | Occurrence of this day of the week in this month (for example, Tuesday the 10th is the second Tuesday of the month)
`DAY_OF_YEAR` | The day of the year
`DST_OFFSET` | Offset from UTC for daylight saving time in this time zone
`ERA` | The era in which this date occurs (for example, A.D. or B.C.)
`HOUR` | Hour in 12-hour clock
`HOUR_OF_DAY` | Hour in 24-hour clock
`MILLISECOND` | Milliseconds within the second
`MINUTE` | The minute of the hour
`MONTH` | The month of the year
`SECOND` | The second within the minute
`WEEK_OF_MONTH` | The week of the month
`WEEK_OF_YEAR` | The week of the year
`YEAR` | The year within the era
`ZONE_OFFSET` | Offset from UTC in this time zone
Any of these fields can be retrieved from a `Calendar` object with the `get()` method or be set with the `set()` method. All the fields are stored as integers; to translate to textual representations, use the `DateFormat` class described in "Formatted Output and Input," later in this chapter. After changing the week of the month, I was able to retrieve the new `Date` object, which represents the moment exactly one week after the original `Date`. Notice that there are a couple of funny things going on in this step. The first question you might ask is this: What if the `WEEK_OF_MONTH` field was already set to `5`? By incrementing it, we set the new date to be in the sixth week of the month. But that doesn't make any sense because months don't have six weeks. The answer is that `Calendar` objects, by default, are very permissive about the way dates are specified. If you specify a date as being in the seventh week of the twelfth month, the calendar assumes that you mean the second week of the first month of the following year. If you specify a time as `25:00`, the resulting time is `1:00 A.M.` of the following day. This behavior can be turned off by calling `setLenient(false)`. Another question may have occurred to you: How does the calendar make sense of the fields after the `WEEK_OF_MONTH` field is incremented? After all, the calendar also knows about fields representing the day of the year and the day of the month. It seems as though changing just one of the fields would cause a conflict with some of the other fields. The answer is that `Calendar` keeps track of which fields are explicitly set and gives them precedence over fields that have been inferred from the time value. Because we set only the `WEEK_OF_MONTH` field, that value has precedence over other fields that contain contradictory information. In fact, `Calendar` has rules for choosing between contradictory fields even if they have all been explicitly set, but in our example, those rules aren't required. Java 1.1 includes one specialized calendar implementation, `GregorianCalendar`, which implements the standard calendar system used by most western countries. #### Time Zones `Calendar` can localize its operations based on locales, and it can also understand time zones. Instances of `java.util.TimeZone` represent time zones and incorporate knowledge about time zones around the world, including offsets from GMT and rules about daylight saving time. Other classes in the Java library in addition to the `Calendar` class make use of `TimeZone`, including `DateFormat` (discussed in "Formatted Output and Input," later in this chapter). There are several ways to create `TimeZone` objects. To get the current time zone where the computer is running, use the `getDefault()` method. If you want an object for a particular zone, call `getTimeZone()` with a string containing that zone's ID (for example, United States Central Standard Time uses an ID of `CST`). You can also create a `TimeZone` using the constructor and set an explicit offset from GMT using the `setRawOffset()` method. Most of the time, you don't have to query or manipulate `TimeZone` objects directly; you can just create the appropriate instances and pass them, as needed, to other objects (such as `Calendar`) which use the time zone information appropriately. Therefore, you can think of `TimeZone` objects as similar to `Locale` objects: they primarily serve as names or tokens. ### Text Processing Textual data presents an entirely different class of problems. As I write this chapter, I am (not surprisingly) using a word processing program. It is instructive to consider the various ways in which such an application uses and manipulates text, and what kinds of problems might be involved where internationalization is concerned. For one thing, the interface for most word processors makes copious use of lists of names: styles, fonts, cross-reference tags, and so on. Such lists are ordered alphabetically. Sorting localized text is an internationalization problem, not merely because of different alphabets, but also because of different rules used across cultures. When I use the arrow keys to move around in my document, the cursor moves one letter or line at a time. But by holding down various combinations of modifier keys, I can move (or, for that matter, delete or select text) by other units: words, sentences, or paragraphs. Furthermore, my word processor adjusts line breaks as I type. But Unicode complicates the task of recognizing boundaries between textual units, including acceptable places for line breaking. In addition, Unicode incorporates the concept of combining characters: two 16-bit Unicode characters (usually a base character and an accent mark) that combine to produce the appearance of one character. So even moving letter by letter is related to internationalization! When I search for text in my document, I can choose whether the search should be sensitive to the difference between uppercase and lowercase letters. The program can also change the capitalization of text automatically, whether at my direction or in the process of formatting headings or entries in the table of contents. The difference between uppercase and lowercase is easy to deal with in ASCII, but the issue is much more complicated in an international character set like Unicode. Java 1.1 provides features for dealing with all these issues. They are divided into three categories: collation, text boundaries, and character classification. #### Collation The term collation refers to the act of comparison. It can also refer to sorting, but the Java library uses it in the first sense. Java 1.1 does not provide sorting routines, but it does provide the collation facilities--the comparison facilities--required to implement sort routines on text objects. The primary collation class is called `java.text.Collator`. As with many of the Java internationalization classes, `Collator` objects are not created directly using the constructor; instead, they are created using the static `Collator.getInstance()` and `Collator.getInstance(Locale)` methods, which create localized versions of `Collator`. The basic collation functionality of `Collator` lies in the `compare(String,` `String)` method. Just as similar facilities in other languages such as C, this method returns an integer value: zero if the strings are equal, less than or greater than zero if the first string is, respectively, less than or greater than the second. If you are interested only in whether the strings are equal, the convenience method `equals(String,` `String)` returns a boolean value indicating equality. For one-time comparisons, calls to `compare()` and `equals()` are sufficient. If certain strings are to be compared multiple times (as might happen when sorting a list of strings), it is better to use `CollationKey` objects. The `getCollationKey(String)` method returns an instance of `CollationKey` that represents the given string for purposes of comparison within the `Collator` object's locale. In general, `CollationKey` objects can be compared more efficiently than `String` objects can be. Given two `CollationKey` objects called `k1` and `k2`, you compare them in this way:
    k1.compareTo(k2)
You can also retrieve the original source string from a `CollationKey`, using the `getSourceString()` method, so that you don't have to keep track of which keys represent which strings. The way `Collator` objects decide on the relationship between two strings can be tuned to follow desired conventions. The strength of a comparison refers to how literal it is. For example, an extremely strong comparison considers `a` and `A` to be different characters; a weaker comparison might consider `a`, `A`, `ä`, and `Å` to be all the same. Four strength levels are provided. The precise meaning of the strength values depends on the locale, but here are some common definitions: `PRIMARY`: Two characters are considered to be the same if their base letters are the same, regardless of accents, other diacritical marks, or case distinctions. This is the weakest kind of collation. `SECONDARY`: Two characters are considered to be the same if the base letter and any diacritical marks are the same, regardless of case distinctions. `TERTIARY`: Two characters are considered to be the same only if they have the same base letter, the same diacritical marks, and the same case. `IDENTICAL`: Two characters are considered to be the same only if they have the same bit pattern. This is the strongest form of collation. It may seem that there is no difference between `TERTIARY` and `IDENTICAL`, but there is a difference. Unicode may provide more than one way to specify the same letter--for example, the character `ë` can be specified either as a single 16-bit character or as a combination of `e` and a combining `[dieresis]` mark. The two versions are considered equal under a `TERTIARY` comparison but are different under the rules for `IDENTICAL` comparisons. The strength a `Collator` object uses for comparisons can be set with the `setStrength()` method; the strength can be queried using `getStrength()`. #### Text Divisions and Boundaries The `BreakIterator` class provides a locale-independent way to find the boundaries between certain kinds of textual elements, such as words and sentences. `BreakIterator` is even helpful for finding boundaries between printable characters; again, this is because two 16-bit Unicode characters can combine to produce one printed glyph. (It wouldn't be a good idea to try to put the insertion cursor between a letter and its accent!) Once again, you don't create instances of `BreakIterator` with a constructor. There are actually several different static factory methods for creating `BreakIterator` objects, depending on what kind of textual element you want to learn about: `getCharacterInstance()` returns a `BreakIterator` that can find boundaries between characters `getLineInstance()` returns a `BreakIterator` that can find valid places for line breaks `getSentenceInstance()` returns a `BreakIterator` that can find sentence boundaries `getWordInstance()` returns a `BreakIterator` that can find word boundaries Each of these methods comes in two varieties: one with a `Locale` parameter, and one with no parameter (this form uses the default locale). Once you have a `BreakIterator` object, how do you use it? First, you must inform the object about the text you want to examine, using the `setText(String)` method. Then you can move through the text looking at boundaries using the `first()`, `last()`, `next()`, and `previous()` methods. Each of these methods moves the current position of the `BreakIterator` to the requested boundary and returns the integer position of that boundary within the text. The `next()` and `previous()` methods return a special value, `BreakIterator.DONE`, if there are no more boundaries in the requested direction. Additionally, the `next()` method has a variant that takes an integer parameter for moving ahead by multiple boundaries in one step. Iterating through text with a `BreakIterator` is a little more complicated than traversing a data structure using an `Enumeration` object. The reason is that the information you get back from a `BreakIterator` is usually not the complete element you are interested in; instead, it is the start or end point of that element within the text. You must save that value and then find the other boundary before you can do useful things. This is a case in which it makes sense to declare and use two loop variables in a `for` statement. For example, here is a code fragment that loops through all the words in a `String` called `textBuffer`, printing them as it goes:
    BreakIterator words = BreakIterator.getWordInstance();
    words.setText(textBuffer);

    for (    int start = words.first(), end = words.next();
             end != BreakIterator.DONE;
             start = end, end = words.next()) {
        System.out.println(textBuffer.substring(start,end));
    }
Another method, `current()`, returns the index of the current boundary without changing or moving the current point. The `following(int)` method returns the first boundary after the specified position in the text. What do you do if you need some of the `BreakIterator` facilities but can't afford to store your application's text in a `String` object? If efficiency or ease of access dictate that you use a text data structure (such as a trie--a specialized text data structure) instead of a string, you should define a `CharacterIterator` class for that data structure. `CharacterIterator` is an interface, similar in some ways to the `java.util.Enumeration` interface. It provides an abstract interface for iterating over the elements in a data structure. Classes that implement `CharacterIterator` don't have to contain all the data; they just have to know how to read it sequentially from the data structure and how to keep track of the current position. `CharacterIterator` differs from `Enumeration` in that it is specialized for character data and allows bidirectional scanning. `BreakIterator` actually has two `setText()` methods: One takes a `String` parameter, and the other takes a `CharacterIterator` parameter. Once you build a `CharacterIterator` for your data structure, you can use the `BreakIterator` facilities to analyze the text stored there. #### Character Classification Even with all the useful text-handling facilities already described, occasionally a program has to work at the level of individual characters. Operations that were trivial using the ASCII character set (such as converting between uppercase and lowercase) are much more complicated with Unicode. The `java.lang.Character` class contains several methods for making various tests on character values and for converting them in some way. You can test for the case of a character, or learn whether it is a whitespace character, for example, and you can convert between uppercase and lowercase and back again. ## Input and Output Manipulating data is all very well and good, but at some point, it comes down to reading that data from somewhere and writing (or displaying) it. Java provides facilities to help with several aspects of internationalized I/O, including Unicode streams and formatting and parsing classes. ### Unicode Streams In Java 1.1, the `java.io` package contains several I/O stream classes that read and write Unicode data. These classes are called readers and writers, and extend the `Reader` and `Writer` classes. Because the `String` and `char` data types in Java represent Unicode data, Unicode I/O streams are the natural and preferred mechanism for text input and output. And it doesn't take any extra work to use them instead of the byte-oriented streams. I won't go into much detail about Unicode streams in this chapter; if you want more details about Unicode I/O streams, see Chapter 12, "The I/O Package." ### Formatted Output and Input The hard part about internationalized input and output is formatting and parsing. Data that is intrinsically textual can be read, manipulated, and written again rather easily, but what about data that is represented as text but must be manipulated in some other form? How do you convert back and forth between the values and their textual representations in a locale- independent way? That's the job of the `Format` classes. The `java.text` package contains the abstract `Format` class, which describes a generic facility for formatting and parsing textual data representations. Several subclasses of `Format` perform internationalized handling of data types such as `Date` and numeric values, plus messages for users. The interface defined by the `Format` class consists of four methods: two for formatted output, and two for parsing formatted input. The intent is that strings generated by `Format` and its subclasses can also be reparsed by those same classes to generate an equivalent object or set of objects. The primary formatting method is `format(Object)`, which returns a formatted string. The other formatting method is more complicated: `format(Object,` `StringBuffer,` `FieldPosition)` returns the `StringBuffer` object that is passed as the second parameter, after appending the formatted value to it. Subclasses of `Format` use the `FieldPosition` object to communicate information about the formatting process. When the method is called, the `FieldPosition` parameter contains an integer, the field identifier, which the caller uses to express interest in a particular portion of the formatted string. When the method returns, the `FieldPosition` object has been updated to contain the beginning and ending positions of that field within the `StringBuffer`. Such information might be useful for choosing appropriate sizes for GUI elements, and this variation of the `format()` method is useful for building a formatted string in pieces. The two parsing methods parallel the formatting methods. `ParseObject(String)` returns an `Object` created from the information in the string. `ParseObject(String,` `ParsePosition)` also returns an `Object` representing the parsed value, but the `ParsePosition` parameter is used to control the current position when parsing a string piece by piece. Instances of `ParsePosition` contain an integer representing a position within a string. When the method is called, the `ParsePosition` parameter indicates where in the string the method should begin parsing; when the method returns, the parameter has been updated to point to the first character following the parsed value. Thus, it is ready to pass to the next `ParseObject` method to parse the next piece of the string. Once again, `Format` is an abstract class and doesn't provide implementations of all these methods. It does provide default implementations of the simple, single-parameter versions, which work by calling the more general methods so that you can build a working `Format` object simply by supplying implementations for only two methods. #### Formatting and Parsing Individual Objects The Java 1.1 library provides specialized subclasses of `Format` for handling two kinds of locale-sensitive data: dates and numbers. `DateFormat` and `NumberFormat` each provide the following facilities: Static `getInstance()` methods that create instances specialized for a particular locale or for the default locale Specialized `getInstance()` methods, such as `NumberFormat.getCurrencyInstance()`, for creating instances specialized for certain interpretations of the data Static constants that identify various parts of a formatted value, for use as the field identifiers in `FieldPosition` objects (for example, `DateFormat.MONTH_FIELD`) Methods for setting various style properties for the formatted values (for example, `NumberFormat.setMaximumFractionDigits()`) Various specialized versions of `format()` that take parameters of the appropriate type instead of `Object` Specialized `parse()` methods that return the appropriate type instead of `Object` (these methods can't be versions of `parseObject()` because methods can't be overloaded on the return type) `DateFormat` can format and parse `Date` objects as dates, or times, or both. `NumberFormat` can interpret numbers--that is, values of any of the Java numeric types--as ordinary numbers, currency values, or percentages. When parsing numbers using the `parse()` method, numbers are returned as instances of `java.lang.Number`. NOTE:`NumberFormat` can even handle the two arbitrary-precision numeric types, `java.math.BigInteger` and `java.math.BigDecimal`. However, an instance of one of these types is formatted by first calling its `longValue()` method and then formatting the result as a `long`. Therefore, the resulting output isn't accurate unless the actual value is within the range that can be represented as a `long`. Additionally, `NumberFormat` does not ever return a `BigInteger` or `BigDecimal` object when parsing. You may be surprised to learn that, even though `DateFormat` and `NumberFormat` provide all those extra facilities on top of the interface defined by `Format`, they don't actually implement the parsing and formatting functions! `DateFormat` and `NumberFormat` are themselves abstract classes. You must create instances using `getInstance()` or one of its variants. When you call `getInstance()` on one of these classes, you get a preconfigured instance of `DecimalFormat` (for numbers) or `SimpleDateFormat` (for dates). These classes can be configured with rules and patterns to format and parse values according to the conventions of a wide variety of locales. The configuration rules are documented in the API documentation, and it's possible to use these classes directly to handle specialized formatting and parsing needs. Unless you really need something special, though, it's best to just call one of the `getInstance()` methods on `DateFormat` or `NumberFormat` and use the object that is returned. Why? If `DecimalFormat` and `SimpleDateFormat` are so configurable, why aren't they the standard objects? Why the extra level of inheritance, with abstract classes that don't actually provide an implementation of the core functionality? The answer is that, as configurable as they are, `DecimalFormat` and `SimpleDateFormat` may not be flexible enough to handle all the locales in the world. Some locales may require entirely new formatting classes to be written if they are to be supported correctly. If you asked for a format object for such a locale, you would actually get an instance of one of the new classes. Keeping the basic interface definition and factory methods in a separate class, independent of the actual functionality, makes it easier to support such atypical locales without having to modify any of the classes in the core library. #### Formatting and Parsing Textual Messages Being able to format and parse dates, times, and numbers is nice, but such items rarely occur in isolation; usually they are embedded in other text. The `MessageFormat` class is designed for formatting and parsing textual strings that may incorporate other data items. Error messages are a prime example of what `MessageFormat` is good for, but the facility can be used to prepare any text meant for human conceptions, including GUI elements and printed reports. In many respects, `MessageFormat` is similar to C's `printf()` function. In fact, there is a static method, `MessageFormat.format(String,` `Object[])`, which is very similar indeed. One parameter is a format string that functions as a pattern, with embedded format specifiers indicating how the other parameters should be processed and substituted into the pattern string. Unlike `printf()`, however, the `format()` method doesn't actually print the formatted message; it just returns it as a `String`. Because Java doesn't permit methods to take a variable number of parameters, the additional items are passed as an `Object` array. Also, the syntax for format specifiers is different; among other things, they incorporate the number of the data item to which they refer. This is because localization of text often involves changing not only the words, but the structure of sentences. Thus, the item that occurs first in the English version of a sentence may have to come last in the German version. (Format patterns are usually taken from a localized resource bundle rather than being included directly in the code.) Format specifiers within patterns are surrounded by curly braces. Here is a simple example:
    MessageFormat.format("No entry for {0} in the database", new Object[] {name});
The format specifier `{0}` indicates that the first element of the array (with index `0`) should be substituted into the string at that point. The element should be a `String`. What if one of your data items is not a `String`? One solution is to format it separately (for example, with `NumberFormat` or `DateFormat`) and use the resulting `String`. There's no need to do that, however, because you can include the fact that the element is a number or date in the format specifier. This example includes a number, a string, and a date:
    // numAppt is an int, pName is a String, and apptDate is a Date

    MessageFormat.format("{0,number} appointments for {1} on {2,date,medium}",

                         new Object[] {new Integer(numAppt), pName, apptDate});
There are four possibilities for the type selector after the comma: `date`, `time`, `number`, and `choice`. The `date` selector results in a call to `DateFormat.getDateInstance()` to do the formatting; the `time` selector results in a call to `DateFormat.getTimeInstance()`. The `number` selector by itself uses `NumberFormat.getInstance()`, but style options can be specified to modify that behavior. The `choice` selector is explained in the next section. If the type selector is followed by another comma, then whatever follows (up to the curly brace that ends the format specifier) is a style option. The format specifier for the date in the preceding example includes the style option `medium`, which is one of the styles for dates and times. The others are `short`, `long`, and `full`, and they correspond to the `SHORT`, `MEDIUM`, `LONG`, and `FULL` constants that `DateFormat` provides for selecting styles with the `getInstance()` methods. Additionally, the style for a `date` or `time` format can be a valid configuration pattern understood by the `SimpleDateFormat` class. The valid styles for `number` formats are `currency`, `percent`, and `integer`; these styles modify the way the number is interpreted appropriately. The style can also be a configuration pattern accepted by the `DecimalFormat` class. So far, I've discussed only the `static` `format(String,` `Object[])` method. But instances of `MessageFormat` are useful, too. In fact, the `static` method is implemented in terms of a throwaway instance:
    public static String format(String pattern, Object[] arguments) {
        MessageFormat temp = new MessageFormat(pattern);
        return temp.format(arguments);

    }
Why might you want to create an instance of `MessageFormat`? There are several possible reasons. For one thing, it would be a good thing to do if you had to print the same message multiple times with different data values. Because parsing and processing the format pattern is reasonably expensive, it would be a good idea to do so only once if the pattern is going to be reused many times. Another reason is that the static version of the method uses the default locale, whereas instances can be created for specific locales. Therefore, you should never use the static method in a multilingual program. You would also create an instance of `MessageFormat` if you were using it to parse text rather than to format it (more about that topic later). The final reason why `MessageFormat` instances are useful is that they give us more flexibility. With an instance, you can build a pattern programmatically. For example, the preceding example used three different objects; but you could have written it this way:
    MessageFormat msg = new MessageFormat("{0} appointments for {1} on {2}");
    msg.setFormat(0, NumberFormat.getInstance());
    // format 1, for the string, is already set.
    msg.setFormat(2, DateFormat.getDateInstance(Date.MEDIUM));
    msg.format(new Object[] {new Integer(numAppt), pName, apptDate});
That seems like an awful lot of trouble to go through when you could just encode the information in the format string. But it can be useful in some complicated situations--and it is particularly useful if you have to include some data that is not a number, date, time, or string. You can extend the `Format` class to handle locale-independent formatting of any data type you want, but you can't extend the `MessageFormat` format specifier syntax to support your new `Format` classes. You can, however, make use of the new classes by explicitly including them with the `setFormat()` method, as just shown. I mentioned earlier that the format specifiers include the ordinal number of the array element to which they refer, so that they can be reordered if necessary during the localization process. It's important to note that the numbers used to identify the specifiers in the `setFormat()` call are different from the numbers actually included in the format specifiers. The numbers used by the `setFormat()` method always refer to the specifiers in the order they occur in the pattern string, starting with zero. That's a problem for internationalization, because when the order of the specifiers changes during localization, the numbers used to set the format objects for the message have to change, too. I hope this problem is resolved in a future Java release. As you can with the other subclasses of `Format`, you can use `MessageFormat` for parsing as well as formatting. The `parse()` methods return arrays of `Object`, and the patterns are the same as for formatting. The intent is that a message formatted with a pattern can be parsed with the same pattern. Although this logic may fail in some situations (including some uses of `ChoiceFormat`, described in the next section), it works in general. There are two other things to know about `MessageFormat`. There is currently an arbitrary limit of 10 format specifiers for a single pattern. There's no need for a limit at all, and a comment in the source code indicates that this limit may someday be removed. (On the other hand, if you are using format patterns with 83 specifiers, you should probably consider a different strategy.) TIP: If you have to include a `{` or `}` character in a format pattern, you must enclose it within the string in single quotation marks. If you have to include a single quote character, double it. This quoting mechanism is, in my opinion, a little strange and inconsistent with the backslash-quoting mechanism used for the first level of quoting in Java strings. However, there are problems with using that mechanism in this case as well, so it is difficult to say whether another option may have been better. Just remember to be on the lookout for occasions when any of those three characters must appear in a string formatted by `MessageFormat`. #### The ChoiceFormat Class Occasionally, some part of the text surrounding a value has to change depending on the value itself. There are many examples of this, but the classic example involves singular and plural forms of words. There was a time when computer users accepted messages like `There were 1 match(es) found`, but today, people expect better. Java 1.1 provides the `ChoiceFormat` class for solving this problem. Assuming that the variable `numMatches` contains the number of matches found, here's one simple way to format the preceding message:
    MessageFormat msg = new MessageFormat("There {0} found.");

    double[] limits = {0, 1, 2};
    String[] choices = {"were no matches",
                        "was 1 match",
                        "were {0,number} matches"};
    ChoiceFormat = new ChoiceFormat(limits, choices);
    msg.setFormat(0, choice);

    msg.format(new Object[] {numMatches})
The `ChoiceFormat` object is created with two arrays: an array of limits and an array of choices. The two arrays should be the same size. The limits array consists of `double` values, sorted in ascending order. The choices array consists of `String` values. `ChoiceFormat` is invoked to format a number. It compares that number to the numbers in the limits array, and formats the number using the element of the choices array that corresponds to the chosen limit. If there are N entries in each array, and we call the number to be formatted x, the matching element is chosen this way:
`1` | `if` x`< limit[1]`
i | `if limit[i] <=` x`< limit[i+1]`
N | `if limit[`N`] <=` x
The selected choice is substituted into the pattern and processed recursively. Note that the third choice in the preceding example--used when `numMatches` is `2` or greater--has a format specifier embedded within it. That specifier is used to format the number into the message if the third choice is selected. That's a bit cumbersome, but fortunately there's an easier way. The `ChoiceFormat` class understands patterns of its own, and you can embed those patterns in the format specifiers for `MessageFormat`. For example, the previous message can also be specified like this:
    MessageFormat.format("There {0,choice,0#were no matches|1#was 1 match|"
                         + "2#were {0,number} matches} found.");
`ChoiceFormat` can also be used to parse strings like this one. ## Internationalizing Graphical Interfaces Java 1.1 provides a new feature for the AWT package that is designed to help with internationalizing graphical interfaces. In Java 1.0, buttons and several other kinds of components displayed their names as a user-visible label. In some cases, the name was useful for determining which button was clicked and was hard-coded into the application, making the label text difficult to localize. Java 1.1 components have an explicit `label`, distinct from the name, that is used for the user-visible display if it has been set. This separation of values allows the label to be localized while enabling you to use the same component name in all locales. ## Summary In this chapter, you learned about internationalization and all the Java 1.1 features that support this task. Internationalization is primarily a problem of managing program structure so that locale-sensitive data and operations are easy to find, and of using appropriate facilities to manipulate certain kinds of data. Java's resource bundles help with the structuring issues, and a wide variety of other facilities are included for manipulating, formatting, and parsing locale-sensitive data, including dates, times, numbers, and text, in an internationalized manner.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.