Welcome to the Spring Internationalization (i18n) tutorial. Any web application with users all around the world, internationalization (i18n) or localization (L10n) is very important for better user interaction. Most of the web application frameworks provide easy ways to localize the application based on user locale settings. Spring also follows the pattern and provides extensive support for internationalization (i18n) through the use of Spring interceptors, Locale Resolvers and Resource Bundles for different locales. Some earlier articles about i18n in java.
Let’s create a simple Spring MVC project where we will use request parameter to get the user locale and based on that set the response page label values from locale specific resource bundles. Create a Spring MVC Project in the Spring Tool Suite to have the base code for our application. If you are not familiar with Spring Tool Suite or Spring MVC Projects, please read Spring MVC Example. Our final project with localization changes looks like below image. We will look into all the parts of the application one by one.
Our Spring MVC pom.xml looks like below.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev</groupId>
<artifactId>spring</artifactId>
<name>Springi18nExample</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Most of the code is auto generated by STS, except that I have updated the Spring version to use the latest one as 4.0.2.RELEASE. We can remove dependencies or update the versions of other dependencies too but I have left them as it is for simplicity.
For simplicity, let’s assume that our application supports only two locales - en and fr. If no user locale is specified, we will use english as default locale. Let’s create spring resource bundles for both these locales that will be used in the JSP page. messages_en.properties code:
label.title=Login Page
label.firstName=First Name
label.lastName=Last Name
label.submit=Login
messages_fr.properties code:
label.title=Connectez-vous page
label.firstName=Pr\u00E9nom
label.lastName=Nom
label.submit=Connexion
Note that I am using unicode for special characters in the french locale resource bundles, so that it gets interpreted properly in the response HTML sent to client requests. Another important point to note is that both the resource bundles are in the classpath of the application and their name has pattern as “messages_{locale}.properties”. We will see why these are important later on.
Our controller class is very simple, it just logs the user locale and return the home.jsp page as response.
package com.journaldev.spring;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
return "home";
}
}
Our home.jsp page code looks like below.
<%@taglib uri="https://www.springframework.org/tags" prefix="spring"%>
<%@ page session="false"%>
<html>
<head>
<title><spring:message code="label.title" /></title>
</head>
<body>
<form method="post" action="login">
<table>
<tr>
<td><label> <strong><spring:message
code="label.firstName" /></strong>
</label></td>
<td><input name="firstName" /></td>
</tr>
<tr>
<td><label> <strong><spring:message
code="label.lastName" /></strong>
</label></td>
<td><input name="lastName" /></td>
</tr>
<tr>
<spring:message code="label.submit" var="labelSubmit"></spring:message>
<td colspan="2"><input type="submit" value="${labelSubmit}" /></td>
</tr>
</table>
</form>
</body>
</html>
The only part worth mentioning is the use of spring:message to retrieve the message with the given code. Make sure Spring tag libraries are configured using taglib jsp directive.Spring takes care of loading the appropriate resource bundle messages and make it available for the JSP pages to use.
Spring Bean configuration file is the place where all the magic happens. This is the beauty of Spring framework as it helps us to focus more on business logic rather than coding for trivial tasks. Let’s see how our spring bean configuration file looks and we will look at each of the beans one be one. servlet-context.xml code:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="classpath:messages" />
<beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>
<beans:bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<beans:property name="defaultLocale" value="en" />
<beans:property name="cookieName" value="myAppLocaleCookie"></beans:property>
<beans:property name="cookieMaxAge" value="3600"></beans:property>
</beans:bean>
<interceptors>
<beans:bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<beans:property name="paramName" value="locale" />
</beans:bean>
</interceptors>
<context:component-scan base-package="com.journaldev.spring" />
</beans:beans>
annotation-driven tag enables the Controller programming model, without it Spring won’t recognize our HomeController as handler for client requests.
context:component-scan provides the package where Spring will look for the annotated components and register them automatically as Spring bean.
messageSource bean is configured to enable i18n for our application. basename property is used to provide the location of resource bundles. classpath:messages
means that resource bundles are located in the classpath and follows name pattern as messages_{locale}.properties
. defaultEncoding property is used to define the encoding used for the messages.
localeResolver bean of type org.springframework.web.servlet.i18n.CookieLocaleResolver
is used to set a cookie in the client request so that further requests can easily recognize the user locale. For example, we can ask user to select the locale when he launches the web application for the first time and with the use of cookie, we can identify the user locale and automatically send locale specific response. We can also specify the default locale, cookie name and maximum age of the cookie before it gets expired and deleted by the client browser. If your application maintains user sessions, then you can also use org.springframework.web.servlet.i18n.SessionLocaleResolver
as localeResolver to use a locale attribute in the user’s session. The configuration is similar to CookieLocaleResolver.
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en" />
</bean>
If we don’t register any “localeResolver”, AcceptHeaderLocaleResolver will be used by default, which resolves user locale by checking the accept-language
header in the client HTTP request.
org.springframework.web.servlet.i18n.LocaleChangeInterceptor
interceptor is configured to intercept the user request and identify the user locale. The parameter name is configurable and we are using request parameter name for locale as “locale”. Without this interceptor, we won’t be able to change the user locale and send the response based on the new locale settings of the user. It needs to be part of interceptors element otherwise Spring won’t configure it as an interceptor.
If you are wondering about the configuration that tells Spring framework to load our context configurations, it’s present in deployment descriptor of our MVC application.
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
We can change the location or name of the context file by changing the web.xml configuration. Our Spring i18n application is ready, just deploy it in any servlet container. Usually I export it as WAR file in a standalone tomcat web server webapps directory. Here are the screenshots of our application home page with different locales. Default Home Page (en locale): Passing Locale as parameter (fr locale): Further requests without locale: As you can see in the above image that we are not passing locale information in the client request but still our application identifies the user locale. You must have guessed by now that it’s because of the CookieLocaleResolver bean that we configured in our spring bean configuration file. However you can check your browser cookies data to confirm it. I am using chrome and below image shows the cookie data stored by the application. Notice that cookie expiry time is one hour i.e 3600 seconds as configured by cookieMaxAge property. If you will check server logs, you can see that locale is getting logged.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is en.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is fr.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is fr.
That’s all for Spring i18n example application, download the example project from below link and play around with it to learn more.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
suppose if need to support 30 different regional language. Will we creating 30 properites file ? Please advice how will design the application.
- srikanth
How to do it, if my site dont allow GET requests, but only POST?
- SVK PETO
Hi Friends, I am facing problem in implementing Internationalization in spring, as per my knowledge I have configured all the required resources. Please help me, the following is my project location. https://github.com/VamshiKrishna2828/SpringI18NDemo Thanks in advance.
- Vamshi
Hello. How can I retrieve message from properties file programmatically??
- Jeferson
Hello, how can I change the language of a calendar…datepicker in spring??
- cindy
1.7 1.7 … updated the pom.xml with above chages. But I am getting below error using Jdk 7. org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.5.1:compile (default-compile) on project spring: Fatal error compiling Please help me…
- Vishal Wagh
I think you don’t need the annotation-driven tag in your app config as context:component-scan does the same: Element : component-scan Scans the classpath for annotated components that will be auto-registered as Spring beans. By default, the Spring-provided @Component, @Repository, @Service, and @Controller stereotypes will be detected. Note: This tag implies the effects of the ‘annotation-config’ tag, activating @Required, @Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and @PersistenceUnit annotations in the component classes
- zoli
Thank you very much ! you have just saved my life :D
- daj
According to Google, it is bad practice to use request parameters and cookies to show translated versions of the page: https://support.google.com/webmasters/answer/182192
- Håvard
I got following exceptions when I copied and ran this example using IntelliJ. If I go to my tomcat directory, I can see messages_X.properties are under WEB-INF/classes and no spelling error. Please help. Thanks. SEVERE: Servlet.service() for servlet [jsp] in context with path [/spring] threw exception [javax.servlet.ServletException: javax.servlet.jsp.JspTagException: No message found under code ‘label.title’ for locale ‘en_US’.] with root cause javax.servlet.jsp.JspTagException: No message found under code ‘label.title’ for locale ‘en_US’. at org.springframework.web.servlet.tags.MessageTag.doEndTag(MessageTag.java:202) at org.apache.jsp.home_jsp._jspx_meth_spring_005fmessage_005f0(home_jsp.java:135) at org.apache.jsp.home_jsp._jspService(home_jsp.java:73) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:744)
- Vince