一直想做一个多语言的程序,研究了一下.net的本地化方法,觉得做起来比较麻烦,而且不能快速切换,就自己琢磨着写一个。
以我做的一个C# winform 项目为例。
首先设计多语言文件,这里我用XML来保存,基本结构如下。
在Form里面,其每个子树分别对应一个窗体。XML每项有三个域,id 这个只是用来标号,程序中为用,key,value形成一个字典,key是控件
的名称,value是控件的text。在Dialog中key用数字编号。
做其他语言文件时,只用将value里面的值改成对应的语言即可。
当然,我们也不一定用XML来写语言文件,简单的ini文件也行。我现在觉得在一般的中小型程序中用XML,出于一个最基本的目的就是装B。
下面设计读取这个XML的类,
Forms, Menu, Toolbar, Dialog几个属性分别对应XML中的子树,使用.net中的Dictionary范型,Forms嵌套了一层Dictionary。
Load方法是加载语言文件,readLanguage 和paraseXML 函数对XML进行解析,并保存字符串到对应的属性中。
AddForm这个方法是将每个窗体的动态的添加到forms 里面。
在程序开始main 函数中,首先调用AddForm方法,添加所有窗体。
在每个Form的Load事件中初始化每个控件的Text。
递归更新每个控件Text
另外主窗体的Menu和Toolbar,我采用以下的方法更新。
需要转变为不同语言时只需要再调用一次Localization.Load方法。
小结:
这种实现多语言界面的方式我思考了很久,也在网上查了一些资料,最后设计了这样一种方式,XML中利用字典来记录控件的语言
在添加,读取时非常方便,Localization类做成静态类,在运行时就相当于一个常量,没有构造函数这样的开销,整个界面也可以再运行时
直接改变界面语言。
当然这种方法不一定是最好的, 如果有更好的方法欢迎指点。
以我做的一个C# winform 项目为例。
首先设计多语言文件,这里我用XML来保存,基本结构如下。
1
<?
xml version = "1.0" encoding = "GB2312"
?>
2 < AirControl language ="简体中文" >
3 < Menu >
4 < Project >
5 < Item id ="0" key ="MenuProject" value ="项目(&P)" />
6 < Item id ="1" key ="MenuProjectItem1" value ="新建(&N)" />
7 < Item id ="2" key ="MenuProjectItem2" value ="打开(&O)" />
8 < Item id ="3" key ="MenuProjectItem3" value ="保存(&S)" />
9 < Item id ="5" key ="MenuProjectItem5" value ="退出(&X)" />
10 </ Project >
11 < Manage >
12 < Item id ="0" key ="MenuManage" value ="管理(&M)" />
13 < Item id ="1" key ="MenuManageItem1" value ="登录(&I)" />
14 < Item id ="2" key ="MenuManageItem2" value ="注销(&O)" />
15 < Item id ="3" key ="MenuManageItem3" value ="修改密码(&C)" />
16 < Item id ="4" key ="MenuManageItem4" value ="用户管理(&U)" />
17 </ Manage >
18 < Help >
19 < Item id ="0" key ="MenuHelp" value ="帮助(&H)" />
20 < Item id ="1" key ="MenuHelpItem1" value ="帮助内容(&H)" />
21 < Item id ="2" key ="MenuHelpItem2" value ="关于(&A)" />
22 </ Help >
23 </ Menu >
24 < Toolbar >
25 < Statusbar >
26 < Item id ="1" key ="StatusItem1" value ="用户名: " />
27 < Item id ="2" key ="StatusItem2" value ="用户组: " />
28 < Item id ="3" key ="StatusItem3" value ="上次登录时间: " />
29 < Item id ="4" key ="StatusItem4" value ="本次登录时间:" />
30 </ Statusbar >
31 </ Toolbar >
32 < Form >
33 < MainForm >
34 < Item id ="0" key ="MainForm" value ="xx" />
35 < Item id ="1" key ="buttonGo" value ="开始" />
36 < Item id ="2" key ="buttonStop" value ="停止" />
37 < Item id ="3" key ="groupBox1" value ="用户信息" />
38 < Item id ="4" key ="groupBox2" value ="常规数据" />
39 </ MainForm >
40 < UserLoginForm >
41 < Item id ="0" key ="UserLoginForm" value ="用户登录" />
42 < Item id ="1" key ="labelTitle" value ="xx" />
43 < Item id ="2" key ="labelUsername" value ="用户名" />
44 < Item id ="3" key ="labelPassword" value ="密码" />
45 < Item id ="4" key ="buttonLogin" value ="登录" />
46 </ UserLoginForm >
47 < ChangePasswordForm >
48 < Item id ="0" key ="ChangePasswordForm" value ="修改密码" />
49 < Item id ="1" key ="label1" value ="原密码" />
50 < Item id ="2" key ="label2" value ="新密码" />
51 < Item id ="3" key ="label3" value ="再输入" />
52 < Item id ="4" key ="buttonConfirm" value ="确认" />
53 < Item id ="5" key ="buttonCancel" value ="取消" />
54 </ ChangePasswordForm >
55 </ Form >
56 < Dialog >
57 < Title >
58 < Item id ="0" key ="0001" value ="xx" />
59 < Item id ="1" key ="0002" value ="添加测试" />
60 < Item id ="2" key ="0003" value ="添加用户" />
61 < Item id ="3" key ="0004" value ="修改密码" />
62 </ Title >
63 < Message >
64 < Item id ="0" key ="0000" value ="一切正常" />
65 < Item id ="1" key ="2001" value ="用户名或密码错误" />
66 < Item id ="5" key ="2002" value ="密码不一致" />
67 < Item id ="6" key ="2003" value ="用户名已存在" />
68 < Item id ="7" key ="2004" value ="添加用户成功" />
69 </ Message >
70 </ Dialog >
71 </ AirControl >
这里是语言文件的局部,主体分为四个部分,Menu, Toolbar, Form 和 Dialog,分别对应菜单,工具栏,窗体和对话框的显示字符串。
2 < AirControl language ="简体中文" >
3 < Menu >
4 < Project >
5 < Item id ="0" key ="MenuProject" value ="项目(&P)" />
6 < Item id ="1" key ="MenuProjectItem1" value ="新建(&N)" />
7 < Item id ="2" key ="MenuProjectItem2" value ="打开(&O)" />
8 < Item id ="3" key ="MenuProjectItem3" value ="保存(&S)" />
9 < Item id ="5" key ="MenuProjectItem5" value ="退出(&X)" />
10 </ Project >
11 < Manage >
12 < Item id ="0" key ="MenuManage" value ="管理(&M)" />
13 < Item id ="1" key ="MenuManageItem1" value ="登录(&I)" />
14 < Item id ="2" key ="MenuManageItem2" value ="注销(&O)" />
15 < Item id ="3" key ="MenuManageItem3" value ="修改密码(&C)" />
16 < Item id ="4" key ="MenuManageItem4" value ="用户管理(&U)" />
17 </ Manage >
18 < Help >
19 < Item id ="0" key ="MenuHelp" value ="帮助(&H)" />
20 < Item id ="1" key ="MenuHelpItem1" value ="帮助内容(&H)" />
21 < Item id ="2" key ="MenuHelpItem2" value ="关于(&A)" />
22 </ Help >
23 </ Menu >
24 < Toolbar >
25 < Statusbar >
26 < Item id ="1" key ="StatusItem1" value ="用户名: " />
27 < Item id ="2" key ="StatusItem2" value ="用户组: " />
28 < Item id ="3" key ="StatusItem3" value ="上次登录时间: " />
29 < Item id ="4" key ="StatusItem4" value ="本次登录时间:" />
30 </ Statusbar >
31 </ Toolbar >
32 < Form >
33 < MainForm >
34 < Item id ="0" key ="MainForm" value ="xx" />
35 < Item id ="1" key ="buttonGo" value ="开始" />
36 < Item id ="2" key ="buttonStop" value ="停止" />
37 < Item id ="3" key ="groupBox1" value ="用户信息" />
38 < Item id ="4" key ="groupBox2" value ="常规数据" />
39 </ MainForm >
40 < UserLoginForm >
41 < Item id ="0" key ="UserLoginForm" value ="用户登录" />
42 < Item id ="1" key ="labelTitle" value ="xx" />
43 < Item id ="2" key ="labelUsername" value ="用户名" />
44 < Item id ="3" key ="labelPassword" value ="密码" />
45 < Item id ="4" key ="buttonLogin" value ="登录" />
46 </ UserLoginForm >
47 < ChangePasswordForm >
48 < Item id ="0" key ="ChangePasswordForm" value ="修改密码" />
49 < Item id ="1" key ="label1" value ="原密码" />
50 < Item id ="2" key ="label2" value ="新密码" />
51 < Item id ="3" key ="label3" value ="再输入" />
52 < Item id ="4" key ="buttonConfirm" value ="确认" />
53 < Item id ="5" key ="buttonCancel" value ="取消" />
54 </ ChangePasswordForm >
55 </ Form >
56 < Dialog >
57 < Title >
58 < Item id ="0" key ="0001" value ="xx" />
59 < Item id ="1" key ="0002" value ="添加测试" />
60 < Item id ="2" key ="0003" value ="添加用户" />
61 < Item id ="3" key ="0004" value ="修改密码" />
62 </ Title >
63 < Message >
64 < Item id ="0" key ="0000" value ="一切正常" />
65 < Item id ="1" key ="2001" value ="用户名或密码错误" />
66 < Item id ="5" key ="2002" value ="密码不一致" />
67 < Item id ="6" key ="2003" value ="用户名已存在" />
68 < Item id ="7" key ="2004" value ="添加用户成功" />
69 </ Message >
70 </ Dialog >
71 </ AirControl >
在Form里面,其每个子树分别对应一个窗体。XML每项有三个域,id 这个只是用来标号,程序中为用,key,value形成一个字典,key是控件
的名称,value是控件的text。在Dialog中key用数字编号。
做其他语言文件时,只用将value里面的值改成对应的语言即可。
当然,我们也不一定用XML来写语言文件,简单的ini文件也行。我现在觉得在一般的中小型程序中用XML,出于一个最基本的目的就是装B。
下面设计读取这个XML的类,
1
using
System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Xml;
6
7 namespace AirLibrary
8 {
9 /// <summary>
10 /// 本地化类
11 /// </summary>
12 public static class Localization
13 {
14 #region Property
15 public static string Lang { get; private set; }
16 public static bool HasLang { get; set; }
17 #endregion //Property
18
19 #region Attribute
20 private static Dictionary<string, Dictionary<string, string>> forms = new Dictionary<string, Dictionary<string, string>>();
21 private static Dictionary<string, string> menu = new Dictionary<string, string>();
22 private static Dictionary<string, string> toolbar = new Dictionary<string, string>();
23 private static Dictionary<string, string> dialog = new Dictionary<string, string>();
24 #endregion //Attribute
25
26 #region Method
27 public static void AddForm(string formName)
28 {
29 forms.Add(formName, new Dictionary<string, string>());
30 //formMap.Add(formName, count++);
31 }
32
33 /// <summary>
34 /// 加载语言文件
35 /// </summary>
36 /// <param name="lang">语言</param>
37 /// <returns></returns>
38 public static bool Load(string lang)
39 {
40 string path = "";
41 Localization.Lang = "English";
42
43 menu.Clear();
44 toolbar.Clear();
45 dialog.Clear();
46 exception.Clear();
47 foreach (Dictionary<string, string> form in forms.Values)
48 form.Clear();
49
50 switch (lang)
51 {
52 case "zh":
53 path = @"resources/lang-zh.xml";
54 break;
55 case "en":
56 path = @"resources/lang-en.xml";
57 break;
58 default:
59 path = @"resources/lang-zh.xml";
60 break;
61 }
62
63 return readLanguage(path);
64 }
65 #endregion //Method
66
67 #region Function
68 private static bool readLanguage(string path)
69 {
70 // Read the language file
71 XmlReader reader;
72 try
73 {
74 reader = XmlReader.Create(path);
75 }
76 catch (Exception)
77 {
78 return false;
79 }
80
81 // Begin to parase
82 try
83 {
84 reader.ReadToFollowing("AirControl");
85 Localization.Lang = reader.GetAttribute("language");
86
87 paraseXml(reader, "Menu", menu);
88 paraseXml(reader, "Toolbar", toolbar);
89
90 foreach (string formName in forms.Keys)
91 {
92 paraseXml(reader, formName, forms[formName]);
93 }
94 paraseXml(reader, "Dialog", dialog);
95 }
96 catch (Exception)
97 {
98 return false;
99 }
100 return true;
101 }
102
103 private static void paraseXml(XmlReader reader, string item, Dictionary<string, string> obj)
104 {
105 // Get the attribute key & value
106 reader.ReadToFollowing(item);
107
108 XmlReader subreader = reader.ReadSubtree();
109 while (subreader.Read())
110 {
111 if (subreader.NodeType == XmlNodeType.Element && subreader.Name == "Item")
112 obj.Add(subreader.GetAttribute("key"), subreader.GetAttribute("value"));
113 }
114 }
115 #endregion //Function
116
117 #region Property
118 public static Dictionary<string, string> Menu
119 {
120 get
121 {
122 return menu;
123 }
124 private set
125 { }
126 }
127
128 public static Dictionary<string, string> Toolbar
129 {
130 get
131 {
132 return toolbar;
133 }
134 private set
135 { }
136 }
137
138 public static Dictionary<string, Dictionary<string, string>> Forms
139 {
140 get
141 {
142 return forms;
143 }
144 private set
145 { }
146 }
147
148 public static Dictionary<string, string> Dialog
149 {
150 get
151 {
152 return dialog;
153 }
154 private set
155 { }
156 }
157 #endregion //Property
158 }
159}
160
这里我使用静态类来读取和保存,这样效率相对会高一些。读取XML时,我使用的是XmlReader,它使用流式读取,速度也比较快。
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Xml;
6
7 namespace AirLibrary
8 {
9 /// <summary>
10 /// 本地化类
11 /// </summary>
12 public static class Localization
13 {
14 #region Property
15 public static string Lang { get; private set; }
16 public static bool HasLang { get; set; }
17 #endregion //Property
18
19 #region Attribute
20 private static Dictionary<string, Dictionary<string, string>> forms = new Dictionary<string, Dictionary<string, string>>();
21 private static Dictionary<string, string> menu = new Dictionary<string, string>();
22 private static Dictionary<string, string> toolbar = new Dictionary<string, string>();
23 private static Dictionary<string, string> dialog = new Dictionary<string, string>();
24 #endregion //Attribute
25
26 #region Method
27 public static void AddForm(string formName)
28 {
29 forms.Add(formName, new Dictionary<string, string>());
30 //formMap.Add(formName, count++);
31 }
32
33 /// <summary>
34 /// 加载语言文件
35 /// </summary>
36 /// <param name="lang">语言</param>
37 /// <returns></returns>
38 public static bool Load(string lang)
39 {
40 string path = "";
41 Localization.Lang = "English";
42
43 menu.Clear();
44 toolbar.Clear();
45 dialog.Clear();
46 exception.Clear();
47 foreach (Dictionary<string, string> form in forms.Values)
48 form.Clear();
49
50 switch (lang)
51 {
52 case "zh":
53 path = @"resources/lang-zh.xml";
54 break;
55 case "en":
56 path = @"resources/lang-en.xml";
57 break;
58 default:
59 path = @"resources/lang-zh.xml";
60 break;
61 }
62
63 return readLanguage(path);
64 }
65 #endregion //Method
66
67 #region Function
68 private static bool readLanguage(string path)
69 {
70 // Read the language file
71 XmlReader reader;
72 try
73 {
74 reader = XmlReader.Create(path);
75 }
76 catch (Exception)
77 {
78 return false;
79 }
80
81 // Begin to parase
82 try
83 {
84 reader.ReadToFollowing("AirControl");
85 Localization.Lang = reader.GetAttribute("language");
86
87 paraseXml(reader, "Menu", menu);
88 paraseXml(reader, "Toolbar", toolbar);
89
90 foreach (string formName in forms.Keys)
91 {
92 paraseXml(reader, formName, forms[formName]);
93 }
94 paraseXml(reader, "Dialog", dialog);
95 }
96 catch (Exception)
97 {
98 return false;
99 }
100 return true;
101 }
102
103 private static void paraseXml(XmlReader reader, string item, Dictionary<string, string> obj)
104 {
105 // Get the attribute key & value
106 reader.ReadToFollowing(item);
107
108 XmlReader subreader = reader.ReadSubtree();
109 while (subreader.Read())
110 {
111 if (subreader.NodeType == XmlNodeType.Element && subreader.Name == "Item")
112 obj.Add(subreader.GetAttribute("key"), subreader.GetAttribute("value"));
113 }
114 }
115 #endregion //Function
116
117 #region Property
118 public static Dictionary<string, string> Menu
119 {
120 get
121 {
122 return menu;
123 }
124 private set
125 { }
126 }
127
128 public static Dictionary<string, string> Toolbar
129 {
130 get
131 {
132 return toolbar;
133 }
134 private set
135 { }
136 }
137
138 public static Dictionary<string, Dictionary<string, string>> Forms
139 {
140 get
141 {
142 return forms;
143 }
144 private set
145 { }
146 }
147
148 public static Dictionary<string, string> Dialog
149 {
150 get
151 {
152 return dialog;
153 }
154 private set
155 { }
156 }
157 #endregion //Property
158 }
159}
160
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 }
然后加载语言文件。
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 ;
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();
2 RefreshLanguage();
//
更新窗体语言
public static void RefreshLanguage(Form form)
{
form.Text = Localization.Forms[form.Name][form.Name];
SetControlsLanguage(form, Localization.Forms[form.Name]);
}
public static void RefreshLanguage(Form form)
{
form.Text = Localization.Forms[form.Name][form.Name];
SetControlsLanguage(form, Localization.Forms[form.Name]);
}
递归更新每个控件Text
1
///
<summary>
2 /// 设置control子控件语言
3 /// </summary>
4 /// <param name="control"> 父控件 </param>
5 /// <param name="obj"> 语言字典 </param>
6 public static void SetControlsLanguage(Control control, Dictionary < string , string > obj)
7 {
8 foreach (Control ctrl in control.Controls)
9 {
10 // set the control which one's key in the dictionary
11 string text = "" ;
12 if (obj.TryGetValue(ctrl.Name, out text))
13 ctrl.Text = text;
14
15 if (ctrl.HasChildren)
16 SetControlsLanguage(ctrl, obj);
17 }
18 }
2 /// 设置control子控件语言
3 /// </summary>
4 /// <param name="control"> 父控件 </param>
5 /// <param name="obj"> 语言字典 </param>
6 public static void SetControlsLanguage(Control control, Dictionary < string , string > obj)
7 {
8 foreach (Control ctrl in control.Controls)
9 {
10 // set the control which one's key in the dictionary
11 string text = "" ;
12 if (obj.TryGetValue(ctrl.Name, out text))
13 ctrl.Text = text;
14
15 if (ctrl.HasChildren)
16 SetControlsLanguage(ctrl, obj);
17 }
18 }
另外主窗体的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
16 // Refresh the statusbar language
17 foreach (ToolStripItem item in mainStatus.Items)
18 {
19 string text = "" ;
20 if (Localization.Toolbar.TryGetValue(item.Name, out text))
21 item.Text = text;
22 }
Dialog就直接调用Localization中的Dialog属性即可。
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
16 // Refresh the statusbar language
17 foreach (ToolStripItem item in mainStatus.Items)
18 {
19 string text = "" ;
20 if (Localization.Toolbar.TryGetValue(item.Name, out text))
21 item.Text = text;
22 }
需要转变为不同语言时只需要再调用一次Localization.Load方法。
小结:
这种实现多语言界面的方式我思考了很久,也在网上查了一些资料,最后设计了这样一种方式,XML中利用字典来记录控件的语言
在添加,读取时非常方便,Localization类做成静态类,在运行时就相当于一个常量,没有构造函数这样的开销,整个界面也可以再运行时
直接改变界面语言。
当然这种方法不一定是最好的, 如果有更好的方法欢迎指点。