C# Winform本地化
来源: | 作者:JYTEK | 发布时间: 2023-02-10 | 1619 次浏览 | 分享到:

关键词:Winform、本地化


1.简介

软件行业发展到今天,本地化问题一直都占据非常重要的位置,而且应该越来越被重视。对于开发人员而言,在编写程序之前,本地化问题是首先要考虑的一个问题,也许有时候这个问题已经在设计者的考虑范围之内,但终归要开发人员去做实现的。因此,如何实现本地化,是开发人员必须掌握的一项基本技能。

Visual Studio 项目系统为本地化窗体应用程序提供相当大的支持。要在C#中实现本地化,需要相关资源文件,比如要在一个软件中支持英文、中文两种语言,那么就必须有这两种语言的资源文件,这在C#中可以采用资源文件(后缀名为.resx)来实现。在Visual Studio开发环境中生成资源文件有两种方式:

  • 使项目系统为可本地化的用户界面元素(如窗体上的文本和图像)生成资源文件。然后将该资源文件生成到附属程序集中。上述资源文件称作基于窗体的资源。

  • 添加一个资源文件模板然后使用 XML 设计器编辑该模板。采取后一种方法的原因之一是为了生成在对话框和错误信息中出现的可本地化字符串。然后,必须编写代码以访问这些资源。上述资源文件称作项目资源。

通常,应对特定于 Windows 窗体应用程序中的某个窗体的所有资源使用基于窗体的资源。应对所有不是基于窗体的用户界面字符串和图像(如错误消息)使用项目资源。

下面我们将通过具体的例子来演示如何使用基于窗体的资源和项目资源。然后我们会通过一个辅助类来实现更为简单的程序本地化过程。


2.Winform本地化基础操作

关于Unicode

Unicode是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。许多亚洲语言比如中文含有许多非Unicode字符,如果编程语言不支持Unicode那么在多语言环境下就会变成乱码。

Windows 窗体应用程序是完全支持 Unicode 的,这意味着每个字符都由一个唯一的数字表示,而与平台、程序或语言无关。因此我们无须担心由于语言问题应用程序可能会出现无法识别的乱码现象。有关 Unicode 的更多信息,请查看参考链接中的Unicode 联合会网站。


区域性名称和标识符

在.NET Framework中有专门的System.Globalization命名空间用于实现本地化,其中 CultureInfo 类保存区域性特定的信息,如关联的语言、子语言、国家/地区、日历和区域性约定。此类还提供对 DateTimeFormatInfo、NumberFormatInfo、CompareInfo 和 TextInfo 的区域性特定实例的访问。这些对象包含区域性特定操作(如大小写、格式化日期和数字以及比较字符串)所需的信息。当对界面语言修改时,我们需要提供给CultureInfo类一个语言字符串(叫做区域性名称)实现区域初始化,而这个字符串需要满足一定的格式要求。例如中国中文是“zh-CN”,台湾中文是“zh-TW”,美国英语是“en-US”。具体的格式要求可以查看参考链接区域性名称和标识符。


本地化Windows窗体

下面我们将通过一个简单的Windows窗体程序演示如何使用基于窗体的资源和项目资源对程序进行本地化。

  • 基于窗体的资源

1.在Visual Studio中新建一个名称为“Winform本地化1”的窗体应用程序;

2.在窗体的属性窗口中,将窗体的Localizable属性修改为True,而Language属性已经设置为(Default);

3.在窗体中放入一个button控件,将它的Text属性设置为Hello World

4.将窗体的Language属性设置为“德语(德国)”,将button的Text属性设置为Hallo Welt

5.重复上述步骤,将窗体的Language属性设置为“法语(法国)”,将button的Text属性设置为Bonjour le Monde,由于法语字母较长,可以调整button大小来容纳较长的字符串;

6.保存和生成解决方案。打开解决方案资源管理器,可以看到Form1.cs下面的资源文件,这些文件我们刚才在窗体中修改Language属性时自动生成的。Form1.resx 是默认区域性的资源文件,它将生成到主程序集中。Form1.de-DE.resx 是在德国讲的德语的资源文件,而Form1.fr-FR.resx 是在法国讲的法语的资源文件。双击打开任意.resx文件可以查看其中的内容,比如Form1.de-DE.resx中就显示了我们刚才在button中输入的Text,如图1所示。如果窗体上有更多的控件,我们可以手动在这个资源文件中添加更多信息,需要注意的是名称一列格式必须是控件名称(与控件属性中的text属性完全一致) + .Text;


图1 Form1.de-DE.resx文件中的信息

7.启动程序,我们将看到button中将出现英语、法语或者德语的问候,具体看到哪一种语言取决于操作系统的当前显示语言。如果Windows系统支持多界面语言并且已经安装了多语言用户界面包,可以在控制面板中更改用户界面语言。下面我们将介绍如何通过编程的方式去设置用户界面的区域性,从而使我们的程序可以显示其它的语言资源;

8.在代码编辑器的引用部分添加对以下两个命名空间的引用:

using System.Globalization;

using System.Threading;

9.在窗体构造函数中输入以下代码,用于修改当前程序界面的显示语言,代码如下所示。其中”fr-FR”指代区域名称,与Form1.fr-FR.resx文件名相对应:

Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");

10.运行程序之后我们可以看到button的Text中将出现法语的问候。如果在代码中把区域名称修改为”de-DE”,就可以看到德语的问候;如果变为空字符串就会显示默认的英语的问候。


  • 项目资源

使用基于窗体的资源让我们可以让我们对当前的界面元素进行本地化,但是对于对话框或者错误信息中的元素我们需要使用项目资源对其进行本地化。下面我们将手动向项目中添加资源文件并编辑它们,然后在窗体中放入一个新的控件并对其进行本地化。

1.在项目中选择添加->新建项,在对话框中选择资源文件,文件名称一栏中填入”WinFormStrings.resx”,它将包含英语的后备资源。每当应用程序找不到更适合于用户界面区域性的资源时,就将访问这些资源;

2.双击打开刚才创建的资源文件,在名称列中输入strMessage,在值列中输入 Hello World,保存文件;

3.重复刚才的步骤再次创建德语和法语的资源文件,并且按照图2在文件中输入字符串信息,WinFormStrings.de-DE.resx 文件将包含特定于在德国讲的德语的资源。WinFormStrings.fr-FR.resx 文件将包含特定于在法国讲的法语的资源;


图2 资源文件中的字符串信息

4. 在代码编辑器的引用部分导入以下命名空间:

using System.Resources;

5. 双击button添加Click事件,在方法中添加如下的代码。其中ResourceManager 构造函数带两个参数。第一个参数是不带区域性和 .resx 后缀的资源文件的名称,第二个参数是主程序集。这里当按下button后,对话框中将显示刚才在资源文件中填写的strMessage信息,显示的语言类型还是通过刚才的CultureInfo的参数来设置;

1. private void button1_Click(object sender, EventArgs e)  

2. {  

3.     ResourceManager LocRM = new ResourceManager("Winform本地化1.WinFormStrings"typeof(Form1).Assembly);  

4.     MessageBox.Show(LocRM.GetString("strMessage"));  

5. } 


6. 运行程序,当点击button后会显示项目资源中填入的字符串资源。

最后我们可以查看项目生成的可执行文件。在debug目录(根据项目配置或者在release目录)中会自动创建语言子目录,如de-DE和fr-FR。在子目录下保存了对应语言的资源dll文件,这个文件就是只包含本地化资源的附属程序集。可以使用ildasm工具打开此程序集查看已嵌入资源的程序集清单以及一个定义好的地区。


3.进阶操作

  • 借助辅助类轻松实现本地化

在刚才的例子中我们通过窗体的Localizable和Language属性对界面进行了本地化,这种方式虽然很简洁,但是会有一些局限性。因为这种方式需要在窗体初始化之前也就是在窗体构造函数中的InitializeComponent方法之前设定CurrentUICulture属性(用户界面的区域性),这样的话在程序运行过程中很难再去把界面更改为其它语言。

下面我们将借助一个辅助类,从而极为方便的在运行时实现界面的本地化。

首先我们仍然需要新建一个窗体程序,然后在项目中添加一个LanguageHelper辅助类,具体代码如下:

1. public class LanguageHelper  

2. {  

3.     #region SetAllLang  

4.     /// <summary>  

5.     /// Set language  

6.     /// </summary>  

7.     /// <param name="lang">language:zh-CN, en-US</param>  

8.     private static void SetAllLang(string lang)  

9.     {  

10.         System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(lang);  

11.         Form frm = null;  

12.         string name = "MainForm";  

13.         frm = (Form)Assembly.Load("CameraTest").CreateInstance(name);  

14.         if (frm != null)  

15.         {  

16.             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager();  

17.             resources.ApplyResources(frm, "$this");  

18.             AppLang(frm, resources);  

19.         }  

20.     }  

21.     #endregion  

22.   

23.     #region SetLang  

24.     /// <summary>  

25.     ///   

26.     /// </summary>  

27.     /// <param name="lang">language:zh-CN, en-US</param>  

28.     /// <param name="form">the form you need to set</param>  

29.     /// <param name="formType">the type of the form </param>  

30.     public static void SetLang(string lang, Form form, Type formType)  

31.     {  

32.         System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(lang);  

33.         if (form != null)  

34.         {  

35.             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(formType);  

36.             resources.ApplyResources(form, "$this");  

37.             AppLang(form, resources);  

38.         }  

39.     }  

40.     #endregion  

41.   

42.     #region AppLang for control  

43.     /// <summary>  

44.     ///  loop set the propery of the control  

45.     /// </summary>  

46.     /// <param name="control"></param>  

47.     /// <param name="resources"></param>  

48.     private static void AppLang(Control control, System.ComponentModel.ComponentResourceManager resources)  

49.     {  

50.         if (control is MenuStrip)  

51.         {  

52.             resources.ApplyResources(control, control.Name);  

53.             MenuStrip ms = (MenuStrip)control;  

54.             if (ms.Items.Count > 0)  

55.             {  

56.                 foreach (ToolStripMenuItem c in ms.Items)  

57.                 {  

58.                     AppLang(c, resources);  

59.                 }  

60.             }  

61.         }  

62.         foreach (Control c in control.Controls)  

63.         {  

64.             resources.ApplyResources(c, c.Name);  

65.             AppLang(c, resources);  

66.         }  

67.     }  

68.     #endregion  

69.   

70.     #region AppLang for menuitem  

71.     /// <summary>  

72.     /// set the toolscript   

73.     /// </summary>  

74.     /// <param name="item"></param>  

75.     /// <param name="resources"></param>  

76.     private static void AppLang(ToolStripMenuItem item, System.ComponentModel.ComponentResourceManager resources)  

77.     {  

78.         if (item is ToolStripMenuItem)  

79.         {  

80.             resources.ApplyResources(item, item.Name);  

81.             ToolStripMenuItem tsmi = (ToolStripMenuItem)item;  

82.             if (tsmi.DropDownItems.Count > 0)  

83.             {  

84.                 foreach (ToolStripMenuItem c in tsmi.DropDownItems)  

85.                 {  

86.                     AppLang(c, resources);  

87.                 }  

88.             }  

89.         }  

90.     }  

91.     #endregion  

92. }  

 

我们在解决方案资源管理器中可以看到名称为Form1.resx的资源文件,这是每个项目自动生成的与窗体绑定的资源文件,也就是基于窗体的资源文件,用来保存用户界面元素。浏览进入项目的本地文件夹中,找到此文件并进行复制和重命名,命名格式必须为Form名.语言名.rexs,其中语言名必须符合前面区域性名称和标识符部分中的格式,否则不会被CultureInfo类识别。这里我们新增加了保存简体中文、繁体中文和英语的资源文件,如图3所示。


图3 复制的资源文件


这时候我们需要重新打开项目才可以在解决方案资源管理器中看到刚才新建的文件,我们可以看到虽然在Form1.cs下已经可以看到新的文件,但是图标是虚线也没有箭头,与自动创建的Form1.resx不太一样,因为此时文件还没有被添加进项目中,我们需要单击文件右键选择包括在项目中,这样文件就全部被添加到项目中来了,如图4所示。


图4 把资源文件包括在项目中


我们要回到窗体中创建如图5所示的用户界面,窗体上包括许多常用的控件比如button、textBox、groupBox、radioButton、menuStrip和pictureBox等。我们对界面设置了简体中文、繁体中文和英语三种语言,选中对应的单选按钮后界面就会切换到选中的语言


图5 用户界面


然后我们就需要在对应语言的资源文件中填入信息了,首先我们编辑基于窗体的资源文件。比如在Form1.en-Us.resx文件中我们将写入英语字符串,如图6所示。其余两种语言类似。


图6 Form1.en-Us.resx文件中的字符串信息


对于图像和对话框信息,我们仍然需要手动创建项目资源文件。比如我们在项目中添加名称为Resources.en-Us.resx的项目资源文件用来保存美国英语的信息。在资源编辑器的字符串页面中编写按下按钮后对话框将会显示的信息,如图7所示。分别创建Resources.zh-CN.resx和Resources.zh-TW.resx文件并写入中国中文和台湾中文的信息。


图7 Resources.en-Us.resx文件中的字符串信息

 

除了字符串信息,我们还需要在资源文件中添加图像信息,这里是每一个国家的国旗,国旗应当根据国家的不同而改变。使用资源编辑器重新打开Resources.en-Us.resx文件并选择图像类别,添加项目文件夹下的americanflag.bmp文件。为了实现图像的本地化,图像必须在所有语言中有相同的名称,在这里所有的图像名称是Flag。可以在属性编辑器中给图像重命名,还可以指定图像是链接式还是嵌入式。为了提高资源的性能,图像默认为在编译时链接,此时图像文件必须与链接文件一起发布。如果要在程序集中嵌入图像就可以把Persistence属性修改为嵌入在.resx中,如图8所示。分别在另两个项目资源文件中添加对应语言的国旗本地化版本。


图8 Resources.en-Us.resx文件中的图像信息

 

最后我们就需要编写少量的代码来实现运行时界面语言的切换。双击按钮创建按钮的Click事件,在事件方法中输入以下的代码。关于代码有两点说明:

这里我们调用了LanguageHelper辅助类中的SetLang方法来修改语言,其中方法的第一个参数字符串的名称需要与刚才创建的资源文件的名字一致,第二个参数表示需要修改语言的窗体名称(this代表当前窗体),第三个参数表示窗体类型;

最后两行代码用来访问Properties中的Resources资源,比如字符串和图像信息。执行完SetLang方法后区域信息就已经被修改,然后程序就会访问对应语言的资源文件。

1. private void buttonSelect_Click(object sender, EventArgs e)  

2. {  

3.     if (radioButtonCN.Checked)  

4.     {  

5.         LanguageHelper.SetLang("zh-Cn"thistypeof(Form1));              

6.     }  

7.     if (radioButtonTW.Checked)  

8.     {  

9.         LanguageHelper.SetLang("zh-Tw"thistypeof(Form1));  

10.     }  

11.     if (radioButtonEnglish.Checked)  

12.     {  

13.         LanguageHelper.SetLang("en-Us"thistypeof(Form1));  

14.     }  

15.     pictureBoxFlag.Image = Properties.Resources.Flag;  

16.     MessageBox.Show(Properties.Resources.PopMessage);  

17. }  


运行程序可以看到切换单选按钮时,界面语言也会自动进行切换,如图9所示。


图9 程序运行结果


  • 多语言应用工具包

刚才我们对资源文件的修改都是采用手动翻译的方式,其实Visual Studio本身包含了一个多语言应用工具包扩展,将多语言应用工具包与 Visual Studio 结合使用,可针对WPF, WinForms, Windows Phone等项目简化本地化工作流。该工具包有助于使用本地化文件管理、翻译支持以及编辑工具本地化应用。该扩展通过专注于以下领域来创建更简单的翻译工作流:

与 Visual Studio IDE 集成:使用标准的 Visual Studio 菜单和对话框向项目解决方案中添加翻译文件并对其进行管理。

伪语言集成:通过识别开发过程中出现的本地化问题,对本地化的应用进行“内部”测试。这些问题可能包括硬编码字符串、连接的字符串或截断的字符串以及处理不同语言时出现的视觉问题。伪翻译采用本地化行业标准 XLIFF 文件格式进行存储,并且可以像任何其他语言翻译一样编辑这些翻译。因此你可以对伪翻译测试进行更细化的控制。

Microsoft 语言门户集成:通过 Microsoft 术语服务 API(需要有效的 Internet 连接)获取来自实际 Microsoft 产品的术语和 UI 翻译。

机器翻译器集成:通过集成的 Microsoft Translator 服务(需要有效的 Internet 连接)获取建议翻译。通过与 Microsoft Translator 服务集成,可以快速可视化多种语言并对其进行测试,而无需人工翻译的协助。

专用本地化编辑器:用于轻松编辑已翻译的字符串。通过集成的 Microsoft 语言门户和 Microsoft Translator 服务(需要有效的 Internet 连接)快速获取翻译和建议翻译。你还可以通过调整伪翻译和实际翻译快速编辑 XLIFF 文件中存储的数据。

这里我们就不再对这个工具包进行详细的介绍了,具体的使用方法可以参考文末的链接。


  • AutoSize 属性和 TableLayoutPanel 控件

当在本地化您的应用程序时可能遇到的困难之一是不同长度的文本字符串会造成对布局的更改。AutoSize 属性和TableLayoutPanel 控件在创建能够适应在设计时无法预知的不同文本字符串长度的布局时很有用。

AutoSize 属性导致控件按其内容调整自身的大小。TableLayoutPanel 控件提供了按比例调整大小的能力,它使控件可以在调整大小以适应其内容时保持相对比例不变。对于控件来讲,只需要将其AutoSize属性设置成True即可,而对于TableLayoutPanel则需要在列和行样式中把大小类型修改为自动调整大小,如图10所示。


图10 修改TableLayoutPanel为自动调整


4.参考链接

Unicode 联合会网站:http://www.unicode.org/

区域性名称和标识符:https://msdn.microsoft.com/en-us/library/cc233982.aspx

全球化 Windows 窗体:https://msdn.microsoft.com/zh-cn/library/9xdxwwkc(VS.90).aspx

多语言应用工具包 4.0:https://developer.microsoft.com/zh-cn/windows/develop/multilingual-app-toolkit