Resource Bundles

An internationalized application contains no hard-coded text or other locale-specific elements (such as a specific currency format). Instead, each supported locale's version of these elements is stored outside of the application.

NOTE: Creating a set of locale-specific elements is known as localization.

Java is responsible for storing each locale's version of certain elements, such as currency formats. In contrast, it is your responsibility to store each supported locale's version of other elements, such as text, audio clips, and locale-sensitive images.

Java facilitates this element storage by providing resource bundles, which are containers that hold one or more locale-specific elements, and which are each associated with one and only one locale.

Many applications work with one or more resource bundle families. Each family consists of resource bundles for all supported locales and typically contains one kind of element (perhaps text, or audio clips that contain language-specific verbal instructions).

Each family also shares a common family name (also known as a base name); each of its resource bundles has a unique locale designation that is appended to the family name, to differentiate one resource bundle from another within the family.

Consider an internationalized text-based game application for English and French users. After choosing game as the family name, and en and fr as the English and French locale designations, you end up with the following complete resource bundle names:

■ game_en is the complete resource bundle name for English users.

■ game_fr is the complete resource bundle name for French users.

Although you can store all of your game's English text in the game_en resource bundle, you might want to differentiate between American and British text (such as elevator versus lift). This differentiation leads to the following complete resource bundle names:

■ game_en_US is the complete resource bundle name for users who speak the United States version of the English language.

■ game_en_GB is the complete resource bundle name for users who speak the British version of the English language.

An application loads its resource bundles by calling the various getBundle() utility methods that are located in the abstract java.util.ResourceBundle class. For example, the application might call the following getBundle() factory methods:

■ static ResourceBundle getBundle(String baseName)loads a resource bundle using the specified baseName and the default locale. For example, ResourceBundle resources =

ResourceBundle.getBundle("game"); attempts to load the resource bundle whose base name is game, and whose locale designation matches the default locale. If the default locale is en_US, getBundle() attempts to load game_en_US.

■ static ResourceBundle getBundle(String baseName, Locale locale) loads a resource bundle using the specified baseName and locale. For example, ResourceBundle resources = ResourceBundle.getBundle("game", new Locale("zh", "CN", "WIN")); attempts to load the resource bundle whose base name is game, and whose locale designation is Chinese with a Windows variant. In other words, getBundle() attempts to load game_zh_CN_WIN.

NOTE: ResourceBundle is an example of a pattern that you will discover throughout the internationalization APIs. With few exceptions, each API is architected around an abstract entry-point class whose factory methods return instances of concrete subclasses.

If the resource bundle identified by the base name and locale designation does not exist, the getBundle() methods search for the next closest bundle. For example, if the locale is en_US and game_en_US does not exist, getBundle() looks for game_en.

The getBundle() methods first generate a sequence of candidate bundle names for the specified locale (language1, country1, and variant1) and the default locale (language2, country2, and variant2) in the following order:

baseName + "_

_" +

language1

+

"_" + country1 + "_

_" + variant1

baseName + "_

_" +

language1

+

"_" + country1

baseName + "

_" +

language1

baseName + "

_" +

language2

+

"_" + country2 + "_

_" + variant2

baseName + "

_" +

language2

+

"_" + country2

baseName + "

_" +

language2

baseName

Candidate bundle names in which the final component is an empty string are omitted from the sequence. For example, if country1 is an empty string, the second candidate bundle name is omitted.

The getBundle() methods iterate over the candidate bundle names to find the first name for which they can instantiate an actual resource bundle. For each candidate bundle name, getBundle() attempts to create a resource bundle as follows:

■ It first attempts to load a class that extends the abstract java.util.ListResourceBundle class using the candidate bundle name. If such a class can be found and loaded using the specified class loader, is assignment compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated, getBundle() creates a new instance of this class and uses it as the result resource bundle.

■ Otherwise, getBundle() attempts to locate a properties file. It generates a pathname from the candidate bundle name by replacing all "." characters with "/" and appending ".properties." It attempts to find a "resource" with this name using java.lang.ClassLoader. getResource(). (Note that a "resource" in the sense of getResource() has nothing to do with the contents of a resource bundle; it is just a container of data, such as a file.) If getResource() finds a "resource," it attempts to create a new java.util.PropertyResourceBundle instance from its contents. If successful, this instance becomes the result resource bundle.

If no result resource bundle is found, getBundle() throws an instance of the java.util.MissingResourceException class; otherwise, getBundle() instantiates the bundle's parent resource bundle chain.

NOTE: The parent resource bundle chain makes it possible to obtain fallback values when resources are missing. The chain is built by using ResourceBundle's protected void setParent(ResourceBundle parent) method.

getBundle() builds the chain by iterating over the candidate bundle names that can be obtained by successively removing variant, country, and language (each time with the preceding "_") from the complete resource bundle name of the result resource bundle.

NOTE: Candidate bundle names where the final component is an empty string are omitted.

With each candidate bundle name, getBundle() tries to instantiate a resource bundle, as just described. If it succeeds, it calls the previously instantiated resource bundle's setParent() method with the new resource bundle, unless the previously instantiated resource bundle already has a nonnull parent.

NOTE: getBundle() caches instantiated resource bundles and may return the same resource bundle instance multiple times.

ResourceBundle declares various methods for accessing a resource bundle's resources. For example, Object getObject(String key) gets an object for the given key from this resource bundle or one of its parent bundles.

getObject() first tries to obtain the object from this resource bundle using the protected abstract handleGetObject() method, which is implemented by concrete subclasses of ResourceBundle (such as PropertyResourceBundle).

If handleGetObject() returns null, and if a nonnull parent resource bundle exists, getObject() calls the parent's getObject() method. If still not successful, it throws MissingResourceException.

Two other resource-access methods are String getString(String key) and String[] getStringArray(String key). These convenience methods are wrappers for (String) getObject(key) and (String[]) getObject(key).

NOTE: Java version 6 introduced numerous ResourceBundle enhancements. Enhancements range from new clearCache() methods for removing resource bundles from the cache to a new nested Control class whose methods let you take control of the resource bundle search order, and even load resource bundles from another source (such as an XML file). Although Android does not support these enhancements at the time of writing, this may change in a future release of Android. I explore the enhancements to ResourceBundle in Beginning Java SE 6 Platform: From Novice to Professional.

Property Resource Bundles

A property resource bundle is a resource bundle that is backed by a properties file, a text file (with a .properties extension) that stores textual elements as a series of key=value entries. The key is a nonlocalized identifier that an application uses to obtain the localized value.

NOTE: Properties files are accessed via instances of the java.util.Properties class. In Chapter 8, I mentioned that the Preferences API (discussed later in this chapter) has made Properties largely obsolete. Property resource bundles prove that the Properties class is not entirely obsolete.

PropertyResourceBundle, a concrete subclass of ResourceBundle, manages property resource bundles. You should rarely (if ever) need to work with this subclass. Instead, for maximum portability, you should only work with ResourceBundle, as Listing 9-10 demonstrates.

Listing 9-10. Accessing a localized elevator entry in game resource bundles import java.util.ResourceBundle;

public class PropertyResourceBundleDemo {

public static void main(String[] args) {

ResourceBundle resources = ResourceBundle.getBundle("game"); System.out.println("elevator = " + resources.getString("elevator"));

Listing 9-10 refers to ResourceBundle instead of PropertyResourceBundle, which lets you easily migrate to ListResourceBundle as necessary. I use getString() instead of getObject() for convenience; text resources are stored in textual properties files.

When you run this application, it generates the following output:

Exception in thread "main" java.util.MissingResourceException: Can't find bundle for base name game, locale en_US

at java.util.ResourceBundle.throwMissingResourceException(Unknown Source) at java.util.ResourceBundle.getBundleImpl(Unknown Source) at java.util.ResourceBundle.getBundle(Unknown Source) at PropertyResourceBundleDemo.main(PropertyResourceBundleDemo.java:7)

This exception is thrown because no property resource bundles exist. You can easily remedy this situation by copying Listing 9-11 into a game.properties file, which is the basis for a property resource bundle.

Listing 9-11. A fallback game.properties resource bundle elevator=elevator

Assuming that game.properties is located in the same directory as PropertyResourceBundleDemo.class, execute java PropertyResourceBundleDemo and you will see the following output:

elevator = elevator

Because my locale is en_US, getBundle() first tries to load game_en_US.properties. Because this file does not exist, getBundle() tries to load game_en.properties. Because this file does not exist, getBundle() tries to load game.properties, and succeeds.

Copy Listing 9-12 into a game_en_GB.properties file. Listing 9-12. A game resource bundle for the en_GB locale elevator=lift

Execute java -Duser.language=en -Duser.country=GB PropertyResourceBundleDemo. This time, you should see the following output:

elevator = lift

With the locale set to en_GB, getBundle() first tries to load game_en_GB.properties, and succeeds.

Comment out elevator = lift by prepending a # character to this line (as in #elevator = lift). Then execute java -Duser.language=en -Duser.country=GB PropertyResourceBundleDemo and you should see the following output:

elevator = elevator

Although getBundle() loaded game_en_GB.properties, getString() (via getObject()) could not find an elevator entry. As a result, getString()/getObject() searched the parent resource bundle chain, encountering game.properties' elevator=elevator entry, whose elevator value was subsequently returned.

NOTE: A common reason for getString() throwing MissingResourceException in a property resource bundle context is forgetting to append .properties to a properties file's name.

List Resource Bundles

A list resource bundle is a resource bundle that is backed by a classfile, which describes a concrete subclass of ListResourceBundle (an abstract subclass of ResourceBundle). As a result, list resource bundles can store binary data (such as images or audio) as well as text. In contrast, property resource bundles can only store text.

NOTE: If a property resource bundle and a list resource bundle have the same complete resource bundle name, the list resource bundle takes precedence over the property resource bundle. For example, when getBundle() is confronted with game_en.properties and game_en.class, it loads game_en.class instead of game_en.properties.

Listing 9-13 demonstrates a list resource bundle by presenting a flags_en_CA class that extends ListResourceBundle.

Listing 9-13. A resource bundle containing a small Canadian flag Image and English/French text import java.awt.Toolkit;

import java.util.ListResourceBundle;

public class flags_en_CA extends ListResourceBundle

(byte) 137, (byte) 80, (byte) 78, (byte) 71, (byte) 13, (byte) 10, (byte) 26, (byte) 10,

(byte) 0, (byte) 0, (byte) 73, (byte) 69, (byte) 78, (byte) 68, (byte) 174, (byte) 66, (byte) 96, (byte) 130

{ "flag", Toolkit.getDefaultToolkit().createImage(image) }, { "msg", "Welcome to Canada! | Bienvenue vers le Canada !" }, { "title", "CANADA | LA CANADA" }

public Object[][] getContents()

return contents;

Listing 9-13's flags_en_CA class describes a list resource bundle whose base name is flags and whose locale designation is en_CA. This class's image array stores a Portable Network Graphics (PNG)-based sequence of byte integers that describes an image of the Canadian flag, contents stores key/value pairs, and getContents() returns contents.

NOTE: For brevity, Listing 9-13 does not present the complete image array with Canadian flag image data. You must obtain flags_en_CA.java from this book's companion code file (see the book's introduction for instructions on how to obtain this file) to get the complete listing.

The first key/value pair consists of a key named flag (which will be passed to ResourceBundle's getObject() method) and an instance of the java.awt.Image class. This instance represents the flag image and is obtained with the help of the java.awt.Toolkit class and its createImage() utility method.

NOTE: AWT stands for Abstract Window Toolkit, a windowing toolkit that makes it possible to create crude user interfaces consisting of windows, buttons, text fields, and so on. The AWT was released as part of Java version 1.0 in 1995, and continues to be part of Java's standard class library. The AWT is not supported by Android.

Listing 9-14 shows you how to load the default flags_en_CA list resource bundle (or another list resource bundle via command-line arguments) and display its flag and text.

Listing 9-14. Obtaining and displaying a list resource bundle's Hag image and text import java.awt.EventQueue; import java.awt.Image;

import java.util.Locale; import java.util.ResourceBundle;

import javax.swing.ImageIcon; import javax.swing.JOptionPane;

public class ListResourceBundleDemo {

public static void main(String[] args) {

l = new Locale(args[0], args[1]); final ResourceBundle resources = ResourceBundle.getBundle("flags", l);

Image image = (Image) resources.getObject("flag"); String msg = resources.getString("msg"); String title = resources.getString("title"); ImageIcon ii = new ImageIcon(image); JOptionPane.showMessageDialog(null, msg, title,

JOptionPane.PLAIN_MESSAGE, ii);

EventQueue.invokeLater(r);

Listing 9-14's main() method begins by selecting CANADA as its default Locale. If it detects that two arguments were passed on the command line, main() assumes that the first argument is the language code and the second argument is the country code, and creates a new Locale object based on these arguments as its default locale.

main() next attempts to load a list resource bundle by passing the flags base name and the previously chosen Locale object to ResourceBundle's getBundle() method. Assuming that MissingResourceException is not thrown, main() creates a runnable task on which to load resources from the list resource bundle and display them graphically.

main() relies on a windowing toolkit known as Swing to present a simple user interface that displays the flag and text. Because Swing is single threaded, where everything runs on a special thread known as the event-dispatching thread, it is important that all Swing-based operations occur on this thread. EventQueue.invokeLater() makes this happen.

NOTE: Swing is built on top of the AWT and provides many sophisticated features. This windowing toolkit was officially released as part of Java version 1.2, and continues to be part of Java's standard class library. Swing is not supported by Android.

If you would like to learn more about Swing, I recommend the definitive Java Swing, Second Edition, by Marc Loy, Robert Eckstein, David Wood, James Elliott, and Brian Cole (O'Reilly Media, 2002; ISBN: 0596004087).

Shortly after EventOueue.invokeLater() is executed on the main thread, the event-dispatching thread starts running and executes the runnable task. This task first obtains the Image object from the list resource bundle by passing flag to getObject() and casting this method's return value to Image.

The task then obtains the msg and title strings by passing these keys to getString(), and converts the Image object to a javax.swing.ImageIcon instance. This instance is required by the subsequent JOptionPane.showMessageDialog() method call, which presents a simple message-oriented dialog box.

NOTE: JOptionPane is a Swing component that makes it easy to display a standard dialog box that prompts the user to enter a value or informs the user of something important.

Now that you understand how ListResourceBundleDemo works, obtain the complete flags_en_CA.java source file, compile this source code (javac flags_en_CA.java) and Listing 9-14 (javac ListResourceBundleDemo.java), and execute the application (java ListResourceBundleDemo). Figure 9-1 shows the resulting user interface on Windows XP.

CANADA I LE CANADA Q

Welcome to Canada! | Bienvenue vers le Canada !

Figure 9-1. An almost completely localized dialog box (OK is not localized) displaying Canada-specific resources on Windows XP

NOTE: I obtained the language translations for this section's examples from Yahoo! Babel Fish (http://babelfish.yahoo.com/), an online text translation service.

This book's accompanying code file also contains flags_fr_FR.java, which presents resources localized for the France locale. After compiling this source file, execute java ListResourceBundleDemo fr FR and you will see Figure 9-2.

LA FRANCE |¡§

Bienvenue v<

?rs la F OK

ranee!

Figure 9-2. An almost completely localized dialog box displaying France-specific resources on Windows XP

Finally, this book's accompanying code file also contains flags_ru_RU.java, which presents resources localized for the Russia locale. After compiling this source file, execute java ListResourceBundleDemo ru RU and you will see Figure 9-3.

flo5po noxcanoBaTb k Pocchh!

Figure 9-3. An almost completely localized dialog box displaying Russia-specific resources on Windows XP

NOTE: You will need to ensure that appropriate Cyrillic fonts are installed to view the Russian text. Also, because I stored Russian characters verbatim (and not as Unicode escape sequences, such as '\u0041'), flags_ru_RU.java is a Unicode file and must be compiled via javac -encoding Unicode flags_ru_RU.java.

Was this article helpful?

0 0

Post a comment