1、背景
公司業務遍及全球各地,對應業務系統國際化就是順理成章的事情。最近就接手了一批新老系統的國際化任務,這裡把一些探索經驗、案例記錄下來。本身改造和探索過程包括.NET MVC的,以及.NET CORE WEB API的,但這裡舊版MVC的就不描述了,重點介紹netcore下的國際化方案。國際化重點在於多語言支援,以及多時區支援,本文就從這兩個方面入手。
預設:有一個前後端分離的系統,前端由i18n負責多語言支援,後端不渲染檢視,提供api返回資料給前端。
Demo解決方案截圖:
2、多語言
如上解決方案截圖,Common.Resource是多語言資源工程,ExceptionHandlerTest是示例web api專案,Service是api專案依賴的服務工程。之所以這麼設計場景,是為了探索資原始檔放在單獨工程下,以及非Web Api工程中的多語言方案,這點在官方教程中基本是沒有的。
先來看demo要乾的事情:HomeController中有個SayHello方法,此方法呼叫HomeService中的SayHello方法返回歡迎語資訊,我們要做的就是對HomeService中返回的歡迎語進行語言協商。下邊來看看具體怎麼實現:
2.1、定義多語言資原始檔
以支援中英文為例,定義如下圖資原始檔,步驟與FX下的很類似。
唯一的重大區別,是如果你希望在單獨工程中放置資源配置,那就新增一個單獨類程式碼檔案,假如你的資源是Common.en.rex,那對應類就應該是Common,這點在跨程式集尋找資原始檔中至關重要,官網文件中可沒有描述這至關重要的一點,別問我怎麼知道的, 問就是看core底層原始碼。。。
資原始檔中定義的資源配置項如下:
2.2、配置多語言服務及中介軟體
1)註冊本地化服務及HomeService服務
HomeService必須使用容器解析,否則core底層沒法注入多語言基礎服務到我們的元件,那你就只能手動傳入。
2)註冊本地化中介軟體
2.3、系統中引入多語言設定項
1)HomeService中注入IStringLocalizer服務
2)SayHello方法引用多語言配置項
2.4、實際效果
1)預設訪問
不做任何設定,系統也無設定對應cookie情況下,netcore直接取瀏覽器語言環境設定,就是下圖這個地方:
假如我們將瀏覽器語言環境改成英文,那預設情況下系統就會選取英文了。
2)通過查詢字串切換語言
如上圖,我們使用netcore規定的culture=en格式向後端傳遞語言環境資訊。具體語言環境選擇優先順序是這樣的:查詢字串 > cookie > 瀏覽器語言環境設定,這在官網有詳細介紹,看底層原始碼也證實了這個。基於cookie選取語言環境時候,cookie名稱是可以修改的,我實際專案就是如此,官網文件也有介紹,這裡不做贅述。
3、多時區
3.1、場景預設
預設1:HomeController中有兩個方法,GetTime返回服務端或資料庫中儲存的UTC時間,系統根據客戶本地時區自動轉換成其對應時間;SetTime方法接收客戶本地時區下的時間,轉換成UTC時間存入伺服器或資料庫
預設2:系統支援中國東八區時間及印度東5區時間
3.2、自定義時間轉換器
/// <summary> /// 日期轉換 /// </summary> public class DateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset> { private TimeZoneInfo chinaZoneInfo = TimeZoneInfo.CreateCustomTimeZone("zh", TimeSpan.FromHours(8), "中國時區", "China time zone"); private TimeZoneInfo indiaZoneInfo = TimeZoneInfo.CreateCustomTimeZone("en-IN", TimeSpan.FromHours(5), "印度時區", "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")); } }
如上所述,自定義時間序列化轉換器,讀取時間時,根據客戶語言環境匹配其對應時區,時區中有對應UTC偏離時間資訊,據此轉換成UTC時間;序列化寫入時候,同樣根據語言環境匹配時區資訊,將伺服器端的UTC時間按照時區偏離轉換成本地時間返給客戶端。
3.3、時間轉換測試
1)獲取伺服器時間
其中currentTime是模擬伺服器上或資料庫中取出來的UTC時間,然後什麼不做直接返回,具體時間轉換交由時間轉換器負責。下邊看效果:
中文環境時間:
可以看到,原始UTC時間2019-07-15 08:30:00在中國東八區8個小時偏離下,返給客戶端變成了16:30:00,即中國本地時間;
英文環境:
當語言環境切換為英文,則匹配到印度東5區時區資訊,UTC時間2019-07-15 08:30:00轉換成印度本地時間2019-07-15 13:30:00。
2)寫入時間到伺服器
同樣的,接收到客戶端時間後,我們業務程式碼層不做任何設定,交由時間轉換器去負責,具體看效果:
中文環境:
傳入本地時間2019-07-15 16:30:00,到了伺服器,時間如下:
可以看到,中國東八區時間2019-07-15 16:30:00在伺服器上轉換成UTC時間2019-07-15 08:30:00;
同樣的本地時間,但語言環境為英語:
可以看到,印度東5區的本地時間2019-07-15 16:30:00到伺服器,轉換成UTC時間2019-07-15 11:30:00。
4、總結
系統國際化的重點,在於語言環境國際化,以及多時區自適應,解決這兩點,剩下就不是啥問題了。關於時區,這裡是以伺服器及資料庫中統一儲存UTC時間為例,但也有一定麻煩,比如你需要後臺維護資料,尤其是直接在資料庫中維護這種,就需要做本地時間和UTC時間的手動處理,除非你是英國人,身處英國,用英國的時區。針對這點可以做對應發散,例如假如系統中文使用者佔多數,運維也主要是中國員工,那就可以採取伺服器或資料庫統一儲存中國東8區的時間,其他本地時間向中國時間進行轉換的做法,思路、解決方案是一致的。