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.
Your web application needs to support the language settings of the client’s browser.
If you are using the default settings of Struts, you don’t have to do anything; the browser’s locale will be automatically detected.
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.
/** * <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
HttpSession
s. 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">
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).
You want the
JSTL formatting tags
(fmt
) tags to use the same
MessageResources
properties file used by Struts.
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.
<?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>...
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>
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/.
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.
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.
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.
<?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> ...
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.
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.
You want to allow a user to choose the language and country to be used by the web application for his session.
Use my ChangeLocaleAction
, based on the Struts
built-in
LocaleAction
, as
shown in Example 12-5.
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, & 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); } }
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.
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.
You need to create a localized message from within an
Action
.
MessageResources resources = getResources(request); Locale locale = getLocale(request); String msg1 = resources.getMessage(locale, "message.success")); String msg2 = resources.getMessage(locale, "msg.hello", "Bill"));
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.
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.
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.
Your Struts application needs to display correctly formatted text—particularly numbers, dates, and messages—based on a user’s locale.
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>
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.
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.
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.
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"/>
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.
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.
Your Struts application needs to show a graphic image specific to a user’s locale.
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"/>
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.
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.
<%@ 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> <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.
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.
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.
Your Struts application needs to display characters from any language correctly.
Use Tomcat’s SetCharacterEncoding
filter shown in Example 12-7.
/* * 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>
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.
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.
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.
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.
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.
You want your HTML pages to have a different style depending on the user’s locale.
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.
<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>
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.
CSS have advanced significantly in recent years. Check out Cascading Style Sheets: The Definitive Guide by Eric Meyer (O’Reilly) for complete details.
3.21.46.92