Software localization

A Quick I18n Tutorial for Spring MVC Apps

Learn all the steps for equipping a Spring MVC web application with internationalization and localization support.
Software localization blog category featured image | Phrase

1. Project Setup

1.1. Dependency

We start by adding spring-webmvcjstl and javax.servlet-api dependencies into our pom.xml file:

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-webmvc</artifactId>

    <version>${spring.version}</version>

</dependency>

<dependency>

    <groupId>jstl</groupId>

    <artifactId>jstl</artifactId>

    <version>${jstl.version}</version>

</dependency>

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>${servlet.version}</version>

    <scope>provided</scope>

</dependency>

The latest version of all dependencies above can be checked out at Maven Central: spring-webmvc, jstl, javax.servlet-api.

In this article, the versions we use for those dependencies are declared as below:

<properties>

    <spring.version>5.0.4.RELEASE</spring.version>

    <servlet.version>4.0.0</servlet.version>

    <jstl.version>1.2</jstl.version>

</properties>

1.2. WebMvcConfigurer

Next, we will add an implementation of WebMvcConfigurer to enable MVC support:

@EnableWebMvc

@Configuration

@ComponentScan

  (basePackages = { "com.phraseapp.internationalization.mvclocale.controller" })

public class WebConfig implements WebMvcConfigurer {

    @Override

    public void configureViewResolvers(ViewResolverRegistry registry) {

        registry.jsp("/WEB-INF/pages/", ".jsp");

    }

    //....

}

In the configuration above, we decorate our WebConfig with the @Configuration annotation to state that the class will declare one or more @Bean methods in its body. Spring container will generate bean definition for those beans at runtime.

The @ComponentScan(basePackages = { "com.phraseapp.internationalization.mvclocale.controller" }) will refer to the package where we will implement our controllers later.

We also put the @EnableWebMvc annotation to enable the default configuration for Spring MVC.

Moreover, we override the configureViewResolvers method to register our JSP pages in /WEB-INF/pages/.

1.3. Welcome Page

Now, we will create welcome.jsp page under WEB-INF/pages folder as below:

<!DOCTYPE html>

<html>

    <head>

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <title>Internationalziation in Spring MVC</title>

    </head>

    <body>

        Congratulation, your web application has been set up successfully!

    </body>

</html>

Furthermore, we need to implement a Controller which will process the request and return back our welcome page:

@Controller

public class HomeController {

    Logger log = Logger.getLogger(HomeController.class.getName());

    @GetMapping(value = "/welcome")

    public String welcomePage() {

        log.info("INTO welcomePage");

        return "welcome";

    }

    //...

}

1.4. WebApplicationInitializer

Up until here, most of the configuration is ready. We will register all our configuration to the ApplicationContext and the ServletContext by implementing the WebApplicationInitializer interface:

public class WebAppInitializer implements WebApplicationInitializer {

    @Override

    public void onStartup(ServletContext container) {

        AnnotationConfigWebApplicationContext rootContext

          = new AnnotationConfigWebApplicationContext();

        rootContext.register(WebConfig.class);

        container.addListener(new ContextLoaderListener(rootContext));

        AnnotationConfigWebApplicationContext dispatcherServlet

          = new AnnotationConfigWebApplicationContext();

        ServletRegistration.Dynamic dispatcher

          = container.addServlet("dispatcher",

          (Servlet) new DispatcherServlet(dispatcherServlet));

        dispatcher.setLoadOnStartup(1);

        dispatcher.addMapping("/");

    }

}

In the WebAppInitializer class above, we registered our WebConfig to the Application Context. In addition, we also create a simple ServletContext which will handle all incoming requests.

Hence, we can deploy our application on an application server and try to access the welcome page to verify our setup. In this example, we use Tomcat 8.5 at port 8080 for demonstration.

When everything is done properly, consequently we will see below result when access http://localhost:8080/mvclocale/welcome URL:

Successfully set up web app | Phrase

2. Internationalization And Localization

2.1. Message Properties

We begin to add the possibility of supporting multiple languages to our project by declaring a message properties file for each supported language.

The default language will be declared in a file called message.properties. Let's choose the default language is English. Our message.properties file will be like below:

label.welcome = Welcome

label.content = This is sample project for Internalization and Localization in Spring MVC

label.changeLang = Supported languages

label.lang.en = English

label.lang.fr = French

label.lang.cn = Chinese

label.lang.de = German

As can be seen, the file contains a set of key-value pair. We will use the key to refer to a message in our view later on.

In this article, we will support the displaying of a page in English, Chinese, German and French. Hence, we also need to add a message properties file for the other three languages. To do this, with each specific language, we create a message file in the name messages_XX.properties, where XX is the locale code of the preferred language.

Therefore, we need to create additional 3 message properties files called: message_zh.properties (Chinese), message_fr.properties (French) and message_de.properties (German).

Similarly, all the keys which are present in default message.properties file should also be available in the specific language message file.

For example, the content of message_fr.properties will be like below:

label.welcome = Bienvenue

label.content = Ceci est un exemple de projet pour l'internalisation et la localisation dans Spring MVC

label.changeLang = Langues supportées

label.lang.en = Anglais

label.lang.fr = Français

label.lang.cn = Chinois

label.lang.de = Allemand

Finally, we will put all message properties files under resources/languages folder.

2.2. MessageResource

Until now, we need to register our message properties files to Spring by declaring a MessageResource bean in our WebConfig :

@Bean("messageSource")

public MessageSource messageSource() {

    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

    messageSource.setBasenames("languages/messages");

    messageSource.setDefaultEncoding("UTF-8");

    return messageSource;

}

In the declaration above, we create an instance of the ResourceBundleMessageSource as MessageResource bean. Moreover, we register basename as "languages/messages" because we have put all messages properties file under the languages folder.

It's worth to note that Spring will load all the messages properties files content and store in memory. Thus, if in any case, we want to update the messages properties file and expect the changes are updated to the system at runtime, we can consider using the ReloadableResourceBundleMessageSource instead.

2.3. LocaleResolver

Additionally, we need to declare a LocaleResovler bean that helps to identify which locale is being used. Spring provides 4 concrete implementations of LocaleResolver as below:

  • AcceptHeaderLocaleResolver: uses the primary locale specified in the "accept-language" header of the HTTP request
  • FixedLocaleResolver: always return a fixed default locale and optionally time zone
  • SessionLocaleResolver: use the locale attribute in the user session, with a fallback to the specified default locale or the request's accept-header locale.
  • CookieLocaleResolver: persists a custom locale and/or a time zone information as the browser cookie. We use this CookieLocaleResolver in case the application has to be stateless

In this article, we will use the CookieLocaleResolver:

@Bean

public LocaleResolver localeResolver() {

    return new CookieLocaleResolver();

}

2.4. LocaleChangeInterceptor

At the same time, we register a LocaleChangeInterceptor to allow specifying the desired locale on every request:

@Override

public void addInterceptors(InterceptorRegistry registry) {

    LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();

    localeChangeInterceptor.setParamName("lang");

    registry.addInterceptor(localeChangeInterceptor);

}

The configuration above provides the possibility to choose a specific language for each request via a request parameter lang.

2.5. Using Message In View

Let's create an index.jsp page as below:

<%@ page language="java" contentType="text/html; charset=UTF-8"

	pageEncoding="UTF-8"%>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt"%>

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<body>

	<h2>

		<fmt:message key="label.welcome" />

	</h2>

	<div>

		<span><fmt:message key="label.content" /></span>

	</div>

	<div>

		<fmt:message key="label.changeLang" />

	</div>

	<ul>

		<li><a href="?lang=en"><fmt:message key="label.lang.en" /></a></li>

		<li><a href="?lang=de"><fmt:message key="label.lang.de" /></a></li>

		<li><a href="?lang=fr"><fmt:message key="label.lang.fr" /></a></li>

		<li><a href="?lang=zh"><fmt:message key="label.lang.cn" /></a></li>

	</ul>

</body>

</html>

In this file, we use the <fmt:message> tag to map a key to a localized message.

Next, we need a Controller method for this page:

@GetMapping(value = "/index")

public String index() {

    log.info("INTO index");

    return "index";

}

At this point, our project structure will be like below:

Project structure | Phrase

2.6. Testing

Accordingly, we can deploy our application and access the index page to see the result:

Project index page in English | Phrase

Click on German to view the page in the German language:

Project index page in German | Phrase

Similarly for Chinese:

3. Conclusion

In this article, we have explored how to set up a Spring MVC Web Application with internationalization and localization support from the beginning.

Basically, we need a MessageResource, a LocaleResolver and LocaleChangeInterceptor to be able to support multiple languages and dynamically switching between languages.

Besides, to be able to update current message files as well as adding new language at runtime we may need to use the ReloadableResourceBundleMessageSource implementation of MessageResource interface, which we cover in our article Database-Stored Messages for I18n in Spring Boot.

Worry not, the whole project can be found on our Github.

All fired up to work with the Spring framework? So are we! Check out our further resources: