幾行SpringBoot代碼輕松實(shí)現(xiàn)國際化
編輯:業(yè)余草
blog.csdn.net/chenlixiao007
推薦:https://www.xttblog.com/?p=5182
i18n 國際化
在開發(fā)中,國際化(Internationalization),也叫本地化,指的是一個(gè)網(wǎng)站(或應(yīng)用)可以支持多種不同的語言,即可以根據(jù)用戶所在的語言類型和國家/地區(qū),顯示不同的文字。能夠讓不同國家,不同語種的用戶方便使用,提高用戶體驗(yàn)性。
實(shí)現(xiàn)國際化,比較簡單的實(shí)現(xiàn)方案就是根據(jù)不同的國家和語言開發(fā)不同的程序,分別用相應(yīng)的語言文字顯示,例如Oracle英文官網(wǎng)地址:https://www.oracle.com/index.html,中文官網(wǎng)地址:https://www.oracle.com/cn/index.html。
一般比較大型的公司會使用這種根據(jù)不同的國家和語言開發(fā)不同的程序的形式實(shí)現(xiàn)國家化,其一人家公司有資源投入開發(fā),其二可以根據(jù)不同國家,不同語種用戶習(xí)慣開發(fā)更加符合當(dāng)?shù)厝说牟季謽邮剑换サ取?/p>
還有另外一種國家化實(shí)現(xiàn)方案,就是開發(fā)一套程序,可以根據(jù)用戶所在區(qū)域顯示不同的語言文字,但是網(wǎng)站/應(yīng)用的布局樣式等不會發(fā)生很大變化。這個(gè)方案也是我們要將的i18n國際化實(shí)現(xiàn),i18n其實(shí)就是英文單詞Internationalization(國際化)的縮寫,i和n代表單詞首尾字母,18代表中間的18個(gè)字母。
i18n 實(shí)現(xiàn)
在Java中,通過java.util.Locale類表示本地化對象,它通過語言類型和國家/地區(qū)等元素來確定創(chuàng)建一個(gè)本地化對象 。Locale對象表示具體的地理,時(shí)區(qū),語言,政治等。
我們可以通過以下方法,獲取本地系統(tǒng)的語言,國家等信息;以及獲取代表指定地區(qū)的語言,國家信息Local對象。當(dāng)然你也可以調(diào)用 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);
}
}
// 輸出結(jié)果
zh_CN
zh
CN
zh_CN
en_US
en_US
我們一般會將不同的語言的屬性值存放在不同的配置文件中,ResourceBundle類可以根據(jù)指定的baseName和Local對象,就可以找到相應(yīng)的配置文件,從而讀取到相應(yīng)的語言文字,從而構(gòu)建出ResourceBundle對象,然后我們可以通過ResourceBundle.getString(key)就可以取得key在不同地域的語言文字了。
Properties配置文件命名規(guī)則:baseName_local.properties
假如baseName為i18n,則相應(yīng)的配置文件應(yīng)該命名為如下:
中文的配置文件:i18n_zh_CN.properties 英文的配置文件:i18n_en_US.properties

然后在兩個(gè)配置文件中,存放著鍵值對,對應(yīng)不同的語言文字
# 在i18n_zh_CN.properties文件中
userName=陳皮
# 在i18n_en_US.properties文件中
userName=Peel
我們通過如下方式,就可以獲取相應(yīng)語言環(huán)境下的信息了,如下:
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);
// 輸出結(jié)果
陳皮
Peel
對于不同地域語言環(huán)境的用戶,我們是如何處理國際化呢?其實(shí)原理很簡單,假設(shè)客戶端發(fā)送一個(gè)請求到服務(wù)端,在請求頭中設(shè)置了鍵值對,“Accept-Language”:“zh-CN”,根據(jù)這個(gè)信息,可以構(gòu)建出一個(gè)代表這個(gè)區(qū)域的本地化對象Locale,根據(jù)配置文件的baseName和Locale對象就可以知道讀取哪個(gè)配置文件的屬性,將要顯示的文字格式化處理,最終返回給客戶端進(jìn)行顯示。
Springboot 集成 i18n
在Springboot中,我們會使用到一個(gè)MessageSource接口,用于訪問國際化信息,此接口定義了幾個(gè)重載的方法。code即國際化資源的屬性名(鍵);args即傳遞給格式化字符串中占位符的運(yùn)行時(shí)參數(shù)值;local即本地化對象;resolvable封裝了國際化資源屬性名,參數(shù),默認(rèn)信息等。
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提供了國際化信息自動(dòng)配置類MessageSourceAutoConfiguration,它可以生成MessageSource接口的實(shí)現(xiàn)類ResourceBundleMessageSource,注入到Spring容器中。MessageSource配置生效依靠ResourceBundleCondition條件,從環(huán)境變量中讀取spring.messages.basename的值(默認(rèn)值messages),這個(gè)值就是MessageSource對應(yīng)的資源文件名稱,資源文件擴(kuò)展名是.properties,然后通過PathMatchingResourcePatternResolver從classpath*:目錄下讀取對應(yīng)的資源文件,如果能正常讀取到資源文件,則加載配置類。源碼如下:
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前綴的默認(rèn)值,比如修改basename的值
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
// 生成ResourceBundleMessageSource實(shí)例,注入容器中
@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;
}
}
}
}
以下這個(gè)類是Spring國際化處理的屬性配置類,我們可以在application.properties文件中自定義修改這些默認(rèn)值,例如: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
}
我們在類路徑下創(chuàng)建好國際化配置文件之后,就可以注入MessageSource實(shí)例,進(jìn)行國際化處理了:
i18n.properties文件是默認(rèn)文件,當(dāng)找不到語言的配置的時(shí)候,使用該文件進(jìn)行展示。

@Autowired
private MessageSource messageSource;
@GetMapping("test")
public GeneralResult<String> test() {
// 獲取客戶端的語言環(huán)境Locale對象,即取的請求頭Accept-Language鍵的值來判斷,我們也可以自定義請求頭鍵,來獲取語言標(biāo)識
Locale locale = LocaleContextHolder.getLocale();
String userName = messageSource.getMessage("userName", null, locale);
System.out.println(userName);
return GeneralResult.genSuccessResult(userName);
}
上面我們是利用Spirng自帶的LocaleContextHolder來獲取本地對象Locale,它是取的請求頭Accept-Language鍵的語言值來判斷生成相應(yīng)Locale對象。我們也可以根據(jù)其他方式,例如請求頭中自定義鍵的值,來生成Locale對象,然后再通過messageSource.getMessage()方法來實(shí)現(xiàn)最終的國家化。
--完--
最近給大家找了 Vue進(jìn)階
資源,怎么領(lǐng)取?
掃二維碼,加我微信,回復(fù):Vue進(jìn)階
注意,不要亂回復(fù)
沒錯(cuò),不是機(jī)器人 記得一定要等待,等待才有好東西
最近給大家找了 Vue進(jìn)階
資源,怎么領(lǐng)取?
掃二維碼,加我微信,回復(fù):Vue進(jìn)階
注意,不要亂回復(fù) 沒錯(cuò),不是機(jī)器人 記得一定要等待,等待才有好東西
