Your Step-by-Step Guide to Spring Boot Internationalization

spring_boot_i18n

This Spring Boot internationalization guide will show you how to create an internationalized web application based on Spring Boot from scratch.

We will start off our guide to Spring Boot internationalization by setting dependencies and project configuration. We will then ensure the necessary setup for successful localization. Finally, we will also add the capability to switch between languages on the page. Our project will use Thymleaf as the template engine, as well as Maven to build the project. To best understand this article, readers should have basic knowledge of the Spring Framework, Thymleaf, and Maven. The source code used in this guide is also available on GitHub.

Project Setup

Dependency

Use any IDE to make a Maven-based project. To implement our project in Spring Boot and the Thymeleaf templating engine, we will make use of Maven for managing dependencies.

The pom.xml file is as below:

<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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.phraseapp.i18n</groupId>
	<artifactId>spring-boot-i18n</artifactId>
	<packaging>jar</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-i18n</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.1.RELEASE</version>
		<relativePath />
	</parent>

	<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>
	</dependencies>
</project>

As we are using Spring Boot, we specify the project parent to spring-boot-starter-parent. This helps to provide dependency and plugin management for applications based on Spring Boot. We can check for the latest version at Maven Central.

Additionally, we specify spring-boot-starter-web and spring-boot-starter-thymeleaf as project dependencies. The spring-boot-starter-parent also contains default versions of dependencies that Spring Boot uses. Hence, we do not need to specify the version for those two dependencies in our pom file.

MVC Configuration

Next, we will add all the configuration to enable Spring Web MVC on our project:

@EnableWebMvc
@Configuration
public class WebConfig implements WebMvcConfigurer {
	
	@Autowired
	private WebApplicationContext context;
	
	@Bean
	public ServletContextTemplateResolver templateResolver() {
		final ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(context.getServletContext());
		resolver.setPrefix("/WEB-INF/views/");
		resolver.setSuffix(".html");
		resolver.setTemplateMode(TemplateMode.HTML);
		return resolver;
	}

	@Bean
	public SpringTemplateEngine templateEngine() {
		SpringTemplateEngine templateEngine = new SpringTemplateEngine();
		templateEngine.setTemplateResolver(templateResolver());
		return templateEngine;
	}

	@Bean
	public ThymeleafViewResolver viewResolver() {
		ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
		viewResolver.setTemplateEngine(templateEngine());
		return viewResolver;
	}
//...
}

We add the @EnableWebMVC annotation here to import the Spring MVC configuration. Moreover, we also implement the WebMvcConfigurer to be able to customize Spring MVC‘s default configuration.

The @Configuration indicates that we will have some @Bean declarations in this class. So, we declared Template Resolver, a Template Engine, and a View Resolver here.

HTML Pages And Controller

Next, we create a simple welcome.html page as below:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>I18N Spring Boot</title>
  </head>
  <body>
    <h2>Hello, your Spring Boot application is ready</h2>
  </body>
</html>

Based on the configuration of the templateResolver bean in WebConfig class, we will put our HTML page under WEB-INF/views.

Together with the HTML page, we declare a simple Controller method as below:

@Controller
public class HomeController {

	@GetMapping("/")
	public String welcome() {
		return "welcome";
	}
//...
}

Start the Application

Now, we can create the main class to start the Spring Boot Application:

package com.phrase.i18n.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class I18NSpringBootApplication {
	public static void main(String[] args) {
		SpringApplication.run(I18NSpringBootApplication.class, args);
	}
}

Run this main class, and access the URL http://localhost:8080/, we will be able to see below result:

Internationalization and Localization Setup

Message Properties Files

Now it is time to dive ourselves into language-specific messages.

With Spring Boot, we can use the messages.properties file to store localized messages for the default locale.

To add more locale-specific messages, we can add more files such as messages_XX.properties, where XX is the locale code, like the US is for US English, SQ is for Albanian, etc. You can have a look at all the language codes here.

Let us define some points to be noted before we move on to defining the messages:

  • We identify a message with a key. The same message will use the same key in all locale-specific files
  • When retrieving a message, Spring Boot will look at the current set locale and will use that locale’s message file to pick up the String against that key
  • If a key is not present in the current locale message file, the framework will switch to the default file only for that key

With the above points in mind, let us move to define the message files now. Define the following files in the src/main/resources/lang folder which is in the classpath for this application.

Here is the default messages.properties file:

label.welcome = Welcome
label.content = This is sample project for Internalization and Localization in SpringBoot
label.changeLang = Supported languages

label.lang.en = English
label.lang.fr = French
label.lang.cn = Chinese
label.lang.de = German

Message Resources Bean

Next, we define a MessageResource bean to tell Spring where to look for our localized messages:

@Bean("messageSource")
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames("lang/messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
}

As we store our localized messages in the properties file, we use the ResourceBundleMessageSource implementation here. Thus, the configuration specifies that we put our localized messages under classpath:lang folder.

We can consider using another implementation which is ReloadableResourceBundleMessageSource if our localized messages will be changed at runtime.

Locale Resolver

To make our project capable of determining the locale which is currently being used, we will be using a LocaleResolver Spring bean:

@Bean
public LocaleResolver localeResolver() {
	return new CookieLocaleResolver();
}

The LocaleResolver comes from very smart interface definitions which makes use of not one but four different techniques to determine current locale, these are:

  • SessionLocaleResolver: uses a locale attribute in the user’s session
  • CookieLocaleResolver: store current locale in the browser’s cookie
  • AcceptHeaderLocaleResolver: uses the primary locale specified in the “accept-language” header of the HTTP request
  • FixedLocaleResolver: always return a fixed default locale

Note that these techniques are used in the same order we mentioned here, as more than one value can be present at any given time.

In the above bean definition, we used the CookieLocaleResolver.

Locale Change Interceptor

Our application supports multiple locales. For this reason, we will add a LocaleChangeInterceptor to be able to specify a locale for an HTTP Request.

@Override
public void addInterceptors(InterceptorRegistry registry) {
    LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
    localeChangeInterceptor.setParamName("lang");
    registry.addInterceptor(localeChangeInterceptor);
}

As stated, we add a parameter named lang in all requests to provide information about the locale for these requests.

Implement a Localization Page

Now, let define the below index.html page using Thymleaf syntax to refer to our localized messages:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>I18N Spring Boot</title>
  </head>
  <body>
    <h2 data-th-text="#{label.welcome}"></h2>
    <p data-th-text="#{label.content}"></p>
    <p data-th-text="#{label.changeLang}"></p>
    <ul>
    	<li>
    		<a href="/index?lang=en" data-th-text="#{label.lang.en}"></a>
    	</li>
    	<li>
    		<a href="/index?lang=de" data-th-text="#{label.lang.de}"></a>
    	</li>
    	<li>
    		<a href="/index?lang=fr" data-th-text="#{label.lang.fr}"></a>
    	</li>
    </ul>
  </body>
</html>

Of course, we need a controller method for this page:

@GetMapping("/index")
public String index() {
    return "index";
}

Now, start our application again. Then access http://localhost:8080/index, we will see below page:

Click on French link, the page will be reloaded as below:

Exceptions with Locale Messages

When using Spring Boot, we can also use Global Exception Handler which provides Exception messages in user locales itself. This is easy to do and repetitive. We won’t be covering in this section, but we will cover a more interesting aspect.

In the file messages.properties and other locale-specific files, we can define messages for Spring Boot Validation API as well. Once we apply validation to our models, Spring Boot will pick these messages and construct the locale message itself, based on available locales. Let’s see how to do this:

NotNull.user.name=Please provide a name.
NotNull.user.pwd=Please, provide a password to sign up.
Size.user.title=Title must contain between {2} and {1} chars.
Size.user.pwd=Password must contain {2}-{1} characters.
Exception.notFound=No record for {0} was found with ID: {1}.
Exception.unexpected=Request processing failed.

Now, whenever validation fails for the conditions mentioned above, Spring Boot will return the mentioned messages for the mentioned entity (user) and property (name, pwd, etc.). In length validation cases, strings like {2}-{1} says that these will be replaced with the minimum and maximum limit mentioned during validation annotations.

Two messages, namely Exception.notFound and Exception.unexpected are global messages.

Using the Phrase Spring Starter

To simplify the integration of Phrase In-Context Editor into your Spring Boot application, there is an existing Phrase Spring Boot Starter that you can use to get started quickly. Learn here how you can start using this project.

To Sum It Up

In this guide, we integrated the internationalization process in our demo Spring-Boot based application which we processed with Thymeleaf to present a layout to the user. We saw how easily we can process a user request and extract locale information from various techniques.

Spring Boot can manage any number of locales in an application flawlessly. If you need a challenge, try localization in an application with a Dynamic API like Google Translate which converts the messages at runtime. It can be slow but it is worth a try.

Internationalization is a great way to increase users on a product so there are no limits in terms of how users use your product.

However, writing code to localize your app is one task, but working with translations comes with a whole new set of challenges. Fortunately, Phrase can make your life as a developer easier! Feel free to learn more about Phrase, referring to the Getting Started guide.

If you’re interested in the topic of Spring Boot i18n make sure to also check out our other article which also takes a closer look at Spring Boot localization.

4.6 (91.18%) 34 votes
Comments