乾貨 | 用JAVA實現多語言翻譯組件升級

不一樣領域對國際化的定義各不相同,這裏談論的是 W3C 國際化活動材料的高級工做定義。有些人使用其餘術語(例如全球化)來指代同一律念。java

國際化是產品、應用程序或文檔內容的設計和開發,它能夠爲不一樣文化、地區或語言的目標受衆輕鬆實現本地化。國際化(Internationalization)一般用英文寫成i18n,其中 18 是英文單詞中i和n之間的字母數。[1]web

本文主要基於java,針對語言國際化進行闡述。 任何一個面向全世界的軟件都會面臨多語言國際化的問題,對於java web應用,要實現國際化功能,就是在數據展現給用戶以前,替換成用戶可識別的語言。redis

使用spring自帶的i18n(國際化)

這部分比較簡單,在網上搜索就能夠找到教程,這裏將會手動配置一次spring i18n國際化來介紹一下。如下是筆者使用的spring boot版本號。spring

1.png

1-1.在properties或yml資源文件裏面配置i18n

筆者用的是yml文件,能夠自行轉換成properties文件格式。
basename:以逗號分隔的基名列表(本質上是一個徹底限定的類路徑位置),每一個基名都遵循 ResourceBundle 約定,對基於斜槓的位置提供寬鬆的支持。若是它不包含包限定符(例如 「org.mypackage」 ),它將從類路徑根目錄解析。數據庫

cache-duration:加載的資源包文件緩存的持續時間。若是沒有設置,捆綁包將被永久緩存。若是沒有指定持續時間後綴,將使用秒。緩存

2.png

1-2.在resources文件夾下新增i18n文件夾,並新建相應的國際化文件

spring容器啓動的時候,會根據配置的basename去對應的路徑加載資源文件到MessageSource裏,至因而怎麼加載到MessageSource裏的,在這裏就不展開闡述了。markdown

文件配置如圖 1-1所示。
注意:紅色框裏的就是i18n資源文件的配置,綠色框是idea自動生成的文件夾,實際並不存在,無視就行。ide

3.png
圖 1-1 i18n資源文件oop

4.png

5.png

6.png

1-3.在代碼中使用i18n進行國際化

這裏演示的是比較簡單的手動參數替換,還有更好一些的方法,好比說在響應數據寫入流的時候進行參數替換。佈局

7.png

1-4.測試spring i18n

啓動項目後,查看調用/test/console 接口返回的數據,調用三次,分別設置header中的語言:默認、中文、英文。
筆者用的是idea的HTTP Client,如下是請求參數: 8.png如下是響應參數:

10.png

1-5.分析執行結果

1-5-1. 測試默認和測試中文的響應數據是同樣的,能夠肯定系統默認使用的中文環境。
1-5-2. 調用 getMessage() 方法時,不傳第2個參數就是無參替換;不然,反之。
1-5-3. 使用有參替換時,還能夠在 properties 文件里加入 date、time 等參數,spring 能夠自動格式化成對應的日期和時間。

1-6.結論

這裏只是簡單的演示了spring i18n的功能,能夠知足一些簡單場景的需求,若是須要進行擴展的話,有幾種思路。
1-6-1. 若是使用了 nacos 等配置中心,則須要去註冊中心手動拉取i18n的 properties 文件內容,並加載到應用程序的內存裏,也能夠在本地用戶文件夾存放一份。
1-6-2. 若是須要一些正則翻譯的話,則須要本身動手寫正則替換的表達式。
1-6-3. 該例子展現的是在controller裏進行替換,更好一點的方式是在filter,甚至是在響應數據寫入流的時候進行替換,好比說指定某個響應對象的某個屬性的序列化類(@JsonSerialize(using = TestJsonSerializer.class)),則該字段序列化的時候,就會使用TestJsonSerializer.class進行序列化。在這個類裏面就能夠針對性地作咱們想要的替換了。

自定義i18n

spring i18n的功能較爲好用,可是面對複雜的業務需求,還不夠強大。好比,用戶想添加一種語言;遞歸替換;佈局能夠自定義,用戶添加布局字段時,針對該項目或組織的不一樣地區的人員,設置不一樣的翻譯內容等等。

基於各類各樣的緣由,擴展i18n已經是必需要作的事。那麼怎麼擴展呢?

國際化的本質就是將 key 替換成不一樣語言的 value ,這句話中有幾個關鍵點:key、替換、語言、value。 其中 key 、語言、value 都是名詞,表明着具體的數據;替換是動詞,表明具體的翻譯邏輯。那咱們就須要針對這幾個點進行設計與實現。

2-1.設計數據表

思路:經過一種語言和鍵找到對應的值。表結構設計比較簡單,key、value、語言各建一個表,如圖 2-1所示。

Snipaste_2021-07-16_14-53-19.png

圖 2-1 國際化表結構設計

其中每一個表只展現了主鍵編號字段,其實還有一些字段沒展現出來,好比 code、name,這些能夠根據本身的風格去設計。若是是多租戶的系統,在每張表後面加入對應的租戶id,便可進行數據隔離。
2-1-1. 若是用戶新增的字段須要翻譯,往語言鍵裏增長一條數據,以及往語言值裏增長與語言定義相同數量的記錄便可。
2-1-2. 若是用戶新增語言定義,則往語言值裏面增長與語言鍵相同數量的記錄便可。
2-1-3. 更新、刪除同理。

2-2.數據緩存設計

在一個面向世界的應用裏面,翻譯的頻率是很高的,並且隨着時間的流逝,翻譯的數據確定會愈來愈多,若是每次響應數據的翻譯都去查詢數據庫的話,那勢必會形成數據庫性能以及應用自己性能的浪費。對於這種修改頻率不算高的數據,我們能夠緩存起來,用空間換時間。

這裏打算用兩級緩存的設計來適應該翻譯場景,一級是redis,二級是應用內存。

step1: 將用到的數據從數據庫緩存在redis裏面,而且生成一個更新標誌放入redis。應用獲取翻譯數據的時候先判斷redis更新標誌是否爲空。
step2: 若爲空,則表明redis還沒有緩存翻譯數據,將翻譯數據從數據庫拉取到內存,且推送到redis;若不爲空,則表明redis已緩存翻譯數據,而後再比對redis的更新標誌和應用內存的更新標誌是否一致。
step3: 若不一致,則說明翻譯數據已經改變,須要從redis從新拉取一次翻譯數據,緩存在應用內存中;若一致,則說明翻譯數據還沒有改變,能夠直接使用應用內存中的翻譯數據。
step4: 將最後拿到的翻譯數據(key-value)返回給實現翻譯邏輯的組件。

如圖 2-2所示。

9.png 圖 2-2 國際化兩級緩存設計
代碼以下所示。 11.png

12.png

13.png

14.png

15.png

2-3.將替換邏輯嵌入spring的filter或者序列化

筆者在這裏只演示簡單的key->value替換,至於遞歸替換、正則替換能夠自行考慮加上。
A. 當一個請求進來的時候,首先須要作一些前置處理。
B.根據請求的語言設置當前線程的語言環境。
C.更新一次當前應用內存的語言緩存數據。
D.當返回響應的時候,經過序列化對響應數據進行替換。
代碼以下所示。

16.png

17.png

18.png

19.png

20.png

21.png

2-4.測試自定義i18n

啓動項目後,查看調用 test/custom-i18n 接口返回的數據,調用三次,分別設置header中的語言:默認、中文、英文。
筆者用的是idea的HTTP Client,如下是請求參數:

22.png

如下是返回參數:

23.png

2-5.分析執行結果

2-5-1.測試默認和測試中文的響應數據是同樣的,能夠肯定系統默認使用的中文環境。
2-5-2.對於使用了@JsonSerialize(using = I18nJsonSerializer.class)註解的屬性,會根據key自動替換成對應的值。
2-5-3.根據key沒找到值時,仍是會使用本來的key。

2-6.結論

這裏只是簡單的演示了自定義i18n的功能,可是已然支持用戶新增語言、自定義翻譯後的值、多機部署等。若是想要支持正則替換、遞歸翻譯也能夠自行擴展。

總結

這裏演示了兩種i18n的實現方案,具體想用哪一種就見仁見智了。圖方便,開箱即用,那就選spring i18n;圖靈活,可擴展性強,那就選自定義i18n。天然,確定還有不少我沒想到的方案,期待交流。

後續LigaAI會繼續分享更多技術乾貨的文章,歡迎關注 Liga@juejin~更多詳情,請點擊咱們的官方網站 LigaAI-智能研發管理平臺

[1] 「Localization vs. Internationalization」.W3C
做者:rookie0peng
原文連接:www.jianshu.com/p/95e0f1df8… 本博客全部文章除特別聲明外,均採用 CC BY-SA 4.0 協議 ,轉載請註明出處!

相關文章
相關標籤/搜索