在国际化环境下,愈来愈多的程序须要作多语言版本,以适应各类业务需求的变化。在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这些窗体基类不变,只是调整了这些基类的加载,增长一行代码就能够顺利实现了多语言的效果了。
这样咱们就把核心的工做放在提取界面中的中文资源并进行整理便可,这是核心的工做但翻译也基本不用本身从头作,窗体代码几乎不须要作其余修改就实现了咱们所须要的多语言效果了,这样作极大提升了开发效率,对于咱们已经开发好的模块,更是四两拨千斤了。