Locale是平常開發中比較容易忽視的技術點。特別是開發一些只作國內市場,只有中文的項目時,Locale可能就直接被忽視了。並且在項目提出多語言支持的時候,由於沒有很好的理解,可能給本身埋了不少坑。javascript
其實java.util.Locale
的Java Doc有很詳細的解釋,我就不過多解釋。html
A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.
主要記住Locale實例包括如下信息就能夠了。在多年的開發經驗中,script和variant基本沒有用到。就不過多介紹。前端
ISO 639 alpha-2 or alpha-3 language code
在實際使用中,基本咱們碰不到3位字母表示的語言。java
ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code.
一樣實際使用中,基本使用2位字母表示的國家react
IANA Language Subtag Registry 定義了完整列表。
感受script是地區的別稱,variant是方言。瞭解一下就好。後端
在實際使用中,大部分同窗第一反應多是會寫出如下Locale。api
若是你也是隻想到這些,請打開你的瀏覽器->開發者工具->控制檯中輸入如下js瀏覽器
window.navigator.language
瀏覽器爲中文,你又在國內。輸出結果就是zh-CN
而後若是從新你設置瀏覽器語言,好比設置成英文。再執行一下,輸出結果是en-CN
What?? en-CN?? 這是什麼鬼? 首先經過這個Locale,咱們能夠知道country,是和你實際在哪一個地區有關。
那該怎麼處理? 後面詳細說怎麼應用。前端框架
Locale的使用場景基本就是根據不一樣國家和語言,進行不一樣的顯示。實際經驗如下2點爲主。oracle
以金額顯示爲例,若是沒有相似開發經驗的話,你能夠爲會想,金額還有不一樣的格式。通常不都是 1,000,000.00。那你就錯了。舉兩個例子。
正確理解Locale,並正確使用能夠寫出既規範,又簡練,質量又高的代碼。而不是見招拆招,每一個語言寫一個本身的實現。
使用正確的姿式建立很是重要,這在後面Spring裏應用部分很是重要。
如下這段代碼是我見過最多的建立方式。
Locale locale = new Locale("zh_CN");
其中zh_CN多是前端直接傳入,爲了方便直接做爲Locale構造方法參數。其實這是一個錯誤的用法。這樣的使用,建立出來的Locale.language就是zh_cn。
如下先列舉一下兩種正確姿式。而後比較一下結果
// 使用Locale構造方法 // 若是前端傳入"zh_CN",此處須要自行解析並拆分 Locale locale = new Locale("zh", "CN"); // 使用Locale預置常量。請自行查看Locale源代碼。 Locale locale = Locale.SIMPLIFIED_CHINESE
LocaleUtils.toLocale()
Locale locale = LocaleUtils.toLocale("zh_CN");
以上三種建立方式,能夠建立出同樣的Locale object。
本人推薦使用Commons Lang3 LocaleUtils.toLocale()
下面咱們的來對比一下錯誤和正解方式建立的Locale有什麼區別。
public class LocaleShowCase { public static void main(String[] args) { logLocale(new Locale("zh_CN")); logLocale(Locale.SIMPLIFIED_CHINESE); } private static void logLocale(Locale locale) { System.out.println("================================="); System.out.println(String.format("Locale.toString: %s", locale.toString())); System.out.println(String.format("Language: %s", locale.getLanguage())); System.out.println(String.format("Country: %s", locale.getCountry())); System.out.println(String.format("LanguageTag: %s", locale.toLanguageTag())); System.out.println("================================="); } }
輸出結果
================================= Locale.toString: zh_cn Language: zh_cn Country: LanguageTag: und ================================= ================================= Locale.toString: zh_CN Language: zh Country: CN LanguageTag: zh-CN =================================
讓咱們來分析一下結果
何時使用"_",何時使用"-",確實比較搞。
好比request.getLocale()中解析Locale時,能夠同時處理兩種格式。而Commons Lang3 LocaleUtils.toLocale()的入參只支持下劃線格式。
不過咱們能夠定義這樣的規範,在後端服務中只使用"_"格式,而前端只使用"-"格式。
前端框架太多,就只說一下最近在玩的umi+dva+react。
umi開發的項目中使用umi-plugin-react/locale
來處理Locale。
import { setLocale, getLocale } from 'umi-plugin-react/locale'; setLocale(language, true); getLocale();
資源文件使用"-"格式命名。
. |-- en-US | |-- common.ts | `-- form.ts |-- ja-JP | |-- common.ts | `-- form.ts |-- zh-CN | |-- common.ts | `-- form.ts |-- en-US.ts |-- ja-JP.ts `-- zh-CN.ts
import { formatMessage } from 'umi-plugin-react/locale'; formatMessage({id: 'xxx'})
import { formatDate } from 'umi-plugin-react/locale'; formatDate(new Date());
formatNumber(10000000.00);
這裏只介紹基於Spring Boot開發的Stateless Rest API。SpringMVC已通過時,就不作介紹。
Spring Boot使用LocaleResolver來肯定當前API調用使用什麼Locale。在LocaleResolver獲取Locale以後,將Locale存入LocaleContextHolder中。
Spring Boot提供了幾個標準實現,主要區別是針對Locale存放的地方不同提供對應獲取方式
雖然Spring已經提供了多種獲取LocaleResolver實現,可是在具體業務場景中會有更復雜的場景。好比須要根據當前登陸用戶的語言設置。這個時間就須要咱們本身實現一套LocaleResovler。
在Spring中,咱們能夠添加properties文件來作多語言支持。
. |-- java .... `-- resources `-- i18n |-- messages.properties |-- messages_ja.properties |-- messages_ja_JP.properties |-- messages_xx.properties |-- messages_zh.properties |-- messages_zh_CN.properties |-- another.properties |-- another_zh_CN.properties |-- another_zh_TW.properties |-- another_en.properties `-- another_ja.properties
能夠看到在例子
language+country+variant > language+country > lanaguage
以message*.properties爲例:
MessageSource
@Configuration public class MessageConfiguration { @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setDefaultEncoding("UTF-8"); messageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/another"); return messageSource; } }
這裏注意messageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/another")
BaseName就是資源文件的組名。
@Service public class XXXService { private final MessageSource messageSource; public XXXService(MessageSource messageSource) { this.messageSource = messageSource; } public String getI18N(String key, Object[] params) { return messageSource.getMessage(key, params, LocaleContextHolder.getLocale()) } }
DateFormat fullDF = DateFormat.getDateInstance(DateFormat.FULL, locale); System.out.println(fullDF.format(new Date()));
System.out.println(NumberFormat.getInstance(locale).format(10000000));
通常產品基本須要用戶登陸,在LocaleResovler中也提到。咱們能夠根據當前用戶的語言設置做爲使用Locale。這樣比較好控制服務接收到的Locale。並且咱們在開發時能夠定義好系統支持的語言,好比支持zh_CN, en_US, ja_JP。這樣在用戶登陸後的API調用就不用擔憂接收到不支持的Locale。而由於須要使用用戶設置語言,咱們須要本身實現一個LocaleResovler。
@Data public class Principal { private String username; private String language; ... } public class CustomLocaleResolver implements LocaleResolver { private Locale defaultLocale; public CustomLocaleResolver(Locale defaultLocale) { this.defaultLocale = defaultLocale; } public Locale resolveLocale(HttpServletRequest request) { Principal principal = (Principal) SecurityContextHolder.getContext().getAuthentication(); if (principal != null && !StringUtils.isEmpty(principal.getLanguage())) { return LocaleUtils.toLocale(principal.getLanguage()); } else { return request.getHeader("Accept-Language") != null ? request.getLocale() : this.defaultLocale; } } public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { throw new UnsupportedOperationException("Cannot change Principal data - use a different locale resolution strategy"); } } @Configuration public class LocaleConfiguration { @Bean public CustomLocaleResolver localeResolver(@Value("${default-language:zh_CN}") String defaultLanguage) { return new CustomLocaleResolver(LocaleUtils.toLocale(defaultLanguage)); } }
而用戶沒有登陸以前,而前端不作任何處理時,後端會接收到相似en_CN的Locale,而沒法匹配資源文件。若是按如下資源文件設計,給每一個語言設置一個默認翻譯,則能夠解決接收到不規則Locale問題。
. |-- java .... `-- resources `-- i18n |-- messages.properties |-- messages_ja.properties |-- messages_ja_JP.properties |-- messages_en.properties |-- messages_en_US.properties |-- messages_en_GB.properties |-- messages_zh.properties |-- messages_zh_CN.properties `-- messages_zh_TW.properties
這樣的編排方式,無論你在什麼國家。對於中文,英文,日文都有一個默認的匹配。