C#多语言实现,二

一直想做一个多语言的程序,研究了一下.net的本地化方法,觉得做起来比较麻烦,而且不能快速切换,就自己琢磨着写一个。

以我做的一个C# winform 项目为例。

在建立C#实现多语言界面程序之前,首先设计多语言文件,这里我用XML来保存,基本结构如下。

  1. < ?xml version = "1.0" encoding = "GB2312"?>
  2. < AirControl language="简体中文">
  3. < Menu>
  4. < Project>
  5. < Item key="MenuProject" value="项目(&P)" />
  6. < Item key="MenuProjectItem1" value="新建(&N)" />
  7. < Item key="MenuProjectItem2" value="打开(&O)" />
  8. < Item key="MenuProjectItem3" value="保存(&S)" />
  9. < Item key="MenuProjectItem5" value="退出(&X)" />
  10. < /Project>
  11. < Manage>
  12. < Item key="MenuManage" value="管理(&M)" />
  13. < Item key="MenuManageItem1" value="登录(&I)" />
  14. < Item key="MenuManageItem2" value="注销(&O)" />
  15. < Item key="MenuManageItem3" value="修改密码(&C)" />
  16. < Item key="MenuManageItem4" value="用户管理(&U)" />
  17. < /Manage>
  18. < Help>
  19. < Item key="MenuHelp" value="帮助(&H)" />
  20. < Item key="MenuHelpItem1" value="帮助内容(&H)" />
  21. < Item key="MenuHelpItem2" value="关于(&A)" />
  22. < /Help>
  23. < /Menu>
  24. < Toolbar>
  25. < Statusbar>
  26. < Item key="StatusItem1" value="用户名: " />
  27. < Item key="StatusItem2" value="用户组: " />
  28. < Item key="StatusItem3" value="上次登录时间: " />
  29. < Item key="StatusItem4" value="本次登录时间:" />
  30. < /Statusbar>
  31. < /Toolbar>
  32. < Form>
  33. < MainForm>
  34. < Item key="MainForm" value="xx" />
  35. < Item key="buttonGo" value="开始" />
  36. < Item key="buttonStop" value="停止" />
  37. < Item key="groupBox1" value="用户信息" />
  38. < Item key="groupBox2" value="常规数据" />
  39. < /MainForm>
  40. < UserLoginForm>
  41. < Item key="UserLoginForm" value="用户登录" />
  42. < Item key="labelTitle" value="xx" />
  43. < Item key="labelUsername" value="用户名" />
  44. < Item key="labelPassword" value="密码" />
  45. < Item key="buttonLogin" value="登录" />
  46. < /UserLoginForm>
  47. < ChangePasswordForm>
  48. < Item key="ChangePasswordForm" value="修改密码" />
  49. < Item key="label1" value="原密码" />
  50. < Item key="label2" value="新密码" />
  51. < Item key="label3" value="再输入" />
  52. < Item key="buttonConfirm" value="确认" />
  53. < Item key="buttonCancel" value="取消" />
  54. < /ChangePasswordForm>
  55. < /Form>
  56. < Dialog>
  57. < Title>
  58. < Item key="0001" value="xx" />
  59. < Item key="0002" value="添加测试" />
  60. < Item key="0003" value="添加用户" />
  61. < Item key="0004" value="修改密码" />
  62. < /Title>
  63. < Message>
  64. < Item key="0000" value="一切正常" />
  65. < Item key="2001" value="用户名或密码错误" />
  66. < Item key="2002" value="密码不一致" />
  67. < Item key="2003" value="用户名已存在" />
  68. < Item key="2004" value="添加用户成功" />
  69. < /Message>
  70. < /Dialog>
  71. < /AirControl>

这里是语言文件的局部,主体分为四个部分,Menu, Toolbar, Form 和 Dialog,分别对应菜单,工具栏,窗体和对话框的显示字符串。

在Form里面,其每个子树分别对应一个窗体。XML每项有三个域,id 这个只是用来标号,程序中为用,key,value形成一个字典,key是控件的名称,value是控件的text。在Dialog中key用数字编号。

做其他语言文件时,只用将value里面的值改成对应的语言即可。

当然,我们也不一定用XML来写语言文件,简单的ini文件也行。下面设计读取这个XML的类,

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Xml;
  6. namespace AirLibrary
  7. {
  8. /**//// < summary>
  9. /// 本地化类
  10. /// < /summary>
  11. public static class Localization
  12. {
  13. Property#region Property
  14. public static string Lang { get; private set; }
  15. public static bool HasLang { get; set; }
  16. #endregion //Property
  17. Attribute#region Attribute
  18. private static Dictionary< string, Dictionary< string, string>> forms = new Dictionary< string, Dictionary< string, string>>();
  19. private static Dictionary< string, string> menu = new Dictionary< string, string>();
  20. private static Dictionary< string, string> toolbar = new Dictionary< string, string>();
  21. private static Dictionary< string, string> dialog = new Dictionary< string, string>();
  22. #endregion //Attribute
  23. Method#region Method
  24. public static void AddForm(string formName)
  25. {
  26. forms.Add(formName, new Dictionary< string, string>());
  27. //formMap.Add(formName, count++);
  28. }
  29. /**//// < summary>
  30. /// 加载语言文件
  31. /// < /summary>
  32. /// < param name="lang">语言< /param>
  33. /// < returns>< /returns>
  34. public static bool Load(string lang)
  35. {
  36. string path = "";
  37. Localization.Lang = "English";
  38. menu.Clear();
  39. toolbar.Clear();
  40. dialog.Clear();
  41. exception.Clear();
  42. foreach (Dictionary< string, string> form in forms.Values)
  43. form.Clear();
  44. switch (lang)
  45. {
  46. case "zh":
  47. path = @"resources/lang-zh.xml";
  48. break;
  49. case "en":
  50. path = @"resources/lang-en.xml";
  51. break;
  52. default:
  53. path = @"resources/lang-zh.xml";
  54. break;
  55. }
  56. return readLanguage(path);
  57. }
  58. #endregion //Method
  59. Function#region Function
  60. private static bool readLanguage(string path)
  61. {
  62. // Read the language file
  63. XmlReader reader;
  64. try
  65. {
  66. reader = XmlReader.Create(path);
  67. }
  68. catch (Exception)
  69. {
  70. return false;
  71. }
  72. // Begin to parase
  73. try
  74. {
  75. reader.ReadToFollowing("AirControl");
  76. Localization.Lang = reader.GetAttribute("language");
  77. paraseXml(reader, "Menu", menu);
  78. paraseXml(reader, "Toolbar", toolbar);
  79. foreach (string formName in forms.Keys)
  80. {
  81. paraseXml(reader, formName, forms[formName]);
  82. }
  83. paraseXml(reader, "Dialog", dialog);
  84. }
  85. catch (Exception)
  86. {
  87. return false;
  88. }
  89. return true;
  90. }
  91. private static void paraseXml(XmlReader reader, string item, Dictionary< string, string> obj)
  92. {
  93. // Get the attribute key & value
  94. reader.ReadToFollowing(item);
  95. XmlReader subreader = reader.ReadSubtree();
  96. while (subreader.Read())
  97. {
  98. if (subreader.NodeType == XmlNodeType.Element && subreader.Name == "Item")
  99. obj.Add(subreader.GetAttribute("key"), subreader.GetAttribute("value"));
  100. }
  101. }
  102. #endregion //Function
  103. Property#region Property
  104. public static Dictionary< string, string> Menu
  105. {
  106. get
  107. {
  108. return menu;
  109. }
  110. private set
  111. { }
  112. }
  113. public static Dictionary< string, string> Toolbar
  114. {
  115. get
  116. {
  117. return toolbar;
  118. }
  119. private set
  120. { }
  121. }
  122. public static Dictionary< string, Dictionary< string, string>> Forms
  123. {
  124. get
  125. {
  126. return forms;
  127. }
  128. private set
  129. { }
  130. }
  131. public static Dictionary< string, string> Dialog
  132. {
  133. get
  134. {
  135. return dialog;
  136. }
  137. private set
  138. { }
  139. }
  140. #endregion //Property
  141. }
  142. }

这里我使用静态类来读取和保存,这样效率相对会高一些。读取XML时,我使用的是XmlReader,它使用流式读取,速度也比较快。

Forms, Menu, Toolbar, Dialog几个属性分别对应XML中的子树,使用.net中的Dictionary范型,Forms嵌套了一层Dictionary。

Load方法是加载语言文件,readLanguage 和paraseXML 函数对XML进行解析,并保存字符串到对应的属性中。

AddForm这个方法是将每个窗体的动态的添加到forms 里面。

在程序开始main 函数中,首先调用AddForm方法,添加所有窗体。

  1. // 添加所有窗体用于本地化(按XML中顺序)
  2. private static void AddForm()
  3. {
  4. Localization.AddForm("MainForm");
  5. Localization.AddForm("UserLoginForm");
  6. Localization.AddForm("UserManageForm");
  7. Localization.AddForm("ChangePasswordForm");
  8. }

然后加载语言文件。

  1. if (!Localization.Load("zh"))
  2. {
  3. MessageBox.Show("无法加载语言配置文件, 将显示英文.", "错误", MessageBoxButtons.OK,
  4. MessageBoxIcon.Exclamation);
  5. Localization.HasLang = false;
  6. }
  7. else
  8. Localization.HasLang = true;

在每个Form的Load事件中初始化每个控件的Text。

  1. if (Localization.HasLang)
  2. RefreshLanguage();
  3. // 更新窗体语言
  4. public static void RefreshLanguage(Form form)
  5. {
  6. form.Text = Localization.Forms[form.Name][form.Name];
  7. SetControlsLanguage(form, Localization.Forms[form.Name]);
  8. }
  9. 递归更新每个控件Text
  10. /// < summary>
  11. /// 设置control子控件语言
  12. /// < /summary>
  13. /// < param name="control">父控件< /param>
  14. /// < param name="obj">语言字典< /param>
  15. public static void SetControlsLanguage(Control control, Dictionary< string, string> obj)
  16. {
  17. foreach (Control ctrl in control.Controls)
  18. {
  19. // set the control which one's key in the dictionary
  20. string text = "";
  21. if (obj.TryGetValue(ctrl.Name, out text))
  22. ctrl.Text = text;
  23. if (ctrl.HasChildren)
  24. SetControlsLanguage(ctrl, obj);
  25. }
  26. }

另外主窗体的Menu和Toolbar,我采用以下的方法更新。

  1. // Refresh the menu language
  2. foreach (ToolStripMenuItem topItem in MainMenuStrip.Items)
  3. {
  4. topItem.Text = Localization.Menu[topItem.Name];
  5. foreach (ToolStripItem item in topItem.DropDownItems)
  6. {
  7. if (item is ToolStripMenuItem)
  8. {
  9. string text = "";
  10. if (Localization.Menu.TryGetValue(item.Name, out text))
  11. item.Text = text;
  12. }
  13. }
  14. }
  15. // Refresh the statusbar language
  16. foreach (ToolStripItem item in mainStatus.Items)
  17. {
  18. string text = "";
  19. if (Localization.Toolbar.TryGetValue(item.Name, out text))
  20. item.Text = text;
  21. }

Dialog就直接调用Localization中的Dialog属性即可。

需要转变为不同语言时只需要再调用一次Localization.Load方法。

这样,就完成了C#实现多语言界面程序。