C#实现多语言界面程序
一直想做一个多语言的程序,研究了一下.net的本地化方法,觉得做起来比较麻烦,而且不能快速切换,就自己琢磨着写一个。
以我做的一个C# winform 项目为例。
在建立C#实现多语言界面程序之前,首先设计多语言文件,这里我用XML来保存,基本结构如下。
- < ?xml version = "1.0" encoding = "GB2312"?>
- < AirControl language="简体中文">
- < Menu>
- < Project>
- < Item id="0" key="MenuProject" value="项目(&P)" />
- < Item id="1" key="MenuProjectItem1" value="新建(&N)" />
- < Item id="2" key="MenuProjectItem2" value="打开(&O)" />
- < Item id="3" key="MenuProjectItem3" value="保存(&S)" />
- < Item id="5" key="MenuProjectItem5" value="退出(&X)" />
- < /Project>
- < Manage>
- < Item id="0" key="MenuManage" value="管理(&M)" />
- < Item id="1" key="MenuManageItem1" value="登录(&I)" />
- < Item id="2" key="MenuManageItem2" value="注销(&O)" />
- < Item id="3" key="MenuManageItem3" value="修改密码(&C)" />
- < Item id="4" key="MenuManageItem4" value="用户管理(&U)" />
- < /Manage>
- < Help>
- < Item id="0" key="MenuHelp" value="帮助(&H)" />
- < Item id="1" key="MenuHelpItem1" value="帮助内容(&H)" />
- < Item id="2" key="MenuHelpItem2" value="关于(&A)" />
- < /Help>
- < /Menu>
- < Toolbar>
- < Statusbar>
- < Item id="1" key="StatusItem1" value="用户名: " />
- < Item id="2" key="StatusItem2" value="用户组: " />
- < Item id="3" key="StatusItem3" value="上次登录时间: " />
- < Item id="4" key="StatusItem4" value="本次登录时间:" />
- < /Statusbar>
- < /Toolbar>
- < Form>
- < MainForm>
- < Item id="0" key="MainForm" value="xx" />
- < Item id="1" key="buttonGo" value="开始" />
- < Item id="2" key="buttonStop" value="停止" />
- < Item id="3" key="groupBox1" value="用户信息" />
- < Item id="4" key="groupBox2" value="常规数据" />
- < /MainForm>
- < UserLoginForm>
- < Item id="0" key="UserLoginForm" value="用户登录" />
- < Item id="1" key="labelTitle" value="xx" />
- < Item id="2" key="labelUsername" value="用户名" />
- < Item id="3" key="labelPassword" value="密码" />
- < Item id="4" key="buttonLogin" value="登录" />
- < /UserLoginForm>
- < ChangePasswordForm>
- < Item id="0" key="ChangePasswordForm" value="修改密码" />
- < Item id="1" key="label1" value="原密码" />
- < Item id="2" key="label2" value="新密码" />
- < Item id="3" key="label3" value="再输入" />
- < Item id="4" key="buttonConfirm" value="确认" />
- < Item id="5" key="buttonCancel" value="取消" />
- < /ChangePasswordForm>
- < /Form>
- < Dialog>
- < Title>
- < Item id="0" key="0001" value="xx" />
- < Item id="1" key="0002" value="添加测试" />
- < Item id="2" key="0003" value="添加用户" />
- < Item id="3" key="0004" value="修改密码" />
- < /Title>
- < Message>
- < Item id="0" key="0000" value="一切正常" />
- < Item id="1" key="2001" value="用户名或密码错误" />
- < Item id="5" key="2002" value="密码不一致" />
- < Item id="6" key="2003" value="用户名已存在" />
- < Item id="7" key="2004" value="添加用户成功" />
- < /Message>
- < /Dialog>
- < /AirControl>
这里是语言文件的局部,主体分为四个部分,Menu, Toolbar, Form 和 Dialog,分别对应菜单,工具栏,窗体和对话框的显示字符串。
在Form里面,其每个子树分别对应一个窗体。XML每项有三个域,id 这个只是用来标号,程序中为用,key,value形成一个字典,key是控件的名称,value是控件的text。在Dialog中key用数字编号。
做其他语言文件时,只用将value里面的值改成对应的语言即可。
当然,我们也不一定用XML来写语言文件,简单的ini文件也行。下面设计读取这个XML的类,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Xml;
- namespace AirLibrary
- {
- /**//// < summary>
- /// 本地化类
- /// < /summary>
- public static class Localization
- {
- Property#region Property
- public static string Lang { get; private set; }
- public static bool HasLang { get; set; }
- #endregion //Property
- Attribute#region Attribute
- private static Dictionary< string, Dictionary< string, string>> forms = new Dictionary< string, Dictionary< string, string>>();
- private static Dictionary< string, string> menu = new Dictionary< string, string>();
- private static Dictionary< string, string> toolbar = new Dictionary< string, string>();
- private static Dictionary< string, string> dialog = new Dictionary< string, string>();
- #endregion //Attribute
- Method#region Method
- public static void AddForm(string formName)
- {
- forms.Add(formName, new Dictionary< string, string>());
- //formMap.Add(formName, count++);
- }
- /**//// < summary>
- /// 加载语言文件
- /// < /summary>
- /// < param name="lang">语言< /param>
- /// < returns>< /returns>
- public static bool Load(string lang)
- {
- string path = "";
- Localization.Lang = "English";
- menu.Clear();
- toolbar.Clear();
- dialog.Clear();
- exception.Clear();
- foreach (Dictionary< string, string> form in forms.Values)
- form.Clear();
- switch (lang)
- {
- case "zh":
- path = @"resources/lang-zh.xml";
- break;
- case "en":
- path = @"resources/lang-en.xml";
- break;
- default:
- path = @"resources/lang-zh.xml";
- break;
- }
- return readLanguage(path);
- }
- #endregion //Method
- Function#region Function
- private static bool readLanguage(string path)
- {
- // Read the language file
- XmlReader reader;
- try
- {
- reader = XmlReader.Create(path);
- }
- catch (Exception)
- {
- return false;
- }
- // Begin to parase
- try
- {
- reader.ReadToFollowing("AirControl");
- Localization.Lang = reader.GetAttribute("language");
- paraseXml(reader, "Menu", menu);
- paraseXml(reader, "Toolbar", toolbar);
- foreach (string formName in forms.Keys)
- {
- paraseXml(reader, formName, forms[formName]);
- }
- paraseXml(reader, "Dialog", dialog);
- }
- catch (Exception)
- {
- return false;
- }
- return true;
- }
- private static void paraseXml(XmlReader reader, string item, Dictionary< string, string> obj)
- {
- // Get the attribute key & value
- reader.ReadToFollowing(item);
- XmlReader subreader = reader.ReadSubtree();
- while (subreader.Read())
- {
- if (subreader.NodeType == XmlNodeType.Element && subreader.Name == "Item")
- obj.Add(subreader.GetAttribute("key"), subreader.GetAttribute("value"));
- }
- }
- #endregion //Function
- Property#region Property
- public static Dictionary< string, string> Menu
- {
- get
- {
- return menu;
- }
- private set
- { }
- }
- public static Dictionary< string, string> Toolbar
- {
- get
- {
- return toolbar;
- }
- private set
- { }
- }
- public static Dictionary< string, Dictionary< string, string>> Forms
- {
- get
- {
- return forms;
- }
- private set
- { }
- }
- public static Dictionary< string, string> Dialog
- {
- get
- {
- return dialog;
- }
- private set
- { }
- }
- #endregion //Property
- }
- }
这里我使用静态类来读取和保存,这样效率相对会高一些。读取XML时,我使用的是XmlReader,它使用流式读取,速度也比较快。
Forms, Menu, Toolbar, Dialog几个属性分别对应XML中的子树,使用.net中的Dictionary范型,Forms嵌套了一层Dictionary。
Load方法是加载语言文件,readLanguage 和paraseXML 函数对XML进行解析,并保存字符串到对应的属性中。
AddForm这个方法是将每个窗体的动态的添加到forms 里面。
在程序开始main 函数中,首先调用AddForm方法,添加所有窗体。
- // 添加所有窗体用于本地化(按XML中顺序)
- private static void AddForm()
- {
- Localization.AddForm("MainForm");
- Localization.AddForm("UserLoginForm");
- Localization.AddForm("UserManageForm");
- Localization.AddForm("ChangePasswordForm");
- }
然后加载语言文件。
- if (!Localization.Load("zh"))
- {
- MessageBox.Show("无法加载语言配置文件, 将显示英文.", "错误", MessageBoxButtons.OK,
- MessageBoxIcon.Exclamation);
- Localization.HasLang = false;
- }
- else
- Localization.HasLang = true;
在每个Form的Load事件中初始化每个控件的Text。
- if (Localization.HasLang)
- RefreshLanguage();
- // 更新窗体语言
- public static void RefreshLanguage(Form form)
- {
- form.Text = Localization.Forms[form.Name][form.Name];
- SetControlsLanguage(form, Localization.Forms[form.Name]);
- }
- 递归更新每个控件Text
- /// < summary>
- /// 设置control子控件语言
- /// < /summary>
- /// < param name="control">父控件< /param>
- /// < param name="obj">语言字典< /param>
- public static void SetControlsLanguage(Control control, Dictionary< string, string> obj)
- {
- foreach (Control ctrl in control.Controls)
- {
- // set the control which one's key in the dictionary
- string text = "";
- if (obj.TryGetValue(ctrl.Name, out text))
- ctrl.Text = text;
- if (ctrl.HasChildren)
- SetControlsLanguage(ctrl, obj);
- }
- }
另外主窗体的Menu和Toolbar,我采用以下的方法更新。
- // Refresh the menu language
- foreach (ToolStripMenuItem topItem in MainMenuStrip.Items)
- {
- topItem.Text = Localization.Menu[topItem.Name];
- foreach (ToolStripItem item in topItem.DropDownItems)
- {
- if (item is ToolStripMenuItem)
- {
- string text = "";
- if (Localization.Menu.TryGetValue(item.Name, out text))
- item.Text = text;
- }
- }
- }
- // Refresh the statusbar language
- foreach (ToolStripItem item in mainStatus.Items)
- {
- string text = "";
- if (Localization.Toolbar.TryGetValue(item.Name, out text))
- item.Text = text;
- }
Dialog就直接调用Localization中的Dialog属性即可。
需要转变为不同语言时只需要再调用一次Localization.Load方法。
这样,就完成了C#实现多语言界面程序。
小结:
这种C#实现多语言界面程序的方式我思考了很久,也在网上查了一些资料,最后设计了这样一种方式,XML中利用字典来记录控件的语言在添加,读取时非常方便,Localization类做成静态类,在运行时就相当于一个常量,没有构造函数这样的开销,整个界面也可以再运行时直接改变界面语言。当然这种方法不一定是最好的, 如果有更好的方法欢迎指点。