12.2 Support for I18N in Java

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 Java Internationalization by Andy Deitsch and David Czarnecki (O'Reilly).

12.2.1 The Locale Class

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.

Be careful when using the java.text.Format class or any of its descendants, including DateFormat, NumberFormat, and SimpleDateFormat, because they are not thread-safe. The thread-safety problem exists because an instance of the Calendar class is stored as a member variable and accessed during the parse() and format() method invocations. You will need to use a separate instance for each thread or synchronize access externally. Don't store a single instance in somewhere like the application scope and allow multiple client threads to access it. You can, however, store instances in the users' sessions and use different instances for each user to help ensure thread-safety. The thread-safety problem includes all versions of Java, including 1.4. The API documentation for the Format classes has been updated to indicate the known design issue.

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 default locale

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. Determining the user's locale

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.

Because the web server doesn't keep a long-term connection open with a browser, the client locale preference is sent to the servlet container with each request. Although the user's locale information may be sent with each request, Struts will, by default, retrieve the information only once and store it into the user's session. The Locale object, if stored into the session, is stored with a key of Globals.LOCALE_KEY, which translates to the string org.apache.struts.action.LOCALE.

You can configure whether Struts stores the user's locale into the session by setting the locale attribute in the controller element within the Struts application configuration file. If you don't provide a value for the locale attribute, it defaults to true. See Chapter 4 for more information on configuring the locale.

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.

Example 12-1. Determining the user's locale information in 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.

Figure 12-1. Browser output from Example 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.

etting 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(Globals.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(Globals.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.

12.2.2 Java Resource Bundles

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.

In retrospect, the MessageResources class should at least have been a subclass of the Java ResourceBundle.

You'll see an example of creating a resource bundle for a Struts application later in this chapter in the section "The Struts Resource Bundle."

12.2.3 The MessageFormat Class

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.

Example 12-2. Using the MessageFormat class to format messages with variable text
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.

Using compound messages in your resource bundles can make translation a little harder because the text contains substitution values that are not known until runtime. Also, human translators must take into account where the variable text goes in the localized message, because the substitution values may need to be in different positions in the message for different languages.

The Struts framework includes the capabilities of the MessageFormat class but encapsulates the functionality behind the components within the framework.

12.2.4 Multilingual Support

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.

Programming Jakarta Struts
Programming Jakarta Struts, 2nd Edition
ISBN: 0596006519
EAN: 2147483647
Year: 2003
Pages: 180

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net