๐งญ Table of Contents
- ๐งญ Table of Contents
- Introduction
- ๐ ๏ธ Maven Dependencies
- ๐ป Core Code Implementation
- ๐ Resource Files for Messages
- ๐ Key Observations
- ๐ Architectural Insight
- โ Benefits and Drawbacks
- ๐ Suggested Improvements
Introduction
Internationalization, or i18n, is the process of designing your application to adapt to various languages and regions without requiring engineering changes.
- In a Spring Boot microservice, this is crucial for creating a global user experience. Springโs
MessageSourceabstraction simplifies this process by resolving messages (text strings) based on a userโs locale.
๐ ๏ธ Maven Dependencies
Key Point
The necessary API for i18n, primarily the MessageSource interface, is part of the spring-context dependency. When you use the standard spring-boot-starter-web, this is included automatically, so no explicit extra dependency is required in most cases.
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
๐ป Core Code Implementation
Key Point
The implementation involves autowiring the MessageSource bean, which Spring Boot auto-configures. The key method is getMessage(), where we provide the message key, optional arguments, a default message, and the desired Locale. The Locale is typically extracted from the incoming HTTP requestโs Accept-Language header using LocaleContextHolder.getLocale().
Java Controller Example
import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldI18n {
private final MessageSource messageSource;
// Use constructor injection for MessageSource (Best Practice)
public HelloWorldI18n(MessageSource messageSource) {
this.messageSource = messageSource;
}
@GetMapping("/say-hello-i18n")
public String sayHello() {
// Get the Locale from the current HTTP Request's Accept-Language header
var locale = LocaleContextHolder.getLocale();
// Resolve the message key based on the determined locale
return this.messageSource.getMessage(
"good.morning", // The message key
null, // Arguments for parametric messages
"Default - Good Morning", // Fallback message if key not found
locale // The target locale
);
}
}
๐ Resource Files for Messages
Key Point
Messages are stored in standard Java .properties files following the naming convention: messages[-<locale>].properties. Spring Boot automatically looks for these files on the classpath (typically in src/main/resources/).
File Structure Examples
| File Name | Locale | Description |
|---|---|---|
messages.properties | Default (Fallback) | Used when no specific locale file is found. |
messages_en.properties | English (US/UK) | Used for English-speaking users. |
messages_es.properties | Spanish | Used for Spanish-speaking users. |
messages_de.properties | German | Used for German-speaking users. (Note: ger is non-standard) |
Content Example
messages_es.properties
good.morning=Buenos dรญas
messages.properties
good.morning=Good Morning (Default)
Project Reference: You can view the source code for this setup on Github: Internationalization POC App
๐ Key Observations
How Locale is Determined
- The mechanism relies on Spring reading the value of the
Accept-LanguageHTTP Header sent by the client (e.g., a browser or another microservice). - Based on this header (e.g.,
es,de-DE,en-US), Spring finds the most appropriatemessages[-<locale>].propertiesfile. - The default message file prefix is
messagesand the suffix is.properties.- This can be customized in
application.propertiesusingspring.messages.basename.
- This can be customized in
Example Request Header
Header:
Accept-Language: esAction: Spring resolves the message from
messages_es.properties.
๐ Architectural Insight
i18n Message Resolution Flow
This diagram illustrates how a requestโs locale determines the message resolution.
sequenceDiagram
participant C as Client (e.g., Browser)
participant A as Spring Boot Microservice
participant MS as MessageSource (Internal)
participant RF as Resource Files (.properties)
C->>A: HTTP GET /say-hello-i18n (Header: Accept-Language: es)
A->>A: Get Locale from LocaleContextHolder
A->>MS: messageSource.getMessage("good.morning", ..., locale)
MS->>RF: Look for messages_es.properties
alt File Found
RF-->>MS: Return message "Buenos dรญas"
else File Not Found
RF-->>MS: Look for messages.properties (Default)
RF-->>MS: Return message "Good Morning (Default)"
end
MS-->>A: Resolved Message
A-->>C: HTTP Response (Body: Resolved Message)
โ Benefits and Drawbacks
| Aspect | Benefits ๐ | Drawbacks ๐ |
|---|---|---|
| Accessibility | Wider market reach and better user experience for global users. | Maintenance Overhead for translating and managing numerous resource files. |
| Code | Clean separation of text content from the Java code (no hardcoded strings). | Requires discipline to use message keys everywhere, not just hardcode strings. |
| Scalability | Easy to add new languages by simply adding a new .properties file. | Context Loss during translation; translators might lack context without proper comments/guidance. |
๐ Suggested Improvements
For a robust, production-ready microservice implementation, consider these enhancements:
Custom Base Name: Define the base name explicitly in
application.propertiesto ensure future-proofing and clarity.spring.messages.basename=i18n/messages(This would require placing files in a sub-directory, e.g.,
src/main/resources/i18n/)Parametric Messages: Use placeholders in messages for dynamic content, e.g.,
good.morning.user=Buenos dรญas, {0}. ThegetMessagemethod can then pass the argument:messageSource.getMessage("good.morning.user", new Object[]{"Vivek"}, ...)Externalizing MessageSource: For large microservice deployments or applications requiring real-time updates, consider implementing a custom
MessageSourcethat loads messages from a centralized database or a configuration service (e.g., Consul, Spring Cloud Config).