| Red Hat Docs > Manuals > Red Hat Web Application Framework > |
This document describes how to use the internationalization (I18N) features of Java and Red Hat Web Application Framework to localize your web application.
Internationalization (I18N) is the process of designing and implementing an application to facilitate localization. Localization (L10N) is the process of adapting an internationalized application to one or more culturally and/or linguistically different markets. A Locale represents a particular culture/language/region combination. Globalization (G11N) is the term that encompasses the entire process of internationalization through localization of an application.
Unlike previous versions of CCM, Red Hat Web Application Framework implicitly supports globalization. By following good programming practices and the guidelines set forth in this document, you can ensure that your application will be ready for localization without very much additional effort.
You should not have to re-compile your application (or Red Hat Web Application Framework) to localize it for a particular culture and language. All binaries should remain the same through the localization process.
Red Hat Web Application Framework supports globalization in a variety of ways:
It allows the user to select a preferred Locale, either by registering a preference with Red Hat Web Application Framework, or by sending the appropriate Accept-Language HTTP header with each request.
It allows the user to send and receive data in his or her preferred character set encoding.
It provides mechanisms for storing multiple localized versions of resources for an application. This is true for both static and dynamic resources.
It offers APIs for creating and accessing these localized resources.
It provides a globalized WUI toolkit (Bebop).
It allows stylesheets (used for styling of content) to be associated with multiple Locales, thereby allowing you to create different layouts based on Locale. These will be applied automatically by the system as determined by the user's preferred Locale.
When a user requests a URL, the server determines the appropriate Locale for the request. It does this by negotiating between the client's preferences and the server's capabilities.
All valid URLs on the server are associated with an instance of some application. If that application instance has an associated Locale, this will be used as the Locale for the request. If the application instance does not have an associated Locale, the server checks whether the user has a preferred Locale as kept by the Red Hat Web Application Framework preferences service. If so, and if that Locale is supported by the application, that is the chosen Locale for the request. If not, the server continues on to check the Accept-Language HTTP headers. Checking each of the values in this HTTP header in order of descending q-value, the server selects the first one that is supported by the application being served. If no Locale can be determined, the server finally selects the default Locale for the server as the Locale for the request. This algorithm is well documented in the LocaleNegotiator class.
This negotiation allows applications to support one or many languages without interacting with other applications on the system. Thus, your application automatically tries to present itself to the user in his or her preferred language, regardless of the other applications running on that server. Note that if your application relies on a service provided by another package, that package must also be globalized and must support any languages your application is intended to support. For example, if your application relies on the Places service to relate and/or present geographical data, you must ensure that this service supports all the Locales that you wish your application to support.
You can retrieve the Locale for the current request programmatically by calling the getLocale() method of the current RequestContext. For example:
java.util.Locale locale =
DispatcherHelper.getRequestContext(request).getLocale();
System.out.println("The locale for the current request is: "
+ locale.toString());
|
Character set encoding negotiation, from now on referred to as charset negotiation, is more complicated than Locale negotiation.
TODO: Add description of charset negotiation algorithm.
ResourceBundle s are Java classes for grouping localizable resources. The class also provides methods for finding the appropriate ResourceBundle based on a Locale and for retrieving resources from it by performing a lookup based on a key.
ResourceBundles have a base name and optionally an associated Locale. A base name is basically a namespace used to group resources. A ResourceBundle without an associated Locale is the default ResourceBundle for that base name. If a ResourceBundle for a particular Locale hierarchy is not found, the ResourceBundle with no associated Locale is used. These are used to store resources that are common across all Locales.
The Locale hierarchy is simply the mechanism that is used to fall back from very specific Locales to more generic ones. For example, from en_US_WIN95, the hierarchy falls back to en_US and then to en and finally to the empty Locale.
Typically, one base name is used per application. That is, all resources for an application or service are grouped in the same ResourceBundle. In cases where this is not convenient, different base names can be used.
An example of the normal use of ResourceBundles is the Notes application. The base name it uses is com.arsdigita.notes.NotesResources. It groups all its localizable resources into that ResourceBundle. On the other hand, the Places service uses multiple base names, since it maintains a large amount of data, which may not be needed by all developers. For example, a developer might just want to translate the names of countries into a particular language, and not require any other place data. Therefore, the Places service divides its resources into different base names, including com.arsdigita.places.CountriesResources, com.arsdigita.places.USStatesResources, and so on.
It is recommended that you use PropertyResourceBundle s to store static resources. Static resources remain the same for the lifetime of the running server. This includes strings on WUI elements (such as form labels, page titles, and error messages) and names of objects that do not change frequently (such as country and city names).
For example, PropertyResourceBundles might appear as follows for a HelloWorld application, in English, Spanish, and French, respectively:
HelloWorldResources_en.properties:
hello_world=Hello world!
|
HelloWorldResources_es.properties:
hello_world=Hola todo el mundo!
|
HelloWorldResources_fr.properties:
hello_world=Salut tout le monde!
|
For dynamic resources, the MessageCatalog class is provided. Dynamic resources are those that change while the server is running. Most of these have to do with user-contributed content, where a user can be an administrator of the site or a regular user. For example, if the Categorization service was internationalized and application users were to be allowed to create categories, the application could allow the user to create a category and name it in more than one language, or mark the category name for translation into the different languages that the application supports. Since this kind of data is hard to maintain in PropertyResourceBundles, it is stored it in database-backed MessageCatalogs.
A MessageCatalog is simply a way to store ResourceBundles in the database. To use them, you create a MixedResourceBundle . This inherits from Java's ResourceBundle and combines a PropertyResourceBundle and a MessageCatalog, which have the same base name, into one. It does this by reading the PropertyResourceBundle from the file system and the MessageCatalog from the database.
No code goes into a MixedResourceBundle; all the code is in the superclass itself. A MixedResourceBundle for the Notes application might appear as follows:
package com.arsdigita.notes;
import com.arsdigita.globalization.MixedResourceBundle;
public class NotesResources_en_US extends MixedResourceBundle {}
|
Note that this MixedResourceBundle is for the base name com.arsdigita.notes.NotesResources and the Locale en_US. This stems from the ResourceBundle name and indicates the Locale associated with the ResourceBundle. Thus, this ResourceBundle should contain resources in American English.
The GlobalizedMessage class is specifically designed to facilitate management and access to localized resources. This class represents a single globalized resource. It contains the base name of the ResourceBundle, which contains the resource and the key to use to lookup the resource, and an optional array of arguments to interpolate into the localized resource using Java's MessageFormat class. It also contains a localize(java.util.Locale) method, which is used to localize the resource to a particular Locale.
You should use this class to represent all localizable data for your application. Later in this document, you will learn how to pass GlobalizedMessages to Bebop components so that they will perform the lookup when it is time to display the localized resource to the user.
The following example demonstrates how to access the hello_world resource defined in the PropertyResourceBundles above. Assuming that those files are in the com.arsdigita.helloworld package, you would create a GlobalizedMessage to represent that resource and localize it as follows:
GlobalizedMessage message = new GlobalizedMessage(
"hello_world",
"com.arsdigita.helloworld.HelloWorldResources"
);
// print in English:
System.out.println((String) message.localize(new Locale("en", "", "")));
// print in Spanish:
System.out.println((String) message.localize(new Locale("es", "", "")));
// print in French:
System.out.println((String) message.localize(new Locale("fr", "", "")));
|
The output will appear as follows:
Hello world!
Hola todo el mundo!
Salut tout le monde!
|
You can also parameterize resources that will be interpolated using the MessageFormat class. The next example will use the following PropertyResourceBundles:
WineResources_en.properties:
this_wine_is=This is a {0} wine.
wine_color=red
|
WineResources_es.properties:
this_wine_is=Este es un vino {0}.
wine_color=rojo
|
WineResources_fr.properties:
this_wine_is=C'est un vin {0}.
wine_color=rouge
|
You can create a GlobalizedMessage to perform the proper interpolation of the resource. (Imagine that the wine_color resource is retrieved from a MessageCatalog and is not stored statically in the PropertyResourceBundle).
GlobalizedMessage message = new GlobalizedMessage(
"this_wine_is",
"com.arsdigita.wine.WineResources",
{"wine_color"}
);
// print in English:
System.out.println((String) message.localize(new Locale("en", "", "")));
// print in Spanish:
System.out.println((String) message.localize(new Locale("es", "", "")));
// print in French:
System.out.println((String) message.localize(new Locale("fr", "", "")));
|
The output will appear as follows:
This is a red wine.
Este es un vino rojo.
C'est un vin wine_color.
|
Note that when a key is not found in a ResourceBundle, or when the ResourceBundle is not found at all, the localize(java.util.Locale) method returns the key. This is done for two reasons: first, it is more user-friendly to display the key than it is to display an exception or error message. Second, this method makes it easier for a translator to see all the resources that need to be translated on a particular page.
There are also GlobalizedMessage constructors that do not have the base name of the ResourceBundle as a parameter. When these constructors are used (which is usually discouraged), the base name of the ResourceBundle for the current application is used. This information is retrieved from the current ApplicationContext. This is discouraged because this method is non-deterministic with regard to changes in the application stack. That is, if someone writes a Portal application that runs on top of your application (Notes, for example), your application is no longer the current running application, and therefore the base name of the ResourceBundle for your GlobalizedMessages will be set incorrectly. It will be set to the base name of the ResourceBundle for the Portal application. Only use these constructors if you are sure that your application will always be at the top of the application stack.
Bebop is the WUI toolkit developed by ArsDigita. It is already set up to use GlobalizedMessages wherever the localizable content is displayed to the user.
To display localized content to the user using Bebop, you pass a GlobalizedMessage to the Bebop widget. For example, to write a globalized HelloWorld page using Bebop:
package com.arsdigita.helloworld;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Page;
import com.arsdigita.dispatcher.Dispatcher;
import com.arsdigita.globalization.GlobalizedMessage;
public class HelloWorldDispatcher extends Dispatcher {
// do some stuff, set up URL to method map, etc.
private Page buildHelloWorldPage() {
Label message = new Label(new GlobalizedMessage(
"hello_world",
"com.arsdigita.helloworld.HelloWorldResources"
));
Page page = new Page(message);
page.add(message);
page.lock();
return page;
}
}
|
When the Page is served, Bebop will automatically call the localize(java.util.Locale) method of each of the GlobalizedMessages on the Page with the Locale of the current request as an argument. This will allow the pages to be displayed to each user in his or her preferred language.
Red Hat Web Application Framework uses XSL stylesheets to lay out XML content that is usually produced by Bebop, but which could be produced by anyone. These stylesheets can be associated with Locales. This allows you to lay out your Page differently based on Locale.
Stylesheets can also contain static text. This text can be translated in another stylesheet that is associated with the appropriate Locale.
The system selects the stylesheet to use for each request by matching it to the current request's Locale. It performs matching in the same way that ResourceBundles do, by using the Locale hierarchy.
The com.arsdigita.globalization Java package. This package contains all the classes, interfaces, and utilities for handling globalization situations. Included are utilities for Locale negotiation, localization of objects, and bundling of localizable resources, among other useful classes.
The com.arsdigita.bebop Java package. This package contains globalized bebop components that can handle display, input, and output of Locale-sensitive data.
The java.text Java package. This class contains most of Sun's globalization classes.
The java.util.Locale class is Sun's representation of a Locale.
The java.util.ResourceBundle class and its subclasses help maintain and retrieve externalized localized resources.