Internationalizing Struts Application
- Internationalizing Struts Applications
- Understanding Java’s Internationalization Support
- The java.util.Locale Class
- The java.util.ResourceBundle Class
- The java.text.MessageFormat Class
- Understanding Struts’ Internationalization Support
- Struts’ Tag Library Support for Internationalization
- Example for Internationalizing Struts Application
Internationalizing Struts Applications |
- The Internet has created a global, electronic shopping mall. Whereas businesses once catered only to customers in their same geographic region, today business is transacted with customers worldwide.
- A product of this globalization is the need for software that can adapt to the conventions and languages used by customers in different countries.
- To achieve this, applications must be internationalized. Internationalization, also known as I18N, encompasses tailoring content specific to locales based on their different languages, currencies, and formatting conventions.
Note:- The acronym I18N is sometimes used in place of the word internationalization, because it is such a long word to type. I18N represents the first letter i, followed by 18 characters, and then the final letter n
From the beginning, Java was designed with internationalization in mind. For example, it offers support for Unicode character sets and provides built-in classes that manage locale-specific content. Struts builds upon Java’s support, making development of internationalized Struts applications straightforward. This chapter presents an overview of Java’s built-in internationalization support and then explains how Struts’ internationalization builds upon it.
Understanding Java’s Internationalization Support |
Java’s built-in internationalization support is centered on three main classes. The following table lists each class and its description.
Class | Description |
---|---|
java.util.Locale | Encapsulates the language, country, and variant for a specific locale. |
java.util.ResourceBundle | Encapsulates locale-specific resources. |
java.text.MessageFormat | Provides methods for creating locale-specific formatted messages. |
The following three sections provide a brief introduction to each class.
The java.util.Locale Class |
- At the core of Java’s internationalization API is the Locale class.
- The Locale class encapsulates the language, country, and variant for a specific locale.
- That is, the Locale class is used to uniquely represent a specific locale.
- All other locale-oriented classes use instances of this class to tailor their behavior and output to a specific locale.
When you create a Locale object, you specify the locale’s language; language and country; or language, country, and variant. Thus, Locale lets you represent as specific a locale as needed. The constructors for the Locale class are shown here:
Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String variant)
The following snippet illustrates each of these uses in order:
Locale locale1 = new Locale("en");
Locale locale2 = new Locale("en", "US");
Locale locale3 = new Locale("en", "US", "WIN");
The first example creates an English language-specific locale; the second example creates a United States English language-specific locale; and the third example creates a United States English language Windows-specific locale. The more attributes you specify, the more specific the locale is. For example, if you create a Spanish language Locale object, as shown next, it will serve all users whose language is Spanish, independent of what country they are from:
Locale spanishLocale = new Locale("es");
Alternatively, to create a Spanish language locale specific to Mexico, you can use this declaration:
Locale mexicoSpanishLocale = new Locale("es", "MX");
Using combinations of language, country, and variant together narrows the scope of Locale objects.
The Locale object’s language argument must be specified using a valid two-letter ISO-639 language code (e.g., “en” for English or “es” for Spanish). The country argument must be specified using a valid uppercase two-letter ISO-3166 country code (e.g., “US” for United States or “CA” for Canada”). The variant argument is for a vendor- or browser-specific code (e.g., “WIN” for Windows or “MAC” for Macintosh). For a listing of each of the ISO language and country codes, visit their respective specification Web sites:
http://www.unicode.org/unicode/onlinedat/languages.html , http://www.unicode.org/unicode/onlinedat/countries.html
Here are a couple of important notes about Locale objects that you should know. First, Locale objects are immutable; thus, once you have created an instance, you cannot change any of its attributes (i.e., language, country, or variant). Because of this, if you need to change a locale for some reason, you must create a new locale instance. Second, the Locale class provides several static locale constants as a convenience. For example, Locale.US and Locale.FRANCE are country code constants and Locale.ENGLISH and Locale.FRENCH are language code constants. Following is an example use of the constants:
Locale us = Locale.US;
Locale french = Locale.FRENCH;
The java.util.ResourceBundle Class |
- The ResourceBundle class encapsulates locale-specific resources for an application.
- Essentially, the ResourceBundle class is a central repository that applications use to hold resources.
- Applications simply provide the name of a resource and a locale, and the bundle returns the application resource for the specified locale.
- The resource names are the same across all locales, enabling an application to know only the name of a resource and how it should be handled.
For example, assume that a label is displayed beside a field on an HTML form. Using a resource bundle, an application can obtain the text for the label by requesting the label from the resource bundle by name. When requesting the label, the application passes a locale that specifies what version of the label’s text to obtain, such as English or Spanish. The resource bundle returns to the application the proper version of the text, which it can then display beside the field. The application does not need to have separate logic for each translation. It just needs to know how to get the text and display it. This is the premise and power of internationalization.
Resource bundles most often contain locale-specific text (e.g., error messages, field and button labels, and so on) for applications, but can also be used to manage any locale-specific resources.
- The ResourceBundle class provides the core interface for working with resource bundles, but it is not intended to be used directly, because it is an abstract class.
- Java provides two subclasses of ResourceBundle: java.util.ListResourceBundle and java.util. PropertyResourceBundle.
- The ListResourceBundle class is an abstract class that provides a mechanism for using lists to store resources; because it’s abstract you must provide a concrete subclass implementation to make use of it.
- The PropertyResourceBundle class is a concrete subclass that provides a mechanism for using properties files to store resources.
- This class is the most commonly used for working with resource bundles in Java and is the default mechanism used by the ResourceBundle class’s static getBundle( ) methods.
Because the PropertyResourceBundle class is the default (and the most commonly used) implementation, it’s important to know how it works. The PropertyResourceBundle class provides an interface to access resources stored in properties files. Internally, it uses the java.util.Properties class. The PropertyResourceBundle class requires that resource bundle properties files be named using a special scheme, which takes the following format:
bundlename_language_country_variant.properties
For example, if you had a bundle named MessageResources for the English language in the United States for the Windows platform, the properties file would be MessageResources_ en_US_WIN.properties. Of course, not all locale components are required, so the name for a simple English file would be MessageResources_en.properties. Resource bundles also support the concept of a default resource bundle, which in this case is simply MessageResources.properties.
The default resource bundle is used when there is not a bundle for a locale or if a locale-specific bundle does not have an entry for a certain resource. For example, if you have a French bundle and it does not contain an entry for a requested resource, the ResourceBundle classes will attempt to find an entry for that resource in the default bundle. Typically, applications use the default bundle to store the English version of resources and then create language-specific bundles for other languages. However, if a language-specific bundle does not have an entry for a resource, the English version will be used from the default bundle.
The java.text.MessageFormat Class |
- The MessageFormat class provides methods for creating locale-specific formatted messages.
- As you saw in the preceding section, resource bundles can be used to retrieve locale-specific static messages from properties files.
- Although that is quite useful, there will be times when a message needs to be constructed dynamically, at run time.
- The MessageFormat class provides the means for creating locale-specific dynamic messages.
- To understand what dynamic messages are, it helps to first review properties file-based static messages.
An example snippet from a properties file is shown next:
error.firstName.required=First Name is required.
error.lastName.required=Last Name is required.
This snippet shows two properties whose values are very similar. The main difference between them is simply the subject of what is required. Using this model, a property would have to be created for every required field, with each property being almost identical to the next. Thus, significant duplication would exist.
With the MessageFormat class, you can create a dynamic message to solve the problem of duplication. Instead of creating a separate static property for each required field, you create one dynamic property that can be used to generate specific messages. Following is an example of a dynamic message:
error.required={0} is required.
This property specifies a placeholder with {0}. The MessageFormat class takes a dynamic message and a list of substitution data and replaces the dynamic message’s placeholders with the substitution data. To illustrate how this works, consider the following example code:
ResourceBundle bundle = ResourceBundle.getBundle("MessageResources");
String requiredMessage = bundle.getString("error.required");
String[] substituteData1 = {"First Name"};
String firstNameMessage = MessageFormat.format(requiredMessage, substituteData1);
String[] substituteData2 = {"Last Name"};
String lastNameMessage = MessageFormat.format(requiredMessage, substituteData2);
In this example, the dynamic message is used twice, with different substitution data to create unique messages. This is a powerful and often used technique in internationalized applications. Of course, the MessageFormat class accepts a locale in its constructor so that dynamic messages can be tailored to specific locales.
Understanding Struts’ Internationalization Support |
- Now that you have reviewed Java’s built-in support for internationalization, you are ready to see how internationalization works in Struts.
- Struts is similar to Java in that internationalization support was a base requirement and not an afterthought.
- Struts was designed from the ground up to provide strong support for internationalizing an application.
- This is one of its most valuable features. For the most part, Struts’ internationalization support is built on top of Java’s.
- However, Struts uses some of its own facilities for internationalization where necessary.
Like Java, Struts’ internationalization support is centered on the Locale class and resource bundles. However, Struts differs in that it uses its own custom set of classes for managing and accessing resource bundles.
The Struts org.apache.struts.util.MessageResources class and its descendant class, org.apache.struts.util.PropertyMessageResources, provide functionality parallel to the ResourceBundle and PropertyResourceBundle classes built into Java.
Struts uses these custom classes to manage resource bundles because the built-in Java classes are not serializable. Because Struts applications can be deployed in distributed (i.e., clustered) application server environments, Struts requires that any objects it manages be serializable (that is, they must implement the java.io.Serializable interface).
Because Java’s ResourceBundle classes are not serializable, Struts has created its own classes that duplicate their functionality and are serializable.
The following three sections cover the specifics of how internationalization works in Struts, starting with an overview of how Struts handles locale, followed by an overview of Struts’ message resources, and concluding with a section on how Struts’ tag libraries support internationalization.
Locale |
- Like other Java applications, Struts applications use Locale objects to store users’ locale settings and to customize content based on those settings.
- Struts determines a user’s locale from the HTTP requests made to the application.
- When making a request to a Web server, browsers pass along an HTTP header (Accept-Language) that indicates the user’s locale.
- Java’s javax.servlet.http.HttpServletRequest request object makes this locale setting available to servlets via a getLocale( ) method, which returns a Locale object containing the settings from the request.
When a user first accesses a Struts application, Struts captures the user’s locale and stores it in the user’s session. Storing the locale in the session allows it to be easily accessed throughout the Struts framework from one convenient place. A side benefit of placing the Locale object in a user’s session (instead of simply requesting it each time from a request) is that applications can update the locale settings for a user independent of the browser settings. For example, assume that a user accesses an application from a browser with English set as its locale. You can change the user’s locale (say to French) without making the user change the browser settings by simply placing another Locale object into the user’s session. Struts would then operate based on the new locale because Struts determines locale settings from the object stored in the session, not the settings passed in on each request.
Conversely, sometimes it’s necessary to override Struts’ default behavior of storing locale settings in the session. For example, your application may want to always use the locale settings specified in the request. That way if the user changes his or her browser settings, Struts will pick up the change. To do this, you have to configure Struts not to store locales in the session by setting the controller element’s locale attribute to false in your application’s Struts configuration file, as shown here:
<controller locale=”false”/>
Message Resources |
- Message resources are a fundamental building block in Struts applications whether you’re intending to internationalize your application or not.
- Struts uses message resources as a means of dynamically inserting text into JSPs as well as for storing error messages that get returned from data validations.
- By using message resources in this way, it’s easy to internationalize an application, whether it is an initial requirement for your application or an enhancement.
- You simply store all of your application’s text in resource bundles.
- Then, when it comes time to internationalize the application, all you have to do is create locale-specific versions of the resource bundle. Struts transparently handles selecting the right resource bundle from which to obtain resources based on the user’s locale.
To use message resources, you must create a properties file for your resources to be stored in. The standard name that Struts applications use for this file is MessageResources.properties; however, you can use any filename with an extension of .properties. Like Java’s built-in PropertyResourceBundle class, PropertyMessageResources requires that locale-specific versions of the file specify the locale information in the filename. For example, to create a French version of the MessageResources.properties file, you would create a file named MessageResources_fr.properties. Also in parallel with Java’s version, PropertyMessageResources will attempt to load resources for a locale from the corresponding locale-specific resource bundle. If the resource bundle does not exist or the resource does not exist in the bundle, Struts will attempt to load the resource from the default resource bundle.
Once you have created a properties file, it has to be placed somewhere on your application’s classpath (i.e., somewhere beneath the /WEB-INF/classes/ directory or inside a .jar file underneath the /WEB-INF/lib directory). This is necessary because Struts uses Java’s class loader to load the properties file. This is very important. If the file is not on your application’s classpath, Struts will not be able to access the file. Next, you have to configure Struts to know where the file is located. This is done by placing a message-resources element in your application’s Struts configuration file, as shown next:
<!-- Message Resources Configuration -->
<message-resources parameter="com.javaskool.minihr.MessageResources"/>
The parameter attribute of the message-resources element informs Struts of the location of your application’s resource bundle. Notice that the directory of the file is specified using Java’s package notation with dots. Thus, the resource bundle in the preceding example is in a directory named com/jamesholmes/minihr that is somewhere on the classpath, and the resource bundle filename is MessageResources. Struts automatically appends .properties to the filename specified with the parameter attribute. If you mistakenly specify the .properties extension, Struts will not be able to locate your file.
You can define multiple resource bundles in the Struts configuration files by using multiple message-resources elements. Each additional resource bundle beyond the main, default bundle has to specify a logical bundle name using the key attribute, as shown next:
<!-- Message Resources Configuration -->
<message-resources parameter="com.javaskool.minihr.MessageResources"/>
<message-resources key="alternate" parameter="com.javaskool.minihr.AlternateResources"/>
The main resource bundle does not specify a logical name because it is the default bundle. Additional bundles have to specify a logical name so that they can be explicitly referenced by name by the application.
After you have created a resource bundle and configured Struts to use it, you can make use of it in your application a couple ways. First, you can dynamically insert text into JSPs by using the Bean Tag Library’s message tag, as shown here:
...
<head>
<title><bean:message key="searchPage.title"/></title>
</head>
...
This example dynamically inserts the value of the searchPage.title resource into the JSP at run time. Second, you can return a set of messages from an action to a JSP by using Struts’ ActionMessages class.
Note :- When your application first accesses a resource bundle, Struts loads the bundle into memory and caches it so that it can be quickly accessed again. Because of this, if you modify your resource bundles while your application is running, you will have to restart your application server before you can see the changes.
Struts’ Tag Library Support for Internationalization |
- Several of the Struts tag library tags support internationalization by way of allowing certain attributes’ values to be specified as keys to properties in a message resources bundle.
- At run time, Struts uses the keys to look up a corresponding value and then uses the value for the given attribute.
- The following table lists each of the tags and their attributes that support internationalization.
Library | Tag | Attribute |
---|---|---|
Bean | message | key |
Bean | write | formatKey |
HTML | button | altKey |
HTML | button | titleKey |
HTML | cancel | altKey |
HTML | cancel | titleKey |
HTML | checkbox | altKey |
HTML | checkbox | titleKey |
HTML | file | altKey |
HTML | file | titleKey |
HTML | frame | titleKey |
HTML | hidden | altKey |
HTML | hidden | titleKey |
HTML | image | altKey |
HTML | image | pageKey |
HTML | image | srcKey |
HTML | image | titleKey |
HTML | img | altKey |
HTML | img | pageKey |
HTML | img | srcKey |
HTML | img | titleKey |
HTML | link | titleKey |
HTML | messages | footer |
HTML | messages | header |
HTML | multibox | altKey |
HTML | multibox | titleKey |
HTML | option | key |
HTML | password | altKey |
HTML | password | titleKey |
HTML | radio | altKey |
HTML | radio | titleKey |
HTML | reset | altKey |
HTML | reset | titleKey |
HTML | select | altKey |
HTML | select | titleKey |
HTML | submit | altKey |
HTML | submit | titleKey |
HTML | text | altKey |
HTML | text | titleKey |
HTML | textarea | altKey |
HTML | textarea | titleKey |
Note:- Many of the Struts tags have a bundle attribute that allows you to specify the name of a bundle from which values are loaded. That way you’re not limited to putting all of your internationalized properties into a single resource bundle.
Example for Internationalizing Struts Application |
Internationalizing the Mini HR Application
Now that you’ve seen how Struts’ internationalization support works, you are ready to revisit the Mini HR application and update it to support internationalization.
Following is the list of steps involved in adding internationalization support to Mini HR:
- Add entries for all application text to the MessageResources.properties resource bundle file.
- Create a Spanish version of the MessageResources.properties file.
- Update JSPs to retrieve all application text from the MessageResources.properties file.
- Repackage and run the updated application.
The following sections walk through each step of the process in detail.
Add Entries for All Application Text to the MessageResources.properties Resource Bundle File
The first step in updating Mini HR to support internationalization is to add entries for all application text to the MessageResources.properties resource bundle file. As you’ll see, having all application text in this file allows it to be easily translated into other languages. The MessageResources.properties file from the original Mini HR application contains some of the application’s text; however, all text needs to be placed in this file for internationalization to work.
Following is the updated MessageResources.properties file in its entirety:
# Title Resources
title.application=ABC, Inc. Human Resources Portal
title.employee.search=Employee Search
# Link Resources
link.employee.add=Add an Employee
link.employee.search=Search for Employees
# Label Resources
label.search.name=Name
label.search.ssNum=Social Security Number
label.submit=Submit
# Error Resources
error.search.criteria.missing=Search Criteria Missing
error.search.ssNum.invalid=Invalid Social Security Number
error.search.not.found=No Employees Found
errors.header=<font color="red"><cTypeface:Bold>Validation Error(s)</b></font><ul>
errors.footer=</ul><hr width="100%" size="1" noshade="true">
errors.prefix=<li>
errors.suffix=</li>
Notice that there are several new entries. All of the text from the JSPs has been moved into this file so that the application can have both English and Spanish versions.
Create a Spanish Version of the MessageResources.properties File
Once entries for all of the application text have been put in the MessageResources.properties file, you have to create a Spanish version of the file to support Spanish users. Do this by creating the MessageResources_es.properties file, shown here:
# Title Resources
title.application=ABC, Inc. Recursos Humanos Porta
title.employee.search=Búsqueda Del Empleado
# Link Resources
link.employee.add=Agregue a empleado
link.employee.search=Búsqueda para los empleados
# Label Resources
label.search.name=Nombre
label.search.ssNum=Número De la Seguridad Social
label.submit=Someter
# Error Resources
error.search.criteria.missing=El Faltar De los Criterios De la Búsqueda
error.search.ssNum.invalid=Número Inválido De la Seguridad Social
error.search.not.found=Ningunos Empleados Encontraron
errors.header=<font color="red"><cTypeface:Bold>Error De la Validación(s)</b></font><ul>
errors.footer=</ul><hr width="100%" size="1" noshade="true">
errors.suffix=<li>
errors.prefix=</li>
Notice that this file’s name is only slightly different from MessageResources.properties in that it has _es appended to the MessageResources part of the original filename. The _es denotes which locale the file is for-in this case, the Spanish language. You could, of course, include a country code along with the language code to make the file specific to a certain country/language combination. This file contains all the same entries as the MessageResources.properties file, but the property values are in Spanish. The property names are universal across all locales’ files. That’s how applications reference the text.
Note :- The translations in this section were performed using Google’s translation service and are for demonstration purposes only
Update JSPs to Retrieve All Application Text from the MessageResources. properties File
After you have updated MessageResources.properties and created a MessageResources_ es.properties file, you have to update Mini HR’s JSPs. The original JSPs have a mix of hard-coded text and text that is dynamically inserted into the page from the MessageResources. properties file. In order to support internationalization, all of the hard-coded text has to be moved into the MessageResources.properties file so that it can be obtained dynamically, based on locale. Following are the updated JSPs, index.jsp and search.jsp, with all the hard-coded text replaced with <bean:message> tags that dynamically insert the text into the pages.
index.jsp
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<html>
<head>
<title><bean:message key="title.application"/></title>
</head>
<body>
<font size="+1"><bean:message key="title.application"/></font><br>
<hr>
<bean:message key="link.employee.add"/><br>
<html:link forward="search">
<bean:message key="link.employee.search"/></html:link><br>
</body>
</html>
search.jsp
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
<html>
<head>
<title>
<bean:message key="title.application"/> -
<bean:message key="title.employee.search"/>
</title>
</head>
<body>
<font size="+1">
<bean:message key="title.application"/> -
<bean:message key="title.employee.search"/>
</font><br>
<hr width="100%" noshade="true">
<html:errors/>
<html:form action="/search">
<table>
<tr>
<td align="right"><bean:message key="label.search.name"/>:</td>
<td><html:text property="name"/></td>
</tr>
<tr>
<td></td>
<td>-- or --</td>
</tr>
<tr>
<td align="right"><bean:message key="label.search.ssNum"/>:</td>
<td><html:text property="ssNum"/> (xxx-xx-xxxx)</td>
</tr>
<tr>
<td></td>
<td><html:submit><bean:message key="label.submit"/></html:submit></td>
</tr>
</table>
</html:form>
<logic:present name="searchForm" property="results">
<hr width="100%" size="1" noshade="true">
<bean:size id="size" name="searchForm" property="results"/>
<logic:equal name="size" value="0">
<center><font color="red"><b>
<bean:message key="error.search.not.found"/>
</b></font></center>
</logic:equal>
<logic:greaterThan name="size" value="0">
<table border="1">
<tr>
<th><bean:message key="label.search.name"/></th>
<th><bean:message key="label.search.ssNum"/></th>
</tr>
<logic:iterate id="result" name="searchForm" property="results">
<tr>
<td><bean:write name="result" property="name"/></td>
<td><bean:write name="result" property="ssNum"/></td>
</tr>
</logic:iterate>
</table>
</logic:greaterThan>
</logic:present>
</body>
<html>
Note that the Bean Tag Library definition had to be added to index.jsp so that it could use the <bean:message> tag to source in text.
Repackage and Run the Updated Application
Because no Java source code files had to be modified to update the Mini HR application to support internationalization, you do not have to recompile the application. All you have to do is repackage the application and redeploy it before running it again. Assuming that you’ve made modifications to the original Mini HR application and it was set up in the c:\java\MiniHR directory, the following command line will repackage the application:
jar cf MiniHR.war *
This command should be run from the directory where you have set up the Mini HR application (e.g., c:\java\MiniHR).
To test the Spanish version of the application, you have to change your browser’s language settings to Spanish. Following are the instructions for doing this with Microsoft Internet Explorer;
I am truly grateful to the holder of this website who has shared this wonderful paragraph at at this time.