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 messages.properties 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 messages_ja.properties to hold the Japanese entries where in the key will have to be same as in messages.properties 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="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="messages"/>
    </bean>

  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"/>
    </bean>

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

    <!-- Add the Interceptor to handler mapping -->
    <bean id="urlMapping"
    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
    <list>
    <ref bean="localeChangeInterceptor"/>
    </list>
    </property>
    <property name="mappings">
    <value>/**/*.view=myController</value>
    </property>
    </bean>

  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" %>


9 comments:

Anonymous said...

Thank you, this worked perfectly!

Anonymous said...

Good words.

venkat said...

I'm very new to spring & i18n. when i tried to imlement using your code, I'm getting following errors:
1. Error creating bean with name 'urlMapping' defined in ServletContext resource
2. No bean named 'myController' is defined

Can you suggest steps to fix it?.

vani said...

very nicely explained!

Anonymous said...

Thank you, very useful!

Anonymous said...

Nice article. The links to the Spring APIs are out of date now though.

Anonymous said...

I want not concur on it. I assume polite post. Expressly the title-deed attracted me to read the intact story.

Anonymous said...

Nice dispatch and this mail helped me alot in my college assignement. Say thank you you seeking your information.

Anonymous said...

i definitely adore all your writing style, very useful.
don't give up and also keep penning simply because it simply very well worth to follow it,
impatient to read more and more of your web content, have a pleasant day!