Java provides a rich set of I18N features in the core library. This section briefly discusses a few of those core features. The I18N support in the Struts framework relies heavily on these components, and understanding how the Java I18N components cooperate with each other will help you understand how to internationalize your Struts applications.
The topic of internationalization is too broad to cover in depth in this book. A more complete discussion of the topic can be found in the book JavaInternationalization by Andy Deitsch and David Czarnecki (O'Reilly).
The java.util.Locale class is undeniably the most important I18N class in the Java library. Almost all of the support for internationalization and localization in or around the Java language relies on this class.
The Locale class provides Java with instances of the locale concept mentioned earlier. A particular instance of the Locale represents a unique language and region. When a class in the Java library modifies its functionality during runtime based on a Locale object, it's said to be locale-sensitive. For example, the java.text.DateFormat is locale-sensitive because it will format a date differently depending on a particular Locale object.
The Locale objects don't do any of the I18N formatting or parsing work. They are used as identifiers by the locale-sensitive classes. When you acquire an instance of the DateFormat class, you can pass in a Locale object for the United States. The DateFormat class does all of the locale-sensitive parsing and formatting; it relies on the Locale only to identify the proper format.
When you create a Locale object, you typically specify the language and country code. The following code fragment illustrates the creation of two Locale objects, one for the U.S. and the other for Great Britain:
Locale usLocale = new Locale("en", "US");
Locale gbLocale = new Locale("en", "GB");
The first argument in the constructor is the language code. The language code consists of two lowercase letters and must conform to the ISO-639 specification. You can find a complete listing of the available language codes at http://www.unicode.org/unicode/onlinedat/languages.html.
The second argument is the country code. It consists of two uppercase letters that must conform to the ISO-3166 specification. The list of available country codes is available at http://www.unicode.org/unicode/onlinedat/countries.html.
The Locale class provides several static convenience constants that allow you to acquire an instance of the most-often-used locales. For example, to get an instance of a Japanese locale, you could use either of these:
Locale locale1 = Locale.JAPAN;
Locale locale2 = new Locale("ja", "JP");
The JVM will query the operating system when it's first started and set a default locale for the environment. You can obtain the information for this default locale by calling the getDefault( ) method on the Locale class:
Locale defaultLocale = Locale.getDefault( );
The web container will normally use the default locale for its local environment, while using the one passed from the client in the HttpServletRequest to display locale-sensitive information back to the end user.
In the last section, you saw how to create Locale objects in Java by passing in the language and country code to the Locale constructor. Within web applications, including those built using the Struts framework, you rarely have to create your own locale instances because the container does it for you. The ServletRequest interface contains two methods that can be called to retrieve the locale preferences of a client:
public java.util.Locale getLocale( );
public java.util.Enumeration getLocales( );
Both of these methods use the Accept-Language header that is part of each client request sent to the servlet container.
|
Calling the getLocale( ) method on the HttpServletRequest object returns the preferred locale of the client, while the getLocales( ) method returns an Enumeration of preferred locales in decreasing order of preference. If a client doesn't have a preferred locale configured, the servlet container will return its default locale. Example 12-1 illustrates how to determine this information using a servlet.
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Locale;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Prints out information about a user's preferred locales
*/
public class LocaleServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html";
/**
* Initialize the servlet
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
/**
* Process the HTTP Get request
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter( );
out.println("<html>");
out.println("<head><title>The Example Locale Servlet</title></head>");
out.println("<body>");
// Retrieve and print out the user's preferred locale
Locale preferredLocale = request.getLocale( );
out.println("<p>The user's preffered Locale is " + preferredLocale + "</p>");
// Retrieve all of the supported locales of the user
out.println("<p>A list of preferred Locales in descreasing order</p>");
Enumeration allUserSupportedLocales = request.getLocales( );
out.println("<ul>");
while( allUserSupportedLocales.hasMoreElements( ) ){
Locale supportedLocale = (Locale)allUserSupportedLocales.nextElement( );
StringBuffer buf = new StringBuffer( );
buf.append("<li>");
buf.append("Locale: ");
buf.append( supportedLocale );
buf.append( " - " );
buf.append( supportedLocale.getDisplayName( ) );
buf.append("</li>");
// Print out the line for a single Locale
out.println( buf.toString( ) );
}
out.println("</ul>");
// Get the container's default locale
Locale servletContainerLocale = Locale.getDefault( );
out.println("<p>The container's Locale " + servletContainerLocale + "</p>");
out.println("</body></html>");
}
}
When you execute the servlet in Example 12-1, you should see output similar to the browser output in Figure 12-1.
The output may be different if you have different locales configured for your system. Most web browsers allow you to configure the locales you prefer to support. With Microsoft Internet Explorer, for example, you can edit the languages in the Tools Internet Options pulldown menu.
Getting the user's locale within the Struts framework is easy. There are, in fact, several ways of getting the stored Locale for the user, depending on where you are trying to access it. If you are within an Action class, for example, you can simply call the getLocale( ) method defined in the Struts base Action class. The following fragment shows the getLocale( ) method:
protected Locale getLocale(HttpServletRequest request) {
HttpSession session = request.getSession( );
Locale locale = (Locale) session.getAttribute(LOCALE_KEY);
if (locale == null)
locale = defaultLocale;
return (locale);
}
You will need to pass the request object to this method because it will need to use the HttpSession to obtain the locale.
The getLocale( ) method will always return an instance of the Locale, even if one isn't stored in the session for the user. The method will return the defaultLocale if necessary. The defaultLocale property is stored as a static member variable that every Action subclass has access to:
protected static Locale defaultLocale = Locale.getDefault( );
Obtaining the user's locale from anywhere else is also straightforward. You can simply get it directly from the session as the getLocale( ) method does, using the Action.LOCALE_KEY:
Locale userLocale = (Locale)session.getAttribute( Action.LOCALE_KEY);
// With this approach, always check the Locale to see if it's null
if ( userLocale != null ){
// Access the Locale
}
Because it's possible that no Locale is stored in the user's session, you should compare the returned Locale to null before attempting to use it.
If your application allows a user to change locales on the fly, you may have to call the getLocale( ) method on each new request to see if the user has changed locales. An example of doing this was shown in the CustomRequestProcessor class in Example 5-4.
The java.util.ResourceBundle class provides the ability to group together a set of resources for a given locale. The resources are usually textual elements such as field and button labels and status messages, but they can also be items such as image names, error messages, and page titles.
The Struts framework does not use the ResourceBundle class provided by the core language. Instead, it provides similar functionality with the classes within its framework. The org.apache.struts.util.MessageResources class and its only concrete subclass, org.apache.struts.util.PropertyMessageResources , are used to perform parallel functionality to that of the ResourceBundle hierarchy. If you understand the fundamentals of the ResourceBundle in the code library, you basically understand how the version within the Struts framework operates.
|
You'll see an example of creating a resource bundle for a Struts application later in this chapter in Section 12.3.1.
The Java ResourceBundle and the Struts MessageResources class allow for both static and dynamic text. Static text is used for elements such as field and button labels where the localized strings are used exactly as they are in the bundle—in other words, when the text for the message is known ahead of time. With dynamic text, part of the message may not be known until runtime. To help make the difference clearer, let's look at an example.
Suppose you need to display a message to the user informing him that the name and phone input fields are required in order to save. One approach would be to add entries like these to the resource bundle:
error.requiredfield.name=The Name field is required to save.
error.requiredfield.phone=The Phone field is required to save.
// other resource messages
This approach works fine, but what if there were hundreds of required fields? You would need a resource message for each required field, and the resource bundle would become very large and difficult to maintain. Note, however, that the only difference between the two messages is the name of the field that is required.
A much easier and more maintainable approach is to use the functionality of the java.text.MessageFormat class. This allows you to do something like this:
error.requiredfield=The {0} field is required to save.
label.phone=Phone
label.name=Name
The values that are not known until runtime are substituted in the message by a set of braces and an integer value. The integer inside the braces is used as an index into an Object[] that is passed in with the format( ) message of the MessageFormat class. Example 12-2 provides an example of this.
import java.util.ResourceBundle;
import java.util.Locale;
import java.text.MessageFormat;
public class FormatExample {
public static void main(String[] args) {
// Load the resource bundle
ResourceBundle bundle = ResourceBundle.getBundle( "ApplicationResources" );
// Get the message template
String requiredFieldMessage = bundle.getString( "error.requiredfield" );
// Create a String array of size one to hold the arguments
String[] messageArgs = new String[1];
// Get the "Name" field from the bundle and load it in as an argument
messageArgs[0] = bundle.getString( "label.name" );
// Format the message using the message and the arguments
String formattedNameMessage =
MessageFormat.format( requiredFieldMessage, messageArgs );
System.out.println( formattedNameMessage );
// Get the "Phone" field from the bundle and load it in as an argument
messageArgs[0] = bundle.getString( "label.phone" );
// Format the message using the message and the arguments
String formattedPhoneMessage =
MessageFormat.format( requiredFieldMessage, messageArgs );
System.out.println( formattedPhoneMessage );
}
}
Messages that contain variable data are known as compound messages. Using compound messages allows you to substitute application-specific data into messages from the resource bundle at runtime. It can also reduce the number of messages that your application requires in the resource bundle, which can decrease the amount of time that it takes to translate to other locales.
|
The Struts framework includes the capabilities of the MessageFormat class but encapsulates the functionality behind the components within the framework.
Most of us cringe at the thought of supporting user groups that are in one of several possible locales. In many cases, however, once an application has been installed and localized, it's like any other single-locale application. The users that access the application are either all from the same locale or are from locales similar enough that the language and cultural differences are insignificant.
Multilingual applications, on the other hand, take internationalization to the next level by allowing users from different locales to access the same application. This means that the application has to be flexible enough to detect the user's locale and format everything based on that locale. This is much harder to do than just localizing an application.
The discussion of building multilingual applications is so large that it can't be covered satisfactorily in this book. For the remainder of this chapter, we'll stick with just the everyday internationalization problems that you'll face, and not focus on multilingual support.