Back to the Web Developer's Journal Main Page
internet.com
side nav bar

How to use the Java Internationalization API to build server-side code that alters its output based on the location and language of the user. Since the displayed content is not hard-coded, you will not need to write multiple versions of a class for each locale that you want to support. And adding a new language is as easy as creating a new text file of display labels. We will demonstrate the use of the API by building a simple method which displays a product entry for a shopping cart application.
When you've read this, try these:

HOW DID THEY DO THAT???

Find out in:
Amazing HTML



Site Map

Jobs at webdeveloper.com


Check out our Web-based
Discussion Groups:

Check out and join our email-based Mailing Lists for Web developers.


Developer Channel
FlashKit
Jobs.webdeveloper
JavaScript.com
JavaScriptSource
JustSMIL
ScriptSearch
Streaming Media World
WebDeveloper.com
WebReference
XMLFiles
WDVL
Discussion Groups Book Reviews Software Reviews Download Web Tools

An easy way to adapt your site for international users.

Internationalizing Servlets

by Mark Webber

In this article I will show you how to use the Java Internationalization API to build server-side code that alters its output based on the location and language of the user. Since the displayed content is not hard-coded, you will not need to write multiple versions of a class for each locale that you want to support. And adding a new language is as easy as creating a new text file of display labels. We will demonstrate the use of the API by building a simple method which displays a product entry for a shopping cart application. This will make use of a class of mine called ResourceBundleStore which is intended to optimize the display of internationalized data. You are free to use this class in your own projects.
June 6, 2000

The power and ease of use of the internationalization classes are further reasons to choose Java as a server-side language. And don't forget that Java handles text internally in Unicode, so just about every language under the sun can be represented as long as the client browser is capable of displaying the character set and handling any intermediate encodings that may be required.

A Brief Introduction to the API

java.util.Locale

A Locale object represents a geographical, political, or cultural region, and is used with the various Java classes that perform locale-specific operations. Its constructor takes an ISO language code and an ISO country code. It is simply an identifier and holds no information about how the country and language should be represented. The following line of code creates a Locale for British English.

Locale locale = new Locale("en", "GB");

java.text.Format

The Format class is the abstract parent of those classes that perform locale-specific formatting of dates, times, and numbers. The DateFormat class is used for parsing and formatting dates and times, and the NumberFormat class is used to handle things such as decimal points and thousands separators, which is useful for the display of currencies. Our sample method below will use the NumberFormat class for this purpose. NumberFormat is abstract, and an instance of a subclass is normally obtained by one of the NumberFormat.getInstance() methods.

java.util.ResourceBundle

A ResourceBundle is a container that a program can use to retrieve an object for a particular Locale. When a program needs locale-specific objects it makes a call to ResourceBundle.getBundle(). This first searches for subclasses of ResourceBundle with a name that points to the specified Locale. If none are found it goes on to search for properties files in the classpath that are named for the Locale. If it still can't find anything then it throws a MissingResourceException. The ResourceBundle class is abstract and you can either rely on it to use one of its own subclasses or you can subclass it yourself. The latter approach would be taken if you wanted to store locale-specific objects in a class. However, since we are only interested in displaying Strings we will take a far simpler approach and make use of the PropertyResourceBundle class, which is designed to load textual data from properties files. We never have to handle this class directly since the call to ResourceBundle.getBundle() will create an instance of it to store the Strings. The name of the properties file is very important and will be in the order <basename>_<language>_<country>. The call to ResourceBundle.getBundle("MyBaseName", new Locale("fr", "FR")) will attempt to retrieve the PropertyResourceBundle for the file with the name "MyBaseName_fr_FR.properties". (Actually there is far more to the lookup mechanism than this because it uses a search order and can settle for a default class or file if one exists. This is part of the real power of the API and you should consult the documentation for specific details on the naming patterns and search order.) The file is a standard properties file with name=value pairs, one per line, and it should be visible on the classpath otherwise it will not be found. Once the PropertyResourceBundle is loaded you can use getString(String key) to retrieve the required value.

The Drawbacks of Using PropertyResourceBundle in Servlets

There are two issues to be considered when using a PropertyResourceBundle in a servlet which my ResourceBundleStore class will address. The first is that file access is required to load the text, and we don't want this to occur at each client request. The second is that if you examine the source code of the class, you'll see that it stores the name-value pairs in a java.util.Properties object which is a subclass of java.util.Hashtable. The Collections Framework, new to Java 1.2, offers improved performance over older classes such as Vector and Hashtable by providing alternatives like ArrayList and HashMap that have unsynchronized access methods.

In a multi-threaded environment such as a servlet engine, threads have to wait to obtain a lock on a Hashtable before they can access its contents. With a busy web site and a large quantity of internationalized labels this would cause a degradation in performance. But by using a HashMap instead of a Hashtable we can avoid this problem. Our ResourceBundleStore class will therefore transfer the contents of the Hashtable to a HashMap. As long as the HashMap is not structurally modified it is safe to have multiple concurrent access. The problems arise when a key is added or removed while another thread is iterating over the contents of the map. But our ResourceBundleStore class simply accesses single mappings so there is no danger of a ConcurrentModificationException being thrown.

The ResourceBundleStore class

We could of course write our own simple lookup mechanism, but the ResourceBundle classes are powerful and extensible and so they should be utilized. The ResourceBundleStore is simply a wrapper around the PropertyResourceBundle which eliminates the performance worries of retreiving Strings while keeping the lookup functionality of the ResourceBundle API. It should be instantiated in the init() method of the servlet and given class scope so that each request does not create it anew. If you are using JSP, it should be used as a bean and given application scope.

ResourceBundleStore.java

import java.util.*;
import javax.servlet.*;

public class ResourceBundleStore {

private String strBaseName;
private HashMap localeDataHash; // The top level hash containing
//the second-level
// hashes for each Locale. The top-level key is
// the Locale object.

public ResourceBundleStore() {
localeDataHash = new HashMap();
}

public void setBaseName(String strBaseName) {
this.strBaseName = strBaseName;
}

// If the key is not found in the bundle, this method will return null.
public String getString(String strServletName,
String strKey,
Locale locale) throws ServletException {

// Check to see if the PropertyResourceBundle for this Locale has
//been loaded.
if(!localeDataHash.containsKey(locale)) {
try {
loadLocale(locale);
}
catch(MissingResourceException mre) {
throw new ServletException("The attempt to locate a resource file failed. ",
mre);
}
}
return (String)((HashMap)localeDataHash.get(locale)).get(strServletName + "." + strKey);

}

// This will load the resource bundle and add the data it contains
//to our top-level hash.
private void loadLocale(Locale locale) throws MissingResourceException {
ResourceBundle bundle = ResourceBundle.getBundle(this.strBaseName, locale);
HashMap secondaryHash = new HashMap();
Enumeration enum = bundle.getKeys();
while(enum.hasMoreElements()) {
String strKey = (String)enum.nextElement();
secondaryHash.put(strKey, bundle.getString(strKey));
}
// Now add the newly-created hash to our top-level hash.
localeDataHash.put(locale, secondaryHash);

}

}

Each ResourceBundleStore object holds all of the locale-specific data for a single web application (if the application is large it may be more convenient to break this down further and have one store per servlet). Each individual properties file contains all of the application's labels for a single Locale. The label identifiers are in the form <servlet_name>.<label_name>. The class is in the form of a bean with a no-argument constructor and a public SET method. On the first call to getString() for a particular Locale, all of its data is loaded and stored. Subsequent calls simply retrieve the String from the store and not via another call to the bundle. The PropertiesResourceBundle functionality is in the loadLocale() method, which uses the standard call to getBundle() and then enumerates through all of the keys and puts them along with their values into a HashMap which itself is put into the top-level HashMap with the Locale object as key.

A Sample Application

Let's look at how ResourceBundleStore can be used. Below is the code for a method that is part of a servlet which displays a list of items in a shopping basket. The method is passed the single instance of the ResourceBundleStore which it uses to retrieve two text labels. It also uses the NumberFormat class for displaying the currency.

private String getProductEntry(String strProductName,
double dblPrice,
int intQuantity,
Locale locale,
ResourceBundleStore rbs) throws ServletException {
StringBuffer sb = new StringBuffer();
sb.append("<TR><TD>");
sb.append(strProductName);
sb.append("</TD></TR>");
sb.append("<TR><TD>");
sb.append(rbs.getString(this.getClass().getName(),
"price_label",
locale));
sb.append(": ");
sb.append(NumberFormat.getCurrencyInstance(locale).format(dblPrice));
sb.append("</TD></TR>");
sb.append("<TR><TD>");
sb.append(rbs.getString(this.getClass().getName(),
"quantity_label",
locale));
sb.append(": " + intQuantity);
sb.append("</TD></TR>");
return sb.toString();

}

This is a rather artificial example. It is certainly not wise to create a new NumberFormat instance for each invocation of the method as I have done here, since this would introduce too much of a performance hit. The purpose of the code is to demonstrate the use of the class. And you would probably also want to convert the £ sign to its HTML character entity equivalent.

The contents of two sample properties files on the classpath are as follows.

MyApplication_en_GB.properties:

SampleServlet.price_label=price
SampleServlet.quantity_label=quantity

MyApplication_fr_FR.properties:

SampleServlet.price_label=prix
SampleServlet.quantity_label=quantité

Calling the method as getProductEntry("Product A", 9.99, 2, new Locale("en", "GB"), rbs) produces the following output (where rbs is the reference to the ResourceBundleStore instance):

Product A
price: £9.99
quantity: 2

And using getProductEntry("Product A", 9.99, 2, new Locale("fr", "FR"), rbs) results in:

Product A
prix: 9,99 F
quantité: 2

And that's how easy it is! The only difference between these two method invocations is the Locale object. It would be a good idea to store the Locale object as session object which is set when the user first enters the web application. And in a JSP you would include the labels in the HTML with a call like the following (where rbs is the reference to the bean instance of ResourceBundleStore):

<%= rbs.getString( . . .) %>

This article has demonstrated some of the advantages in using the Java internationaliation classes in servlets. We have only touched on a few possibilities, and we also have seen how it is sometimes necessary to adapt core classes before they are used in the context of server-side code, where performance is all-important.



Mark Webber is a Web Developer with Demon Internet, a brand of Thus plc. He is a Sun Certified Java Programmer.
Back to the Web Developer's Journal
Contact WDJ   •    Suits!   •    Propheads!   •    Ponytails!
Discuss   •    Subscribe   •    Search


internet.com

IT | Developer | Internet News | Small Business | Personal Technology | International | Search internet.com | Advertise | Corporate Info
Newsletters | Tech Jobs | E-mail Offers

internet.commerce
Be a Commerce Partner