.net core國際化
1、背景
公司業(yè)務遍及全球各地,對應業(yè)務系統(tǒng)國際化就是順理成章的事情。最近就接手了一批新老系統(tǒng)的國際化任務,這里把一些探索經(jīng)驗、案例記錄下來。本身改造和探索過程包括.NET MVC的,以及.NET CORE WEB API的,但這里舊版MVC的就不描述了,重點介紹netcore下的國際化方案。國際化重點在于多語言支持,以及多時區(qū)支持,本文就從這兩個方面入手。
預設:有一個前后端分離的系統(tǒng),前端由i18n負責多語言支持,后端不渲染視圖,提供api返回數(shù)據(jù)給前端。
Demo解決方案截圖:

?
?
?
2、多語言
如上解決方案截圖,Common.Resource是多語言資源工程,ExceptionHandlerTest是示例web api項目,Service是api項目依賴的服務工程。之所以這么設計場景,是為了探索資源文件放在單獨工程下,以及非Web Api工程中的多語言方案,這點在官方教程中基本是沒有的。
先來看demo要干的事情:HomeController中有個SayHello方法,此方法調(diào)用HomeService中的SayHello方法返回歡迎語信息,我們要做的就是對HomeService中返回的歡迎語進行語言協(xié)商。下邊來看看具體怎么實現(xiàn):
?
2.1、定義多語言資源文件
以支持中英文為例,定義如下圖資源文件,步驟與FX下的很類似。

?
?
唯一的重大區(qū)別,是如果你希望在單獨工程中放置資源配置,那就添加一個單獨類代碼文件,假如你的資源是Common.en.rex,那對應類就應該是Common,這點在跨程序集尋找資源文件中至關重要,官網(wǎng)文檔中可沒有描述這至關重要的一點,別問我怎么知道的, 問就是看core底層源碼。。。
資源文件中定義的資源配置項如下:

?
?
?
?
?
?
?
2.2、配置多語言服務及中間件
1)注冊本地化服務及HomeService服務

? HomeService必須使用容器解析,否則core底層沒法注入多語言基礎服務到我們的組件,那你就只能手動傳入。
2)注冊本地化中間件

?
2.3、系統(tǒng)中引入多語言設置項
1)HomeService中注入IStringLocalizer服務
?
?2)SayHello方法引用多語言配置項
?
2.4、實際效果
1)默認訪問

?
?
?不做任何設置,系統(tǒng)也無設置對應cookie情況下,netcore直接取瀏覽器語言環(huán)境設置,就是下圖這個地方:

?
假如我們將瀏覽器語言環(huán)境改成英文,那默認情況下系統(tǒng)就會選取英文了。
2)通過查詢字符串切換語言

?
?
? 如上圖,我們使用netcore規(guī)定的culture=en格式向后端傳遞語言環(huán)境信息。具體語言環(huán)境選擇優(yōu)先級是這樣的:查詢字符串? >? cookie ?>? 瀏覽器語言環(huán)境設置,這在官網(wǎng)有詳細介紹,看底層源碼也證實了這個?;赾ookie選取語言環(huán)境時候,cookie名稱是可以修改的,我實際項目就是如此,官網(wǎng)文檔也有介紹,這里不做贅述。
?
3、多時區(qū)
3.1、場景預設
預設1:HomeController中有兩個方法,GetTime返回服務端或數(shù)據(jù)庫中存儲的UTC時間,系統(tǒng)根據(jù)客戶本地時區(qū)自動轉換成其對應時間;SetTime方法接收客戶本地時區(qū)下的時間,轉換成UTC時間存入服務器或數(shù)據(jù)庫
預設2:系統(tǒng)支持中國東八區(qū)時間及印度東5區(qū)時間
3.2、自定義時間轉換器

///
/// 日期轉換
///
public class DateTimeOffsetJsonConverter : JsonConverter
{
private TimeZoneInfo chinaZoneInfo = TimeZoneInfo.CreateCustomTimeZone("zh", TimeSpan.FromHours(8), "中國時區(qū)", "China time zone");
private TimeZoneInfo indiaZoneInfo = TimeZoneInfo.CreateCustomTimeZone("en-IN", TimeSpan.FromHours(5), "印度時區(qū)", "India time zone");
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var currentZoneInfo = Thread.CurrentThread.CurrentCulture.Name.Contains("zh") ? chinaZoneInfo : indiaZoneInfo;
//var time1 = DateTimeOffset.Parse(reader.GetString());
//var time2 = time1.ToOffset(currentZoneInfo.BaseUtcOffset);
var time1 = new DateTimeOffset(DateTime.Parse(reader.GetString()), currentZoneInfo.BaseUtcOffset);
var time2 = time1.ToUniversalTime();
//var time3 = time2.ToUniversalTime();
return time2;
}
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
var currentZoneInfo = Thread.CurrentThread.CurrentCulture.Name.Contains("zh") ? chinaZoneInfo : indiaZoneInfo;
writer.WriteStringValue(value.ToOffset(currentZoneInfo.BaseUtcOffset).ToString("yyyy-MM-dd HH:mm:ss"));
}
}

?
如上所述,自定義時間序列化轉換器,讀取時間時,根據(jù)客戶語言環(huán)境匹配其對應時區(qū),時區(qū)中有對應UTC偏離時間信息,據(jù)此轉換成UTC時間;序列化寫入時候,同樣根據(jù)語言環(huán)境匹配時區(qū)信息,將服務器端的UTC時間按照時區(qū)偏離轉換成本地時間返給客戶端。
3.3、時間轉換測試
1)獲取服務器時間

?
?
?
?
?
? 其中currentTime是模擬服務器上或數(shù)據(jù)庫中取出來的UTC時間,然后什么不做直接返回,具體時間轉換交由時間轉換器負責。下邊看效果:
中文環(huán)境時間:

?
?
? 可以看到,原始UTC時間2019-07-15 08:30:00在中國東八區(qū)8個小時偏離下,返給客戶端變成了16:30:00,即中國本地時間;
英文環(huán)境:

?
?
? 當語言環(huán)境切換為英文,則匹配到印度東5區(qū)時區(qū)信息,UTC時間2019-07-15 08:30:00轉換成印度本地時間2019-07-15 13:30:00。
?
2)寫入時間到服務器

?

?
? 同樣的,接收到客戶端時間后,我們業(yè)務代碼層不做任何設置,交由時間轉換器去負責,具體看效果:
中文環(huán)境:

?
? 傳入本地時間2019-07-15 16:30:00,到了服務器,時間如下:

?
? 可以看到,中國東八區(qū)時間2019-07-15 16:30:00在服務器上轉換成UTC時間2019-07-15 08:30:00;
同樣的本地時間,但語言環(huán)境為英語:

?
?
?
?
? 可以看到,印度東5區(qū)的本地時間2019-07-15 16:30:00到服務器,轉換成UTC時間2019-07-15 11:30:00。
4、總結
系統(tǒng)國際化的重點,在于語言環(huán)境國際化,以及多時區(qū)自適應,解決這兩點,剩下就不是啥問題了。關于時區(qū),這里是以服務器及數(shù)據(jù)庫中統(tǒng)一保存UTC時間為例,但也有一定麻煩,比如你需要后臺維護數(shù)據(jù),尤其是直接在數(shù)據(jù)庫中維護這種,就需要做本地時間和UTC時間的手動處理,除非你是英國人,身處英國,用英國的時區(qū)。針對這點可以做對應發(fā)散,例如假如系統(tǒng)中文用戶占多數(shù),運維也主要是中國員工,那就可以采取服務器或數(shù)據(jù)庫統(tǒng)一存儲中國東8區(qū)的時間,其他本地時間向中國時間進行轉換的做法,思路、解決方案是一致的。
