在WinForm應(yīng)用程序中快速實(shí)現(xiàn)多語言的處理

在國際化環(huán)境下黎烈,越來越多的程序需要做多語言版本,以適應(yīng)各種業(yè)務(wù)需求的變化芳来。在Winform應(yīng)用程序中實(shí)現(xiàn)多語言也有常規(guī)的處理方式處理单刁,不過需要針對每個語言版本祝蝠,重新修改Winform界面的顯示,對一些常規(guī)的輔助類幻碱,也需要引入一個統(tǒng)一的資源管理類來處理多語言的問題,相對比較繁瑣细溅。本篇隨筆針對多語言的需求褥傍,希望盡量避免繁瑣的操作,既能符合本地語種開發(fā)人員的開發(fā)習(xí)慣喇聊,又能快速實(shí)現(xiàn)Winform程序的多語言場景處理恍风。

1、多語言開發(fā)的困惑和思路

在常規(guī)的多語言版本程序中誓篱,開發(fā)總是伴隨著很多不愉快的事情朋贬,大概列舉一些僅供參考:

1)對窗體的多語言處理時,維護(hù)多個語言版本的界面非常繁瑣窜骄;

2)多語言處理的時候锦募,以資源參照的時候,默認(rèn)鍵值為一些英文字符串或者單詞邻遏,不太符合如中文語境的開發(fā)糠亩,調(diào)整代碼則需要很多工作量;

3)對于已開發(fā)好的程序准验,全面引入多語言的處理代碼赎线,需要大量修改;

4)對于大量中文的多語言處理糊饱,工作量望而卻步垂寥;

5)對于常規(guī)Resx文件的處理覺得繁瑣

6)缺乏一個統(tǒng)一處理多語言需求的方案

在多語言的處理上,我一直希望找出一種高效的處理方式另锋,由于我的Winform開發(fā)框架中很多模塊是現(xiàn)成的滞项,希望能夠使用繼承處理的方式,實(shí)現(xiàn)最簡化的處理夭坪;

同時大量中文的英文(針對英文版本)翻譯也是一個頭痛的事情蓖扑,突然想到百度的翻譯API接口可以利用,那么我們可以利用翻譯接口實(shí)現(xiàn)開始的翻譯台舱,然后對資源進(jìn)行一定的調(diào)整則可以提高效率和準(zhǔn)確率律杠。

對于編輯和承載多語言的信息潭流,我一直覺得JSON格式挺好的,可以利用它序列化為字典集合柜去,通過字典獲取對應(yīng)鍵值的多語言版本字符串也是很高效的一種方式灰嫉,那么就決定用JSON來存儲多語言信息了,易讀好用嗓奢。

對于多余的處理邏輯讼撒,盡量封裝為獨(dú)立的模塊,可以在多個模塊中進(jìn)行調(diào)用處理股耽。

2根盒、多語言的處理實(shí)現(xiàn)

在思考多語言的合理處理方案過程中,參考了另一位博友的文章《分享兩種實(shí)現(xiàn)Winform程序的多語言支持的解決方案》物蝙,思路有點(diǎn)符合我的期望炎滞,因此吸收了一些處理思想進(jìn)行處理,目的就是提高開發(fā)效率诬乞。

1)多語言的信息存儲和加載

首先册赛,我們來看看多語言處理的目錄和格式問題,目錄大概是根據(jù)多語言的簡稱進(jìn)行放置震嫉,如下所示森瘪。

image

這個目錄就是會輸出到debug或者Release的運(yùn)行目錄中,我們就是根據(jù)相對于運(yùn)行目錄進(jìn)行資源讀取即可票堵,所有模塊共用同一的多語言文件扼睬,我們可以把各個模塊基礎(chǔ)通用的多語言文件放在Basic.json文件中,也可以根據(jù)模塊獨(dú)立起名悴势,主程序如TestMultiLanguage的多語言文件我則放在TestMultiLanguage.json文件中痰驱。實(shí)際上目錄名稱是為了區(qū)分而已,程序加載的時候瞳浦,會把目錄下面所有的JSON文件進(jìn)行加載担映,讀取里面的鍵值作為資源的字典參照。

多語言的JSON文件是標(biāo)準(zhǔn)的Json格式叫潦,只是我們只用鍵值的字典參考即可蝇完,不需要使用復(fù)雜的JSON對象格式,如下是basic.json文件的部分內(nèi)容矗蕊。

image

這些資源文件采用中文-英文的參照方式短蜕,我們以我們常規(guī)的母語開發(fā),即使我們不做多語言傻咖,也不影響代碼的正常處理朋魔,我們只需要把窗體上和代碼里面的中文提取出來,然后進(jìn)行多語言處理(如變?yōu)橛⑽模┘纯伞?/p>

由于我們使用鍵值字典對象的JSON內(nèi)容卿操,那么我們就可以把這些內(nèi)容序列號為字典集合警检,如下代碼我們可以通過 JSON.NET 組件把它們序列化為字典集合孙援,這些字典集合就是我們用來做多語言的關(guān)鍵。

            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)
                {
                    //遍歷集合如果語言資源鍵值不存在扇雕,則創(chuàng)建拓售,否則更新
                    if (!resources.ContainsKey(key))
                    {
                        resources.Add(key, dict[key]);
                    }
                    else
                    {
                        resources[key] = dict[key];
                    }
                }
            }

加載多語言處理的時候,我們遍歷相對目錄下的lang/***里面的文件即可實(shí)現(xiàn)多語言信息的加載镶奉,如下代碼所示础淤。

        /// <summary>
        /// 根據(jù)語言初始化信息。
        /// 加載對應(yīng)語言的JSON信息哨苛,把翻譯信息存儲在全屬性resources里面鸽凶。
        /// </summary>
        /// <param name="language">默認(rèn)的語言類型,如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);
                }
            }
        }

我們把多語言的加載和翻譯處理玻侥,放在一個獨(dú)立的項(xiàng)目上,如我定義為框架的一個模塊:WHC.Framework.Language

這樣我們在各個模塊中使用多語言處理過程的時候迹缀,包含這個模塊就可以了。

2)多語言信息的翻譯

做多語言的版本程序蜜徽,翻譯工作也是一個繁瑣的工作祝懂,如果你是非常精通各種語言(如中文、英文拘鞋、日文等等)砚蓬,那當(dāng)然不在話下,不過我們做開發(fā)的多少也是會一些的盆色,如英語吧灰蛙,即時不能非常準(zhǔn)確,那么也可以做到差不多隔躲,但是做這個還是累摩梧,還容易敲打錯別字,那么用第三方提供的翻譯API來預(yù)處理后調(diào)整宣旱,結(jié)果就簡化很多了仅父,可以極大提高效率的。

這里以我們經(jīng)常使用的百度翻譯來實(shí)現(xiàn)(用Google翻譯也可以浑吟,增加接口實(shí)現(xiàn)即可)

百度翻譯接口的使用笙纤,你先注冊一個開發(fā)賬戶,獲得相應(yīng)的秘鑰信息就可以使用免費(fèi)的翻譯接口了(http://api.fanyi.baidu.com/api/trans/product/index)组力。

image

有了這些準(zhǔn)備后省容,就可以利用C#代碼進(jìn)行翻譯處理了。

百度翻譯的接口處理代碼如下所示燎字。

        /// <summary>
        /// 百度接口翻譯
        /// </summary>
        /// <param name="inputString">輸入字符串</param>
        /// <param name="from">源內(nèi)容語言</param>
        /// <param name="to">目標(biāo)語言</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結(jié)果
                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轉(zhuǎn)換為類對象需要兩個類腥椒,對翻譯結(jié)果進(jìn)行轉(zhuǎn)換阿宅,如下代碼所示。

    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; }
    }

這樣我們在多語言處理的時候寞酿,可以對默認(rèn)輸入為空的鍵值進(jìn)行翻譯即可(如英文翻譯)家夺。

    //遍歷集合進(jìn)行翻譯
    var value = dict[key];
    if (string.IsNullOrWhiteSpace(value))
    {
        //如果值為空,那么調(diào)用翻譯接口處理
        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);

如果需要對鍵值進(jìn)行排序伐弹,那么使用SortDictionary進(jìn)行包裝下即可

    //進(jìn)行排序
    SortedDictionary<string, string> sortedDict = new SortedDictionary<string, string>(dict);
    var newContent = JsonConvert.SerializeObject(sortedDict, Formatting.Indented);

在多語言處理的時候拉馋,我們一般不必要一次填寫完畢中英文對照的資源,我們可以先把字典鍵值的鍵寫出來惨好,值保留為空煌茴,如下文件所示。

image

運(yùn)行程序的時候日川,讓翻譯的接口先行翻譯蔓腐,然后我們再對翻譯的資源進(jìn)行調(diào)整,適應(yīng)我們程序的語境即可龄句,翻譯后的內(nèi)容后如下所示回论。

image

好了,彈藥都準(zhǔn)備好了分歇,就看我們?nèi)绾问褂茫?下一步介紹如何使用這些資源傀蓉。

3、多語言在界面中的應(yīng)用

前面介紹都是為程序界面準(zhǔn)備好對應(yīng)的多語言資源內(nèi)容职抡,我們在程序啟動的時候葬燎,可以通過常規(guī)的方式,設(shè)置界面的CurrentUICulture區(qū)域信息缚甩,如下代碼所示谱净。

            //界面多語言
            //System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-Hans");//中文界面
            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");//英文界面

然后我們在Winform程序中開發(fā)設(shè)計(jì)我們的界面內(nèi)容,例如設(shè)計(jì)一個普通的界面如下所示擅威。

image

這個窗體我們添加了幾個按鈕壕探,并設(shè)置它的中文顯示內(nèi)容,它的基類默認(rèn)還是保持它的DevExpress基類 XtraForm郊丛,如下所示浩蓉。

    /// <summary>
    /// 測試多語言的窗體界面
    /// </summary>
    public partial class Form1 : XtraForm

那么我們?nèi)绻詣訉?shí)現(xiàn)多語言的處理,那么還需要在窗體的Load或者Shown事件里面實(shí)現(xiàn)處理宾袜,如下代碼所示捻艳。

        private void Form1_Shown(object sender, EventArgs e)
        {
            //窗體加載并顯示后,對窗體實(shí)現(xiàn)多語言處理
            if (!this.DesignMode)
            {
                LanguageHelper.InitLanguage(this);
            }
        }

如果我們?yōu)槊總€窗體都需要添加這些代碼庆猫,也是繁瑣的事情认轨,那么我們可以把這個處理邏輯,放到我們常規(guī)自定義的窗體基類里面(如BaseForm)月培,那么我們就不需要任何額外的代碼了嘁字。

所需的就是集成窗體基類即可恩急,這也是我們一般開發(fā)都做的事情,通過繼承使得我們的代碼又省去了纪蜒。

    /// <summary>
    /// 測試多語言的窗體界面
    /// </summary>
    public partial class Form1 : BaseForm

那么我們真正關(guān)注的就是我們前面介紹的邏輯代碼實(shí)現(xiàn)了

LanguageHelper.InitLanguage(this);

這個輔助類衷恭,主要就是在窗體初始化后,遍歷界面的所有類型控件纯续,對控件進(jìn)行相應(yīng)的多語言處理随珠。

    /// <summary>
    /// 對界面控件進(jìn)行多語言的處理輔助類
    /// </summary>
    public class LanguageHelper
    {             
        /// <summary>
        /// 初始化語言
        /// </summary>
        public static void InitLanguage(Control control)
        {
            //如果沒有資源,那么不必遍歷控件猬错,提高速度
            if (!JsonLanguage.Default.HasResource)
                return;

            //使用遞歸的方式對控件及其子控件進(jìn)行處理
            SetControlLanguage(control);
            foreach (Control ctrl in control.Controls)
            {
                InitLanguage(ctrl);
            }

            //工具欄或者菜單動態(tài)構(gòu)建窗體或者控件的時候窗看,重新對子控件進(jìn)行處理
            control.ControlAdded += (sender, e) =>
            {
                InitLanguage(e.Control);
            };
        }

通過遞歸的方式,我們可以對常規(guī)的如GridControl倦炒,工具欄显沈、NavBar導(dǎo)航欄、菜單逢唤、按鈕等資源進(jìn)行統(tǒng)一的多語言處理拉讯,而這里面對于我們開發(fā)應(yīng)用程序界面,都不需要額外的擔(dān)心鳖藕,極大的提高了效率魔慷。

下面是幾個常規(guī)的界面,我們來體驗(yàn)下英文版本的界面效果吊奢。

image
image
image
image
image

這些英文界面我們只需要把界面的中文提取出來放到JSON文件中盖彭,自動翻譯再調(diào)整即可纹烹,然后界面繼承保持BaseForm或者BaseDock這些窗體基類不變页滚,只是調(diào)整了這些基類的加載,增加一行代碼就可以順利實(shí)現(xiàn)了多語言的效果了铺呵。

這樣我們就把核心的工作放在提取界面中的中文資源并進(jìn)行整理即可裹驰,這是核心的工作但翻譯也基本不用自己從頭做,窗體代碼幾乎不需要做其他修改就實(shí)現(xiàn)了我們所需要的多語言效果了片挂,這樣做極大提高了開發(fā)效率幻林,對于我們已經(jīng)開發(fā)好的模塊,更是四兩撥千斤了音念。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沪饺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子闷愤,更是在濱河造成了極大的恐慌整葡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讥脐,死亡現(xiàn)場離奇詭異遭居,居然都是意外死亡啼器,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門俱萍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來端壳,“玉大人,你說我怎么就攤上這事枪蘑∷鹎” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵腥寇,是天一觀的道長成翩。 經(jīng)常有香客問我,道長赦役,這世上最難降的妖魔是什么麻敌? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮掂摔,結(jié)果婚禮上术羔,老公的妹妹穿的比我還像新娘。我一直安慰自己乙漓,他們只是感情好级历,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叭披,像睡著了一般寥殖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涩蜘,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天嚼贡,我揣著相機(jī)與錄音,去河邊找鬼同诫。 笑死粤策,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的误窖。 我是一名探鬼主播叮盘,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霹俺!你這毒婦竟也來了柔吼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤丙唧,失蹤者是張志新(化名)和其女友劉穎愈魏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝌戒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年串塑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片北苟。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡桩匪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出友鼻,到底是詐尸還是另有隱情傻昙,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布彩扔,位于F島的核電站妆档,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏虫碉。R本人自食惡果不足惜贾惦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敦捧。 院中可真熱鬧须板,春花似錦、人聲如沸兢卵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秽荤。三九已至甜奄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窃款,已是汗流浹背课兄。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雁乡,地道東北人第喳。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓糜俗,卻偏偏與公主長得像踱稍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悠抹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容