SpringBoot i18n使用和查找策略整理


SpringBoot本身提供了很好的国际化的封装,通过简单的配置即可使用,极大提高了开发效率。

使用

在配置文件中增加以下配置:

spring:
  messages:
    basename: i18n/messages
    fallbackToSystemLocale: false

然后就可以在src/main/resources下增加相关的i18n文件了,如下:
IDEA下i18n文件定义

这里messages.properties是作为兜底策略存在的,当根据一定的查找策略找不到i18n文件后,会使用该文件的信息返回。那么框架本身是基于什么样的策略查找i18n文件的呢?继续往下看。

技术实现

自动配置

SpringBoot是基于MessageSourceAutoConfiguration进行自动配置初始化,代码如下:

@AutoConfiguration
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

        private static final Resource[] NO_RESOURCES = {};

        @Bean
        @ConfigurationProperties(prefix = "spring.messages")
        public MessageSourceProperties messageSourceProperties() {
                return new MessageSourceProperties();
        }

        @Bean
        public MessageSource messageSource(MessageSourceProperties properties) {
                ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
                if (StringUtils.hasText(properties.getBasename())) {
                        messageSource.setBasenames(StringUtils
                                        .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
                }
                if (properties.getEncoding() != null) {
                        messageSource.setDefaultEncoding(properties.getEncoding().name());
                }
                messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
                Duration cacheDuration = properties.getCacheDuration();
                if (cacheDuration != null) {
                        messageSource.setCacheMillis(cacheDuration.toMillis());
                }
                messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
                messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
                return messageSource;
        }

        protected static class ResourceBundleCondition extends SpringBootCondition {

                private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

                @Override
                public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
                        String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
                        ConditionOutcome outcome = cache.get(basename);
                        if (outcome == null) {
                                outcome = getMatchOutcomeForBasename(context, basename);
                                cache.put(basename, outcome);
                        }
                        return outcome;
                }

                private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
                        ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
                        for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
                                for (Resource resource : getResources(context.getClassLoader(), name)) {
                                        if (resource.exists()) {
                                                return ConditionOutcome.match(message.found("bundle").items(resource));
                                        }
                                }
                        }
                        return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
                }

                private Resource[] getResources(ClassLoader classLoader, String name) {
                        String target = name.replace('.', '/');
                        try {
                                return new PathMatchingResourcePatternResolver(classLoader)
                                                .getResources("classpath*:" + target + ".properties");
                        }
                        catch (Exception ex) {
                                return NO_RESOURCES;
                        }
                }

        }

}

i18n文件查找策略

在开始查找策略之前先介绍下几个概念:

  • language:语种,例如:中文、英文
  • script:洲,例如亚洲、拉丁美洲等
  • country:国家,例如中国
  • variant:方言,例如闽南语、四川话等

i18n文件查找策略详见java.util.ResourceBundle,优先级如下:

baseName + "_" + language + "_" + script + "_" + country + "_" + variant
baseName + "_" + language + "_" + script + "_" + country
baseName + "_" + language + "_" + script
baseName + "_" + language + "_" + country + "_" + variant
baseName + "_" + language + "_" + country
baseName + "_" + language

举例来说:

MyResource_en_Latn_US_WINDOWS_VISTA
MyResource_en_Latn_US_WINDOWS
MyResource_en_Latn_US
MyResource_en_Latn
MyResource_en_US_WINDOWS_VISTA
MyResource_en_US_WINDOWS
MyResource_en_US
MyResource_en

文章作者: zzq0324
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zzq0324 !
  目录