Spring supports internationalization (I18n) and localization (L10n)In this tutorial we will focuses on how to display the text labels/messages in different languages based on the provided locale.
An application written in Java, and Spring as well, are capable to support different languages by providing textual messages externally. These messages are usually written in .properties files (a Java standard).
The interface MessageSource
The main Spring interface to support of I18n/L10n messages is MessageSource
Definition of MessageSource(Version: spring-framework 6.1.2) package org.springframework.context;
........
public interface MessageSource {
@Nullable
String getMessage(
String code,
@Nullable Object[] args,
@Nullable String defaultMessage,
Locale locale);
String getMessage(String code, @Nullable Object[] args, Locale locale)
throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale)
throws NoSuchMessageException;
}
where:
MessageSource Implementation
Spring provides two out-of-the-box implementations:
ResourceBundleMessageSource : this internally uses java.util.ResourceBundle.
ReloadableResourceBundleMessageSource : It's able to reload messages from the source files based on timestamp changes, without restarting the application. It doesn't use ResourceBundle but uses it's own message loading and resolving logic. It can also detect XML property files and load them.
Also ApplicationContext interface extends MessageResource but it's implementation uses the underlying configured MessageResource as delegate, so it shouldn't be considered a full-blown implementation as above the two.
Naming convention for resource files?
The following file name format must be used for MessageResource to work (this is actually java.util.ResourceBundle standard):
basename_languageCode_countryCode.properties
'basename' can be related to the application part where these properties belong to. For example orderView_en_us.properties.
We can create as many message properties files as we want and they can be anywhere in the classpath.
Per good practices, the file groups should be divided under different folders based on different modules of the application.
Following project structure shows how to do a maintainable grouping in a maven project (resources folder is in classpath by default):
+--src
`--+--main
|--+--java
| .........
`--+--resources
`--+--messages
|--+--trade-module
| `--+--tradeDetails_en_us.properties
| `--tradeDetails_fr_FR.properties
| .........
`--+--order-module
`--+--orderHistory_en_us.properties
|--orderHistory_fr_FR.properties
|--orderPlacement_en_us.properties
`--orderPlacement_fr_FR.properties
| .........
`
How to use MessageSource?
Spring doesn't provide any annotations based approach for message resolutions. Although, Spring does so for general purpose resource loading via Resource/@Value combination and @PropertySource/@Value combination for general purpose properties loading.
The reason of why there's no annotations support for resource messages is: the values resolved via annotations are static, which are loaded only at start up time and remain the same after that, whereas, Locale, in most practical scenarios, changes depending on different workflow. For example in Spring MVC application, a different locale is used for each different HTTP request client (with the help of 'Accept-Language' header) to generate the content in a different language which the client can understand.
The only appropriate way is to inject the implementation of MessageSource as a bean.
ResourceBundleMessageSource example
src/main/resources/messages/msg_en_us.propertiesapp.name = resource bundle test invoked by {0}
package com.logicbig.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import java.io.IOException;
import java.util.Locale;
public class MessageSourceExample {
public static void main (String[] args) throws IOException {
Locale.setDefault(Locale.US);
//uncomment next line to change the locale
//Locale.setDefault(Locale.FRANCE);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
MyBean bean = context.getBean(MyBean.class);
bean.doSomething();
}
@Configuration
public static class Config {
@Bean
public MyBean myBean () {
return new MyBean();
}
@Bean
public MessageSource messageSource () {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages/msg");
return messageSource;
}
}
public static class MyBean {
@Autowired
private MessageSource messageSource;
public void doSomething () {
System.out.println(messageSource.getMessage("app.name", new Object[]{"Joe"},
Locale.getDefault()));
}
}
} Outputresource bundle test invoked by Joe
Using MessageSourceAware
Instead of autowiring we can implement MessageSourceAware interface to get notified of the MessageSource .
package com.logicbig.example;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import java.io.IOException;
import java.util.Locale;
public class MessageSourceAwareExample {
public static void main (String[] args) throws IOException {
Locale.setDefault(Locale.US);
//uncomment next line to change the locale
// Locale.setDefault(Locale.FRANCE);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
MyBean bean = context.getBean(MyBean.class);
bean.doSomething();
}
@Configuration
public static class Config {
@Bean
public MyBean myBean () {
return new MyBean();
}
@Bean
public MessageSource messageSource () {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages/msg");
return messageSource;
}
}
public static class MyBean implements MessageSourceAware {
private MessageSource messageSource;
public void doSomething () {
System.out.println(messageSource.getMessage("app.name", new Object[]{"Joe"},
Locale.getDefault()));
}
@Override
public void setMessageSource (MessageSource messageSource) {
this.messageSource = messageSource;
}
}
} Outputresource bundle test invoked by Joe
ReloadableResourceBundleMessageSource example
@Configuration
public class Config {
@Bean
public MyBean myBean () {
return new MyBean();
}
@Bean
public MessageSource messageSource () {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages/msg");
messageSource.setDefaultEncoding("UTF-8");
//refresh cache after every 500 mill-secs
messageSource.setCacheMillis(500);
return messageSource;
}
}
The following code access the message 20 times and sleeps for 2 sec every time. So we have enough time to change the messages in the files to see the effect in real time. Also remember to change the correct properties file which are under the target/classes folder (maven specific build folder) because those are the ones which are loaded during runtime.
public static class MyBean {
@Autowired
private MessageSource messageSource;
public void doSomething () {
for (int i = 0; i < 20; i++) {
System.out.println(messageSource.getMessage("app.name", new Object[]{"Joe"},
Locale.getDefault()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Working with multiple message sources
If we want to use multiple sources with different base names, we have two choices:
-
Set multiple sources using setBasenames(String... basenames) : For example:
@Bean
public MessageSource messageSource () {
ResourceBundleMessageSource messageSource =
new ResourceBundleMessageSource();
messageSource.setBasenames("messages/msg", "messages/msg2");
return messageSource;
} The message codes are searched in order the files are added in above method and first match will be returned.
Set sources in hierarchical order using setParentMessageSource(parentMessageSource) : For example:
@Bean
public MessageSource messageSource () {
//child
ResourceBundleMessageSource messageSource =
new ResourceBundleMessageSource();
messageSource.setBasename("messages/msg2");
//parent
ResourceBundleMessageSource parentMessageSource =
new ResourceBundleMessageSource();
parentMessageSource.setBasename("messages/msg");
messageSource.setParentMessageSource(parentMessageSource);
return messageSource;
} This is useful when we want to override some of the messages from parent. The child MessageSource is searched for the message code first, if message is not found then parent is searched.
Example ProjectDependencies and Technologies Used: - spring-context 6.1.2 (Spring Context)
Version Compatibility: 4.3.0.RELEASE - 6.1.2 Version compatibilities of spring-context with this example: Versions in green have been tested.
- JDK 17
- Maven 3.8.1
|