Easier globalization of JavaServer Faces Web applications with IBM Rational Application Developer

Take advantage of the JavaServer Widgets Library, or JWL, in Version 7 and later

Learn how to use IBM Rational Application Developer to enable globalization for JavaServer Faces Web applications. This article describes challenges in developing for global markets and explains how using the JavaServer Faces Widget Library (JWL) can help you handle them with ease.

Ting Wei Feng (fengtw@cn.ibm.com), Software Engineer, IBM

Photo of Ting Wei FengTing Wei Feng is a software engineer at the IBM China Systems and Technology Lab in Shanghai. He joined IBM in 2008 and is working on the IBM Systems Director and Storage Configuration Manager project.



Xiao Chuan Cai (caixiaoc@cn.ibm.com), Software Engineer, IBM

Photo of Xiao Chuan CaiXiao Chuan Cai is a software engineer at the IBM China Systems and Technology Lab in Shanghai. He joined IBM in 2007 and is working on IBM Systems Director and the Storage Configuration Manager project.



09 July 2009

Also available in Chinese Spanish

Starting with Version 7, IBM® Rational® Application Developer includes the JavaServer Faces Widget Library (JWL), which is a Java™Server Faces (JSF)- and JavaScript-based library to use for quicker development of rich Web applications.

The JavaScript library of JWL, hxclient, implements the client-side support for JWL components. It also contains a set of utilities called “JSF converters” that can help developers parse and format date, time, and numbers from or to their locale-specific pattern. More specifically, these utilities are JavaScript implementations of Java SimpleDateFormat and DecimalFormat. These utilities are very useful for applications that are designed to support more than one language, because they help you handle the challenges of locale-sensitive data input and output on the client side.

This article also explains a common globalization challenge related to multi-threading in JavaServer Faces applications and provides a solution. The authors assume that readers have a basic knowledge of both JSF and JWL.

Globalization basics

Globalization basics

If you want a quick start with JSF globalization basics, read Globalizing Web applications with JSF and IBM WebSphere Studio by Peng Hua (IBM® developerWorks®, 2004).

In a Web application, the output language is determined by the HTTP request header’s Accept-Language field. A user can specify a preferred language and locale with a browser setting.

The JSF framework parses the HTTP request header. You can retrieve this value by using the code in Listing 1.

Listing 1. Retrieval request for language and locale
Locale locale = FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();

The locale value is used to determine the language to be used for display.


Use JWL to work with locale-sensitive output and input

After a quick introduction of globalization, we are now ready to discuss these two challenges in globalizing a JSF Web application:

  • Displaying client date and time using the local format
  • Displaying and accepting the local number format

How hxclient is initialized in a page

As long as you are using JWL tags in page, they will ensure that the library is installed in the page and initialized appropriately. If you set your browser locale request to “ja” (for Japanese) and view the source code of the HTML page that is rendered by using the JWL, you will find code like Listing 2 shows.

Listing 2. Structure of a Web page that is using the JWL
<script type="text/JavaScript" language="JavaScript" 
src="/sample/.ibmjsfres/hxclient_core_v3_0_8.js"></script>

<script type="text/JavaScript" language="JavaScript" 
src="/sample/.ibmjsfres/hxclient_S_v3_0_8_ja.js?viewLocale=ja">
</script>

<script type="text/JavaScript" language="JavaScript">
    if (hX_5) hX_5.setResourceServer("/sample/.ibmjsfres");
    if(hX_5 && hX_5.setLocale) hX_5.setLocale("ja");
</script>

That code does three things:

  1. Includes the hxclient core script library on the page
  2. Includes the hxclient locale-specific script library on the page
  3. Sets up the current page locale

As you have already seen, you do not need to set up the locale manually, because the JWL determines the locale automatically by reading locale request. With the automatic initialization of hxclient, you are ready to use it for date, time, and number format.

Display client date and time by using local format with JWL

For a Web application, developers often want to display the last request time on a page. In a globalized application, this must be in the local format.

For instance, a U.S. user might want to see the date/time format displayed this way:

Last Refresh: Friday, May 8, 2009 1:35:07 PM GMT+08:00

But a user in Japan would see the date/time like this:

前回の最新表示: 2009年5月8日金曜日 13時41分07秒GMT+08:00

Because the time should be calculated on the client side, JavaScript does not provide a solution through the built-in API: Date.toLocaleString(). This is because:

  1. The locale of the returned value is not determined by a browser locale setting but by the locale of the client operating system.
  2. The developer has no chance to specify the date format.

DateTimeConverter is a utility of the JWL client script library. It is a JavaScript implementation of the Java SimpleDateFormat class with good support of ICU4J (International Components for Unicode Java Library). It makes client-side date/time format as easy as what you can do with Java™. In this example, we use DateTimeConverter and ICU4J together to generate client-side local date/time.

For a quick start, let’s jump to what the client-side script will be like:

Listing 3. JavaScript for date/time formatting
function getLocalizedCurrentTime()
{
    var converter = hX.getConverterById("date_converter");
    if(null == converter)
    {
        //construct a new DateTimeConverter and add it to converter set
        hX.addConverter("date_converter", new hX.DateTimeConverter(
            "format:EEEE, MMMM d, yyyy h:mm:ss a z",
            "ICU4J:true"));
    }
    converter = hX.getConverterById("date_converter");
    var date = new Date();
    //format client date and return
    return converter.valueToString(date);
}

That is all you need to localize your date/time. But these parameters are passed to the DateTimeConverter constructor:

  • “ICU4J:true” allows the DateTimeConverter to accept ICU-specified characters in patterns.
  • “format:EEEE, MMMM d, yyyy h:mm:ss a z” means the pattern that DateTimeConverter uses to format date/time.

What the pattern means

For more information, read the Date and Time Patterns section on the Java site.

So far, we have shown what client code you need to format date/time. But it is not enough. Think of the pattern. How do you know what the pattern would be like for different locales?

For instance, the pattern for an American user should be like this:

EEEE, MMMM d, yyyy h:mm:ss a z

While the pattern for Japanese user is like this:

yyyy'年'M'月'd'日'EEEE H'時'mm'分'ss'秒'z

Therefore, it is best that the pattern never be hard-coded, because developers will never know all of the patterns for different locales. A pattern such as “yyyy mm dd” might look reasonable to developer but weird to a user.

Therefore, the answer is to derive patterns from the server side, because ICU4J has already prepared patterns for all locales for developers’ use. The code in Listing 4 shows how we can get a pattern according to the locale request:

Listing 4. Generating the date/time pattern by using ICU4J
public class FormatterUtils
{
    public static String getDateTimePattern(int dateFormat)
    {
       Locale locale = 
           FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
       CalendarData calData = new
           CalendarData(ULocale.forLocale(locale), null);
       String[] dateTimePatterns = calData.getStringArray("DateTimePatterns");
       return dateTimePatterns[dateFormat + 4] + " " + dateTimePatterns[dateFormat];
    }
}

The dateFormat parameter tells the method which format pattern to use. Basically, there are four types predefined in the ICU com.ibm.icu.text.DateFormat class:

  • Full
  • Long
  • Medium
  • Short

With different format parameters, the return value of the getDateTimePattern method differs. For instance, with en-us locale, these are the return values of the four types:

  • EEEE, MMMM d, yyyy h:mm:ss a z
  • MMMM d, yyyy h:mm:ss a z
  • MMM d, yyyy h:mm:ss a
  • M/d/yy h:mm a

So now that you have the format pattern ready by choosing one from those four, let us assume that you are using the Full format. The next step is to render this format pattern to the client side for the use of hxclient. With the help of Java™Server Pages (JSP™) scriptlet, this is easy to do. With the pattern bound to the client, the JavaScript code should look like Listing 5.

Listing 5. Bind the format pattern to the client code
<script>
    var datetimeFormatPattern = "<%=FormatterUtils.getDateTimePattern()%>";

    function getLocalizedCurrentTime()
    {
        var converter = hX.getConverterById("date_converter");
        if(null == converter)
        {
            //construct a new DateTimeConverter and add it to converter set
            hX.addConverter("date_converter", new hX.DateTimeConverter(
                "format:" + datetimeFormatPattern, "ICU4J:true"));
        }
        converter = hX.getConverterById("date_converter");
        var date = new Date();
        //format client date and return
        return converter.valueToString(date);
    }
</script>

Now you are finish. By invoking the JavaScript getLocalizedCurrentTime method, you can get the date and time text for the client display (see Figure 1 for English and Figure 2 for Japanese, as examples).

Figure 1. Date and time display with en-us
Shows date and time display with English
Figure 2. Date and time display with ja-jp
Shows date and time display with Japanese

Display and accept local number format with JWL

Displaying the number with a local format is similar to displaying date/time. A decimal number such as 1000.1 should be displayed as 1,000.1 in the U.S. but as 1.000,1 in Germany.

Unlike date and time, these numbers are rendered from the server side, where ICU DecimalFormat can do the job. But in some cases, you might want to format the number on the client side (for example, to accept user input and display the value). In the JWL hxclient library, NumberConverter implements the logic of DecimalFormat on the client side.

Still, let’s jump to the full client code first:

Listing 6. JavaScript for number formatting
<script>
    var decimalFormatPattern = "<%= FormatterUtils.getDecimalFormatPattern() %>";
    var decimalFormatSymbols = "<%= FormatterUtils.getDecimalFormatSymbols() %>";

    function formatDecimal(value)
    {
        var converter = hX.getConverterById("number_converter");
        if(null == converter)
        {
            hX.addConverter("number_converter", 
                new hX.NumberConverter("pattern:" + decimalFormatPattern, 
                "locale:" + decimalFormatSymbols, "ICU4J:true"));
        }
        converter = hX.getConverterById("number_converter");
        var output = cvt.valueToString(value);
        return output;
    }
</script>

What the pattern means

Check the Java site for more about the number pattern specification.

There are three parameters that are passed to the NumberConverter constructor:

  • “ICU4J:true”: This allows NumberConverter to accept ICU specifies characters in pattern.
  • "pattern:" + decimalFormatPattern: This is the number pattern to use when converting the value.
  • "locale:" + decimalFormatSymbols: This is the locale information to be used when converting the value. It contains symbols for decimal separator, percent character, and so forth.

As for DateTimeConverter, pattern and locale information should be derived from the server side:

Listing 7. Generating number pattern and symbol using ICU4J
public class FormatterUtils
{
    private static DecimalFormat getDecimalFormatter()
    {
        Locale locale = 
            FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
        return (DecimalFormat)NumberFormat.getInstance(locale);
    }

    public static String getDecimalFormatPattern()
    {
        return getDecimalFormatter().toPattern();
    }

    public static String getDecimalFormatSymbols()
    {
        Locale locale = 
            FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
        DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
        //The information is provided as a string of 6 characters with fixed format:
        StringBuilder sb = new StringBuilder();
        sb.append(symbols.getGroupingSeparator());
        sb.append(symbols.getDecimalSeparator());
        sb.append(symbols.getPercent());
        sb.append(symbols.getPerMill());
        sb.append(symbols.getMinusSign());
        sb.append(symbols.getCurrencySymbol());
        return sb.toString();
    }
}

As you saw in Listing 6, getDecimalFormatPattern and getDecimalFormatSymbols are used to render number pattern and locale information in the page. With the collaboration of the server-side code, you can use the JavaScript formatDecimal()function to format JavaScript Number type variables. Listing 8 gives you an example.

Listing 8. Format number using client code
<script>
    //Suppose current locale is "de"
    var value = 1000.1; //type of value is Number
    var formatted = formatDecimal(value); //the formatted value is "1.000,1" in Germany
</script>

The number in the page is usually formatted like Figure 3 (this example shows a pool capacity statistic in German):

Figure 3. Formatted number in page:
Shows formatted number in page

While working with numbers, take care of not only output but also input. Input varies a lot with different user habits. A German user might enter either 1.000,1 or 1000,1. Both should be recognized as the decimal number 1000.1. This is a tough task for developers, because they will have to write thousands lines of code to validate user input.

The good news is that the JWL hxclient can convert numbers. You can use this function to convert user input into a JavaScript Number object. This helps separate a number display from its value by performing these steps automatically:

  1. Accept user input.
  2. Parse the input of the String object into a Number object by using NumberConverter.
  3. Use the converted value for calculation.
  4. Format the calculation result back to a String object by using NumberConverter again.
  5. Use the formatted value for display.

The code in Listing 9 gives a sample for how to parse user input (with locale as “de,” for Deutsch, or German):

Listing 9. JavaScript parsing input number
<script>
    var decimalFormatPattern = "<%= FormatterUtils.getDecimalFormatPattern() %>";
    var decimalFormatSymbols = "<%= FormatterUtils.getDecimalFormatSymbols() %>";

    function formatDecimal(input)
    {
        var converter = hX.getConverterById("number_converter");
        if(null == converter)
        {
            hX.addConverter("number_converter", 
                new hX.NumberConverter("pattern:" + decimalFormatPattern, 
                "locale:" + decimalFormatSymbols, "ICU4J:true"));
        }
        converter = hX.getConverterById("number_converter");
        var output = cvt.stringToValue(input);
        return output;
    }

    var parsedValue = formatDecimal("1.000,1"); //the parsed value is 1000.1 
    parsedValue = formatDecimal("1000,1"); //the parsed value is 1000.1 
    parsedValue = formatDecimal("oops"); //parsing fails, null is returned
</script>

The code in Listing 9 is pretty much like Listing 6 in that it derives pattern and locale information from the server side, sets up an instance of NumberConverter, and then performs the task. The only difference is the method invoked: stringToValue(). The name of the method is self-explanatory: it parses a String object and tries to convert it into Number object. If there is any error during conversion, null will be returned. Therefore, NumberConverter can also be used to validate user input.

So far, we have covered how the JWL hxclient script helps you handle date and number parsing and formatting on the client side. In the sample code, from time to time we need to get locale information on the server side to generate the format pattern. Thus, in the next section, we move to a more advanced topic, which discusses how locale information is passed and used in a JSF Web application.


Globalization road hazards in multi-thread JSF applications

Enabling globalization with JSF is easy, but not foolproof. Especially in multi-thread applications, if the design is not well-considered, mistaken assumptions will make your globalization support more like applying a couple of patches. The techniques that we describe next will help you make your multi-thread JSF applications more robust.

Decide on the displayed language

Globalization is usually based on the locale. Therefore, how to enable globalization is about how to work with the locale. After the locale information is retrieved, you are ready to decide on the language to be displayed in user interface, based on locale. In most cases, the JSF framework takes care of the language bundle for you with the <loadBundle> tag, which resolves the bundle according to locale request without any extra coding. But in case you need to use language bundle content in Java code, you will need to use the locale to resolve the bundle path and then format the message yourself. Listing 10 gives you sample code.

Listing 10. A simple message formatter.
public class MessageFormatter {
private static final String MESSAGE_BUNDLE_NAME = "com.ibm.sample.messages";
 
private static String formatMessage(String msgKey, Object[] args,Locale locale) {
    ResourceBundle messageBundle = ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME,locale);
    String message = messageBundle.getString(msgKey);
    if (message != null) {
        if (args == null) {
            return message;
        } else {
            return MessageFormat.format(message, args);
        }
    } else {
        return msgKey;
    }
}
}

Get request locale work behind you

We very often see the code in Listing 10 in a textbook as sample code that explains how to use Java methods to enable globalization. But it is painful in real practice when you see lots of occurrences of getRequestLocale, as in Listing 1, called before formatting the messages.

Tip:
Check the IBM Java technical library for more information on the ThreadLocal variable.

Next, we are going to show you how to make it work elegantly, instead.

As you know, JSF FacesContext is kept as a ThreadLocal variable in a servlet thread. And because all of the Faces backend bean codes run in the servlet thread, you can retrieve the FacesContext object any time. T is also true for the locale object, because it is retrieved from FacesContext. So you can rewrite the code in Listing 10 a little bit by putting the locale get method in a utility class, as Listing 11 shows.

Listing 11. A better way to get message formatter works
public class LocaleUtils(){
public class LocaleUtils{} {
public static Locale getRequestLocale() {
    return FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
}

} 
public class MessageFormatter {
private static final String MESSAGE_BUNDLE_NAME = "com.ibm.sample.messages";

private static String formatMessage(String msgKey, Object[] args) {
    Locale locale = LocaleUtils.getRequestLocale();
    ResourceBundle messageBundle = ResourceBundle.getBundle(MESSAGE_BUNDLE_NAME, locale);
    String message = messageBundle.getString(msgKey);
    if (message != null) {
        if (args == null) {
            return message;
        } else {
            return MessageFormat.format(message, args);
        }
    } else {
        return msgKey;
    }
}
}

Then MessageFormatter.formatMessage is the only method that you will use to get the message from the language bundle, and it returns the message based on the request. Now you can ignore the locale, because it works for you transparently with the LocaleUtils support.

Pass along the request locale to user threads.

This is still not quite all that you need for making locale work automatically.

There is an exception when you are dealing with a multi-thread application. In a multi-thread application, user-defined threads are not initiated by a servlet thread, thus they do not have FacesContext initialized as a ThreadLocal variable. Consequently, the code runs in this thread will not be able to access locale information when trying to format messages.

A general solution for the problem above is to have each user thread hold a locale variable by passing the variable to the user thread during its construction. When the variable in ready inside of that thread, you can use the sample code listed in Listing 10 to get the globalization working for codes running in the user thread. However, this requires modification of the user thread constructor and the code that invokes it. But there is a better way to make it work gracefully,

We know that at least one user thread (or its parent thread) is instantiated in the servlet thread. In other words, there is at least one user thread’s constructor that is called in the servlet thread. As the code in user thread constructor still runs in the caller thread’s context, we have a chance to inherit a locale object from the servlet thread when the user thread is being constructed. Then we can pass along the locale object with each child thread initiation. So we have an enhanced version of LocaleUtils that works better even in multi-threading environments after user threads become locale-sensitive threads by implementing the LocaleSensitive interface, as we did in BaseThread (see Listing 12).

Listing 12. Locale-sensitive base thread
public class LocaleUtils {

    public static Locale DEFAULT_LOCALE = Locale.ENGLISH;

    public static Locale getCurrentRequestLocale() {
        Locale locale = null;
        try {
            //try to retrieve locale information from FacesContext
            locale = FacesContext.getCurrentInstance().
                getExternalContext().getRequestLocale();
        }
        catch(Throwable e){
            //Unable to reach FacesContext
            //Therefore call getLocale to retrieve locale variable from current thread
            Thread t = Thread.currentThread();
            if (t instanceof LocaleSensitive) {
                locale = ((LocaleSensitive) t).getLocale();
        }
        finally{
            if(locale == null){
                locale = DEFAULT_LOCALE;
            }
        }
        return locale;
    }
}

public interface LocaleSensitive {
    public Locale getLocale();
}

public class BaseThread extends Thread implements LocaleSensitive {
    protected Locale locale= null;

    public Locale getLocale() {
        return locale;
    }

    public BaseThread(String name) {
        super(name);
        //Save a copy of locale in thread instance
        this.locale=LocaleUtils.getCurrentRequestLocale();
    }
}

public class UserThread extends BaseThread{
    public void run(){
        …
        String message = MessageFormatter.formatMessage(msgKey,msgParameters);
        …
    }
}

First of all, you define an interface called LocaleSensitive. Any class that implements this interface should provide locale information in the getLocale() method.

Then you use BaseThread to implement the LocaleSensitive thread. The BaseThread constructor gets the locale object by calling LocaleUtils.getCurrentRequestLocale(), which gets locale information either from FacesContext (when in servlet context) or from the parent thread (this explains how the locale information is passed along). Then if any user thread becomes a subclass of the BaseThread, the thread can use the MessageFormatter with no problems with the enhanced version of LocaleUtils class, as we used it in servlet context.

With the code in Listing 12, you are able to pass along and get locale information in any thread without worrying about whether FacesContext is accessible or not. Now the road hazard of multi-thread JSF Web application is no longer in your way.


Recap

The JavaServer Widgets Library in IBM Rational Application Developer provides scripts to help developers handle globalization problems on the client side. With a little help from server-side code, this makes globalization development much easier. Be careful when you are dealing with a multi-thread Web application, because you need to be more skillful to make sure that locale information is accessible anywhere in the application.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Rational software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational, Agile transformation
ArticleID=406280
ArticleTitle=Easier globalization of JavaServer Faces Web applications with IBM Rational Application Developer
publish-date=07092009