在WinForm应用程序中快速实现多语言的处理的方法

在国际化环境下,越来越多的程序需要做多语言版本,以适应各种业务需求的变化。在Winform应用程序中实现多语言也有常规的处理方式处理,不过需要针对每个语言版本,重新修改Winform界面的显示,对一些常规的辅助类,也需要引入一个统一的资源管理类来处理多语言的问题,相对比较繁琐。本篇随笔针对多语言的需求,希望尽量避免繁琐的操作,既能符合本地语种开发人员的开发习惯,又能快速实现Winform程序的多语言场景处理。

1、多语言开发的困惑和思路

在常规的多语言版本程序中,开发总是伴随着很多不愉快的事情,大概列举一些仅供参考:

1)对窗体的多语言处理时,维护多个语言版本的界面非常繁琐;

2)多语言处理的时候,以资源参照的时候,默认键值为一些英文字符串或者单词,不太符合如中文语境的开发,调整代码则需要很多工作量;

3)对于已开发好的程序,全面引入多语言的处理代码,需要大量修改;

4)对于大量中文的多语言处理,工作量望而却步;

5)对于常规Resx文件的处理觉得繁琐

6)缺乏一个统一处理多语言需求的方案

在多语言的处理上,我一直希望找出一种高效的处理方式,由于我的Winform开发框架中很多模块是现成的,希望能够使用继承处理的方式,实现最简化的处理;

同时大量中文的英文(针对英文版本)翻译也是一个头痛的事情,突然想到百度的翻译API接口可以利用,那么我们可以利用翻译接口实现开始的翻译,然后对资源进行一定的调整则可以提高效率和准确率。

对于编辑和承载多语言的信息,我一直觉得JSON格式挺好的,可以利用它序列化为字典集合,通过字典获取对应键值的多语言版本字符串也是很高效的一种方式,那么就决定用JSON来存储多语言信息了,易读好用。

对于多余的处理逻辑,尽量封装为独立的模块,可以在多个模块中进行调用处理。

2、多语言的处理实现

在思考多语言的合理处理方案过程中,参考了另一位博友的文章《分享两种实现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

这样我们在各个模块中使用多语言处理过程的时候,包含这个模块就可以了。

2)多语言信息的翻译

做多语言的版本程序,翻译工作也是一个繁琐的工作,如果你是非常精通各种语言(如中文、英文、日文等等),那当然不在话下,不过我们做开发的多少也是会一些的,如英语吧,即时不能非常准确,那么也可以做到差不多,但是做这个还是累,还容易敲打错别字,那么用第三方提供的翻译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);

在多语言处理的时候,我们一般不必要一次填写完毕中英文对照的资源,我们可以先把字典键值的键写出来,值保留为空,如下文件所示。

运行程序的时候,让翻译的接口先行翻译,然后我们再对翻译的资源进行调整,适应我们程序的语境即可,翻译后的内容后如下所示。

好了,弹药都准备好了,就看我们如何使用, 下一步介绍如何使用这些资源。

3、多语言在界面中的应用

前面介绍都是为程序界面准备好对应的多语言资源内容,我们在程序启动的时候,可以通过常规的方式,设置界面的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这些窗体基类不变,只是调整了这些基类的加载,增加一行代码就可以顺利实现了多语言的效果了。

这样我们就把核心的工作放在提取界面中的中文资源并进行整理即可,这是核心的工作但翻译也基本不用自己从头做,窗体代码几乎不需要做其他修改就实现了我们所需要的多语言效果了,这样做极大提高了开发效率,对于我们已经开发好的模块,更是四两拨千斤了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 分享两种实现Winform程序的多语言支持的多种解决方案

    因公司业务需要,需要将原有的ERP系统加上支持繁体语言,但不能改变原有的编码方式,即:普通程序员感受不到编码有什么不同.经过我与几个同事的多番沟通,确定了以下两种方案: 方案一:在窗体基类中每次加载并显示窗体时,会自动递归遍历含文本显示的控件(Button,CheckBox,GroupBox,Label,LinkLabel,TextBox,StatusStrip,TabPage,ToolStrip,RadioButton,DateTimePicker,DataGridView,CheckedLi

  • 在WinForm应用程序中快速实现多语言的处理的方法

    在国际化环境下,越来越多的程序需要做多语言版本,以适应各种业务需求的变化.在Winform应用程序中实现多语言也有常规的处理方式处理,不过需要针对每个语言版本,重新修改Winform界面的显示,对一些常规的辅助类,也需要引入一个统一的资源管理类来处理多语言的问题,相对比较繁琐.本篇随笔针对多语言的需求,希望尽量避免繁琐的操作,既能符合本地语种开发人员的开发习惯,又能快速实现Winform程序的多语言场景处理. 1.多语言开发的困惑和思路 在常规的多语言版本程序中,开发总是伴随着很多不愉快的事情,

  • 在Winform框架界面中改变并存储界面皮肤样式的方法

    在本篇介绍的Winform界面样式改变及存储操作中,是指基于DevExpress进行界面样式的变化.一般情况下,默认我们会为客户提供多种DevExpress的界面皮肤以供个人喜好选择,默认DevExpress提供40余种皮肤样式,用户可以根据自己的喜好,选择较为美观.得体的皮肤,为了方便,我们对用户的皮肤选择进行记录,并可以动态改变. 1.界面皮肤的选择 Winform开发框架(包括混合式Winform开发框架)皮肤如下界面所示. 在皮肤集合中打开,可以看到很多界面皮肤可供选择 上面初始化的皮肤

  • 微信小程序中使用ECharts 异步加载数据的方法

    官网例子都是同步的,怎么引入及同步demo请移步官网 <view class="container"> <ec-canvas id="mychart-dom-multi-bar" canvas-id="mychart-multi-bar" ec="{{ ecBar }}"></ec-canvas> <ec-canvas id="mychart-dom-multi-scatte

  • 在vscode中快速新建html文件的2种方法总结

    目录 在vscode中新建html文件的两种方法: 第一个方法: 第二个方法: 补充:想要快速打开HTML文件查看编辑效果 总结 在vscode中新建html文件的两种方法: 第一个方法: 1 点击菜单[文件],如图所示. 2 点击[新建文件],如图所示. 3 就会创建一个没有命名的文件,如图所示. 4 点击选择语言开始,会弹出选择语言模式,在自动检测的下拉菜单中选择html,一个没有命名的html文件就创建好了. 5 点击保存或者另存为,给没有命名的html起个名字,可以起字母,数字,中文,起

  • 小程序中监听页面滚动的几种方法实例

    目录 目录 实现效果 下面是Intersection Observer在MDN上的介绍 IntersectionObserver relativeToViewport 接受参数 Object margins observe(string targetSelector, function callback) 参数 Object res 小程序中监听页面的其他方法 page-meta scroll-view 总结 在工作中我们经常遇到需要监听页面中元素位置,当元素超出显示区域或即将显示到页面上,我们

  • Java程序中添加播放MIDI音乐功能的实现方法详解

    JAVA 在多媒体处理方面的确优势不大,但是我们在程序中有些时候又需要一些音乐,如果播放的音乐是wav等波形音频文件,又很大的话,所以背景音乐最好就是MIDI了,可是网上很多播放MIDI的教程都是简单的几句话的例子.没有考虑资源的释放问题,如果程序长久运行的话,就会出现内存越耗越多的情况,最后会抛出一个 java.lang.OutOfMemoryError..在MIDI的播放中,一个类是比较重要的,那就是 MidiSystem 类,负责整个MIDI播放设备等的管理,其实就是 Seqencer,它

  • 基于对话框程序中让对话框捕获WM_KEYDOWN消息的实现方法

    在对话框程序中,我们经常是利用对话框上的子控件进行命令响应来处理一些事件.如果我们想要让对话框(子控件的父窗口)类来响应我们的按键消息,我们可以通过ClassWizard对WM_KEYDOWN消息进行响应,当程序运行后,我们按下键盘上的按键,但对话框不会有任何的反应.这是因为在对话框程序中,某些特定的消息,例如按键消息,它们被Windows内部的对话框过程处理了(即在基类中完成了处理,有兴趣的读者可以查看MFC的源代码),或者被发送给子控件进行处理,所以我们在对话框类中就捕获不到按键的消息了.

  • 在小程序中集成redux/immutable/thunk第三方库的方法

    一.前言 小程序给我们暴露了两个参数 require 和 module , require 用来在模块中加载其他模块, module 用来将模块中的方法暴露出去 module.exports = function(){} 所以只要需要让第三方库的代码使用这种形式的 export 就可以了 二.构建Redux的微信小程序包 打一个 Redux 包,让它可以兼容微信小城的加载方式 git clone https://github.com/reactjs/redux.git npm install #

  • python中快速进行多个字符替换的方法小结

    先给出结论: 要替换的字符数量不多时,可以直接链式replace()方法进行替换,效率非常高: 如果要替换的字符数量较多,则推荐在 for 循环中调用 replace() 进行替换. 可行的方法: 1. 链式replace() string.replace().replace() 1.x 在for循环中调用replace() 「在要替换的字符较多时」 2. 使用string.maketrans 3. 先 re.compile 然后 re.sub -- def a(text): chars = "

  • iOS应用程序中通过dispatch队列控制线程执行的方法

    GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用.所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致. dispatch队列的生成可以有这几种方式: 1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_

随机推荐