Introduction to Internationalization in Spring Boot
In this short post, we will explore how to add Internationalization in Spring Boot application.
1. Introduction
Spring Boot provides several build in features which help us start application development quickly. Spring Boot provides ResourceBundleMessageSource
which is a foundation to the internationalization provided by Spring as part of the Spring Boot.
We will use thymeleaf as our front-end templating engine. We can enable thymeleaf in Spring Boot application by using spring-boot-starter-thymeleaf.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
This is how the final maven pom.xml looks like
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.javadevjournal</groupId>
<artifactId>spring-boot-internationalization-application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Boot Internationalization example</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Application Structure
We will work with a Maven
based Spring Boot application, here is the structure for our internationalization. (it will remain same for Gradle
based project). Spring Boot application by default will look for internationalization key and values under /src/main/resources
folder.
src/
|-- main/
|-- resources/
|-- messages.properties
|-- messages_de.properties
|-- messages_xx.properties
Default locale file will name as messages.properties
and files for other locales will havemessages_xx.properties
a format where xx
is the locale code. In our case, we have another message file containing German data. Keys for internationalization will be the same in all the message_xx.properties
file and only values will be different based on the locale.
If a key does not exist in a certain requested locale, then the application will fall back to the default locale value. Below is our sample message.properties
file
Default
welcome.message=Welcome to Demo Application
language.change=Change Language
lang.eng=English
lang.de= German
messages_DE.properties
welcome.message=Willkommen bei der Demo-Bewerbung
change.language=Sprache ändern
lang.eng=Englisch
lang.de= Deutsche
3. LocaleResolver
LocalResolver
is required to correctly decide which local is currently used. LocalResolver
interface allows for implementations based on a request, session, cookies, etc. The default implementation is AcceptHeaderLocaleResolver
. We will use session based LocalResolver in our sample code. Please read LocaleResolver for more details.
@Bean
public LocaleResolver localeResolver(){
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
return localeResolver;
}
4. LocaleChangeInterceptor
We need to configure an interceptor which allows for changing the current locale on every request, via a configurable request parameter
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
default parameter name used by LocalCangeInterceptor
is “locale” but we will use “language” as the request parameter.
We need to add our LocaleChangeInterceptor
with Spring Boot so as picked correctly by Spring Boot. To register this bean with Spring Boot, we need to override addInterceptor()
method in our Configuration
class.
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(localeChangeInterceptor());
}
5. Controller
To see this in action, we need a controller to serve a welcome page to see Spring Boot internationalization in action.
@Controller
public class WelcomeController {
@RequestMapping("/")
public String hello() {
return "welcome";
}
}
Above Controller will be come in to picture when we open our application home page (in our case it is http://localhost:8080
). It will pick welcome.html template at src/main/resources/templates
.
6. UI / HTML
Here is our sample HTML
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script></head>
<body>
<h1 th:text="#{welcome.message}"></h1>
<span th:text="#{language.change}"></span>:
<select id="locales">
<option value=""></option>
<option value="en" th:text="#{lang.eng}"></option>
<option value="de" th:text="#{lang.de}"></option>
</select>
</body>
<script type="text/javascript">
$(document).ready(function () {
$("#locales").change(function () {
var selectedOption = $('#locales').val();
if (selectedOption != '') {
window.location.replace('?lang=' + selectedOption);
}
});
});
</script>
</html>
7. Demo Application
If we run our Spring Boot application using main class and open http://localhost:8080
@SpringBootApplication
public class JavadevspringbootApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(JavadevspringbootApplication.class, args);
}
@Bean
public LocaleResolver localeResolver(){
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
return localeResolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(localeChangeInterceptor());
}
}
We will get following HTML Page
On changing the URL by adding language in the URL (http://localhost:8080/?language=de
), we will get the German version of the site
[pullquote align=”normal”]Local selection work based on your browser default local, if no locale specified, browser default locale picked by the system. [/pullquote]
7. Localized Exception Handling
We can use Spring Boot Internationalization support to provide the localized exception or validation messages. To handle exceptions we can use global exception handler mechanism provided by Spring.
We can define our validation or exception messages in the localized message.properties file. The messages in English are in a file called messages.properties in the src/main/resource folder. For other local specific message sources, use message_language.properties pattern (e.g. messages_en.properties).
NotNull.first.name=Please provide the first name.
NotNull.last.name=Please provide the last name.
Exception.notFound=No record of {0} could be found with id {1}.
Exception.unexpected=An unexpected error occurred while processing your request.
Whenever validation fails, Spring Boot will return specific message based on the annotation name. (We will cover this topic in another post)
[pullquote align=”normal”]In above example, Exception.notFound and Exception.unexpected are global messages and they do not belong to any specific entity /object. [/pullquote]
8. Character Encoding
In case you are working on a language which requires handling of character encoding, you may need to add following properties in your application.properties file:
# Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly.
spring.http.encoding.charset=UTF-8
# Enable http encoding support.
spring.http.encoding.enabled=true
# Force the encoding to the configured charset on HTTP requests and responses.
spring.http.encoding.force=true
Refer to the updated properties : Common application properties
9. Video
Here is a short video explaining how to enable Internationalization in Spring Boot application.
[responsive_video type=’youtube’ hide_related=’1′ hide_logo=’0′ hide_controls=’0′ hide_title=’0′ hide_fullscreen=’0′ autoplay=’0′]https://www.youtube.com/watch?v=GFq7Kxm2GBk[/responsive_video]
Summary
In this post, we learned how to use Internationalization in Spring Boot. We get an understanding of Spring Boot built-in support for Internationalization. The complete code for this article is available on GitHub.
Hi,
As of Spring 5.0 WebMvcConfigurerAdapter is Deprecated.
How should the interceptor be configured in case we want to not use WebMvcConfigurerAdapter?
Thanks in advance!
With Spring 5, you need to implement the WebMvcConfigurer interface.Thank for the pointer, I will be updating the post for Spring Boot 5.
i am getting error: cannot convert from SessionLocaleResolver to WebMvcProperties.LocaleResolver
have you tried the code available on the Github?
Do you have a guide on how to do i18n for rest only applications? First, the application is stateless, so we cannot rely on a session. Second, localization will be mainly focused on error messages, so I need to retrieve the correct message when building the Exception or at the @RestControllerAdvice.
No, Don’t have such guide/ article, but I can see following option to do this.
Great tutorial indeed…but i have a question, my app has several pages, how do I manage the localization across all pages??
Thanks Fred!!
Your core logic is still same (how the local change is handle by Spring), you need to make sure following things
1. Make sure text is not hard coded on the pages.
2. Use the resource key to let Spring pick the correct information based on user/selected locale.
How is it possible to change the filename from messages.properties into another name?
Hello Simon,
Can you provide more details as what you are trying to accomplish?
I want to use different property files, each for one html site.
Like page1.html, page1.properties, page2.html, page2.properties, etc.
Not sure what is your requirement, since resources will be combined at the end by Spring and we will have a combined view of all the properties files. There are different ways to configure multiple properties file in Spring, here are some of the options
1. Using a command line argument like spring.config.location.
2. Environment variable
3.
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(Application.class) .properties("multiple properties file") .build().run(args);
In application.properties file you can add a / multiple basenames
spring.messages.basename=i18n/messages, lang/page1, lang/page2
“i18n/messages” is my default but you could add lang/page1 eg.
you need “/resources/lang/page1.properties” file then
hello I have a probleme I try to overide public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(localeChangeInterceptor());
} but I have an error . Error method does not override from superclass what I my missing please
You should extend WebMvcConfigurerAdapter in your config class like @Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter { … config .. } for SPring 5.x, I will recommend using WebMvcConfigurer as WebMvcConfigurerAdapter is Deprecated.
Great job! Can you add a unit test example and how to ensure it is selected correctly
awesome
Thanks!!