Internationalization (i18n) is a crucial consideration for modern applications, ensuring they are accessible and user-friendly for a global audience. Spring Boot simplifies the process of implementing i18n when developing dynamic views in a web application or building RESTful APIs.
In this comprehensive guide, we will explore the configuration, naming conventions, and practical examples to master i18n in Spring Boot.
At a very high level, we need to perform the following steps to add the i18n support in a Spring Boot REST API:
- Add the translation files for all supported locales in the ‘/src/main/resources‘ folder
- Configure LocaleResolver bean, if the default AcceptHeaderLocaleResolver is not needed.
- Inject and use the MessageSource bean in the REST controller handler methods.
1. Maven
The i18n support is inbuilt in the core framework so we do not need to include any additional dependencies. We are good with adding only the web-starter module for writing the REST APIs.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
implementation 'org.springframework.boot:spring-boot-starter-web'
2. Adding Translation Files or Message Resource Bundles
Translation files store messages in different languages. These files follow a structured naming convention and provide a centralized way to manage the application’s text content.
2.1. Naming Rules for Translation Files
In the ‘src/main/resources
‘ directory, create an i18n
folder to house your translation files. The naming convention for these files is crucial for Spring Boot to locate and load the correct translations.
For example, consider creating files like messages_en.properties
and messages_fr.properties
for English and French translations, respectively.
# messages_en.properties
greeting.message=Hello, {0}!
error.notfound=Resource not available
# messages_fr.properties
greeting.message=Bonjour, {0}!
error.notfound=Ressource non disponible
In these files, the key greeting.message
is associated with a language-specific greeting message.
3. The i18n Support in Spring Boot
To enable internationalization, the Spring boot autoconfiguration feature scans for resource bundles in the classpath and configures an instance of org.springframework.context.MessageSource.
3.1. Default Configuration
The default autoconfiguration is done by MessageSourceAutoConfiguration which kicks in the following conditions:
- No bean is present with the name “messageSource“.
- Presence of resource bundle with name
messages.properties
or name configured using ‘spring.messages.basename‘ property.
3.2. Custom Names for Translation Files
We can customize the name of the resource bundle using the property ‘spring.messages.basename‘ in the application.properties file.
spring.messages.basename=messages,config.i18n.messages
The auto-configuration creates the default ResourceBundleMessageSource instance and customizes it with other property values in the namespace ‘spring.messages.*‘.
3.3. NoSuchMessageException
The NoSuchMessageException exception indicates that there is no message available for the given key and locale i.e. when the application attempts to retrieve a message with a specific key from a MessageSource, but that key is not found in any of the configured message properties files.
{
"timestamp": "2019-09-28T17:03:36.056+0000",
"status": 500,
"error": "Internal Server Error",
"message": "No message found under code 'error.notfound' for locale 'en_US'.",
"path": "/"
}
To handle a NoSuchMessageException, you can catch it in your code and provide a default or fallback message.
Alternatively, you can add a translation file (messages.properties without language suffix) that will used as a fallback if the locale-specific (e.g. messages_es.properties) does not contain any message key.
4. Locale Resolution with LocaleResolver
The LocaleResolver is a pivotal component for managing locales in Spring Boot applications. It determines the user’s locale based on factors like session attributes, cookies, or request parameters.
Here are several commonly used locale resolvers in Spring applications:
Locale Resolver | Description |
---|---|
AcceptHeaderLocaleResolver | Extracts the locale from the Accept-Language header of the HTTP request. Commonly used where users can specify their preferred language through browser settings or REST APIs. |
CookieLocaleResolver | Stores the user’s locale preference in a cookie in the browser and remembers the user’s language choice across sessions. |
SessionLocaleResolver | Stores the user’s locale preference in the user session. Commonly used in web applications where the language choice should persist throughout a user’s session. |
HeaderLocaleResolver | Retrieves the locale from a specific request header. It allows flexibility in choosing a custom header for determining the user’s language preference. |
UrlLocaleResolver | Extracts the locale from the request URL. Most commonly seen in REST APIs. |
SmartLocaleResolver | Attempts to determine the user’s locale based on various factors, including the request, session, and headers. |
FixedLocaleResolver | Always returns a fixed, pre-configured locale. Used in the application that needs to operate in a specific language regardless of user preferences. |
By default, Spring boot uses Accept-Language header to determine the user locale.
Accept-Language: es_ES
4.1. Java Configuration
We can define the following configuration for any change in the names of translation files or default locale.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
@Configuration
public class AppConfig {
@Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(3600); // Cache for an hour
return messageSource;
}
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.US); // Set the default locale if none is specified
return localeResolver;
}
}
In this configuration, we use ReloadableResourceBundleMessageSource to reload messages when they are updated. The setBasename() method specifies the base name for the message properties files.
4.2. Properties Configuration
We can further customize the behavior using the following properties, also.
spring.messages.always-use-message-format=false
spring.messages.basename=messages
spring.messages.cache-duration=
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=true
spring.messages.use-code-as-default-message=false
- always-use-message-format: controls whether the MessageFormat should always be applied to messages, regardless of the presence of placeholders.
- basename: specifies the base name for the message properties files. It sets the common prefix for all message files.
- cache-duration: sets the duration, in seconds, for which messages should be cached. If not set, caching is disabled. If set, messages will be cached for the defined duration. After the cache duration elapses, the messages will be reloaded.
- encoding: sets the character encoding for message files.
- fallback-to-system-locale – control what to do when the user requests a message (code) that does not exist for the requested locale – either because there is no message properties file for the language or just because the message file does not contain the message code. If set to ‘false‘, the default locale or a specified fallback locale will be used. If set to ‘true‘, the system locale will be used as a fallback.
- use-code-as-default-message – setting to ‘true’ will default the message code if the message is not found in the properties file.
5. Adding i18n for Spring Boot REST APIs
When dealing with RESTful APIs, we need to get the reference of the MessageSource bean in the REST controller. For the demo, we created a simple @RestController that returns the locale-specific message. Notice that even without any configuration, Spring boot auto-configures MessageSource bean.
Now, let’s add a handler method that returns localized messages based on the ‘Accept-Language‘ header.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
@RestController
public class GreetingController {
private final MessageSource messageSource;
@Autowired
public RestI18nController(MessageSource messageSource) {
this.messageSource = messageSource;
}
@GetMapping("/api/greet/{username}")
public String greet(@PathVariable String username,
@RequestHeader(name = "Accept-Language", required = false) Locale locale) {
String greetingMessage = messageSource.getMessage("greeting.message", new Object[]{username}, locale);
return String.format("Locale: %s, Message: %s", locale, greetingMessage);
}
}
In this REST controller, the greet
endpoint takes advantage of the Accept-Language
header to determine the user’s preferred locale. The MessageSource is then used to retrieve the localized greeting message.
5.1. Without ‘Accept-Language’ Header
It will resolve to the default locale file i.e. messages_en.properties.
curl -X GET http://localhost:8080/api/greet/John -I
The API response:
Locale: en, Message: Hello, John!
5.2. With ‘Accept-Language’ Header
Access the endpoint with the appropriate Accept-Language header indicating French.
curl -X GET http://localhost:8080/api/greet/John -H "Accept-Language: fr" -I
The API response:
Locale: fr, Message: Bonjour, John!
6. Conclusion
Spring boot provides excellent support for message localization and internationalization using its auto-configuration feature. All we have to do is provide locale-specific resource bundle properties and MessageSource is automatically configured for us.
We can start sending the ‘Accept-Language‘ header to receive locale-specific messages from the REST APIs.
Happy Learning !!
Comments