Spring Boot 國際化踩坑指南
國際化,也叫 i18n,為啥叫這個(gè)名字呢?因?yàn)閲H化英文是 internationalization ,在 i 和 n 之間有 18 個(gè)字母,所以叫 i18n。我們的應(yīng)用如果做了國際化就可以在不同的語言環(huán)境下,方便的進(jìn)行切換,最常見的就是中文和英文之間的切換,國際化這個(gè)功能也是相當(dāng)?shù)某R姟?/p>
在 Spring 中,就通過 AcceptHeaderLocaleResolver 對(duì)國際化提供了支持,開發(fā)者通過簡(jiǎn)單配置,就可以在項(xiàng)目中直接使用國際化功能了。
這一支持,在 Spring Boot 中得到進(jìn)一步的簡(jiǎn)化,在 Spring Boot 中,我們也可以通過寥寥數(shù)行代碼就能方便的實(shí)現(xiàn)國際化功能,接下來松哥就來和大家說一說 Spring Boot 中的國際化。
首先,需要給大家先說明一點(diǎn),項(xiàng)目中的國際化我們往往需要多方面的支持,例如后端做國際化、前端頁面也要做國際化,共同搭配,才能真正實(shí)現(xiàn)國際化的功能。本文我先來和各位小伙伴們介紹 Spring Boot 中的國際化,后面我們?cè)賮斫榻B Vue 的國際化,最后,再把這兩個(gè)結(jié)合應(yīng)用到我們的 vhr 項(xiàng)目中,所以前后一共可能有三篇文章,本文是第一篇。
1.基本使用
Spring Boot 和 Spring 一脈相承,對(duì)于國際化的支持,默認(rèn)是通過 AcceptHeaderLocaleResolver 解析器來完成的,這個(gè)解析器,默認(rèn)是通過請(qǐng)求頭的 Accept-Language 字段來判斷當(dāng)前請(qǐng)求所屬的環(huán)境的,進(jìn)而給出合適的響應(yīng)。
所以在 Spring Boot 中做國際化,這一塊我們可以不用配置,直接就開搞。
首先創(chuàng)建一個(gè)普通的 Spring Boot 項(xiàng)目,添加 web 依賴即可。項(xiàng)目創(chuàng)建成功后,默認(rèn)的國際化配置文件放在 resources 目錄下,所以我們直接在該目錄下創(chuàng)建四個(gè)測(cè)試文件,如下:
我們的 message 文件是直接創(chuàng)建在 resources 目錄下的,IDEA 在展示的時(shí)候,會(huì)多出一個(gè) Resource Bundle,這個(gè)大家不用管,千萬別手動(dòng)去創(chuàng)建這個(gè)目錄。
messages.properties 這個(gè)是默認(rèn)的配置,其他的則是不同語言環(huán)境下的配置,en_US 是英語(美國),zh_CN 是中文簡(jiǎn)體,zh_TW 是中文繁體(文末附錄里邊有一個(gè)完整的語言簡(jiǎn)稱表格)。
四個(gè)文件創(chuàng)建好之后,第一個(gè)默認(rèn)的我們可以先空著,另外三個(gè)分別填入以下內(nèi)容:
messages_zh_CN.properties
user.name=江南一點(diǎn)雨
messages_zh_TW.properties
user.name=江南壹點(diǎn)雨
messages_en_US.properties
user.name=javaboy
配置完成后,我們就可以直接開始使用了。在需要使用值的地方,直接注入 MessageSource 實(shí)例即可。
在 Spring 中需要配置的 MessageSource 現(xiàn)在不用配置了,Spring Boot 會(huì)通過 org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration 自動(dòng)幫我們配置一個(gè) MessageSource 實(shí)例。
創(chuàng)建一個(gè) HelloController ,內(nèi)容如下:
@RestController
public class HelloController {
@Autowired
MessageSource messageSource;
@GetMapping("/hello")
public String hello() {
return messageSource.getMessage("user.name", null, LocaleContextHolder.getLocale());
}
}
在 HelloController 中我們可以直接注入 MessageSource 實(shí)例,然后調(diào)用該實(shí)例中的 getMessage 方法去獲取變量的值,第一個(gè)參數(shù)是要獲取變量的 key,第二個(gè)參數(shù)是如果 value 中有占位符,可以從這里傳遞參數(shù)進(jìn)去,第三個(gè)參數(shù)傳遞一個(gè) Locale 實(shí)例即可,這相當(dāng)于當(dāng)前的語言環(huán)境。
接下來我們就可以直接去調(diào)用這個(gè)接口了。
默認(rèn)情況下,在接口調(diào)用時(shí),通過請(qǐng)求頭的 Accept-Language 來配置當(dāng)前的環(huán)境,我這里通過 POSTMAN 來進(jìn)行測(cè)試,結(jié)果如下:
小伙伴們看到,我在請(qǐng)求頭中設(shè)置了 Accept-Language 為 zh-CN,所以拿到的就是簡(jiǎn)體中文;如果我設(shè)置了 zh-TW,就會(huì)拿到繁體中文:
是不是很 Easy?
2.自定義切換
有的小伙伴覺得切換參數(shù)放在請(qǐng)求頭里邊好像不太方便,那么也可以自定義解析方式。例如參數(shù)可以當(dāng)成普通參數(shù)放在地址欄上,通過如下配置可以實(shí)現(xiàn)我們的需求。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
registry.addInterceptor(interceptor);
}
@Bean
LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return localeResolver;
}
}
在這段配置中,我們首先提供了一個(gè) SessionLocaleResolver 實(shí)例,這個(gè)實(shí)例會(huì)替換掉默認(rèn)的 AcceptHeaderLocaleResolver,不同于 AcceptHeaderLocaleResolver 通過請(qǐng)求頭來判斷當(dāng)前的環(huán)境信息,SessionLocaleResolver 將客戶端的 Locale 保存到 HttpSession 對(duì)象中,并且可以進(jìn)行修改(這意味著當(dāng)前環(huán)境信息,前端給瀏覽器發(fā)送一次即可記住,只要 session 有效,瀏覽器就不必再次告訴服務(wù)端當(dāng)前的環(huán)境信息)。
另外我們還配置了一個(gè)攔截器,這個(gè)攔截器會(huì)攔截請(qǐng)求中 key 為 lang 的參數(shù)(不配置的話是 locale),這個(gè)參數(shù)則指定了當(dāng)前的環(huán)境信息。
好了,配置完成后,啟動(dòng)項(xiàng)目,訪問方式如下:
我們通過在請(qǐng)求中添加 lang 來指定當(dāng)前環(huán)境信息。這個(gè)指定只需要一次即可,也就是說,在 session 不變的情況下,下次請(qǐng)求可以不必帶上 lang 參數(shù),服務(wù)端已經(jīng)知道當(dāng)前的環(huán)境信息了。
3.其他自定義
默認(rèn)情況下,我們的配置文件放在 resources 目錄下,如果大家想自定義,也是可以的,例如定義在 resources/i18n 目錄下:
但是這種定義方式系統(tǒng)就不知道去哪里加載配置文件了,此時(shí)還需要 application.properties 中進(jìn)行額外配置(注意這是一個(gè)相對(duì)路徑):
spring.messages.basename=i18n/messages
另外還有一些編碼格式的配置等,內(nèi)容如下:
spring.messages.cache-duration=3600
spring.messages.encoding=UTF-8
spring.messages.fallback-to-system-locale=true
spring.messages.cache-duration 表示 messages 文件的緩存失效時(shí)間,如果不配置則緩存一直有效。
spring.messages.fallback-to-system-locale 屬性則略顯神奇,網(wǎng)上竟然看不到一個(gè)明確的答案,后來翻了一會(huì)源碼才看出端倪。
這個(gè)屬性的作用在 org.springframework.context.support.AbstractResourceBasedMessageSource#getDefaultLocale 方法中生效:
protected Locale getDefaultLocale() {
if (this.defaultLocale != null) {
return this.defaultLocale;
}
if (this.fallbackToSystemLocale) {
return Locale.getDefault();
}
return null;
}
從這段代碼可以看出,在找不到當(dāng)前系統(tǒng)對(duì)應(yīng)的資源文件時(shí),如果該屬性為 true,則會(huì)默認(rèn)查找當(dāng)前系統(tǒng)對(duì)應(yīng)的資源文件,否則就返回 null,返回 null 之后,最終又會(huì)調(diào)用到系統(tǒng)默認(rèn)的 messages.properties 文件。
4.附錄
搜刮了一個(gè)語言簡(jiǎn)稱表,分享給各位小伙伴:
語言 | 簡(jiǎn)稱 |
簡(jiǎn)體中文(中國) | zh_CN |
繁體中文(中國臺(tái)灣) | zh_TW |
繁體中文(中國香港) | zh_HK |
英語(中國香港) | en_HK |
英語(美國) | en_US |
英語(英國) | en_GB |
英語(全球) | en_WW |
英語(加拿大) | en_CA |
英語(澳大利亞) | en_AU |
英語(愛爾蘭) | en_IE |
英語(芬蘭) | en_FI |
芬蘭語(芬蘭) | fi_FI |
英語(丹麥) | en_DK |
丹麥語(丹麥) | da_DK |
英語(以色列) | en_IL |
希伯來語(以色列) | he_IL |
英語(南非) | en_ZA |
英語(印度) | en_IN |
英語(挪威) | en_NO |
英語(新加坡) | en_SG |
英語(新西蘭) | en_NZ |
英語(印度尼西亞) | en_ID |
英語(菲律賓) | en_PH |
英語(泰國) | en_TH |
英語(馬來西亞) | en_MY |
英語(阿拉伯) | en_XA |
韓文(韓國) | ko_KR |
日語(日本) | ja_JP |
荷蘭語(荷蘭) | nl_NL |
荷蘭語(比利時(shí)) | nl_BE |
葡萄牙語(葡萄牙) | pt_PT |
葡萄牙語(巴西) | pt_BR |
法語(法國) | fr_FR |
法語(盧森堡) | fr_LU |
法語(瑞士) | fr_CH |
法語(比利時(shí)) | fr_BE |
法語(加拿大) | fr_CA |
西班牙語(拉丁美洲) | es_LA |
西班牙語(西班牙) | es_ES |
西班牙語(阿根廷) | es_AR |
西班牙語(美國) | es_US |
西班牙語(墨西哥) | es_MX |
西班牙語(哥倫比亞) | es_CO |
西班牙語(波多黎各) | es_PR |
德語(德國) | de_DE |
德語(奧地利) | de_AT |
德語(瑞士) | de_CH |
俄語(俄羅斯) | ru_RU |
意大利語(意大利) | it_IT |
希臘語(希臘) | el_GR |
挪威語(挪威) | no_NO |
匈牙利語(匈牙利) | hu_HU |
土耳其語(土耳其) | tr_TR |
捷克語(捷克共和國) | cs_CZ |
斯洛文尼亞語 | sl_SL |
波蘭語(波蘭) | pl_PL |
瑞典語(瑞典) | sv_SE |
西班牙語(智利) | es_CL |