在國際化環境下,愈來愈多的程序須要作多語言版本,以適應各類業務需求的變化。在Winform應用程序中實現多語言也有常規的處理方式處理,不過須要針對每一個語言版本,從新修改Winform界面的顯示,對一些常規的輔助類,也須要引入一個統一的資源管理類來處理多語言的問題,相對比較繁瑣。本篇隨筆針對多語言的需求,但願儘可能避免繁瑣的操做,既能符合本地語種開發人員的開發習慣,又能快速實現Winform程序的多語言場景處理。html
在常規的多語言版本程序中,開發老是伴隨着不少不愉快的事情,大概列舉一些僅供參考:web
1)對窗體的多語言處理時,維護多個語言版本的界面很是繁瑣;json
2)多語言處理的時候,以資源參照的時候,默認鍵值爲一些英文字符串或者單詞,不太符合如中文語境的開發,調整代碼則須要不少工做量;api
3)對於已開發好的程序,全面引入多語言的處理代碼,須要大量修改;app
4)對於大量中文的多語言處理,工做量望而卻步;框架
5)對於常規Resx文件的處理以爲繁瑣ide
6)缺少一個統一處理多語言需求的方案工具
在多語言的處理上,我一直但願找出一種高效的處理方式,因爲個人Winform開發框架中不少模塊是現成的,但願可以使用繼承處理的方式,實現最簡化的處理;post
同時大量中文的英文(針對英文版本)翻譯也是一個頭痛的事情,忽然想到百度的翻譯API接口能夠利用,那麼咱們能夠利用翻譯接口實現開始的翻譯,而後對資源進行必定的調整則能夠提升效率和準確率。測試
對於編輯和承載多語言的信息,我一直以爲JSON格式挺好的,能夠利用它序列化爲字典集合,經過字典獲取對應鍵值的多語言版本字符串也是很高效的一種方式,那麼就決定用JSON來存儲多語言信息了,易讀好用。
對於多餘的處理邏輯,儘可能封裝爲獨立的模塊,能夠在多個模塊中進行調用處理。
在思考多語言的合理處理方案過程當中,參考了另外一位博友的文章《分享兩種實現Winform程序的多語言支持的解決方案》,思路有點符合個人指望,所以吸取了一些處理思想進行處理,目的就是提升開發效率。
1)多語言的信息存儲和加載
首先,咱們來看看多語言處理的目錄和格式問題,目錄大概是根據多語言的簡稱進行放置,以下所示。
這個目錄就是會輸出到debug或者Release的運行目錄中,咱們就是根據相對於運行目錄進行資源讀取便可,全部模塊共用同一的多語言文件,咱們能夠把各個模塊基礎通用的多語言文件放在Basic.json文件中,也能夠根據模塊獨立起名,主程序如TestMultiLanguage的多語言文件我則放在TestMultiLanguage.json文件中。實際上目錄名稱是爲了區分而已,程序加載的時候,會把目錄下面全部的JSON文件進行加載,讀取裏面的鍵值做爲資源的字典參照。
多語言的JSON文件是標準的Json格式,只是咱們只用鍵值的字典參考便可,不須要使用複雜的JSON對象格式,以下是basic.json文件的部份內容。
這些資源文件採用中文-英文的參照方式,咱們以咱們常規的母語開發,即便咱們不作多語言,也不影響代碼的正常處理,咱們只須要把窗體上和代碼裏面的中文提取出來,而後進行多語言處理(如變爲英文)便可。
因爲咱們使用鍵值字典對象的JSON內容,那麼咱們就能夠把這些內容序列號爲字典集合,以下代碼咱們能夠經過 JSON.NET 組件把它們序列化爲字典集合,這些字典集合就是咱們用來作多語言的關鍵。
var content = File.ReadAllText(file, Encoding.UTF8); if (!string.IsNullOrEmpty(content)) { var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(content); foreach (string key in dict.Keys) { //遍歷集合若是語言資源鍵值不存在,則建立,不然更新 if (!resources.ContainsKey(key)) { resources.Add(key, dict[key]); } else { resources[key] = dict[key]; } } }
加載多語言處理的時候,咱們遍歷相對目錄下的lang/***裏面的文件便可實現多語言信息的加載,以下代碼所示。
/// <summary> /// 根據語言初始化信息。 /// 加載對應語言的JSON信息,把翻譯信息存儲在全屬性resources裏面。 /// </summary> /// <param name="language">默認的語言類型,如zh-Hans,en-US等</param> private void LoadLanguage(string language = "") { if (string.IsNullOrEmpty(language)) { language = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; } this.resources = new Dictionary<string, string>(); string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("lang/{0}", language)); if (Directory.Exists(dir)) { var jsonFiles = Directory.GetFiles(dir, "*.json", SearchOption.AllDirectories); foreach (string file in jsonFiles) { LoadFile(file); } } }
咱們把多語言的加載和翻譯處理,放在一個獨立的項目上,如我定義爲框架的一個模塊:WHC.Framework.Language
這樣咱們在各個模塊中使用多語言處理過程的時候,包含這個模塊就能夠了。
作多語言的版本程序,翻譯工做也是一個繁瑣的工做,若是你是很是精通各類語言(如中文、英文、日文等等),那固然不在話下,不過咱們作開發的多少也是會一些的,如英語吧,即時不能很是準確,那麼也能夠作到差很少,可是作這個仍是累,還容易敲打錯別字,那麼用第三方提供的翻譯API來預處理後調整,結果就簡化不少了,能夠極大提升效率的。
這裏以咱們常用的百度翻譯來實現(用Google翻譯也能夠,增長接口實現便可)
百度翻譯接口的使用,你先註冊一個開發帳戶,得到相應的祕鑰信息就可使用免費的翻譯接口了(http://api.fanyi.baidu.com/api/trans/product/index)。
有了這些準備後,就能夠利用C#代碼進行翻譯處理了。
百度翻譯的接口處理代碼以下所示。
/// <summary> /// 百度接口翻譯 /// </summary> /// <param name="inputString">輸入字符串</param> /// <param name="from">源內容語言</param> /// <param name="to">目標語言</param> /// <returns></returns> private static string BaiduTranslate(string inputString, string from = "zh", string to = "en") { string content = ""; string appId = "你的APPID"; string securityId = "你的祕鑰"; int salt = 0; StringBuilder signString = new StringBuilder(); string md5Result = string.Empty; //1.拼接字符,爲了生成sign signString.Append(appId); signString.Append(inputString); signString.Append(salt); signString.Append(securityId); //2.經過md5獲取sign byte[] sourceMd5Byte = Encoding.UTF8.GetBytes(signString.ToString()); MD5 md5 = new MD5CryptoServiceProvider(); byte[] destMd5Byte = md5.ComputeHash(sourceMd5Byte); md5Result = BitConverter.ToString(destMd5Byte).Replace("-", ""); md5Result = md5Result.ToLower(); try { //3.獲取web翻譯的json結果 WebClient client = new WebClient(); string url = string.Format("http://api.fanyi.baidu.com/api/trans/vip/translate?q={0}&from=zh&to=en&appid={1}&salt={2}&sign={3}", inputString, appId, salt, md5Result); byte[] buffer = client.DownloadData(url); string result = Encoding.UTF8.GetString(buffer); var trans = JsonConvert.DeserializeObject<TranslationJson>(result); if (trans != null) { content = trans.trans_result[0].dst; content = StringUtil.ToProperCase(content); } } catch(Exception ex) { Debug.WriteLine(ex); } return content; }
其中把JSON轉換爲類對象須要兩個類,對翻譯結果進行轉換,以下代碼所示。
internal class TranslationJson { public string from { get; set; } public string to { get; set; } public List<TranslationResult> trans_result { get; set; } } internal class TranslationResult { public string src { get; set; } public string dst { get; set; } }
這樣咱們在多語言處理的時候,能夠對默認輸入爲空的鍵值進行翻譯便可(如英文翻譯)。
//遍歷集合進行翻譯 var value = dict[key]; if (string.IsNullOrWhiteSpace(value)) { //若是值爲空,那麼調用翻譯接口處理 var newValue = TranslationHelper.Translate(key, from, to); if (!string.IsNullOrWhiteSpace(newValue)) { dict[key] = newValue; } }
而後從新更新咱們的資源文件就能夠了
//不排序 var newContent = JsonConvert.SerializeObject(dict, Formatting.Indented); File.WriteAllText(file, newContent, Encoding.UTF8);
若是須要對鍵值進行排序,那麼使用SortDictionary進行包裝下便可
//進行排序 SortedDictionary<string, string> sortedDict = new SortedDictionary<string, string>(dict); var newContent = JsonConvert.SerializeObject(sortedDict, Formatting.Indented);
在多語言處理的時候,咱們通常沒必要要一次填寫完畢中英文對照的資源,咱們能夠先把字典鍵值的鍵寫出來,值保留爲空,以下文件所示。
運行程序的時候,讓翻譯的接口先行翻譯,而後咱們再對翻譯的資源進行調整,適應咱們程序的語境便可,翻譯後的內容後以下所示。
好了,彈藥都準備好了,就看咱們如何使用, 下一步介紹如何使用這些資源。
前面介紹都是爲程序界面準備好對應的多語言資源內容,咱們在程序啓動的時候,能夠經過常規的方式,設置界面的CurrentUICulture區域信息,以下代碼所示。
//界面多語言 //System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-Hans");//中文界面 System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");//英文界面
而後咱們在Winform程序中開發設計咱們的界面內容,例如設計一個普通的界面以下所示。
這個窗體咱們添加了幾個按鈕,並設置它的中文顯示內容,它的基類默認仍是保持它的DevExpress基類 XtraForm,以下所示。
/// <summary> /// 測試多語言的窗體界面 /// </summary> public partial class Form1 : XtraForm
那麼咱們若是要自動實現多語言的處理,那麼還須要在窗體的Load或者Shown事件裏面實現處理,以下代碼所示。
private void Form1_Shown(object sender, EventArgs e) { //窗體加載並顯示後,對窗體實現多語言處理 if (!this.DesignMode) { LanguageHelper.InitLanguage(this); } }
若是咱們爲每一個窗體都須要添加這些代碼,也是繁瑣的事情,那麼咱們能夠把這個處理邏輯,放到咱們常規自定義的窗體基類裏面(如BaseForm),那麼咱們就不須要任何額外的代碼了。
所需的就是集成窗體基類便可,這也是咱們通常開發都作的事情,經過繼承使得咱們的代碼又省去了。
/// <summary> /// 測試多語言的窗體界面 /// </summary> public partial class Form1 : BaseForm
那麼咱們真正關注的就是咱們前面介紹的邏輯代碼實現了
LanguageHelper.InitLanguage(this);
這個輔助類,主要就是在窗體初始化後,遍歷界面的全部類型控件,對控件進行相應的多語言處理。
/// <summary> /// 對界面控件進行多語言的處理輔助類 /// </summary> public class LanguageHelper { /// <summary> /// 初始化語言 /// </summary> public static void InitLanguage(Control control) { //若是沒有資源,那麼沒必要遍歷控件,提升速度 if (!JsonLanguage.Default.HasResource) return; //使用遞歸的方式對控件及其子控件進行處理 SetControlLanguage(control); foreach (Control ctrl in control.Controls) { InitLanguage(ctrl); } //工具欄或者菜單動態構建窗體或者控件的時候,從新對子控件進行處理 control.ControlAdded += (sender, e) => { InitLanguage(e.Control); }; }
經過遞歸的方式,咱們能夠對常規的如GridControl,工具欄、NavBar導航欄、菜單、按鈕等資源進行統一的多語言處理,而這裏面對於咱們開發應用程序界面,都不須要額外的擔憂,極大的提升了效率。
下面是幾個常規的界面,咱們來體驗下英文版本的界面效果。
這些英文界面咱們只須要把界面的中文提取出來放到JSON文件中,自動翻譯再調整便可,而後界面繼承保持BaseForm或者BaseDock這些窗體基類不變,只是調整了這些基類的加載,增長一行代碼就能夠順利實現了多語言的效果了。
這樣咱們就把核心的工做放在提取界面中的中文資源並進行整理便可,這是核心的工做但翻譯也基本不用本身從頭作,窗體代碼幾乎不須要作其餘修改就實現了咱們所須要的多語言效果了,這樣作極大提升了開發效率,對於咱們已經開發好的模塊,更是四兩撥千斤了。