Software localization
A Quick I18n Tutorial for Spring MVC Apps
1. Project Setup
1.1. Dependency
We start by adding spring-webmvc, jstl 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:
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:
2.6. Testing
Accordingly, we can deploy our application and access the index page to see the result:
Click on German to view the page in the German language:
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: