For International Support we need to implement properties files for each language.
In order to support this, Spring provides a ResourceBundleMessageSource which we define in the spring-config.xml.
With this in place, we can supply Spring with a list of properties files and a locale to use. We can now use this combination to provide messages and dialog customised for the country we are servicing at the time.
Example
Before we continue, let’s first setup the pom.xml and spring-config.xml for our project as follows:
pom.xml
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.skills421.examples.spring</groupId> <artifactId>Spring3.2.6.Demo</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <spring.version>3.2.6.RELEASE</spring.version> <junit.version>4.11</junit.version> </properties> <dependencies> <!-- Spring 3 dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> </dependencies> </project>
spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config /> <context:component-scan base-package="com.skills421.examples.spring.service" /> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>i18n-messages</value> </list> </property> </bean> </beans>
Here we have specified a context:compoent-scan so that Spring can scan our java package com.skills421.examples.spring.service for Spring Beans specified with the @Component annotation.
In addition, we have specified a ResourceBundleMessageSource bean named messageSource. This takes a list of property files as a basenames parameter. If we do not supply an extension for these files then Spring will assume an extension of .properties.
In our example, we have supplied i18n-messages. Spring will therefore look for i18n-messages.properties, but will also look for all property files with the same name but with a Locale extension.
We will create the properties files i18n-messages_en.properties and i18n_messages_it.properties which will both be included by of definition in the spring-config.xml.
i18n_messages_en.properties
greeting.morning=good morning greeting.afternoon=good afternoon greeting.evening=good evening config.codenotfound=The Message for code {0} could not be found
i18n_messages_it.properties
greeting.morning=buongiorno greeting.afternoon=buon pomeriggio greeting.evening=buonasera config.codenotfound=il messaggio per il codice {0} non è stato trovato
With these in place, we can now create our ConfigService bean. This is a little contrived, but demonstrates what we are trying to achieve.
ConfigService.java
package com.skills421.examples.spring.service; import java.io.IOException; import java.util.Locale; import java.util.Properties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; import org.springframework.stereotype.Component; @Component("config") public class ConfigService { private Properties configMap; private String configfile; private Locale locale; @Autowired private MessageSource messageSource; public Locale getLocale() { if(locale==null) { locale = Locale.UK; } return locale; } public void setLocale(Locale locale) { this.locale = locale; } public String getConfigfile() { return configfile; } @Value("config.properties") public void setConfigfile(String configfile) { this.configfile = configfile; } public Properties getConfigMap() { if(configMap==null) { configMap = new Properties(); try { configMap.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(configfile)); } catch(IOException e) { e.printStackTrace(); } } return configMap; } public MessageSource getMessageSource() { return messageSource; } public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } public String getProperty(String name) { Object value = this.getConfigMap().get(name); if(value==null) { value = messageSource.getMessage("config.codenotfound", new String[]{name}, "config, codenotfound not defined", this.getLocale()); } return (String) value; } }
This file includes a configfile property that is injected using the annotation @Value. This is simply a config file that we want to read to get application configuration information – say for a Gui application or similar.
it also includes a messageSource bean (as defined in our spring-config.xml) that is @Autowired.
The configMap property is lazily loaded using Java in the getConfigMap() method.
The locale method can be injected, but for this example we simply inject it from the test case later on.
Finally, we make use of the messageSource bean to read from the appropriagte i18n-messages properties file, depending on the Locale. Note that in the getLocale() method, if a locale has not been set, we default to Locale.UK.
The messageSource take four parameters:
- the name of the string to be retrieved from the parameters file
- an array of objects – incase our message takes any parameters, or null if no parameters
- a default message that should be returned if the property cannot be found
- the Locale for which we get the message
Let’s modify our ConfigService bean from our previous example to return a localized error message if it cannot find the property we have asked for
TestConfig.java
Finally, let’s create out test suite to test our beans.
package com.skills421.example.spring; import java.util.Locale; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.skills421.examples.spring.service.ConfigService; public class TestConfig { private static AbstractApplicationContext context; @BeforeClass public static void setupAppContext() { context = new ClassPathXmlApplicationContext("spring-config.xml"); } @AfterClass public static void closeAppContext() { if(context!=null) { context.close(); } } @After public void printBlankLine() { System.out.println(); } @Test public void testAllPropertiesFromConfigService() { ConfigService configService = context.getBean("config",ConfigService.class); for(Object propKey : configService.getConfigMap().keySet()) { String propName = (String) propKey; System.out.println(propName+" = "+configService.getProperty(propName)); } } @Test public void testII8N_UK() { String greeting = context.getMessage("greeting.morning", null, "Good Morning to you",Locale.UK); System.out.println(greeting); } @Test public void testII8N_Italy() { String greeting = context.getMessage("greeting.morning", null, "Good Morning to you",Locale.ITALY); System.out.println(greeting); } @Test public void testConfigNameNotFound_UK() { ConfigService configService = context.getBean("config",ConfigService.class); System.out.println(configService.getProperty("unknown")); } @Test public void testConfigNameNotFound_Italy() { ConfigService configService = context.getBean("config",ConfigService.class); configService.setLocale(Locale.ITALY); System.out.println(configService.getProperty("unknown")); } }
Output
In running our test suite, we get the following output.
buongiorno company.name = Skills421 company.url = www.skills421.com company.contact = John Dunning The Message for code unknown could not be found good morning il messaggio per il codice unknown non ? stato trovato
Notice how we are getting messages in both English and Italian.