Lesson: Isolating Locale-Specific Data

来源:百度文库 编辑:神马文学网 时间:2024/04/27 21:36:08
Locale-specific data must be tailored according to the conventions of the end user‘s language and region. The text displayed by a user interface is the most obvious example of locale-specific data. For example, an application with a Cancel button in the U.S. will have an Abbrechen button in Germany. In other countries this button will have other labels. Obviously you don‘t want to hardcode this button label. Wouldn‘t it be nice if you could automatically get the correct label for a given Locale? Fortunately you can, provided that you isolate the locale-specific objects in a ResourceBundle.
In this lesson you‘ll learn how to create and access ResourceBundle objects. If you‘re in a hurry to examine some coding examples, go ahead and check out the last two sections in this lesson. Then you can come back to the first two sections to get some conceptual information about ResourceBundle objects.
About the ResourceBundle Class
ResourceBundle objects contain locale-specific objects. When you need a locale-specific object, you fetch it from a ResourceBundle, which returns the object that matches the end user‘s Locale. This section explains how a ResourceBundle is related to a Locale, and describes the ResourceBundle subclasses.Preparing to Use a ResourceBundle
Before you create your ResourceBundle objects, you should do a little planning. First, identify the locale-specific objects in your program. Then organize them into categories and store them in different ResourceBundle objects accordingly.Backing a ResourceBundle with Properties Files
If your application contains String objects that need to be translated into various languages, you can store these String objects in a PropertyResourceBundle, which is backed up by a set of properties files. Since the properties files are simple text files, they can be created and maintained by your translators. You don‘t have to change the source code. In this section you‘ll learn how to set up the properties files that back up a PropertyResourceBundle.Using a ListResourceBundle
The ListResourceBundle class, which is a subclass of ResourceBundle, manages locale-specific objects with a list. A ListResourceBundle is backed by a class file, which means that you must code and compile a new source file each time support for an additional Locale is needed. However, ListResourceBundle objects are useful because unlike properties files, they can store any type of locale-specific object. By stepping through a sample program, this section demonstrates how to use a ListResourceBundle.Customizing Resource Bundle Loading
This section represents new capabilities to improve the ResourceBundle.getBundle factory flexibility. The ResourceBundle.Control class collaborates with the factory methods for loading resource bundles. This allows to consider every substantial step of the resource bundle-loading process and its cache control as a separate method.
*******************************************************************
About the ResourceBundle Class
How a ResourceBundle is Related to a Locale
Conceptually each ResourceBundle is a set of related subclasses that share the same base name. The list that follows shows a set of related subclasses. ButtonLabel is the base name. The characters following the base name indicate the language code, country code, and variant of a Locale. ButtonLabel_en_GB, for example, matches the Locale specified by the language code for English (en) and the country code for Great Britain (GB).ButtonLabelButtonLabel_deButtonLabel_en_GBButtonLabel_fr_CA_UNIX
To select the appropriate ResourceBundle, invoke the ResourceBundle.getBundle method. The following example selects the ButtonLabel ResourceBundle for the Locale that matches the French language, the country of Canada, and the UNIX platform.Locale currentLocale = new Locale("fr", "CA", "UNIX");ResourceBundle introLabels =ResourceBundle.getBundle("ButtonLabel", currentLocale);
If a ResourceBundle class for the specified Locale does not exist, getBundle tries to find the closest match. For example, if ButtonLabel_fr_CA_UNIX is the desired class and the default Locale is en_US, getBundle will look for classes in the following order:ButtonLabel_fr_CA_UNIXButtonLabel_fr_CAButtonLabel_frButtonLabel_en_USButtonLabel_enButtonLabel
Note that getBundle looks for classes based on the default Locale before it selects the base class (ButtonLabel). If getBundle fails to find a match in the preceding list of classes, it throws a MissingResourceException. To avoid throwing this exception, you should always provide a base class with no suffixes.
The ListResourceBundle and PropertyResourceBundle Subclasses
The abstract class ResourceBundle has two subclasses: PropertyResourceBundle and ListResourceBundle.
A PropertyResourceBundle is backed by a properties file. A properties file is a plain-text file that contains translatable text. Properties files are not part of the Java source code, and they can contain values for String objects only. If you need to store other types of objects, use a ListResourceBundle instead. The sectionBacking a ResourceBundle with Properties Files shows you how to use a PropertyResourceBundle.
The ListResourceBundle class manages resources with a convenient list. Each ListResourceBundle is backed by a class file. You can store any locale-specific object in a ListResourceBundle. To add support for an additional Locale, you create another source file and compile it into a class file. The sectionUsing a ListResource Bundle has a coding example you may find helpful.
The ResourceBundle class is flexible. If you first put your locale-specific String objects in a PropertyResourceBundle and then later decided to use ListResourceBundle instead, there is no impact on your code. For example, the following call to getBundle will retrieve a ResourceBundle for the appropriate Locale, whether ButtonLabel is backed up by a class or by a properties file:ResourceBundle introLabels =ResourceBundle.getBundle("ButtonLabel", currentLocale);Key-Value Pairs
ResourceBundle objects contain an array of key-value pairs. You specify the key, which must be a String, when you want to retrieve the value from the ResourceBundle. The value is the locale-specific object. The keys in the following example are the OkKey and CancelKey strings:class ButtonLabel_en extends ListResourceBundle {// English versionpublic Object[][] getContents() {return contents;}static final Object[][] contents = {{"OkKey", "OK"},{"CancelKey", "Cancel"},};}
To retrieve the OK String from the ResourceBundle, you would specify the appropriate key when invoking getString:String okLabel = ButtonLabel.getString("OkKey");
A properties file contains key-value pairs. The key is on the left side of the equal sign, and the value is on the right. Each pair is on a separate line. The values may represent String objects only. The following example shows the contents of a properties file named ButtonLabel.properties:OkKey = OKCancelKey = Cancel******************************************************************Preparing to Use a ResourceBundleIdentifying the Locale-Specific ObjectsIf your application has a user interface, it contains many locale-specific objects. To get started, you should go through your source
code and look for objects that vary with Locale. Your list might include objects instantiated from the following classes: ·         String ·         Image ·         Color ·         AudioClip You‘ll notice that this list doesn‘t contain objects representing numbers, dates, times, or currencies. The display format of these objects
varies with Locale, but the objects themselves do not. For example, you format a Date according to Locale, but you use the same Date object
regardless of Locale. Instead of isolating these objects in a ResourceBundle, you format them with special locale-sensitive formatting classes.
You‘ll learn how to do this in theDates and Times section of theFormatting lesson. In general, the objects stored in a ResourceBundle are predefined and ship with the product. These objects are not modified while the program is
running. For instance, you should store a Menu label in a ResourceBundle because it is locale-specific and will not change during the program
session. However, you should not isolate in a ResourceBundle a String object the end user enters in a TextField. Data such as this String may vary
from day to day. It is specific to the program session, not to the Locale in which the program runs. Usually most of the objects you need to isolate in a ResourceBundle are String objects. However, not all String objects are locale-specific.
For example, if a String is a protocol element used by interprocess communication, it doesn‘t need to be localized, because the end users never
see it. The decision whether to localize some String objects is not always clear. Log files are a good example. If a log file is written by one program
and read by another, both programs are using the log file as a buffer for communication. Suppose that end users occasionally check the contents of
this log file. Shouldn‘t the log file be localized? On the other hand, if end users rarely check the log file, the cost of translation may not be
worthwhile. Your decision to localize this log file depends on a number of factors: program design, ease of use, cost of translation, and supportability.
Organizing ResourceBundle ObjectsYou can organize your ResourceBundle objects according to the category of objects they contain. For example, you might want to load all of the GUI labels
for an order entry window into a ResourceBundle called OrderLabelsBundle. Using multiple ResourceBundle objects offers several advantages: ·         Your code is easier to read and to maintain. ·         You‘ll avoid huge ResourceBundle objects, which may take too long to load into memory. ·         You can reduce memory usage by loading each ResourceBundle only when needed. ***************************************************** Backing a ResourceBundle with Properties Files
This section steps through a sample program namedPropertiesDemo. 1. Create the Default Properties File
A properties file is a simple text file. You can create and maintain a properties file with just about any text editor.
You should always create a default properties file. The name of this file begins with the base name of your ResourceBundle and
ends with the .properties suffix. In the PropertiesDemo program the base name is LabelsBundle. Therefore the default properties file is
called LabelsBundle.properties. This file contains the following lines: # This is the default LabelsBundle.properties files1 = computers2 = disks3 = monitors4 = keyboard
Note that in the preceding file the comment lines begin with a pound sign (#). The other lines contain key-value pairs. The key is
on the left side of the equal sign and the value is on the right. For instance, s2 is the key that corresponds to the value disk. The key is arbitrary. We could have called s2 something else, like msg5 or diskID. Once defined, however, the key should not change because it is referenced in the source code. The values may be changed. In fact, when your localizers create new properties files to accommodate additional languages, they will translate the values into various languages.
2. Create Additional Properties Files as Needed
To support an additional Locale, your localizers will create a new properties file that contains the translated values. No changes to
your source code are required, because your program references the keys, not the values.
For example, to add support for the German language, your localizers would translate the values in LabelsBundle.properties and place
them in a file named LabelsBundle_de.properties. Notice that the name of this file, like that of the default file, begins with the base
name LabelsBundle and ends with the .properties suffix. However, since this file is intended for a specific Locale, the base name is
followed by the language code (de). The contents of LabelsBundle_de.properties are as follows: # This is the LabelsBundle_de.properties files1 = Computers2 = Plattes3 = Monitors4 = TastaturThe PropertiesDemo sample program ships with three properties files: LabelsBundle.propertiesLabelsBundle_de.propertiesLabelsBundle_fr.properties3. Specify the Locale
The PropertiesDemo program creates the Locale objects as follows: Locale[] supportedLocales = {Locale.FRENCH,Locale.GERMAN,Locale.ENGLISH};
These Locale objects should match the properties files created in the previous two steps. For example, the Locale.FRENCH object
corresponds to the LabelsBundle_fr.properties file. The Locale.ENGLISH has no matching LabelsBundle_en.properties file, so the default
file will be used.
4. Create the ResourceBundle
This step shows how the Locale, the properties files, and the ResourceBundle are related. To create the ResourceBundle, invoke the
getBundlemethod, specifying the base name and Locale:
ResourceBundle labels =ResourceBundle.getBundle("LabelsBundle", currentLocale);
The getBundle method first looks for a class file that matches the base name and the Locale. If it can‘t find a class file, it then
checks for properties files. In the PropertiesDemo program we‘re backing the ResourceBundle with properties files instead of class files.
When the getBundle method locates the correct properties file, it returns a PropertyResourceBundle object containing the key-value pairs
from the properties file.
5. Fetch the Localized Text
To retrieve the translated value from the ResourceBundle, invoke the getString method as follows: String value = labels.getString(key);
The String returned by getString corresponds to the key specified. The String is in the proper language, provided that a properties file
exists for the specified Locale.
6. Iterate through All the Keys
This step is optional. When debugging your program, you might want to fetch values for all of the keys in a ResourceBundle. The getKeys
method returns an Enumeration of all the keys in a ResourceBundle. You can iterate through the Enumeration and fetch each value with the
getString method. The following lines of code, which are from the PropertiesDemo program, show how this is done:
ResourceBundle labels =ResourceBundle.getBundle("LabelsBundle", currentLocale);Enumeration bundleKeys = labels.getKeys();while (bundleKeys.hasMoreElements()) {String key = (String)bundleKeys.nextElement();String value = labels.getString(key);System.out.println("key = " + key + ", " +"value = " + value);}7. Run the Demo Program
Running the PropertiesDemo program generates the following output. The first three lines show the values returned by getString for various
Locale objects. The program displays the last four lines when iterating through the keys with the getKeys method.
Locale = fr, key = s2, value = Disque durLocale = de, key = s2, value = PlatteLocale = en, key = s2, value = diskkey = s4, value = Clavierkey = s3, value = Moniteurkey = s2, value = Disque durkey = s1, value = Ordinateur ***********************************************************************************************************
Using a ListResourceBundle
This section illustrates the use of a ListResourceBundle object with a sample program calledListDemo. The text that follows explains each step involved in creating the ListDemo program, along with the ListResourceBundle subclasses that support it. 1. Create the ListResourceBundle Subclasses
A ListResourceBundle is backed up by a class file. Therefore the first step is to create a class file for every supported Locale. In the ListDemo program the base name of the ListResourceBundle is StatsBundle. Since ListDemo supports three Locale objects, it requires the following three class files:StatsBundle_en_CA.classStatsBundle_fr_FR.classStatsBundle_ja_JP.class
The StatsBundle class for Japan is defined in the source code that follows. Note that the class name is constructed by appending the language and country codes to the base name of the ListResourceBundle. Inside the class the two-dimensional contents array is initialized with the key-value pairs. The keys are the first element in each pair: GDP, Population, and Literacy. The keys must be String objects and they must be the same in every class in the StatsBundle set. The values can be any type of object. In this example the values are two Integer objects and a Double object.import java.util.*;public class StatsBundle_ja_JP extends ListResourceBundle {public Object[][] getContents() {return contents;}private Object[][] contents = {{ "GDP", new Integer(21300) },{ "Population", new Integer(125449703) },{ "Literacy", new Double(0.99) },};}2. Specify the Locale
The ListDemo program defines the Locale objects as follows:Locale[] supportedLocales = {new Locale("en", "CA"),new Locale("ja", "JP"),new Locale("fr", "FR")};Each Locale object corresponds to one of the StatsBundle classes. For example, the Japanese Locale, which was defined with the ja and JP codes, matches StatsBundle_ja_JP.class. 3. Create the ResourceBundle
To create the ListResourceBundle, invoke the getBundle method. The following line of code specifies the base name of the class (StatsBundle) and the Locale:ResourceBundle stats =ResourceBundle.getBundle("StatsBundle", currentLocale);
The getBundle method searches for a class whose name begins with StatsBundle and is followed by the language and country codes of the specified Locale. If the currentLocale is created with the ja and JP codes, getBundle returns a ListResourceBundle corresponding to the class StatsBundle_ja_JP, for example.
4. Fetch the Localized Objects
Now that the program has a ListResourceBundle for the appropriate Locale, it can fetch the localized objects by their keys. The following line of code retrieves the literacy rate by invoking getObject with the Literacy key parameter. Since getObject returns an object, cast it to a Double:Double lit = (Double)stats.getObject("Literacy");5. Run the Demo Program
ListDemo program prints the data it fetched with the getBundle method:Locale = en_CAGDP = 24400Population = 28802671Literacy = 0.97Locale = ja_JPGDP = 21300Population = 125449703Literacy = 0.99Locale = fr_FRGDP = 20200Population = 58317450Literacy = 0.99******************************************************* *******************************************************
Customizing Resource Bundle Loading
Earlier in this lesson you have learned how to create and access objects of the ResourceBundle class. This section extents your knowledge and explains how to take an advantage from theResourceBundel.Control class capabilities.
The ResourceBundle.Control was created to specify how to locate and instantiate resource bundles. It defines a set of callback methods that are invoked by theResourceBundle.getBundle factory methods during the bundle loading process.
Unlike aResourceBundle.getBundle method described earlier, this ResourceBundle.getBundle method defines a resource bundle using the specified base name, the default locale and the specified control.public static final ResourceBundle getBundle(String baseName,ResourceBundle.Control cont...
The specified control provide information for the resource bundle loading process.
The following sample program calledRBControl.java illustrates how to define your own search paths for Chinese locales.1. Create the properties Files.
As it was described before you can load your resources either from classes or from properties files. These files contain descriptions for the following locales:RBControl.properties – GlobalRBControl_zh.properties – Language only: Simplified ChineeseRBControl_zh_cn.properties – Region only: ChinaRBControl_zh_hk.properties – Region only: Hong KongRBControl_zh_tw.properties – Taiwan
In this example an application creates a new locale for the Hong Kong region.2. Create a ResourceBundle instance.
As in the example in the previous section, this application creates a ResourceBundle instance by invoking the getBundle method: private static void test(Locale locale) {ResourceBundle rb = ResourceBundle.getBundle("RBControl", locale,new ResourceBundle.Control() {...
The getBundle method searches for properties files with the RBControl prefix. However, this method contains a Control parameter, which drives the process of searching the Chineese locales.3. Invoke the getCandidateLocales method
The getCandidateLocales method returns a list of the Locales objects as candidate locales for the base name and locale. new ResourceBundle.Control() {@Overridepublic ListThe default implementation returns a list of the Locale objects as follows: Locale(language, country).
However, this method is overriden to implement the following specific behavior: {if (baseName == null)throw new NullPointerException();if (locale.equals(new Locale("zh", "HK"))) {return Arrays.asList(locale,Locale.TAIWAN,// no Locale.CHINESE hereLocale.ROOT);} else if (locale.equals(Locale.TAIWAN)) {return Arrays.asList(locale,// no Locale.CHINESE hereLocale.ROOT);}Note, that the last element of the sequence of candidate locales must be a root locale.4. Call the test class
Call the test class for the following four different locales: public static void main(String[] args) {test(Locale.CHINA);test(new Locale("zh", "HK"));test(Locale.TAIWAN);test(Locale.CANADA);}5. Run the Sample Program
You will see the program output as follows:locale: zh_CNregion: Chinalanguage: Simplified Chineselocale: zh_HKregion: Hong Konglanguage: Traditional Chineselocale: zh_TWregion: Taiwanlanguage: Traditional Chineselocale: en_CAregion: globallanguage: English
Note that the newly created was assigned the Hong Kong region, because it was specified in an appropriate properties file. Traditional Chinese was assigned as the language for the Taiwan locale.
Two other interesting methods of the ResourceBundle.Control class were not used in the RBControl example, but they deserved to be mentioned. The getTimeToLive method is used to determine how long the resource bundle can exist in the cache. If the time limit for a resource bundle in the cache has expired, the needsReload method is invoked to determine whether the resource bundle needs to be reloaded.