Localize this!

Use resource bundles to make your applications multicultural

For you to better understand how we're going to internationalize our application's text, including button labels, label text, and error messages, we need to look closely at what went on last month. Here's what happened from the application's point of view:

  1. The application started with a piece of data, such as an instance of the Date class, stored in some locale-neutral form.

  2. The application created an instance of the proper formatter, such as DateFormat, from the java.text package.

  3. Behind the scenes, the application queried the runtime in order to determine the locale in which the application was running. This likely involved some interaction with the operating system.

  4. Based on the information it received, the application determined how to format the date for the locale, given that the locale was supported.

  5. The application then formatted the date and created a string.

  6. Finally, the application displayed the string.

Steps 3 through 5 were hidden from view. The DateFormat and the NumberFormat classes did the work behind the scenes. (If you'd like more detailed information, take a look at last month's column.) This month, let's take a closer look at step 4.

Because the programmers at Sun wrote the code for the DateFormat and the NumberFormat classes, they were responsible for deciding which locales to support and for providing the code to perform the appropriate data conversions for those particular locales. Because there are only so many variations on dates and numbers, they were able to build the necessary code for formatting data in the Number and Date classes, according to the appropriate locale, directly into the Java library.

That saved us a lot of work!

Unfortunately, such forward planning and implementation isn't quite so easy when it comes to text messages. The developers at Sun could not possibly anticipate all the different text messages used by each application (current and future) and provide the appropriate translations; they left that task to individual developers. It is, therefore, up to us to decide which locales we intend to support and provide translations of our messages for each. In short, the work for step 4 now falls squarely on our shoulders.

Locale

Before we can proceed, we really need to better understand the notion of a locale, and its associated Java class

Locale

.

A locale is a loose term for a specific geographical, political, or cultural region. It's best defined by example. The region of origin of a group of people can often be surmised by the language they speak. Therefore, as you might also guess, language is an important component of locale. So too is nationality. Even though Americans and the British share a common tongue, we each possess our own customs and cultural identity. Thus, each region is its own locale.

In the Java programming language, a locale is represented by an instance of class Locale. An instance of Locale can be created for any language and any country. The Locale class also provides several predefined values for common countries (US and CANADA, for example) and languages (ENGLISH and FRENCH for example).

Operations that can be localized (formatting a date, for example) usually take a Locale instance as an argument. If it is not specified, the argument defaults to the current locale.

Resource bundles

Now let's return to our problem.

To completely internationalize our application, we'll need to gather a collection of strings appropriate for a locale and use them in appropriate places throughout the application. In order to assist us, the Java class library provides resource bundles.

A resource bundle is a collection of all resources and information for a specific locale. It associates each resource with a key, and the key is the same for each particular resource across all locales. In addition, due to the way resource bundles are named (explained below), they provide an orderly, effective way of locating the correct resource bundle.

Let's take a look at a concrete example. Recall that last month's example had five buttons: previous, next, new, add, and delete.

The Record Manager buttons

Each button has a label that indicates its function. Obviously, the labels should be in the appropriate language for the current locale. Therefore, for each locale I plan to support, I create a resource bundle containing the labels of each button, appropriately translated. Each resource bundle has five key/value pairs. The keys are the same across all bundles, while the values change to suit the locale. A key can hold any string. It is usually something significant to the programmer ("btn1," "btnPrevious," or "previous").

Consider the button bundle for my locale (the United States of America). The following table shows the keys and values for this bundle.

KeyValue
btnPrevious"previous"
btnNext"next"
btnNew"new"
btnAdd"add"
btnDelete"delete"

Bundles and bundles of fun

A set of bundles (all the bundles that hold labels for the buttons in our application, for example) all belong to a group. The group has a name, often referred to as the "bundle name." Likewise, each bundle in the group of bundles has a name. The names are constructed in a uniform fashion: the bundle name plus the language, the country (optional), and the variant (a variant is a subregion within a country -- it's optional as well). One bundle has the same name as the group itself. This is the default bundle, and is the one selected if no other bundle is more appropriate.

This naming scheme is not as difficult as it sounds, but I think an example will help. Assume we have a group of bundles that hold the labels of the buttons in our application. The bundle name of the group is something like "ButtonLabels." At the very least, there would be one bundle -- the one with the name "ButtonLabels." If there was a specific bundle holding labels for francophones (French speakers), it would be named "ButtonLabels_fr" (note the underscore). If there was a specific bundle holding labels for French speaking Canadians, it would be named "ButtonLabels_fr_CA". I'll explain what the two letter abbreviations mean and how they're used in a moment. Likewise, for those who speak Japanese, "ButtonLabels_JP". If there was a specific bundle holding labels for the English speakers of Great Britain, it would be named "ButtonLabels_en_GB".

The language and the country are indicated by two-letter abbreviations. In many cases they will be obvious, but in some cases they won't.

Language abbreviations are defined by ISO-639 and are specified in lowercase letters (see the Resources for a link to ISO-639). Country abbreviations are defined by ISO-3166 and are specified in uppercase letters (see the Resources for a link to ISO-3166).

When Java goes looking for the bundle for a given locale in order to use the values it contains, it looks at bundles in the group in the following order, stopping when it finds what it needs:

  1. bundleName_localeLanguage_localeCountry_localeVariant
  2. bundleName_localeLanguage_localeCountry
  3. bundleName_localeLanguage
  4. bundleName_defaultLanguage_defaultCountry_defaultVariant
  5. bundleName_defaultLanguage_defaultCountry
  6. bundleName_defaultLanguage
  7. bundleName

bundleName refers to the base name of the bundle group. localeLanguage, localeCountry, and localeVariant refer to the language, country, and variant specified. defaultLanguage, defaultCountry, and defaultVariant refer to the current default language, country, and variant.

So, how are bundles constructed? Let's take a look.

Better bundle building

In Java, bundles are implemented as concrete subtypes of the

ResourceBundle

abstract class. To help you get started, Java implements two useful subtypes of class

ResourceBundle

: class

ListResouceBundle

and class

PropertyResouceBundle

. Let's look at both of these classes in more detail.

ListResouceBundle

Here's an example of a ListResouceBundle for the strings in my sample application:

import java.util.*;
public class Strings extends ListResourceBundle
{
   public Object [][] getContents()
   {
      return contents;
   }
   static final Object[][] contents =
   {
      {"lblTitle", "Record Editor"}, 
      {"lblDate", "date"},
      {"lblTime", "time"},
      {"lblNumber", "number"},
      {"lblPrice", "price"},
      {"btnPrevious", "previous"},
      {"btnNext", "next"},
      {"btnNew", "new"},
      {"btnAdd", "add"},
      {"btnDelete", "delete"},
      {"strBadDate", "Can't add record: bad date!"},
      {"strBadTime", "Can't add record: bad time!"},
      {"strBadNumber", "Can't add record: bad number!"},
      {"strBadPrice", "Can't add record: bad price!"},
      {"strAdded", "Record added."},
      {"strDeleted", "Record deleted."},
      {"strEmpty", "No records in editor!"}
   };
}

As you can see, the information mapping the keys to values is contained inside the class itself. Each key/value pair is one entry in an array of Objects. Each pair is itself a two element array of Objects. The first element of each pair (the key) must be an instance of the String class. The second element (the value) may be any Object. In my example, all are instances of the String class.

You must name your ListResourceBundle classes using the scheme I described earlier if the system is to find your classes. The listing above shows the definition of the default bundle. The class containing the key/value pairs for, say, Germany would be named Strings_de_DE.class (and the associated source code file would be Strings_de_DE.java).

Using ListResourceBundle is easy and self-contained, but you must recompile at least one class file if any of the values change.

If you're building an application (rather than an applet), you have an alternative to ListResourceBundle. It's called the PropertyResouceBundle and it manages resources contained in a property file.

STRONG>PropertyResouceBundle

Here's an example of the contents of such a property file.

lblTitle=Record Editor
lblDate=date
lblTime=time
lblNumber=number
lblPrice=price
btnPrevious=previous
btnNext=next
btnNew=new
btnAdd=add
btnDelete=delete
strBadDate=Can't add record: bad date!
strBadTime=Can't add record: bad time!
strBadNumber=Can't add record: bad number!
strBadPrice=Can't add record: bad price!
strAdded=Record added.
strDeleted=Record deleted.
strEmpty=No records in editor

As you can see, each key/value pair is on a line by itself. Each line consists of the key, an equal sign (=), and the value. Note that keys are case sensitive.

The name given to the PropertyResouceBundle classes is also very important. You must use the naming scheme described earlier if the system is to find your classes. In addition, the file name must have the extension "properties".

Bundles in action

When an applications begins execution, one of the first steps it should take is to get the correct resource bundle for the locale in which it is running. It does this as follows:

_resbundle = ResourceBundle.getBundle("Strings", Locale.getDefault());

The first argument to the getBundle method is the bundle name of the appropriate group of bundles. My application has one group of bundles, and its name is "Strings". The second argument is the locale. I've specified the default locale. Any other valid locale is acceptable.

Once the application gets the correct resource bundle, it can query the bundle for resources. Here's how it gets the string to use as the label of the "Previous" button:

_ifc.btnPrevious.setLabel(_resbundle.getString("btnPrevious"));

The getString method takes a key as an argument and returns the appropriate String instance.

A very worldly applet

Now let's take a look at an applet that makes use of resource bundles.

You need a Java-enabled browser to see this applet.

Note: You must use a Java 1.1-compliant browser to access the features of this applet.

This applet may look like last month's applet, but appearances are deceiving. While last month's applet blindly used English for all labels and messages, this month's applet supports labels and messages for a small collection of locales.

Let's take a look at the code that handles all of this. The following code excerpt is from Main.java, which is part of the applet's initialization code.

1 2 Page 1