【SpringBoot基礎系列】手把手實現國際化支持實例開發
國際化的支持,對於app開發的小夥伴來講應該比價常見了;做爲java後端的小夥伴,通常來說接觸國際化的機會不太多,畢竟業務開展到海外的企業並無太多html
SpringBoot提供了國際化的支持,網上也有相關的教程,然而實際體驗的時候,發現並無預期的那麼順利;本文將介紹一下SpringBoot如何支持國家化,以及在支持的過程當中,一些注意事項java
<!-- more -->git
本項目藉助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
進行開發github
開一個web服務用於測試web
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies>
配置文件中,指定國際化的參數,thmeleaf的配置信息spring
application.ymljson
spring: messages: basename: i18n/messages/messages encoding: UTF-8 fallbackToSystemLocale: false thymeleaf: mode: HTML encoding: UTF-8 servlet: content-type: text/html cache: false
上面的配置 spring.messages.basename
指定國際化配置文件的目錄與前綴,取值爲i18n/messages/messages
後端
因此在資源目錄下,新建文件 i18n/messages
,國際化文件名爲 messages-xxx.properties
,項目結果如緩存
對應的信息如簡體中文 messages_zh_CN.properties
cookie
200=成功 500=內部異常 name=用戶名 pwd=密碼
英文 messages_en_US.properties
200=success 500=unexpected exception name=user name pwd=password
繁體 messages_zh_TW.properties
200=成功 500=內部異常 name=用戶名 pwd=密碼
說明
注意spring.messages.basename
這個配置的取值爲國際化文件的目錄 + 文件名前綴
,好比上面若少了最後一層的messages
,會提示取不到配置
其次在IDEA中,選中國家化文件以後,點擊下方的Resource Bundle
,能夠進入如上圖中更友好的編輯框,支持一次修改多個語言的信息
前面是國際化的基本配置,那麼如何根據前面配置中的key,獲取不一樣語言的value呢?
在SpringBoot中主要藉助MessageSource
來獲取不一樣語言的value信息
如一個最基本的封裝
public class MsgUtil { private static MessageSource messageSource; public static void inti(MessageSource messageSource) { MsgUtil.messageSource = messageSource; } /** * 獲取單個國際化翻譯值 */ public static String get(String msgKey) { try { return messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale()); } catch (Exception e) { return msgKey; } } }
接下來寫一個基礎的測試demo,根據傳參來修改LocalContextHolder
中的值,從而實現不一樣語言的切換
@Controller @SpringBootApplication public class Application { public Application(MessageSource messageSource) { MsgUtil.inti(messageSource); } public static void main(String[] args) { SpringApplication.run(Application.class); } @Data @Accessors(chain = true) public static class RspWrapper<T> { private int code; private String msg; private T data; } @GetMapping(path = "change") @ResponseBody public String changeLocal(String language) { String[] s = language.split("_"); LocaleContextHolder.setLocale(new Locale(s[0], s[1])); RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true); return JSON.toJSONString(res); } }
演示以下
上面雖然能夠根據請求參數來切換語言,可是有個問題,若是在子線程中進行國際化支持,則會不生效
@GetMapping(path = "change2") @ResponseBody public String changeLocal(String language) { String[] s = language.split("_"); LocaleContextHolder.setLocale(new Locale(s[0], s[1])); RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true); return JSON.toJSONString(res); }
以下圖,即使修改了language,返回都是默認的中文
針對這種解決辦法是在設置Locale時,指定第二個可繼承參數爲true
@GetMapping(path = "change3") @ResponseBody public String changeLocal(String language) { String[] s = language.split("_"); LocaleContextHolder.setLocale(new Locale(s[0], s[1])); RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(true); return JSON.toJSONString(res); }
上面雖然說支持了根據傳參來設置國際化,可是須要每次傳參都帶上這個參數language=zh_CN
,還須要咱們本身來解析這個請求參數,咱們能夠考慮藉助攔截器來實現統一的Local設置
這個攔截器能夠本身按照上面的方式寫,固然更推薦的是直接使用已封裝好的
@Configuration public class AutoConfig implements WebMvcConfigurer { /** * 這個若是不存在,則會拋異常: nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy * * @return */ @Bean public LocaleResolver localeResolver() { // 也能夠換成 SessionLocalResolver, 區別在於國際化的應用範圍 CookieLocaleResolver localeResolver = new CookieLocaleResolver(); localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE); return localeResolver; } /** * 根據請求參數,來設置本地化 * * @return */ @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); // Defaults to "locale" if not set localeChangeInterceptor.setParamName("language"); return localeChangeInterceptor; } @Override public void addInterceptors(InterceptorRegistry interceptorRegistry) { interceptorRegistry.addInterceptor(localeChangeInterceptor()); } }
請注意上面的 localResolver
, 當咱們不註冊這個bean的時候,運行則會拋出異常nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution
上面的實例中,採用的是CookieLocaleResolver
,所以會在cookie中緩存語言信息,一次修改,後續都會生效
測試以下
@GetMapping(path = "say") @ResponseBody public String say(String name) { RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(MsgUtil.get("name") + ":" + name); return JSON.toJSONString(res); } @GetMapping(path = "say2") @ResponseBody public String say2(String name) { RspWrapper res = new RspWrapper<>().setCode(200).setMsg(MsgUtil.get("200")).setData(MsgUtil.get("name") + ":" + name); return JSON.toJSONString(res); }
主要一個地方設置了語言,後續的訪問不帶語言參數時,都會複用以前設置的語言,這樣使用來講就更簡潔了
上面介紹的是返回的json串支持國際化,另一個場景就是咱們返回的頁面,但願渲染的數據也能夠實現國際化支持
在上文的基礎上實現這個也沒什麼難度了
在資源目錄下,新建目錄templates
,新建模板文件 index.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="author" content="YiHui"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>一灰灰blog 國際化測試頁面</title> </head> <body> <div> <div class="title">hello world!</div> <br/> <div class="content" th:text="'name: ' + ${name}">默認用戶名</div> <br/> <div class="sign" th:text="'pwd: ' + ${pwd}">默認密碼</div> <br/> </div> </body> </html>
對應的controller
@GetMapping(path = {"", "/", "/index"}) public String index(Model model) { model.addAttribute("name", MsgUtil.get("name")); model.addAttribute("pwd", MsgUtil.get("pwd")); return "index"; }
雖然說上面這樣實現了國家化的支持,可是看起來不太優雅,難道還須要後端接口進行轉義一下麼,沒有更簡單的方式麼?
Themeleaf提供了更簡單的支持方式,將上面的$改爲#便可
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="author" content="YiHui"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>一灰灰blog 國際化測試頁面</title> </head> <body> <div> <div class="title">hello world!</div> <br/> <div class="content" th:text="'name: ' + #{name}">默認用戶名</div> <br/> <div class="sign" th:text="'pwd: ' + #{pwd}">默認密碼</div> <br/> <div class="content" th:text="'200: ' + #{200}">200</div> <br/> <div class="content" th:text="'500: ' + #{500}">500</div> </div> </body> </html>
對應的rest
@GetMapping(path = "show") public String show() { return "show"; }
在實現國際化的過程當中,遇到了下面幾個問題,特此記錄一下
在使用messageSource.getMessage(msgKey, null, LocaleContextHolder.getLocale())
查詢配置信息,結果提示org.springframework.context.NoSuchMessageException: No message found under code '200' for locale 'en_US'.
出現上面這個問題,固然優先判斷是否真的配置了這個參數,其次確認spring.messages.basename
是否準確,對應的value爲目錄 + 語言的前綴
i18n/messages/messages_en_US.properties
, 那麼這個value就應該是 i18n/messages/messages
spring.messages.encoding=utf-8
若是發現上面這個設置了依然沒有生效,那麼考慮一下配置文件是否爲utf-8編碼
須要添加本地化的攔截器LocaleChangeInterceptor
,來實現根據請求參數,解析語言環境
其次須要註冊LocaleResolver
,好比demo中使用CookieLocaleResolver
,來保存國際化信息 (若是不設置它會拋異常)
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛