A Quick I18n Tutorial for Spring MVC Apps

Learn all the steps for equipping a Spring MVC web application with internationalization and localization support.

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:

Keep exploring

Software localization blog category featured image | Phrase

Blog post

Best Resources to Learn Ruby on Rails

We’ve curated a list of the best online resources to help you learn Ruby on Rails or simply deepen your knowledge in 2020.

Software localization blog category featured image | Phrase

Blog post

Translating Ruby Applications with the R18n Ruby Gem

R18n is a ruby gem that lets you implement i18n for Ruby apps. This guide will walk you through how to use it for translating Ruby apps.

Software localization blog category featured image | Phrase

Blog post

The Best JavaScript I18n Libraries

Finding out which tools best suit your needs is essential before you start translating pure JavaScript apps. Here are the best JavaScript i18n libraries to consider.

Software localization blog category featured image | Phrase

Blog post

The Ultimate Guide to JavaScript Localization

Kick-start your browser JavaScript localization with this comprehensive guide and make your application ready for international users.

Gaming Warrior

Blog post

Navigating the Global Game Market: The Essential Guide to Effective Video Game Localization

Discover the critical role of localization in making video games culturally resonant and globally successful.