Springboot 集成 i8n,两行代码实现国际化,你不想学吗?


i18n 国际化

在开发中,国际化(Internationalization),也叫本地化,指的是一个网站(或应用)可以支持多种不同的语言,即可以根据用户所在的语言类型和国家/地区,显示不同的文字。能够让不同国家,不同语种的用户方便使用,提高用户体验性。

实现国际化,比较简单的实现方案就是根据不同的国家和语言开发不同的程序,分别用相应的语言文字显示,例如Oracle英文官网地址:https://www.oracle.com/index.html,中文官网地址:https://www.oracle.com/cn/index.html

一般比较大型的公司会使用这种根据不同的国家和语言开发不同的程序的形式实现国家化,其一人家公司有资源投入开发,其二可以根据不同国家,不同语种用户习惯开发更加符合当地人的布局样式,交互等。

还有另外一种国家化实现方案,就是开发一套程序,可以根据用户所在区域显示不同的语言文字,但是网站/应用的布局样式等不会发生很大变化。这个方案也是我们要将的i18n国际化实现,i18n其实就是英文单词Internationalization(国际化)的缩写,i和n代表单词首尾字母,18代表中间的18个字母。

i18n 实现

在Java中,通过java.util.Locale类表示本地化对象,它通过语言类型和国家/地区等元素来确定创建一个本地化对象 。Locale对象表示具体的地理,时区,语言,政治等。

我们可以通过以下方法,获取本地系统的语言,国家等信息;以及获取代表指定地区的语言,国家信息Local对象。当然你也可以调用 Locale.getAvailableLocales() 方法查看所有可用的Local对象。

package com.nobody;

import java.util.Locale;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/4/15
 * @Version 1.0
 */
public class LocalTest {
    public static void main(String[] args) {
        Locale defaultLocale = Locale.getDefault();
        Locale chinaLocale = Locale.CHINA;
        Locale usLocale = Locale.US;
        Locale usLocale1 = new Locale("en", "US");
        System.out.println(defaultLocale);
        System.out.println(defaultLocale.getLanguage());
        System.out.println(defaultLocale.getCountry());
        System.out.println(chinaLocale);
        System.out.println(usLocale);
        System.out.println(usLocale1);
    }
}

// 输出结果
zh_CN
zh
CN
zh_CN
en_US
en_US

我们一般会将不同的语言的属性值存放在不同的配置文件中,ResourceBundle类可以根据指定的baseName和Local对象,就可以找到相应的配置文件,从而读取到相应的语言文字,从而构建出ResourceBundle对象,然后我们可以通过ResourceBundle.getString(key)就可以取得key在不同地域的语言文字了。

Properties配置文件命名规则:baseName_local.properties

假如baseName为i18n,则相应的配置文件应该命名为如下:

  • 中文的配置文件:i18n_zh_CN.properties
  • 英文的配置文件:i18n_en_US.properties

在这里插入图片描述
然后在两个配置文件中,存放着键值对,对应不同的语言文字

# 在i18n_zh_CN.properties文件中
userName=陈皮

# 在i18n_en_US.properties文件中
userName=Peel

我们通过如下方式,就可以获取相应语言环境下的信息了,如下:

Locale chinaLocale = Locale.CHINA;
ResourceBundle resourceBundle = ResourceBundle.getBundle("i18n", chinaLocale);
String userName = resourceBundle.getString("userName");
System.out.println(userName);

Locale usLocale = Locale.US;
resourceBundle = ResourceBundle.getBundle("i18n", usLocale);
userName = resourceBundle.getString("userName");
System.out.println(userName);

// 输出结果
陈皮
Peel

对于不同地域语言环境的用户,我们是如何处理国际化呢?其实原理很简单,假设客户端发送一个请求到服务端,在请求头中设置了键值对,“Accept-Language”:“zh-CN”,根据这个信息,可以构建出一个代表这个区域的本地化对象Locale,根据配置文件的baseName和Locale对象就可以知道读取哪个配置文件的属性,将要显示的文字格式化处理,最终返回给客户端进行显示。

Springboot 集成 i18n

在Springboot中,我们会使用到一个MessageSource接口,用于访问国际化信息,此接口定义了几个重载的方法。code即国际化资源的属性名(键);args即传递给格式化字符串中占位符的运行时参数值;local即本地化对象;resolvable封装了国际化资源属性名,参数,默认信息等。

  • String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale)
  • String getMessage(String code, @Nullable Object[] args, Locale locale)
  • String getMessage(MessageSourceResolvable resolvable, Locale locale)

Springboot提供了国际化信息自动配置类MessageSourceAutoConfiguration,它可以生成MessageSource接口的实现类ResourceBundleMessageSource,注入到Spring容器中。MessageSource配置生效依靠ResourceBundleCondition条件,从环境变量中读取spring.messages.basename的值(默认值messages),这个值就是MessageSource对应的资源文件名称,资源文件扩展名是.properties,然后通过PathMatchingResourcePatternResolver从classpath*:目录下读取对应的资源文件,如果能正常读取到资源文件,则加载配置类。源码如下:

package org.springframework.boot.autoconfigure.context;

@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

	private static final Resource[] NO_RESOURCES = {};

	// 我们可以在application.properties文件中修改spring.messages前缀的默认值,比如修改basename的值
	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

    // 生成ResourceBundleMessageSource实例,注入容器中
	@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());
		}

 	    // 读取classpath*:路径下的配置文件
		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;
			}
		}

	}

}

以下这个类是Spring国际化处理的属性配置类,我们可以在application.properties文件中自定义修改这些默认值,例如:spring.messages.basename=i18n

package org.springframework.boot.autoconfigure.context;

/**
 * Configuration properties for Message Source.
 *
 * @author Stephane Nicoll
 * @author Kedar Joshi
 * @since 2.0.0
 */
public class MessageSourceProperties {

	/**
	 * Comma-separated list of basenames (essentially a fully-qualified classpath
	 * location), each following the ResourceBundle convention with relaxed support for
	 * slash based locations. If it doesn't contain a package qualifier (such as
	 * "org.mypackage"), it will be resolved from the classpath root.
	 */
	private String basename = "messages";

	/**
	 * Message bundles encoding.
	 */
	private Charset encoding = StandardCharsets.UTF_8;

	/**
	 * Loaded resource bundle files cache duration. When not set, bundles are cached
	 * forever. If a duration suffix is not specified, seconds will be used.
	 */
	@DurationUnit(ChronoUnit.SECONDS)
	private Duration cacheDuration;

	/**
	 * Whether to fall back to the system Locale if no files for a specific Locale have
	 * been found. if this is turned off, the only fallback will be the default file (e.g.
	 * "messages.properties" for basename "messages").
	 */
	private boolean fallbackToSystemLocale = true;

	/**
	 * Whether to always apply the MessageFormat rules, parsing even messages without
	 * arguments.
	 */
	private boolean alwaysUseMessageFormat = false;

	/**
	 * Whether to use the message code as the default message instead of throwing a
	 * "NoSuchMessageException". Recommended during development only.
	 */
	private boolean useCodeAsDefaultMessage = false;
	
	// 省略get/set
}

我们在类路径下创建好国际化配置文件之后,就可以注入MessageSource实例,进行国际化处理了:

i18n.properties文件是默认文件,当找不到语言的配置的时候,使用该文件进行展示。
在这里插入图片描述

@Autowired
private MessageSource messageSource;

@GetMapping("test")
public GeneralResult<String> test() {
	// 获取客户端的语言环境Locale对象,即取的请求头Accept-Language键的值来判断,我们也可以自定义请求头键,来获取语言标识
    Locale locale = LocaleContextHolder.getLocale();
    String userName = messageSource.getMessage("userName", null, locale);
    System.out.println(userName);
    return GeneralResult.genSuccessResult(userName);
}

上面我们是利用Spirng自带的LocaleContextHolder来获取本地对象Locale,它是取的请求头Accept-Language键的语言值来判断生成相应Locale对象。我们也可以根据其他方式,例如请求头中自定义键的值,来生成Locale对象,然后再通过messageSource.getMessage()方法来实现最终的国家化。

### 配置Spring Boot中的国际化(i18n) 在Spring Boot中配置国际化(Internationalization,简称i18n),可以通过创建资源文件并设置相应的LocaleResolver来实现动态的语言切换功能。以下是关于如何在Spring Boot项目中完成国际化的具体说明。 #### 创建资源文件 首先,在`src/main/resources/`目录下创建多个`.properties`文件用于存储不同语言的消息键值对。例如: - `messages.properties`: 默认语言的资源文件。 - `messages_en_US.properties`: 英语(美国)版本的消息文件。 - `messages_zh_CN.properties`: 中文(中国)版本的消息文件。 这些文件的内容如下所示[^1]: ```properties # messages.properties (default language) welcome.message=Hello, {0}! # messages_en_US.properties (English US locale) welcome.message=Hello, {0}! # messages_zh_CN.properties (Chinese China locale) welcome.message=你好,{0}! ``` #### 注册MessageSource Bean 为了使Spring能够加载上述定义好的资源文件,需注册一个`MessageSource` bean。通常情况下可以直接利用Spring Boot自动配置的功能,或者手动通过Java Config方式声明它: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; @Configuration public class AppConfig { @Bean(name = "messageSource") public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource source = new ResourceBundleMessageSource(); source.setBasename("messages"); // 对应于前面提到的基础名称 source.setDefaultEncoding("UTF-8"); return source; } } ``` 此部分操作可以由Spring Boot默认处理,如果满足特定条件则无需显式编写以上代码片段[^3]。 #### 设置Locale Resolver 为了让Web应用支持多国语言显示效果,还需要指定一种机制让用户可以选择其偏爱的语言环境(locale),这可通过自定义locale resolver达成目标之一便是AcceptHeaderLocaleResolver。然而更灵活的做法是采用SessionLocaleResolver或CookieLocaleResolver以便保存用户的偏好设定直至会话结束或是浏览器关闭为止[^2]。 示例使用SessionLocaleResolver作为例子展示: ```java @Bean public LocaleResolver localeResolver(){ SessionLocaleResolver slr=new SessionLocaleResolver(); slr.setDefaultLocale(Locale.US); // 设定初始缺省区域为美式英语 return slr ; } ``` 另外也可以借助拦截器改变当前请求所使用的local对象实例从而达到页面刷新即刻生效的目的[^4]。 最后提醒一点就是当涉及到前端框架比如Thymeleaf渲染模板时记得引入th:utext标签库以及正确调用msg函数获取翻译后的字符串表达形式[^5]。 ```html <p th:text="#{welcome.message(${name})}"></p> ``` 问题
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈皮的JavaLib

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值