10.4 Localized Resources
Resource bundles enable you to store and access locale-dependent data. This is an important component of internationalizing your application, because it separates your code from information that needs to be localized.
In the Internationalization Module, resource bundles are represented by RWUResourceBundle.
10.4.1 The Resource Hierarchy
Although the resource bundles for an application are placed in a single directory in the file system, they are organized into a logical hierarchy based upon the locales associated with the bundles. At the base of the hierarchy is the root resource bundle. You create the root resource bundle for your application when you develop it, with resource values expressed in accordance with your own locale. (See Section 10.4.2.)
When your application is localized for a particular locale--either by you or by a software translator at some time in the future--a new resource bundle is created that translates the values in the root resource bundle for the new locale.
In the logical resource hierarchy, immediately below the root resource bundle are language-specific bundles; for example, de, en, and ja bundles. Below the language-specific bundles are country-specific bundles; for example, de_DE and de_CH. Below the country-specific bundles are variant-specific bundles; for example, de_DE_PREEURO. A sample resource hierarchy is illustrated in Figure 1.
Figure 1 – Sample resource bundle hierarchy
Not every bundle needs a resource value for each localizable aspect of the application. A fallback mechanism (see Section 10.4.5 and Section 10.4.6) ensures that a child resource bundle need only contain the resources whose values differ from its ancestor bundles.
10.4.2 Defining Resource Bundles
Resource bundles contain one or more resources. They are created as text files using the simple syntax described below, then compiled into an efficient, binary form (see Section 10.4.3). Resource text may be in any recognized encoding; it is converted to Unicode when you compile the text file.
Resources may be simple or complex. Simple resources hold a single value. Complex resources hold one or more simple or complex resources. Simple resources are specified in the text resource files using the following syntax:
 
name:type { value }
A simple resource can be of type string, integer, integer vector, or binary:
*Strings
Strings hold text. For example:
 
helloWorldMessage:string { "Hello World" }
Because string values are so common in resource bundles, the type, the double quotes, or both may be omitted for brevity:
 
helloWorldMessage { Hello World }
The type is required for all other simple resources, so ambiguities do not occur.
*Integers
Integers hold 32-bit integer values. For example:
 
numStatesOrProvinces:int { 50 }
*Integer Vectors
Integer vectors hold vectors of 32-bit integer values. For example:
 
buttonSize:intVector { 10, 15, 10, 15 }
*Binaries
Binary values store binary data, such as processed data or images. For example:
 
pgpkey:bin { a1b2c3d4e5f67890 }
Alternatively, you can import a binary file:
 
logo:import { "logo.gif" }
Complex resources can be arranged as a table or as an array:
*Tables
Tables are collections that hold named resources. The name of each resource is a key into the table. Tables are specified using the following syntax:
 
name:table {
key1 { subresource1 }
key2 { subresource2 }
...
keyN { subresourceN }
}
For example:
 
salutations:table {
morningGreeting:string { "Good morning" }
afternoonGreeting:string { "Good afternoon" }
eveningGreeting:string { "Good evening" }
}
*Arrays
Arrays are collections that hold unnamed resources. Elements in an array are accessed by integer index. Arrays are specified using the following syntax:
 
name:array { subresource1, subresource2, ..., subresourceN }
For example:
 
AmPmMarkers:array { "AM", "PM" }
Note that tables and arrays may themselves contain other tables and arrays, not just simple resources. As with all resources held by arrays, a table held within an array must not be named.
Because tables and arrays can be disambiguated from each other solely on the basis of their syntactic form, you may omit the type information from complex resources. For example, this resource is unambiguously an array:
 
AmPmMarkers { "AM", "PM" }
Resource bundles are themselves tables, named after the locale they are associated with. For example, this is a simple root resource bundle:
 
root {
 
Version { "2.1.0" }
salutations {
morningGreeting { "Good morning" }
afternoonGreeting { "Good afternoon" }
eveningGreeting { "Good night" }
}
}
To define the root resource bundle for your application, gather all the application data that may need to be translated or localized, express it in the resource syntax described above, and save it in a text file called root.txt.
The resources in the root bundle can be localized, as necessary, to particular locales. For example, this might be a localization of the resources above to the es locale, saved in a text file called es.txt:
es {
salutations {
morningGreeting { "Buenos días" }
afternoonGreeting { "Buenas tardes" }
eveningGreeting { "Buenos noches" }
}
}
Note that the Version resource is not localized. If that resource is sought in the es resource bundle, the fallback mechanism ensures it is found in the root resource bundle.
10.4.3 Compiling Resource Bundles
Resource definitions in a text file are compiled into a compact, binary form using the supplied genrb utility, located in:
*Windows
icu-root-dir\bin
*UNIX
icu-root-dir/sbin
...where icu-root-dir is the location where your ICU distribution is installed. If you are using the Rogue Wave-provided ICU distribution, see Chapter 4 in Building Your Applications for the location of those libraries.
The genrb reads text files (see Section 10.4.2), and outputs .res binary resource bundle files. For example:
 
genrb en_US.txt
creates the resource bundle en_US.res. The complete syntax for genrb is:
 
genrb [options] [files]
where files is a list of text files. Some of the more useful options are:
*-s followed by a path specifies the source directory for text files
*-d followed by a path specifies the destination directory for .res resource bundles
*-e followed by an encoding name specifies that strings in the text files are in the specified encoding
*-p followed by package-name prepends package-name_ to the name of the resource bundle.
From ICU 2.2 on, the resource bundle’s pathname is required to contain this prepended package name.
For instance, if your data path is "/usr/local/resources/myApp/" and the locale in question "en_US", older ICU versions look for a file named "/usr/local/resources/myApp/en_US.res", but ICU version 2.2 and above will instead try to find "/usr/local/resources/myapp/myApp_en_US.res".
You may use the genrb utility to update your resource filenames. To prepend the package-name "myApp_" to the resource file, simply add the argument "-p myApp" to the command line.
For a complete list of options, type genrb -h.
When you compile a resource bundle, any string data is converted from the text encoding to a Unicode format. However, to simplify the lookup process, keys are not converted to a Unicode representation, but are used exactly as encoded in the text file. Although you may use any single-byte character set for RWUResourceBundle keys, for maximum portability, it is best to limit the characters used for keys to the basic source character set described in Section 2.6. This set corresponds to the printable ASCII or Latin-1 character set.
Note that binary data, as well as keyed data, are compiled into a platform-specific form. As a result, table data may not be found on a platform where the character encoding (ASCII or EBCDIC, for instance) used for the keys is different than that where compiled. For similar reasons, binary data may be wrong if compiled on a big-endian machine and read on a little-endian machine, or vice versa.
10.4.4 Packaging Resource Bundles
If you have many resource (.res) files, you can package them into a single combined file using the supplied pkgdata utility, located in the same directory as the genrb utility. This tool accepts a list of resource files, and creates a combined file with a .dat extension. For a complete list of options, type pkgdata -h.
10.4.5 Retrieving a Resource Bundle
In the Internationalization Module, when an application needs localized data, it constructs an RWUResourceBundle, then queries the instance for the needed resources. The constructors for RWUResourceBundle accept the name of a directory containing .res or .dat files, and the name of a locale. For example:
 
RWUResourceBundle enUSData =
RWUResourceBundle("C:\\data", "en_US");
If no locale is given, or an empty or null locale is passed, the default locale is used. (See Section 10.3.3.)
The constructor may succeed, succeed through fallback, or fail, depending on whether an exact match to the requested locale is found, a fallback match is found, or no bundle at all is found; for instance, if the resource path does not exist.
The fallback mechanism works as follows:
1. The library first looks for the exact named RWUResourceBundle.
2. Failing that, the library looks for a parent locale along the named locale’s branch in the resource hierarchy (see Section 10.4.1).
3. If no parent locale is found, the library looks for the locale named by RWULocale::getDefault().
4. Failing that, the library looks for a parent locale along the default locale’s branch in the resource hierarchy.
5. Failing that, the library attempts to open the RWUResourceBundle named root.
6. Finally, if it can find no information for any of the above mentioned locales, the library throws an RWUException.
For example, suppose the RWUResourceBundles shown in Figure 2 are defined, and that the current default RWULocale is es_ES.
Figure 2 – Sample resource bundle hierarchy
In this case, if you attempt to open the RWUResourceBundle named ja_JP, the library tries these bundle names in order:
1. ja_JP -- the exact match
2. ja -- the parent
3. es_ES -- the default locale
4. es -- the default parent: Success!
5. if es been missing, root would have been tried next
Constructors throw an RWUException if no bundle is found, but otherwise succeed and cache an RWUStatusCode to indicate the level of success. The getStatus() method returns an RWUStatusCode indicating whether fallback occurred during the creation or retrieval of the resource bundle:
*RWUNoError
There was a bundle with an exact match for the requested RWULocale.
*RWUUsingFallbackWarning
There was a bundle with a fallback match for the requested RWULocale, but there was no exact match.
*RWUUsingDefaultWarning
Neither a bundle for the requested RWULocale nor any of its parents was found. The newly-constructed RWUResourceBundle contains data associated with the current RWULocale::getDefault() locale, with one of its parents, or with the root RWUResourceBundle.
If fallback occurred, you can use method getLocale() to return the actual locale where the resource bundle was found, as opposed to the locale originally requested. Method getLocaleName() returns the name of the locale; it is a convenience synonym for getLocale().getName().
Note that it is normal to expect fallbacks. Some programs don’t need to distinguish resources for all locales.
10.4.6 Accessing a Resource
In the Internationalization Module, resources within a resource bundle are themselves represented as RWUResourceBundles, so RWUResourceBundle instances nest recursively. The RWUResourceBundle instances created by the constructors are called “top-level” resource bundles. Top-level resource bundles are guaranteed to be of type table, containing one or more top-level resources associated with const char* keys.
You can access a top-level resource using the get() method. This method returns the requested resource as an RWUResourceBundle. For example:
 
RWUResourceBundle rb = RWUResourceBundle("C:\\data", "root");
RWUResourceBundle myResource = rb.get("salutations");
When the top-level RWUResourceBundle is queried for a particular top-level resource, the query may succeed, succeed through fallback, or fail.
The fallback mechanism works as follows:
1. The library first looks for the named resource in the currently open RWUResourceBundle.
2. Failing that, the library looks for the named resource in a parent of the currently open RWUResourceBundle.
3. Failing that, the library looks in the root resource bundle.
4. Failing that, the library throws an RWUException.
For example, suppose that in the es resource bundle there is a table named menu containing entries for file, edit, and help. Suppose also that the root resource bundle has, in addition to the menu table, a string resource named currentVersion. If you attempt to access the menu table resource, the library returns the one in the es RWUResourceBundle. If you attempt to access the currentVersion string resource, the library returns the one in root.
Note that fallback occurs only for top-level resources. When you open a resource held within a top level array or table, the library looks only in the already open top-level RWUResourceBundle. If the requested datum is not there, an RWUException is thrown. For example, continuing from the example above, suppose you have opened the menu resource, and now are looking for the tools string within the menu table. Suppose furthermore this entry is available in the menu resource in the root resource bundle, but not in the menu resource in the es resource bundle. Your attempt does not fall back; it fails.
The lookup query throws RWUException if it fails, but otherwise returns an RWUResourceBundle holding a cached RWUStatusCode. The getStatus() method returns the status code:
*RWUNoError
The queried bundle held the resource directly.
*RWUUsingFallbackWarning
The requested bundle was at the top level of a locale’s RWUResourceBundle, but was not found in that RWUResourceBundle directly. It was found in a parent of that bundle.
*RWUUsingDefaultWarning
The requested bundle was at the top-level of a locale’s RWUResourceBundle, but was not found in that RWUResourceBundle directly; instead, the resource was found in the root bundle via fallback.
The get() method returns the requested resource as an RWUResourceBundle. If you know the type of the resource, you can then access it using the appropriate method:
*Strings
The getString() method returns the resource as an RWUString. For example:
 
RWUString s = rb.get("Version").getString();
Because string resources are so common, a getString(const RWCString&) method is also provided as a convenience for get(const RWCString&).getString():
 
RWUString s = rb.getString("Version");
*Integers
The getInt() method returns the resource as a signed int32_t; the getUInt() method returns it as an unsigned int32_t. Thus:
 
int32_t i = rb.get("numStatesOrProvinces").getInt();
*Integer Vectors
The getIntVector() method returns the resource as a pointer to int32_t. Call getSize() to find the number of integers in the vector.
*Binaries
The getBinary() method returns the resource as a pointer to an array of unsigned bytes. Call getSize() to find the number of bytes.
*Tables
As described above for top-level resources, which are tables, the get(const RWCString&) method returns the resource in a table associated with the given string key. If the resource is an element of a table, the getKey() method returns the key of the resource.
*Arrays
The get(int32_t) method returns the resource located at the given offset index within an array. For example:
 
RWUString s = rb.get("AmPmMarkers").get(1).getString();
Because string resources are so common, a getString(int32_t) method is also provided as a convenience for get(int32_t).getString():
 
RWUString s = rb.get("AmPmMarkers").getString(1);
All getX() methods throw RWUException if the resource is not of the specified type. If you don’t know the type of a resource held by an RWUResourceBundle, you can query it using the getType() method, which returns a value from the ResourceType enumeration: None, String, Int, IntVec, Binary, Table, or Array. For example:
 
if (rb.getType() == RWUResourceBundle::Int) {
int i = rb.getInt();
}