Spring Boot 3.2: Internationalization

Jakub Pomykała
Jakub Pomykała
Last updated: March 26, 20245 min read
Spring Boot 3.2: Internationalization

Table of contents

Introduction

Internationalization (i18n) is the process of designing your application to be adapted to various languages, regional peculiarities, and technical requirements of a target market. Internationalization is a crucial step in the process of localizing your product. In this tutorial, we will show you how to use Spring Boot to create a simple internationalized application, how to get translated messages from messages_xx.properties files, render translated HTML from HTML template using Thymeleaf and how to use LocaleResolver to change the language of the application.

Used technologies

  • Java 21
  • Spring Boot 3.2
  • Thymeleaf 3
  • Maven
  • SimpleLocalize Editor
translated spring boot page

Configuration

1. Create messages_xx.properties files

The messages are stored in the messages_XX.properties files. The XX is the language code. For example, messages_pl_PL.properties is the Polish version of the messages.

footerText=© 2023 SimpleLocalize. Wszelkie prawa zastrzeżone.
linkText=Utwórz konto SimpleLocalize
message=Dziękujemy za wypróbowanie naszego demo SimpleLocalize dla Spring Boot!
title=Hej {0}!

If you are using SimpleLocalize, you can download the messages_XX.properties files providing your apiKey in simplelocalize.yml file and invoking simplelocalize download command.

Downloading messages_xx.properties files via SimpleLocalize CLI

2. Configure messages_xx.properties location

The default location for messages is src/main/resources/messages. You can change this by setting the spring.messages.basename property in your application.properties file or by providing your ResourceBundleMessageSource bean.

@Bean
public ResourceBundleMessageSource messageSource() {
    var resourceBundleMessageSource=new ResourceBundleMessageSource();
    resourceBundleMessageSource.setBasenames("i18n/messages"); // directory with messages_XX.properties
    resourceBundleMessageSource.setUseCodeAsDefaultMessage(true);
    resourceBundleMessageSource.setDefaultLocale(Locale.of("en"));
    resourceBundleMessageSource.setDefaultEncoding("UTF-8");
    resourceBundleMessageSource.setAlwaysUseMessageFormat(true);
    return resourceBundleMessageSource;
}
messages_xx.properties in IDE

3. Configure resolving locale from requests

The default locale resolver is AcceptHeaderLocaleResolver which resolves the locale from the Accept-Language header. You can change this by setting the spring.mvc.locale-resolver property in your application.properties file or by providing your LocaleResolver bean, creating LocaleChangeInterceptor and registering it via addInterceptors method (see WebMvcConfigurer class).

@Bean
public LocaleResolver localeResolver() {
    SessionLocaleResolver sessionLocaleResolver=new SessionLocaleResolver();
    sessionLocaleResolver.setDefaultLocale(Locale.of("en"));
    return sessionLocaleResolver;
}

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
    LocaleChangeInterceptor localeChangeInterceptor=new LocaleChangeInterceptor();
    localeChangeInterceptor.setParamName("lang");
    return localeChangeInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(localeChangeInterceptor());
}

4. Create HTML template with Thymeleaf

If you want to get an HTML document with translated messages, for example, to send it via email, you can use Thymeleaf. If you are not familiar with Thymeleaf, you can read more about it here. In short, Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" th:attr="lang=${lang}">

<head>
  <title>Spring Boot Email Example</title>
</head>

<body>
<header>
  <h1 th:utext="#{title(${userName})}"></h1>
</header>

<article>
  <p th:text="#{message}"></p>
  <a th:href="${url}" th:text="#{linkText}"></a>
</article>
<footer>
  <p th:text="#{footerText}"></p>
</footer>
</body>

</html>

Quick Thymeleaf guide:

  • th:attr="lang=${lang}" - sets the language of the document
  • th:utext="#{title(${userName})}" - gets a message with title key and inserts the value of the userName variable
  • th:text="#{message}" - gets a message with message key
  • th:href="${url}" - inserts a value of the url variable
  • You can use th:utext instead of th:text to avoid escaping HTML characters.
  • You can use th:attr to set the lang attribute on the html tag.

You can use https://mjml.io to create responsive HTML emails for free.

Localized texts

Use MessageSource to get translated messages. This is the default way to get translated messages in Spring Boot.

import org.springframework.context.MessageSource;

@Autowired
private MessageSource messageSource;

@Test
void shouldGetTranslatedTextFromLocalFileAndLocale() {
    //given
    Locale locale=Locale.of("pl","PL");

    //when
    String titleTextWithArgument=messageSource.getMessage("title",new Object[]{"Foo Bar"},locale);

    //then
    assert titleTextWithArgument.equals("Hej Foo Bar!");
}

Localized API exceptions

You can also return translated API exceptions by using standard Spring Boot @ControllerAdvice. For this purposes, I created ErrorController with two methods annotated with @ExceptionHandler to catch exceptions.

  • @ExceptionHandler(Exception.class) - catches all exceptions not caught by other methods
  • @ExceptionHandler(IllegalArgumentException.class) - catches IllegalArgumentException exceptions

Both methods gets Locale from LocaleContextHolder and returns translated message from messages_xx.properties file using MessageSource bean.


@RestControllerAdvice
public class ErrorController
{
  @Autowired
  private MessageSource messageSource;

  @ExceptionHandler(Exception.class)
  @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
  public ResponseEntity<ApiError> exception()
  {
    String message = getLocalizedMessage("exception.internalServerError");
    HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
    return ResponseEntity
            .status(status)
            .body(new ApiError(message, status));
  }

  @ExceptionHandler(IllegalArgumentException.class)
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  public ResponseEntity<ApiError> badRequest()
  {
    String message = getLocalizedMessage("exception.badRequest");
    HttpStatus status = HttpStatus.BAD_REQUEST;
    return ResponseEntity
            .status(status)
            .body(new ApiError(message, status));
  }

  private String getLocalizedMessage(String translationKey)
  {
    Locale locale = LocaleContextHolder.getLocale();
    return messageSource.getMessage(translationKey, null, locale);
  }
}

By default, Spring Boot uses Accept-Language header to resolve user locale, but in this tutorial we changed this behavior by registering custom LocaleChangeInterceptor bean . To change the language of exception, you need to use lang parameter, e.g. /api/my-health?lang=pl_PL.

Translated Spring Boot exceptions

The number of ways to return translated messages is endless, but the solution above might be one of the best ones as we keep all the logic and translation keys for exceptions in one place.

Localized HTML content (Thymeleaf)

Use ThymeleafEngine bean to render HTML with translated messages. This is probably the most popular way to render HTML with translated messages in Spring Boot.

@Autowired
private TemplateEngine templateEngine;

public String renderHtmlFromTemplate(Locale locale,String userName)
{
    Context context=new Context();
    context.setLocale(locale);
    context.setVariable("userName",userName);
    context.setVariable("lang",locale.getLanguage());
    context.setVariable("url","https://simplelocalize.io");
    return templateEngine.process("my-html-template",context);
}
render custom HTML with translated texts

Localized web pages (Thymeleaf)

You can also return translated web pages (HTML) by using standard Spring Boot @Controller. Spring Boot will automatically resolve user locale and render HTML from my-html-template.html template with translated messages.

This is the default way to return translated web pages in Spring Boot.

@Controller
public class WelcomeController
{
  @GetMapping("/welcome")
  public String renderHtmlFromTemplate(Model model)
  {
    model.addAttribute("userName", "Jakub");
    return "my-html-template";
  }
}

Run the application and open http://localhost:8080/welcome in your browser. You can change the language by adding ?lang=pl_PL to the URL.

Changing the 'lang' query parameter in Spring Boot
Jakub Pomykała
Jakub Pomykała
Founder of SimpleLocalize