Friday, July 27, 2007

Internationalization: Spring upon it

Supporting different locales using Spring Framework

Internationalization (also known as i18n where 18 is the number of letters between 'i' and 'n') is increasingly becoming a requirement today rather than being just a nice-to-have feature couple of years ago. It requires decoupling of data (date format, text, images, etc.) from source code (logic) so as to enable support for localized preferences. Though Java supports i18n, Spring framework makes it even easier to work with through a host of utility classes.

Let us see what it takes to display "Hello World!" in different languages using the same code base with Spring framework. The language preference is to be supplied in the URL as a parameter. In this article, I'll focus only on the parts that are concerned with our internationalization requirement. I will leave the deployment and testing details for the reader to take care of as I expect her to have some prior experience on web applications.

  1. The first step is to take out all the localized resources (text, image source names, date formats, etc.) from the application code and put it in resource bundles. A ResourceBundle contains key/value pairs. Let us make a properties file and put it in the classpath of our application. Let us place it in WEB-INF/classes folder. I have named my file which has one entry:

  2. message.Hello=Hello World!

  3. We need to maintain messages in other languages as different files with same name, only suffixing the name with the appropriate ISO language code. So, for instance, we'll need a file to hold the Japanese entries where in the key will have to be same as in but the value will be Unicode(UTF-8) representation of the message in Japanese or any other language for that matter.

  4. Next, we need a ResourceBundle bean to represent our message resources. For this example, we'll use ResourceBundleMessageSource. You can also consider using ReloadableResourceBundleMessageSource for an alternative that is capable of refreshing the underlying bundle files. Let us see the entry in configuration metadata. We are using XML-based metadata for our purpose:

  5. <bean id="messageSource" class="">
    <property name="basename" value="messages"/>

  6. Now, we need a mechanism to resolve when to use which resource bundle (messages file). As mentioned earlier, this resolution uses a parameter in the request URL. For our purpose, lets say, the parameter name is "userLang". Fortunately, Spring framework provides LocaleChangeInterceptor thats reads a parameter in the request and change the locale. We will need a LocaleResolver to resolve the messages based on the Locale set in by the interceptor. Here is the configuration entry that provides the whole mechanism:

  7. <!-- Declare the Interceptor -->
    <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>

    <!-- Declare the Resolver -->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>

    <!-- Add the Interceptor to handler mapping -->
    <bean id="urlMapping"
    <property name="interceptors">
    <ref bean="localeChangeInterceptor"/>
    <property name="mappings">

  8. The basic work is done. We have all the right pieces in place and we are ready to start reading the messages based on the value of 'userLang' parameter provided in URL. Spring framework provides a RequestContext that helps getting the messages.

  9. String msg = new RequestContext(request).getMessage(key, default);

    This gives us the localized messages based on the Locale set in LocaleResolver.

  10. As we are reading from a property file for the values in different languages supporting non-latin Unicode characters, it can be a little tricky. Once I had run into trouble while supporting the Japanese language and I resolved it the following way:

  11. String finalMsg = new String(msg.getBytes("ISO-8859-1"),"UTF-8");

  12. It is a good practice to wrap the logic of getting the messages from RequestContext in a helper class instead of using RequestContext directly in a JSP.

  13. Make sure the target JSP supports UTF-8 charset. Put the following directive in your JSP.

  14. <%@ page contentType="text/html; charset=UTF-8" pageEncoding="iso-8859-1" %>