Java 国际化
i18n在Java Web中的基本使用和配置
见:http://www.cnblogs.com/xdp-gacl/p/3945800.html
SpringBoot中的i18n
SpringBoot项目默认会进行i18n的自动配置,因此在class path下增加messages.properties、messages_en.properties等文件即可。
注:不带语言串的文件是fallback方案,即——会根据指定的语言(如en)从对应的文件(如messages_en.properties)中获取指定key对应的值,若 不存在与指定语言对应的文件 或 对应的文件中找不到指定key对应的值,则会从messages.properties中找,若仍找不到则报错。
当然,也可以他通过修改配置来修改默认行为。有两种方式:
通过配置文件修改,相关配置示例如下:
spring: messages: basename: static/i18n/messages, web_base/i18n/messages, validation/i18n/messages default-lang: zh_CHS # non-built-in property # encoding: UTF-8 # cache-seconds: 10 # always-use-message-format: false # fallback-to-system-local: true # use-code-as-default-message: false # other config ...
通过代码修改——自定义一个MessageSource Bean,在Bean的内部逻辑中指定参数值。示例如下(如下代码实际上就是 MessageSourceAutoConfiguration 源码中的一部分):
1 @Bean 2 public MessageSource messageSource() { 3 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 4 if (StringUtils.hasText(this.basename)) { messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( 5 StringUtils.trimAllWhitespace(this.basename))); 6 } 7 if (this.encoding != null) { 8 messageSource.setDefaultEncoding(this.encoding.name()); 9 } 10 messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); 11 messageSource.setCacheSeconds(this.cacheSeconds); 12 messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); 13 return messageSource; 14 }
内部实现:相关的类可参阅 MessageSourceProperties、MessageSourceAutoConfiguration 等,从这些类的内容可以知道配置的默认值、对basename的解析规则等。
1 package org.springframework.boot.autoconfigure.context; 2 3 import java.nio.charset.Charset; 4 import java.nio.charset.StandardCharsets; 5 import java.time.Duration; 6 import java.time.temporal.ChronoUnit; 7 8 import org.springframework.boot.convert.DurationUnit; 9 10 /** 11 * Configuration properties for Message Source. 12 * 13 * @author Stephane Nicoll 14 * @author Kedar Joshi 15 * @since 2.0.0 16 */ 17 public class MessageSourceProperties { 18 19 /** 20 * Comma-separated list of basenames (essentially a fully-qualified classpath 21 * location), each following the ResourceBundle convention with relaxed support for 22 * slash based locations. If it doesn't contain a package qualifier (such as 23 * "org.mypackage"), it will be resolved from the classpath root. 24 */ 25 private String basename = "messages"; 26 27 /** 28 * Message bundles encoding. 29 */ 30 private Charset encoding = StandardCharsets.UTF_8; 31 32 /** 33 * Loaded resource bundle files cache duration. When not set, bundles are cached 34 * forever. If a duration suffix is not specified, seconds will be used. 35 */ 36 @DurationUnit(ChronoUnit.SECONDS) 37 private Duration cacheDuration; 38 39 /** 40 * Whether to fall back to the system Locale if no files for a specific Locale have 41 * been found. if this is turned off, the only fallback will be the default file (e.g. 42 * "messages.properties" for basename "messages"). 43 */ 44 private boolean fallbackToSystemLocale = true; 45 46 /** 47 * Whether to always apply the MessageFormat rules, parsing even messages without 48 * arguments. 49 */ 50 private boolean alwaysUseMessageFormat = false; 51 52 /** 53 * Whether to use the message code as the default message instead of throwing a 54 * "NoSuchMessageException". Recommended during development only. 55 */ 56 private boolean useCodeAsDefaultMessage = false; 57 58 public String getBasename() { 59 return this.basename; 60 } 61 62 public void setBasename(String basename) { 63 this.basename = basename; 64 } 65 66 public Charset getEncoding() { 67 return this.encoding; 68 } 69 70 public void setEncoding(Charset encoding) { 71 this.encoding = encoding; 72 } 73 74 public Duration getCacheDuration() { 75 return this.cacheDuration; 76 } 77 78 public void setCacheDuration(Duration cacheDuration) { 79 this.cacheDuration = cacheDuration; 80 } 81 82 public boolean isFallbackToSystemLocale() { 83 return this.fallbackToSystemLocale; 84 } 85 86 public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { 87 this.fallbackToSystemLocale = fallbackToSystemLocale; 88 } 89 90 public boolean isAlwaysUseMessageFormat() { 91 return this.alwaysUseMessageFormat; 92 } 93 94 public void setAlwaysUseMessageFormat(boolean alwaysUseMessageFormat) { 95 this.alwaysUseMessageFormat = alwaysUseMessageFormat; 96 } 97 98 public boolean isUseCodeAsDefaultMessage() { 99 return this.useCodeAsDefaultMessage; 100 } 101 102 public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) { 103 this.useCodeAsDefaultMessage = useCodeAsDefaultMessage; 104 } 105 106 }
1 package org.springframework.boot.autoconfigure.context; 2 3 import java.time.Duration; 4 5 import org.springframework.boot.autoconfigure.AutoConfigureOrder; 6 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 7 import org.springframework.boot.autoconfigure.condition.ConditionMessage; 8 import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 import org.springframework.boot.autoconfigure.condition.SearchStrategy; 11 import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 12 import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition; 13 import org.springframework.boot.context.properties.ConfigurationProperties; 14 import org.springframework.boot.context.properties.EnableConfigurationProperties; 15 import org.springframework.context.MessageSource; 16 import org.springframework.context.annotation.Bean; 17 import org.springframework.context.annotation.ConditionContext; 18 import org.springframework.context.annotation.Conditional; 19 import org.springframework.context.annotation.Configuration; 20 import org.springframework.context.support.ResourceBundleMessageSource; 21 import org.springframework.core.Ordered; 22 import org.springframework.core.io.Resource; 23 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 24 import org.springframework.core.type.AnnotatedTypeMetadata; 25 import org.springframework.util.ConcurrentReferenceHashMap; 26 import org.springframework.util.StringUtils; 27 28 /** 29 * {@link EnableAutoConfiguration Auto-configuration} for {@link MessageSource}. 30 * 31 * @author Dave Syer 32 * @author Phillip Webb 33 * @author Eddú Meléndez 34 */ 35 @Configuration 36 @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT) 37 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 38 @Conditional(ResourceBundleCondition.class) 39 @EnableConfigurationProperties 40 public class MessageSourceAutoConfiguration { 41 42 private static final Resource[] NO_RESOURCES = {}; 43 44 @Bean 45 @ConfigurationProperties(prefix = "spring.messages") 46 public MessageSourceProperties messageSourceProperties() { 47 return new MessageSourceProperties(); 48 } 49 50 @Bean 51 public MessageSource messageSource(MessageSourceProperties properties) { 52 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 53 if (StringUtils.hasText(properties.getBasename())) { 54 messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( 55 StringUtils.trimAllWhitespace(properties.getBasename()))); 56 } 57 if (properties.getEncoding() != null) { 58 messageSource.setDefaultEncoding(properties.getEncoding().name()); 59 } 60 messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); 61 Duration cacheDuration = properties.getCacheDuration(); 62 if (cacheDuration != null) { 63 messageSource.setCacheMillis(cacheDuration.toMillis()); 64 } 65 messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); 66 messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); 67 return messageSource; 68 } 69 70 protected static class ResourceBundleCondition extends SpringBootCondition { 71 72 private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>(); 73 74 @Override 75 public ConditionOutcome getMatchOutcome(ConditionContext context, 76 AnnotatedTypeMetadata metadata) { 77 String basename = context.getEnvironment() 78 .getProperty("spring.messages.basename", "messages"); 79 ConditionOutcome outcome = cache.get(basename); 80 if (outcome == null) { 81 outcome = getMatchOutcomeForBasename(context, basename); 82 cache.put(basename, outcome); 83 } 84 return outcome; 85 } 86 87 private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, 88 String basename) { 89 ConditionMessage.Builder message = ConditionMessage 90 .forCondition("ResourceBundle"); 91 for (String name : StringUtils.commaDelimitedListToStringArray( 92 StringUtils.trimAllWhitespace(basename))) { 93 for (Resource resource : getResources(context.getClassLoader(), name)) { 94 if (resource.exists()) { 95 return ConditionOutcome 96 .match(message.found("bundle").items(resource)); 97 } 98 } 99 } 100 return ConditionOutcome.noMatch( 101 message.didNotFind("bundle with basename " + basename).atAll()); 102 } 103 104 private Resource[] getResources(ClassLoader classLoader, String name) { 105 String target = name.replace('.', '/'); 106 try { 107 return new PathMatchingResourcePatternResolver(classLoader) 108 .getResources("classpath*:" + target + ".properties"); 109 } 110 catch (Exception ex) { 111 return NO_RESOURCES; 112 } 113 } 114 115 } 116 117 }
1 package org.springframework.boot.autoconfigure.context; 2 3 import java.time.Duration; 4 5 import org.springframework.boot.autoconfigure.AutoConfigureOrder; 6 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 7 import org.springframework.boot.autoconfigure.condition.ConditionMessage; 8 import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 import org.springframework.boot.autoconfigure.condition.SearchStrategy; 11 import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 12 import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition; 13 import org.springframework.boot.context.properties.ConfigurationProperties; 14 import org.springframework.boot.context.properties.EnableConfigurationProperties; 15 import org.springframework.context.MessageSource; 16 import org.springframework.context.annotation.Bean; 17 import org.springframework.context.annotation.ConditionContext; 18 import org.springframework.context.annotation.Conditional; 19 import org.springframework.context.annotation.Configuration; 20 import org.springframework.context.support.ResourceBundleMessageSource; 21 import org.springframework.core.Ordered; 22 import org.springframework.core.io.Resource; 23 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 24 import org.springframework.core.type.AnnotatedTypeMetadata; 25 import org.springframework.util.ConcurrentReferenceHashMap; 26 import org.springframework.util.StringUtils; 27 28 /** 29 * {@link EnableAutoConfiguration Auto-configuration} for {@link MessageSource}. 30 * 31 * @author Dave Syer 32 * @author Phillip Webb 33 * @author Eddú Meléndez 34 */ 35 @Configuration 36 @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT) 37 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 38 @Conditional(ResourceBundleCondition.class) 39 @EnableConfigurationProperties 40 public class MessageSourceAutoConfiguration { 41 42 private static final Resource[] NO_RESOURCES = {}; 43 44 @Bean 45 @ConfigurationProperties(prefix = "spring.messages") 46 public MessageSourceProperties messageSourceProperties() { 47 return new MessageSourceProperties(); 48 } 49 50 @Bean 51 public MessageSource messageSource(MessageSourceProperties properties) { 52 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 53 if (StringUtils.hasText(properties.getBasename())) { 54 messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( 55 StringUtils.trimAllWhitespace(properties.getBasename()))); 56 } 57 if (properties.getEncoding() != null) { 58 messageSource.setDefaultEncoding(properties.getEncoding().name()); 59 } 60 messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); 61 Duration cacheDuration = properties.getCacheDuration(); 62 if (cacheDuration != null) { 63 messageSource.setCacheMillis(cacheDuration.toMillis()); 64 } 65 messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); 66 messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); 67 return messageSource; 68 } 69 70 protected static class ResourceBundleCondition extends SpringBootCondition { 71 72 private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>(); 73 74 @Override 75 public ConditionOutcome getMatchOutcome(ConditionContext context, 76 AnnotatedTypeMetadata metadata) { 77 String basename = context.getEnvironment() 78 .getProperty("spring.messages.basename", "messages"); 79 ConditionOutcome outcome = cache.get(basename); 80 if (outcome == null) { 81 outcome = getMatchOutcomeForBasename(context, basename); 82 cache.put(basename, outcome); 83 } 84 return outcome; 85 } 86 87 private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, 88 String basename) { 89 ConditionMessage.Builder message = ConditionMessage 90 .forCondition("ResourceBundle"); 91 for (String name : StringUtils.commaDelimitedListToStringArray( 92 StringUtils.trimAllWhitespace(basename))) { 93 for (Resource resource : getResources(context.getClassLoader(), name)) { 94 if (resource.exists()) { 95 return ConditionOutcome 96 .match(message.found("bundle").items(resource)); 97 } 98 } 99 } 100 return ConditionOutcome.noMatch( 101 message.didNotFind("bundle with basename " + basename).atAll()); 102 } 103 104 private Resource[] getResources(ClassLoader classLoader, String name) { 105 String target = name.replace('.', '/'); 106 try { 107 return new PathMatchingResourcePatternResolver(classLoader) 108 .getResources("classpath*:" + target + ".properties"); 109 } 110 catch (Exception ex) { 111 return NO_RESOURCES; 112 } 113 } 114 115 } 116 117 }