終於有空整理下多語言實現思路。查閱已有方案,有用不同resx文件的,有每個控件動態設置的,有用反射去整的,頗為繁瑣。
結合項目中實現方法,並做簡化,實現通用的多語言切換方案,以做備忘。
它支持語言自定義添加與擴充,靈活易用,更易於維護。它以xml格式存儲語言信息,支持自定義語言、ToolTip等字串,支持即時切換。
一、語言格式:
每種語言對應一個xml格式文件,比如中文為Chinese.lng,英文為English.lng,置於程序運行目錄之Languages文件夾下,其存放位置可自定義。
本欲以反射獲取已有程序集之字串,卻未有成功,因為字串值初始化在InitializeComponent()中動態設置,反射不出來這些屬性值,所以手動提取,以NotePad++做批量處理,亦是方便。
文件分為自定義字串區(strings節點)、窗體語言文件區(forms節點),結構如下圖示:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <language Id="CHS" Name="Chinese" LocaleName="中文(簡體)" LangID="0x0409" CodePage="1252"> <strings> <INF_AppName>記事本</INF_AppName> <INF_NoTitle>無標題</INF_NoTitle> <INF_Information>提示</INF_Information> <INF_CannotFound>找不到 "{0}"</INF_CannotFound> <INF_ContentChanged>文件內容已被改變,要保存嗎?</INF_ContentChanged> </strings> <forms> <MainForm Text="記事本"> <miFile Text="文件(&F)" /> <miNew Text="新建(&N)" /> <miOpen Text="打開(&0)..." /> <miSave Text="保存(&S)" /> <miSaveAs Text="另存為(&A)..." /> <miPageSet Text="頁面設置(&U)..." /> <miPrint Text="打印(&P)..." /> <miExit Text="退出(&X)" /> ... <miHelp Text="幫助(&H)" /> <miHelpTopic Text="幫助主題(&H)" /> <miLanguage Text="語言" /> <miAbout Text="關於記事本(&A)" /> </MainForm> <GotoLineForm Text="跳轉到指定行"> <lblLine Text="行數(&L):" />
<txtLine ToolTip="請輸入您想要跳轉到的行號:" /> <btnOk Text="確定" /> <btnCancel Text="取消" /> </GotoLineForm> <SearchForm Text="查找"> <btnCancel Text="取消" /> <btnSearch Text="查找下一個(&F)" /> <lblContent Text="查找內容(&N):" /> <cbCaseSensitive Text="區分大小寫" /> <gbDirection Text="方向" /> <rbDown Text="向下" /> <rbUp Text="向上" /> </SearchForm> </forms> </language>
二、解析與加載類
仍然對每個窗體做遍歷,加載對應字串,提取語言文件語種信息。
此文件是個完整的語言管理器類,且可根據需要自行擴充,列碼如下:
//多語言管理類 using System; using System.Collections.Generic; using System.IO; using System.Windows.Forms; using System.Xml; namespace Notepad { //單個語言項 public class LanguageItem { private const string RootNodeName = "language"; private const string StringsNodeName = "strings"; private const string FormsNodeName = "forms"; private XmlNode stringsNode; private XmlNode formsNode; private string id; private string name; private string localeName; public LanguageItem(string fileName) { var xmlDoc = new XmlDocument(); xmlDoc.Load(fileName); var root = xmlDoc.DocumentElement; if (root != null) { this.id = GetAttributeValue(root, "Id"); this.name = GetAttributeValue(root, "Name"); this.localeName = GetAttributeValue(root, "LocaleName"); this.stringsNode = root.SelectSingleNode(StringsNodeName); this.formsNode = root.SelectSingleNode(FormsNodeName); } } public string Id { get { return this.id; } } public string Name { get { return this.name; } } public string LocaleName { get { return this.localeName; } } private void ApplyLanguage(Control control, XmlNode formNode, ToolTip toolTip = null) { if (string.IsNullOrEmpty(control.Name)) return; var ctrlNode = formNode.SelectSingleNode(control.Name); if (ctrlNode != null) { control.Text = GetAttributeValue(ctrlNode, "Text"); string tips = GetAttributeValue(ctrlNode, "ToolTip"); if (!string.IsNullOrEmpty(tips) && toolTip != null) toolTip.SetToolTip(control, tips); } //彈出菜單 if (control.ContextMenuStrip != null) ApplyMenuLanguage(control.ContextMenuStrip, formNode); foreach (Control ctrl in control.Controls) ApplyLanguage(ctrl, formNode, toolTip); //菜單項,特別遍歷 if (control is ToolStrip) { foreach (ToolStripItem toolItem in (control as ToolStrip).Items) ApplyMenuLanguage(toolItem, formNode); } } private void ApplyMenuLanguage(ToolStrip menu, XmlNode formNode) { foreach (ToolStripItem toolItem in menu.Items) ApplyMenuLanguage(toolItem, formNode); } private void ApplyMenuLanguage(ToolStripItem menuItem, XmlNode formNode) { if (string.IsNullOrEmpty(menuItem.Name)) return; var itemNode = formNode.SelectSingleNode(menuItem.Name); if (itemNode != null) { menuItem.Text = GetAttributeValue(itemNode, "Text"); menuItem.ToolTipText = GetAttributeValue(itemNode, "ToolTip"); } if (menuItem is ToolStripDropDownItem) { foreach (ToolStripItem item in (menuItem as ToolStripDropDownItem).DropDownItems) ApplyMenuLanguage(item, formNode); } } public bool LoadLanguageRes(ContainerControl cc) { if (cc == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(cc.Name) == null) return false; //創建ToolTip控件, 以支持ToolTip顯示 var toolTip = new ToolTip(); var formNode = formsNode.SelectSingleNode(cc.Name); cc.Text = GetAttributeValue(formNode, "Text"); foreach (Control ctrl in cc.Controls) ApplyLanguage(ctrl, formNode, toolTip); return true; } public bool LoadLanguageRes(ContainerControl cc, ToolStrip menu) { if (cc == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(cc.Name) == null) return false; var formNode = formsNode.SelectSingleNode(cc.Name); ApplyLanguage(menu, formNode); return true; } private string GetAttributeValue(XmlNode xmlNode, string attrName) { if (xmlNode.Attributes != null && xmlNode.Attributes[attrName] != null) return xmlNode.Attributes[attrName].Value; return string.Empty; } public string GetText(string textID, string defaultText = "") { if (stringsNode == null || !stringsNode.HasChildNodes) return defaultText; foreach (XmlNode node in stringsNode.ChildNodes) if (node.Name.Equals(textID)) return node.InnerText; return defaultText; } } public class LanguageList : List<LanguageItem> { } /// <summary> /// 多語言管理器 /// </summary> public static class ML { private static LanguageItem activeLanguage; private static LanguageList languages; //最初調用 public static int LoadLanguages(string searchPattern, string defaultLanguageId = "") { string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages"); return LoadLanguages(path, searchPattern, defaultLanguageId); } public static int LoadLanguages(string path, string searchPattern, string defaultLanguageId = "") { languages = new LanguageList(); if (!Directory.Exists(path)) return 0; var files = Directory.GetFiles(path, searchPattern); foreach (string file in files) languages.Add(new LanguageItem(file)); if (!string.IsNullOrEmpty(defaultLanguageId)) LoadLanguageById(defaultLanguageId); return languages.Count; } public static string ActiveLanguageId { get { return (activeLanguage != null) ? activeLanguage.Id : string.Empty; } } public static string[] LanguageLocalNames { get { if (languages == null || languages.Count == 0) return new string[0]; var names = new string[languages.Count]; for (int i = 0; i <= languages.Count - 1; i++) names[i] = languages[i].LocaleName; return names; } } public static LanguageItem ActiveLanguage { get { return activeLanguage; } } public static LanguageList Languages { get { return languages; } } public static bool LoadLanguageRes(ContainerControl cc) { return (ActiveLanguage != null) ? ActiveLanguage.LoadLanguageRes(cc) : false; } //為動態彈出菜單設計 public static bool LoadLanguageRes(ContainerControl cc, ToolStrip menu) { return (ActiveLanguage != null) ? ActiveLanguage.LoadLanguageRes(cc, menu) : false; } public static string Text(string textId, string defaultText = "") { return GetText(textId, defaultText); } public static string GetText(string textId, string defaultText = "") { return (ActiveLanguage != null) ? ActiveLanguage.GetText(textId, defaultText) : defaultText; } public static bool LoadLanguageById(string id) { if (languages == null) return false; foreach (var language in languages) { if (language.Id.Equals(id)) { activeLanguage = language; return true; } } return false; } public static bool LoadLanguageByIndex(int index) { if (index < 0 || index > languages.Count - 1) return false; activeLanguage = languages[index]; return true; } } }
OK,一行加載完成。
窗體創建事件中,加入加載語言代碼:
//改寫構造函數 public MainForm(string[] args) { InitializeComponent(); appName = ML.GetText("INF_AppName", APP_NAME); ML.LoadFormLanguage(this);
//若有未與控件關聯的彈出菜單,可加如下代碼實現:
//ML.LoadLanguageRes(this, pmMain); BuildLanguageMenuItems(); this.args = args; }
這樣就完成了語言加載。
這里有個多語言項生成菜單,其動態生成語言菜單項並加入事件觸發,函數片段如下:
private void BuildLanguageMenuItems() { if (ML.LanguageLocalNames.Length == 0) { miLanguage.Visible = false; return; } for (int i = 0; i <= ML.LanguageLocalNames.Length - 1; i++) { string ln = ML.LanguageLocalNames[i]; var menuItem = new ToolStripMenuItem(ln); //menuItem.CheckOnClick = true; menuItem.Tag = i; if (i == 0) menuItem.Checked = true; menuItem.Click += new EventHandler((sender, e) => { foreach (ToolStripMenuItem item in miLanguage.DropDownItems) item.Checked = (item == sender); ML.LoadLanguageByIndex((int)(sender as ToolStripItem).Tag); ML.LoadFormLanguage(this); }); miLanguage.DropDownItems.Add(menuItem); } }
功能完美實現。
四、自定義語言用法
語言串中,自定義語言定義在了strings節點,程序中如此調用:
MessageBox.Show(this.searchForm, string.Format(ML.GetText("INF_CannotFound", "找不到 \"{0}\""), this.searchText), ML.GetText("INF_Information", "提示"), MessageBoxButtons.OK, MessageBoxIcon.Warning);
switch (MessageBox.Show(ML.GetText("INF_ContentChanged", "文件內容已被改變,要保存嗎?"), ML.GetText("INF_Information", "提示"), MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question)) { case DialogResult.Yes: return Save(); case DialogResult.No: return true; case DialogResult.Cancel: return false; default: return true; }
頗為靈活。
五、效果圖:


Demo源碼下載:
