Spring 3 MVC: Internationalization & localization of Podcastpedia.org


Octocat **Promotion** - Efficiently manage your coding bookmarks, aka #codingmarks, on www.codingmarks.org and share your hidden gems with the world. They will be published weekly on Github. Please help us build THE programming-resources location - Star


Podcastpedia.org can be accessed today in four languages – English, German, French and Romanian, with Spanish planned for the near future. In computing this is called internationalization (i18n). The post presents how this is configured under the hood with the help of Spring 3 MVC.

Application Context Configuration

Message Resource Files

Normally in the Java world, the locale-specific data is stored in message resource files. In Spring you configure it by adding the following bean org.springframework.context.support.ReloadableResourceBundleMessageSource to the application context:

<!-- Application Message Bundle -->
<bean id="messageSource"
  class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  <property name="basename" value="classpath:messages/messages" />
  <property name="defaultEncoding" value="UTF-8"/>
</bean>
The configuration specifies that the message resource files should be named messages_xx.properties (xx is the shortcut of the locale), are stored in the messages folder in the classpath, and that the default encoding for the files is UTF-8.
messages classpath

Message resource files in classpath

Note: If the message resources files change often and you don’t want to restart the JVM, you should use org.springframework.context.support.ReloadableResourceBundleMessageSource

Spring’s DispatcherServlet enables you to automatically resolve messages using the client’s locale. This is done with LocaleResolver objects. You can select between

  • an AcceptHeaderLocaleResolver, which inspects the accept-language header in the request that was sent by the client (e.g., a web browser). Usually this header field contains the locale of the client’s operating system.
  • a CookieLocaleResolver,
  • and a SessionLocaleResolver, which allows you to retrieve locales from the session that might be associated with the user’s reques

CookieLocaleResolver

The CookieLocaleResolver made the most sense for Podcastpedia.org . It inspects a cookie named podcastpediaPreferredLanguage, that might exist on the client to see if a locale is specified:

<bean id="localeResolver">
    <property name="cookieName" value="podcastpediaPreferredLanguage"/>
    <property name="defaultLocale" value="en_US" />
    <property name="cookieMaxAge" value="604800"/>
</bean>

If the cookie is not found, then the defaultLocale is set to American English. The cookieMaxAge (the maximum time a cookie will stay persistent on the client) is set to 604800 seconds (one month).

LocaleChangeInterceptor

The LocaleResolver is normally used in combination with the LocaleChangeInterceptor, which allows you to change of the current locale by using a defined parameter in the request (in this case the lang parameter). So, for example, a request for the following URL, <a title="Podcastpedia - categories" href="http://www.podcastpedia.org/categories?lang=de" target="_blank">http://www.podcastpedia.org/categories?<strong>lang=de</strong></a>, will change the site language to German:

<mvc:interceptors>
<!-- Changes the locale when a 'lang' request parameter is sent; e.g. /?lang=de -->
  <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
      <property name="paramName" value="lang"/>
   </bean>
</mvc:interceptors>

Relevant code in Podcastpedia.org

JSP

On Podcastpedia.org you can change the language by selecting the corresponding flag :

Language_selection

Behind the scenes, the jsp file, which common to all pages, looks something like the following:

<div id="flags">
    <a href="?lang=en">
        <img alt="en" title="English" src="<c:url value="/static/images/flags/en.png"/>">
    </a>
    <a href="?lang=de">
        <img alt="de" title="German" src="<c:url value="/static/images/flags/de.png"/>">
    </a>
    <a href="?lang=fr">
        <img alt="fr" title="French" src="<c:url value="/static/images/flags/fr.png"/>">
    </a>
    <!--  TODO uncomment when the translation in Spanish is available
    <a href="?lang=es">
        <c:url value="/static/images/flags/es.png" var="url_flag_image" />
        <img alt="es" title="Spanish" src="${url_flag_image}">
    </a>
    -->
    <a href="?lang=ro">
        <img alt="ro" title="Romanian" src="<c:url value="/static/images/flags/ro.png"/>">
    </a>
</div>

Basically by clicking on the flag the page is reloaded with the corresponding lang parameter, as presented above.

Access the Locale in Spring

If you need to access the current locale in Spring you can use the org.springframework.context.i18n.LocaleContextHolder.LocaleContextHolder.getLocale(), which returns the Locale associated with the current thread, if any, or the system default Locale else. In the following code snippet you can see how I use this in the start page’s controller to display the newest and most popular podcasts based on the language selected:

@ModelAttribute
public void addDataToModel(ModelMap model){
    ...............
    Locale locale = LocaleContextHolder.getLocale();
    String  language = locale.getLanguage();
    List<String> preferredLanguagesList =  Arrays.asList(preferredLanguages);
    if(preferredLanguagesList.contains(language)){
        model.put("newestPodcasts", startPageService.getNewestPodcasts(LanguageCode.get(language)));
        model.put("topRatedPodcasts", startPageService.getTopRatedPodcastsWithLanguage(LanguageCode.get(language), NUMBER_OF_PODCASTS_IN_CHART));
    } else {
        model.put("newestPodcasts", startPageService.getNewestPodcasts());
        model.put("topRatedPodcasts", startPageService.getTopRatedPodcasts(NUMBER_OF_PODCASTS_IN_CHART));
    }
}

Watch out for…

Browser Caching optimization

If you have enabled browser caching, as specified for example in the post How To: Enable compression and leverage browser caching with Apache Server, make sure you set the expiring and cache control for the html pages to 0 seconds:

ExpiresByType text/html                 "access plus 0 seconds"
....
Header set Cache-Control "max-age=0, private"

, so that changing the locale is effective immediately for the page – otherwise if you come back later to the same page, but without the locale parameter in the url, you would have the page displayed in the old locale, if the expiration time was not reached yet.

Well, that’s All Folks! If you would like to have Podcastpedia.org localized in your language, you can download the message resource file for English – messages_en.properties, and contact me at ama [AT] codingpedia DOT org – thanks

If you liked this, please show your support by helping us with Podcastpedia.org

We promise to only share high quality podcasts and episodes.

Resources

Podcastpedia image

Adrian Matei

Creator of Podcastpedia.org and Codingpedia.org, computer science engineer, husband, father, curious and passionate about science, computers, software, education, economics, social equity, philosophy - but these are just outside labels and not that important, deep inside we are all just consciousness, right?

Parallel calls with async-await in javascript - I promise you all performance and simplicity

I was blown away about the simplicity and performance gain of making parallel calls with the new async-await feature in javascript. See the blog post to understand why. Continue reading