Chapter 12. Internationalization

Introduction

Struts has long been known as a good framework for constructing applications that can be used across multiple languages and geographies. The basis for Struts internationalization (i18n) support is Java. Java’s features for internationalization and localization are quite mature, having been around since Java 1.1. At the heart of Struts i18n support is the java.util.Locale object. This object is used throughout Struts to identify a client user’s language and country.

You can use Struts to adapt an application to a specific locale using the following features:

  • MessageResources bundles (e.g., properties files) that provide localized messages, prompts, and data formats

  • Creation of locales based on the user’s browser settings

  • Formatting of dates and numbers using locale-specific patterns

  • Retrieval of locale-specific images

  • Ability to specify an appropriate HTTP response character-encoding

Like Struts, JSTL provides tags for localizing data. The JSTL tags are, in many cases, more robust and easier to use than the Struts tags. Thankfully, you don’t have to choose one or the other (see Recipe 12.2). The recipes in this chapter give you options for using Struts or JSTL where appropriate.

12.1. Detecting Browser Language Settings

Problem

Your web application needs to support the language settings of the client’s browser.

Solution

If you are using the default settings of Struts, you don’t have to do anything; the browser’s locale will be automatically detected.

Discussion

When a request is routed through the Struts RequestProcessor, the processLocale() method is called. Example 12-1 shows the implementation of this method in the RequestProcessor from Struts 1.2.

Example 12-1. How the RequestProcessor sets the locale
/**
 * <p>Automatically select a <code>Locale</code> for the current user, 
 * if requested.
 * <strong>NOTE</strong> - configuring Locale selection will trigger
 * the creation of a new <code>HttpSession</code> if necessary.</p>
 *
 * @param request The servlet request we are processing
 * @param response The servlet response we are creating
 */
protected void processLocale(HttpServletRequest request,
                             HttpServletResponse response) {

    // Are we configured to select the Locale automatically?
    if (!moduleConfig.getControllerConfig( ).getLocale( )) {
        return;
    }

    // Has a Locale already been selected?
    HttpSession session = request.getSession( );
    if (session.getAttribute(Globals.LOCALE_KEY) != null) {
        return;
    }

    // Use the Locale returned by the servlet container (if any)
    Locale locale = request.getLocale( );
    if (locale != null) {
        if (log.isDebugEnabled( )) {
            log.debug(" Setting user locale '" + locale + "'");
        }
        session.setAttribute(Globals.LOCALE_KEY, locale);
    }

}

The method first checks if the controller is configured to set the locale automatically. If you don’t want the controller to set the locale (which results in the creation of an HttpSession), set the controller’s locale attribute to false in the struts-config.xml file.

<controller locale="false"/>

You need to ensure the browser renders your generated HTML appropriately for the user’s language. The HTML html tag supports the lang attribute whose value represents the language generally used within the document. Here’s how it would look if the page consisted primarily of text written in Russian:

<html lang="ru">

The value of the lang attribute should be set to an ISO-639 standard two-character language code. These codes are essentially the same language codes used by the Java Locale. You can indicate a language dialect by adding a subcode name; the subcode equates to the two-character country code of a Locale. For the lang attribute, separate the language code from the subcode with a hyphen; not an underscore (_) as used by Locale. You would indicate U.S. English as “en-US” and French Canadian as “fr-CA.”

Struts can automatically generate an appropriate lang attribute for you. In Struts 1.1, set the locale attribute of the html:html tag to true to create the lang attribute.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>

<html:html locale="true">

The html:html tag uses the same heuristic to resolve the current locale as the processLocale( ) method shown in Example 12-1. The difference is that the tag allows you to process the locale on an as-needed basis, whereas the processLocale( ) method fires on every request unless explicitly disabled.

The html:html tag first looks for a Locale in the session; if one is found, it uses it. Otherwise, the tag looks for the language from the HttpServletRequest. The servlet request returns a Locale based on the Accept-Language HTTP header. If this header value is not set, then the server’s default Locale is returned. The html:html tag stores the Locale in the session, creating the session if it didn’t exist.

Some web applications are written not to use HttpSessions. With Struts 1.1, it was impossible to use the locale attribute of the html:html tag without creating an HttpSession. This problem has been resolved in Struts 1.2. The locale attribute of the html:html tag has been replaced with the lang attribute. Setting the lang attribute to true will render an html tag with the lang attribute set to the appropriate language but won’t create an HttpSession:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<html:html lang="true">

See Also

The Struts User’s Guide includes a section on internationalization that covers this and other related topics. The relevant section can be found at http://struts.apache.org/userGuide/building_view.html#i18n.

Sun’s Java Tutorial has a trail on internationalization at http://java.sun.com/docs/books/tutorial/i18n/.

Information on HTML’s support for internationalization, such as the lang attribute of the html tag, can be found in HTML and XHTML: The Definitive Guide by Chuck Musciano and Bill Kennedy (O’Reilly).

12.2. Sharing Message Resources with JSTL

Problem

You want the JSTL formatting tags (fmt) tags to use the same MessageResources properties file used by Struts.

Solution

Set the value of the JSTL localization context parameter to your Struts MessageResources file, as shown in the partial web.xml file of Example 12-2.

Example 12-2. Setting the JSTL localization context
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
    
<web-app>
    <context-param>      
        <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>path.to.MessageResources
        </param-value>
    </context-param>
...

Discussion

The JSP Standard Tag Library (JSTL) provides powerful tags for support of internationalization. The JSTL formatting tag library, referred to as the fmt tags, support many of the same internationalization features provided by the Struts tags and a lot more. You can configure JSTL to use the same MessageResources file you use in your Struts application. The JSTL tags rely on a default resource bundle of properties. You define this default resource bundle for your JSTL tags with a web application context parameter. Set the param-name to javax.servlet.jsp.jstl.fmt.localizationContext and the param-value to the same value as the parameter of the message-resources element in your struts-config.xml file.

If you use an alternate MessageResources file in your Struts applications, you can refer to those properties with the JSTL tags. You use the fmt:bundle or fmt:setBundle tag to specify the alternate properties file. The properties file must be on your classpath. Suppose your alternate resource bundle was defined in your struts-config.xml as follows:

<message-resources key="alt"
    parameter="AlternateResources">
</message-resources>

You would display a message from this properties file with a Struts tag like this:

<bean:message bundle="alt" key="msg.hello" arg0="Bill"/>

To do the same with JSTL, you need to make the message resources available to the fmt:message tag with the fmt:setBundle tag. The fmt:setBundle tag establishes the resource bundle used on the remainder of the JSP page.

<fmt:setBundle basename="AlternateResources">
<fmt:message key="msg.hello">
    <fmt:param value="Bill"/>
</fmt:message>

Alternatively, you can nest the fmt:message tag within the fmt:bundle tag. The specified bundle only applies to the nested tags.

<fmt:bundle basename="AlternateResources">
    <fmt:message key="msg.hello">
        <fmt:param value="Bill"/>
    </fmt:message>     
</fmt:bundle>

See Also

JavaServer Pages by Hans Bergsten (O’Reilly) covers JSTL in great detail and is an invaluable source. Sun provides an excellent tutorial on JSTL that can be found at http://java.sun.com/tutorials/jstl.

You may want to read the JSTL specification. This well-written document provides a lot of insight to help you understand how and why JSTL works the way it does. This document and other related information can be downloaded from http://java.sun.com/products/jsp/jstl/.

12.3. Using an Application-Wide Locale

Problem

You want your web application to use the same locale settings for all users, regardless of their browser’s language setting. You also want the JSTL tags to honor this application-wide locale.

Solution

Use a servlet Filter to set the Locale to the desired value (see Example 12-3). The Filter ensures that the Locale is set for all web requests, and not just those handled by Struts.

Example 12-3. Using a servlet filter to set the locale
package com.oreilly.strutsckbk.ch12;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.jstl.core.Config;

import org.apache.struts.Globals;

public class LocaleFilter implements Filter {
    
    // the locale code used to create the locale (e.g. en_US)
    private String localeCode;
    
    // indicates if the locale should always be set;
    // even if there is currently one in the session
    private boolean ignore = false;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        localeCode = filterConfig.getInitParameter("locale");
        override = Boolean.valueOf(filterConfig.getInitParameter("ignore")).
                   booleanValue( );
    }
   
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) 
                 throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        
        // create the session if needed
        HttpSession session = req.getSession( );

        Locale currentLocale = (Locale) session.getAttribute(Globals.
                                                             LOCALE_KEY);
        if (currentLocale == null || ignore) {
            // create the new locale
            Locale locale = new Locale(localeCode);

            // reset the Struts locale
            session.setAttribute(Globals.LOCALE_KEY, locale);
                
            // reset the JSTL locale
            Config.set(session, Config.FMT_LOCALE, locale);
        }
        chain.doFilter(request, response);
    }
    
    public void destroy( ) {
        // Nothing necessary
    }

    private FilterConfig filterConfig;
}

Declare the filter in your web.xml, as shown in Example 12-4.

Example 12-4. Locale filter configuration (partial)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
    
<web-app>
    <display-name>Struts Cookbook - Chapter 12 Examples</display-name>
    <context-param>      
        <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>ApplicationResources</param-value>
    </context-param>
   
    <filter>
        <filter-name>LocaleFilter</filter-name>
        <filter-class>
            com.oreilly.strutsckbk.ch12.LocaleFilter
        </filter-class>
        <!-- Language and country -->
        <init-param>
            <param-name>locale</param-name>
            <param-value>en_US</param-value>
         </init-param>
        <!-- True to set locale even if already set in session -->
        <init-param>
            <param-name>ignore</param-name>
            <param-value>true</param-value>
         </init-param>
    </filter>

    <filter-mapping>
        <filter-name>LocaleFilter</filter-name>
        <url-pattern>/*</url-pattern>  
    </filter-mapping>
...

Discussion

Usually, you want the user’s Locale to reflect the browser’s settings, as shown in Recipe 12.1. Some applications, however, need the opposite behavior. You want the application to reflect the same language and country regardless of how the client’s browser, or the server’s operating system, is configured. The servlet filter shown in the Solution provides this ability. The filter accepts two initialization parameters. The locale parameter defines the Locale to use by specifying a locale code (e.g., en_US) as its value. To use this filter, you must turn off locale processing in the Struts RequestProcessor using the controller element in the struts-config.xml file:

<controller locale="false"/>

The second parameter, the ignore parameter, indicates if the filter should ignore any Locale in session, always setting the locale even if one is present. You can set this value to false if you want to define a default locale but still allow a user to select and use a new locale (see Recipe 12.4).

Filters provide an excellent way to apply across-the-board behavior. Though you could override the processLocale method in a custom RequestProcessor, it would only affect requests to Struts actions. The Solution shown here will set the default locale for web requests to Struts actions, JSPs, and static HTML pages.

See Also

Servlet filters are a new addition to the Servlet API, being added in Version 2.3. Java Servlet Programming by Jason Hunter (O’Reilly) covers servlet filters in-depth.

Sun’s Java site has a good article on the essentials of servlet filters found at http://java.sun.com/products/servlet/Filters.html.

12.4. Changing Locale on the Fly

Problem

You want to allow a user to choose the language and country to be used by the web application for his session.

Solution

Use my ChangeLocaleAction, based on the Struts built-in LocaleAction, as shown in Example 12-5.

Example 12-5. Struts action for changing the current locale
package com.oreilly.strutsckbk.ch12;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.jstl.core.Config;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;


/**
 * Implementation of <strong>Action</strong> that changes the user's
 * @link(java.util.Locale and forwards to a page, based on request level
 * parameters that are set  (language, country, variant, &amp; page).
 * Also changes the JSTL locale.
 */
public final class ChangeLocaleAction extends Action {

    private static final String SUCCESS = "success";
    
    /**
     * Commons Logging instance.
     */
    private Log log = LogFactory.getFactory( ).getInstance(this.getClass( ).
                      getName( ));

    /**
     * <p>
     * Change the user's @link(java.util.Locale) based on @link(ActionForm)
     * properties.
     * </p>
     * <p>
     * This <code>Action</code> looks for <code>language</code> and
     * <code>country</code> and <code>variant</code> properties on the given
     * form, constructs an appropriate Locale object, and sets it as the Struts
     * Locale for this user's session as well as the JSTL locale.
     * Any <code>ActionForm, including a @link(DynaActionForm), may be used.
     * </p>
     * <p>
     * If a <code>page</code> property is also provided, then after
     * setting the Locale, control is forwarded to that URI path.
     * Otherwise, control is forwarded to "success".
     * </p>
     *
     * @param mapping The ActionMapping used to select this instance
     * @param form The optional ActionForm bean for this request (if any)
     * @param request The HTTP request we are processing
     * @param response The HTTP response we are creating
     *
     * @return Action to forward to
     * @exception java.lang.Exception if an input/output error or servlet 
     * exception occurs
     */
    public ActionForward execute(ActionMapping mapping,
                 ActionForm form,
                 HttpServletRequest request,
                 HttpServletResponse response)
    throws Exception {

        // Extract attributes we will need
        HttpSession session = request.getSession( );
        Locale locale = getLocale(request);
    
        String language = null;
        String country = null;
        String variant = null;
        String page = null;
    
        try {
            language = (String) PropertyUtils.getSimpleProperty(form, 
                                                                "language");
            country = (String) PropertyUtils.getSimpleProperty(form, 
                                                                "country");
            variant = (String) PropertyUtils.getSimpleProperty(form, 
                                                                "variant");
            page = (String) PropertyUtils.getSimpleProperty(form, "page");
        } catch (Exception e) {
            log.error(e.getMessage( ), e);
        }

        boolean isLanguage = language != null && language.length( ) > 0;
        boolean isCountry  = country != null && country.length( ) > 0;
        boolean isVariant  = variant != null && variant.length( ) > 0;
        
        if ( isLanguage && isCountry && isVariant ) {
           locale = new java.util.Locale(language, country, variant);
        } else if ( isLanguage && isCountry ) {
            locale = new java.util.Locale(language, country);
        } else if ( isLanguage ) {
            locale = new java.util.Locale(language, "");
        }
    
        // reset the Struts locale
        session.setAttribute(Globals.LOCALE_KEY, locale);
            
        // reset the JSTL locale
        Config.set(session, Config.FMT_LOCALE, locale);
            
        if (null==page || "".equals(page))
            return mapping.findForward(SUCCESS);
        else
            return new ActionForward(page);
    }
}

Discussion

The ChangeLocaleAction is based on the Struts 1.2 LocaleAction. This action provides the same capabilities at the Struts LocaleAction, and it supports a locale variant. Variants aren’t defined by an ISO standard like language and country codes but are supported by Java and can give you additional means of organizing your localized messages. Even more importantly, the ChangeLocaleAction updates the Locale used by JSTL as well as the Struts Locale:

// reset the Struts locale
session.setAttribute(Globals.LOCALE_KEY, locale);
            
// reset the JSTL locale
Config.set(session, Config.FMT_LOCALE, locale);

The Struts LocaleAction doesn’t do this because it would be dependent on the JSTL APIs. But if you’re using Struts and JSTL’s internationalization capabilities, you will want to update the Locale for both.

Similar to the LocaleAction, the ChangeLocaleAction reads the language, country, and variant codes from properties on an ActionForm. It creates a Locale using these values and stores this Locale in the session. The action returns an ActionForward using the path specified by the page property or found under the name “success.”

To use the ChangeLocaleAction, you’ll need to create an ActionForm containing the required properties. You can extend ActionForm, or you can use a DynaActionForm specified in your struts-config.xml file:

<form-bean name="LocaleForm" type="org.apache.struts.action.DynaActionForm">
    <form-property name="language" type="java.lang.String"/>
    <form-property name="country" type="java.lang.String"/>
    <form-property name="variant" type="java.lang.String"/>
    <form-property name="page" type="java.lang.String"/>
</form-bean>

Then declare an action in the struts-config.xml file that uses this ActionForm and the ChangeLocaleAction:

<action path="/ChangeLocale"
        name="LocaleForm"
       scope="request"
       type="com.oreilly.strutsckbk.ch12.ChangeLocaleAction"/>

Here’s a set of links using this action that allow the user to change languages between English, Russian, and U.S. English (Southeastern variant):

<html:link action="/ChangeLocale?language=en">
  English
</html:link><br />
<html:link action="/ChangeLocale?language=ru">
  Russian
</html:link><br />
<html:link action="/ChangeLocale? language=en&country=US&variant=SE">
  Southeastern U.S. English
</html:link><br />

Here’s an HTML form that allows a user to change the current language. Once changed the request will be forwarded to the /Welcome.do page.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<html:html lang="true">
<head>
  <title>Change language</title>
</head>
<body>
<html:form action="/ChangeLocale">
    <html:select property="language">
        <html:option value="en">English</html:option>
        <html:option value="fr">French</html:option>
        <html:option value="ru">Russian</html:option>
    </html:select>
    <html:hidden property="page" value="/Welcome.do"/>
    <html:submit/>
</html:form>
</body>
</html:html>

If you’re using Struts 1.2 and you’re not using JSTL or locale variants, then it doesn’t matter if you use the Struts LocaleAction or my ChangeLocaleAction. However, if you aren’t using Struts 1.2 or you’re using JSTL, then my ChangeLocaleAction will work best.

See Also

The API for the LocaleAction can be found at http://struts.apache.org/api/org/apache/struts/actions/LocaleAction.html.

If you’re using Struts and JSTL together, you can share the resource bundles using the Solution in Recipe 12.2.

12.5. Creating Localized Messages from an Action

Problem

You need to create a localized message from within an Action.

Solution

MessageResources resources = getResources(request);
Locale locale = getLocale(request);
String msg1 = resources.getMessage(locale, "message.success"));
String msg2 = resources.getMessage(locale, "msg.hello", "Bill"));

Discussion

Application messages for information and errors are typically created using the ActionMessage and ActionError classes. These messages are saved in the request using the saveMessages( ) and saveErrors( ) methods.

Warning

The ActionError class has been deprecated in Struts 1.2. The ActionMessage class should be used instead.

But you can retrieve localized text messages in the Action itself. You may need the message in the Action because you are logging this information or you need to pass the message to some other service. The Solution shows how you can get a localized message in an Action. The MessageResources and current Locale are retrieved using the getResources( ) and getLocale() methods of the base Action class. MessageResources provides a number of variations of the getMessage( ) method. There are three basic pieces of information you can pass:

locale

The requested message Locale or null for the system default Locale

key

The message key to look up

args

An array of replacement parameters for placeholders in the message

You can retrieve messages from an alternate MessageResources set. Suppose you’ve declared your alternate set in the struts-config.xml file:

<message-resources key="alt"
    parameter="AlternateResources">
</message-resources

The getResources( ) method takes an optional parameter specifying the name of the MessageResources bundle, identified by the key attribute of the message-resources element:

MessageResources resources = getResources(request, "alt");

If you only have access to the servlet request, you can create messages but you have to get the Locale and MessageResources through different means. To get the Locale, use the RequestUtils class in the org.apache.struts.util package:

Locale locale = RequestUtils.getUserLocale(request, null);

The default set of MessageResources can be retrieved using:

MessageResources resources = 
    (MessageResources) request.getAttribute(Globals.MESSAGES_KEY));

You can use one of the MessageResources.getMessage() methods to retrieve the message.

You can retrieve an alternate set of MessageResources, but the code is more involved because modules are taken into account. Take a look at the source for the Action class if you’re interested.

See Also

The JavaDocs for the Struts Action class (http://struts.apache.org/api/org/apache/struts/action/Action.html) provide details on the convenience methods used in this recipe.

12.6. Displaying Locale-Specific Text

Problem

Your Struts application needs to display correctly formatted text—particularly numbers, dates, and messages—based on a user’s locale.

Solution

From the Struts bean tag library, use the following:

<%-- Format a number --%>
<bean:write name="beanName"
        property="numericProperty"
          format="number pattern"/>

<%-- Format a date --%>
<bean:write name="beanName"
        property="dateProperty"
          format="date pattern"/>

<%-- Format a message with parameters --%>
<bean:message key="message.key"
             argn="replacement value"/>

From the JSTL fmt tag library, use the following:

<%-- Format a number --%>
<fmt:formatNumber value="${beanName.numericProperty}"
                pattern="number pattern"/>     

<%-- Format a date --%>
<fmt:formatDate value="${beanName.dateProperty}"
              pattern="date pattern"/>

<%-- Format a message with parameters --%>
<fmt:message key="message.key">
    <fmt:param value="replacement value"/>
</fmt:message>

Discussion

Struts provides the generic bean:write tag to output text formatted for a specific locale and bean:message to render localized messages. If you’re using JSTL, you can use the tags of the fmt tag library.

Using the Struts bean tags

This bean:write tag renders a value specified by the standard Struts name and property attributes. This tag can format dates and numbers before outputting the value using the pattern specified by the format attribute. The format pattern will be applied if the value is a java.lang.Number or java.util.Date object. Numbers are formatted using the java.text.DecimalFormat class and dates with the java.text.SimpleDateFormat class.

The bean:write tag accounts for the current locale when formatting by using a locale-specific instance of the formatter class. For example, suppose you are outputting a numeric value:

<bean:write name="order" property="amount" format="#,##0.00"/>

With the locale set to English (“en”) and the amount to 13995.78, the following text is rendered:

13,995.78

With the locale set to Russian (“ru”), the number is formatted for that locale. Like other European languages, Russian uses a space for the grouping separator and a comma (,) for the decimal separator.

13 995,78

You can use the formatKey attribute of the bean:write tag to refer to a pattern stored in your message resources bundle. For numbers, the pattern retrieved from the bundle will be applied as a localized pattern; in other words, the pattern is expected to be written for that specific locale.

Warning

If you use the formatKey attribute of the bean:write tag, be aware of a known issue (Apache Bugzilla ticket #27636): you must specify a value for the key in all of your resource bundles that may use it. Because the pattern is applied as a localized pattern, a nonlocalized pattern retrieved from a fallback resource bundle can result in meaningless output. Date patterns are not applied as localized patterns and are not affected by this issue.

For the pattern used in the previous example, you would store a name/value pair in ApplicationResources_en.properties like the following:

format.amount = #,##0.00

In the properties file localized for Russian (ApplicationResources_ru.properties), you would have:

format.amount = # ##0,00

This formatKey attribute proves more useful when working with dates. Say you wanted to generate dates such as Sep 14, 2004 for English and 14 __ _ 2004 for Russian. In your English properties file, you would have the following:

format.date=MMM dd, yyyy

In the Russian properties file, you would use the following:

format.date=dd MMM yyyy

You could use one bean:write tag to render the date in the appropriate format using the following:

<bean:write name="order" property="datePlaced" formatKey="format.date"/>

For messages, use the bean:message tag. Suppose you want to render an order confirmation message on a “success” page. The message should display the quantity of items ordered, the part number, and the total cost. Here’s one way you could specify the message in your base resource bundle (ApplicationResources.properties):

msg.confirm.order=You ordered {0} of part {1} at a total cost of ${2}.

On a JSP page, the bean:message tag retrieves the message by key. The arg n attributes contain the substitution values. Unless you are using the Struts-EL version of the bean tags or a JSP 2.0 container, you must resort to scriptlet for the substitution arguments:

<!-- Create a scripting variable -->
<bean:define id="theOrder" name="order"  type="com.oreilly.strutsckbk.ch12.
Order"/>
<!-- Render the message -->
<bean:message key="msg.confirm.order"
             arg0="<%=theOrder.getQuantity( ).toString( )%>"
             arg1="<%=theOrder.getPartNumber( ).toString( )%>"
             arg2="<%=theOrder.getAmount( ).toString( )%>"
/>

To display the message for a different locale, like Spanish, you only need to place the translated message into that locale’s resource bundle (ApplicationResources_es.properties):

msg.confirm.order = Usted ordeno {0} unidades de parte {1} 
resultando en un costo total de ${2}.

The bean:message tag formats the message using the java.text.MessageFormat class. This class even allows you to format the replacement values themselves using a special notation. Check the JavaDocs for this class (online at http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html) if you are using this mechanism.

Using the JSTL format tags

The JSTL formatting tags provide similar capabilities as the Struts tags. The JSTL fmt tag library provides two primary tags for localizing data. The fmt:formatNumber tag formats numeric values. You can specify a specific pattern, or you can specify characteristics such as the minimum and maximum digits. If you are migrating from using bean:write and were using the format attribute to specify a pattern, you can use that same pattern with JSTL:

<fmt:formatNumber value="${order.amount}" pattern="#,##0.00"/>

Warning

If you were using the bean:write’s formatKey attribute, the corresponding pattern can only be used if it localized the server’s default locale. Unlike the bean:write tag, JSTL number formatting tags don’t expect the pattern to be localized.

For formatting dates and times, use the fmt:formatDate tag. Like the fmt:formatNumber tag, you can specify a pattern or formatting characteristics, such as a date style. If you were using bean:write’s formatKey attribute to use a pattern from a resource bundle, you can achieve the same affect with JSTL.

<fmt:message key="format.date" var="dateFmt"/>
<fmt:formatDate value="${order.datePlaced}" pattern="${dateFmt}"/>

Like the bean:message tag, JSTL’s fmt:message tag builds and localizes messages from a resource bundle. The fmt:param tag substitutes a value for a message parameter placeholder.

<fmt:message key="msg.confirm.order">
    <fmt:param value="${order.quantity}"/>
    <fmt:param value="${order.partNumber}"/>
    <fmt:param value="${order.amount}"/>
<fmt:message key="msg.confirm.order">

All in all, JSTL tags tend to be less verbose and cleaner than the corresponding Struts tags. More importantly, the power of the JSTL expression language (EL) alleviates the need for request-time expressions and scriptlet.

See Also

Check out the Struts User’s Guide for more details. The documentation for the bean tag library can be found at http://struts.apache.org/userGuide/struts-bean.html.

For a good source on the JSTL tags, check out Java Server Pages by Hans Bergsten (O’Reilly).

Struts issues are tracked using Bugzilla. You can browse the issues at http://issues.apache.org/bugzilla.

12.7. Displaying Locale-Specific Images

Problem

Your Struts application needs to show a graphic image specific to a user’s locale.

Solution

Create a property in the locale’s resource bundle specifying the path to the image source file. Here’s an example from a Spanish locale properties file (ApplicationResouces_es.properties) file:

# spanish language properties
img.source.yes=images/es/yes.gif

Use the html:img tag with the srcKey or pageKey attribute set to the property key:

<html:img border="0" srcKey="img.source.yes"/>

Discussion

Most production web applications use images extensively. Images are frequently used, in place of text, for buttons and links. In many cases, images are used to create “fancy” buttons, as shown in Figure 12-1.

Using images for buttons
Figure 12-1. Using images for buttons

If the user speaks Spanish, Figure 12-1 must be modified since the text message needs to be translated to Spanish and the “yes” button should be changed to “sì.” The text can be localized using the bean:message or fmt:message tag (Recipe 12.6). For the button images, you can use the html:img to retrieve a locale-specific image and generate an appropriate HTML img element. The html:img tag supports two attributes, srcKey and pageKey, which allow you to specify a key, to be looked up from a resource bundle. The bundle’s value dictates the path to the file. The path will be retrieved from the default Struts MessageResources bundle for the current locale. If srcKey is specified, the resultant value is used “as is” for the image location; if pageKey is used, the resultant value is treated as a module-relative path to the image.

The JSP page (need_help.jsp) shown in Example 12-6 generates the HTML pop-up window in Figure 12-1.

Example 12-6. Using html:img to display localized images
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<html:html lang="true">
<head>
  <title>Ask for Help</title>
</head>
<body>
    <p align="center">
        <bean:message key="prompt.help"/>
    </p>
    <p align="center">
        <a href="javascript:close( )">
            <html:img border="0" srcKey="img.yes.src" titleKey="img.yes.
                                                                   title"/>
        </a>
        &nbsp;
        <a href="javascript:close( )">
            <html:img border="0" srcKey="img.no.src" titleKey="img.no.
                                                                   title"/>
        </a>
    </p>
</body>
</html:html>

The titleKey specifies the key to the text to be used for the title attribute of the generated img tag. Most modern browsers display the title when the mouse hovers over the image. The html:img tag supports the altKey attribute to retrieve localized text for the img tag’s alt attribute (displayed when the image isn’t available).

For the JSP of Example 12-6, here are the resource bundle properties from ApplicationResources.properties (default locale):

prompt.help=Do you need help?
img.yes.src=images/yes.gif
img.yes.title=Yes
img.no.src=images/no.gif
img.no.title=No

Here are those same properties from ApplicationResources_es.properties (Spanish locale):

prompt.help=Necesita ayuda?
img.yes.src=images/es/yes.gif
img.yes.title=Si
img.no.src=images/es/no.gif
img.no.title=No

When the users set their language to Spanish, the JSP of Example 12-6 generates the pop up shown in Figure 12-2.

Localized text and images
Figure 12-2. Localized text and images

The text and images are retrieved from the bundle for the current locale. The images are retrieved from a locale-specific directory. You can organize your images however you wish, but if you have many localized images, placing them under a folder that’s named using the language code, like images/es, works quite well.

See Also

The documentation on the html:img tag provides details on all of the supported attributes. The documentation for this and other Struts tags can be accessed at http://struts.apache.org/userGuide/struts-html.html.

12.8. Supporting Character Sets

Problem

Your Struts application needs to display characters from any language correctly.

Solution

Use Tomcat’s SetCharacterEncoding filter shown in Example 12-7.

Example 12-7. Using a filter to set the character encoding
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package filters;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;


/**
 * <p>Example filter that sets the character encoding to be used in parsing
 * the incoming request, either unconditionally or only if the client did not
 * specify a character encoding.  Configuration of this filter is based on
 * the following initialization parameters:</p>
 * <ul>
 * <li><strong>encoding</strong> - The character encoding to be configured
 *     for this request, either conditionally or unconditionally based on
 *     the <code>ignore</code> initialization parameter.  This parameter
 *     is required, so there is no default.</li>
 * <li><strong>ignore</strong> - If set to "true", any character encoding
 *     specified by the client is ignored, and the value returned by the
 *     <code>selectEncoding( )</code> method is set.  If set to "false,
 *     <code>selectEncoding( )</code> is called <strong>only</strong> if the
 *     client has not already specified an encoding.  By default, this
 *     parameter is set to "true".</li>
 * </ul>
 *
 * <p>Although this filter can be used unchanged, it is also easy to
 * subclass it and make the <code>selectEncoding( )</code> method more
 * intelligent about what encoding to choose, based on characteristics of
 * the incoming request (such as the values of the <code>Accept-Language
 * </code> and <code>User-Agent</code> headers, or a value stashed 
 * in the current user's session.</p>
 *
 * @author Craig McClanahan
 * @version $Revision: 1.5 $ $Date: 2005/03/21 18:08:09 $
 */
public class SetCharacterEncodingFilter implements Filter {

    // ------------------------------------------------- Instance Variables

    /**
     * The default character encoding to set for requests that pass through
     * this filter.
     */
    protected String encoding = null;

    /**
     * The filter configuration object we are associated with.  If this value
     * is null, this filter instance is not currently configured.
     */
    protected FilterConfig filterConfig = null;

    /**
     * Should a character encoding specified by the client be ignored?
     */
    protected boolean ignore = true;

    // ----------------------------------------------------- Public Methods

    /**
     * Take this filter out of service.
     */
    public void destroy( ) {
        this.encoding = null;
        this.filterConfig = null;
    }

    /**
     * Select and set (if specified) the character encoding to be used to
     * interpret request parameters for this request.
     *
     * @param request The servlet request we are processing
     * @param result The servlet response we are creating
     * @param chain The filter chain we are processing
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet error occurs
     */
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
        throws IOException, ServletException {

        // Conditionally select and set the character encoding to be used
        if (ignore || (request.getCharacterEncoding( ) == null)) {
            String encoding = selectEncoding(request);
            if (encoding != null)
                request.setCharacterEncoding(encoding);
        }

        // Pass control on to the next filter
        chain.doFilter(request, response);
    }

    /**
     * Place this filter into service.
     *
     * @param filterConfig The filter configuration object
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        this.encoding = filterConfig.getInitParameter("encoding");
        String value = filterConfig.getInitParameter("ignore");
        if (value == null)
            this.ignore = true;
        else if (value.equalsIgnoreCase("true"))
            this.ignore = true;
        else if (value.equalsIgnoreCase("yes"))
            this.ignore = true;
        else
            this.ignore = false;
    }

    // -------------------------------------------------- Protected Methods

    /**
     * Select an appropriate character encoding to be used, based on the
     * characteristics of the current request and/or filter initialization
     * parameters.  If no character encoding should be set, return
     * <code>null</code>.
     * <p>
     * The default implementation unconditionally returns the value configured
     * by the <strong>encoding</strong> initialization parameter for this
     * filter.
     *
     * @param request The servlet request we are processing
     */
    protected String selectEncoding(ServletRequest request) {
        return (this.encoding);
    }
}

Then declare the filter in your web.xml file, setting filter to use “UTF-8” and mapping the filter to all URLs:

<filter>
    <filter-name>SetCharacterEncodingFilter</filter-name>
    <filter-class>
        filters.SetCharacterEncodingFilter
    </filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
     </init-param>
    <init-param>
        <param-name>ignore</param-name>
        <param-value>true</param-value>
     </init-param>
</filter>

<filter-mapping>
    <filter-name>SetCharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>  
</filter-mapping>

Discussion

You can ensure your application will accept any character encoding using a filter. The Tomcat distribution includes an example servlet filter that sets the servlet request character encoding to any desired value. Specifying an encoding of UTF-8, a well-supported charset of Unicode, ensures all character sets can be handled.

For web applications, character encoding problems typically occur with forms. The user inputs text on a form using non-Western characters, such as in Russian (Cyrllic) as shown in Figure 12-3, and submits the form.

Form fields containing Russian (Cyrillic) characters
Figure 12-3. Form fields containing Russian (Cyrillic) characters

But when the input data is displayed on a successive page, the characters appear as gibberish as in Figure 12-4. When the server received the data, it didn’t know how to translate the byte sequence into the correct Cyrillic characters.

Incorrectly encoded characters
Figure 12-4. Incorrectly encoded characters

However, if you use the SetCharacterEncoding filter, configured to set the character encoding to UTF-8, the page will display correctly, as in Figure 12-5.

Correctly encoded Cyrillic characters
Figure 12-5. Correctly encoded Cyrillic characters

Browser and operating system support for non-Western character encodings varies by vendor and version. Most modern browsers allow you to set the default character encoding to UTF-8. Likewise, most operating systems allow you to input text using non-Western characters. It can be a challenge to keep it all straight, but for your application, this servlet filter solution eliminates a lot of the frustration.

See Also

I18nGurus.com (The Open Internationalization Resources Directory) has a boatload of information internationalization topics. The links on using character sets and character encoding can be found at http://www.i18ngurus.com/docs/984813247.html.

12.9. Localizing Look and Feel

Problem

You want your HTML pages to have a different style depending on the user’s locale.

Solution

Use a combination of a global Cascading Style Sheet (CSS) and a locale-specific style sheet. The paths for the style sheets are stored as resource bundle properties. Because style sheets are merged, you only need to override those locale-specific styles that are different than the global styles.

Using Struts tags

<style>
    <!--
        <bean:define id="globalStyle">
            <bean:message key="css.global"/>
        </bean:define>
        @import url(<html:rewrite page="<%=globalStyle%>"/>);

        <bean:define id="localStyle">
            <bean:message key="css.local"/>
        </bean:define>
        @import url(<html:rewrite page="<%=localStyle%>"/>);
    -->
</style>

Using JSTL tags

<style>
    <!--
    <fmt:message key="css.global" var="globalStyle"/>
    @import url(<c:url value="${globalStyle}"/>)

    <fmt:message key="css.local" var="localStyle"/>
    @import url(<c:url value="${localStyle}"/>)
    -->
</style>

Discussion

Style sheets can be chosen and applied based on locale in the same manner that images are retrieved as shown in Recipe 12.7. You can use the Struts tags or the JSTL tags. Use bean:message or fmt:message to retrieve a context-relative style sheet path. For example, you would configure paths for a global and locale-specific style sheet in your base resource bundle (ApplicationResources.properties):

css.global=/styles/global.css
css.local=/styles/local.css

The locale-specific file, /styles/local.css, needs to be defined but doesn’t need to contain any text. It’s specified in the base resource bundle as a fallback value in case a locale-specific style sheet isn’t defined. To define the locale-specific style sheet, specify a property in that locale’s resource bundle for the style sheet. Here are the properties, for example, for a style sheet with styles and colors specific to Spanish from the ApplicationResources_es.properties file:

css.local=/styles/es/local.css

As with organizing locale-specific images, using directory names that correspond to locales is a good way to organize your style sheets.

See Also

CSS have advanced significantly in recent years. Check out Cascading Style Sheets: The Definitive Guide by Eric Meyer (O’Reilly) for complete details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.46.92