在我目前的项目中(.NET Windows Forms应用程序),我有一个要求,即.NET窗体应该本地化,但本地化元素(仅翻译,不包括图像或控件位置)应来自数据库,以使最终用户能够根据需要修改控件可本地化属性(仅标题/文本)。
为了让开发人员不必承担本地化问题,对我而言最好的解决方案是在VS设计器中将表单标记为可本地化。这将在表单.resx文件中放置所有可本地化属性值。
现在我的问题是如何从数据库提供翻译。一旦表单被标记为可本地化,VS表单设计器将把所有可本地化内容放入表单.resx文件中。设计器还会修改标准的designer.cs InitializeComponent方法,以便实例化ComponentResourceManager,然后使用该资源管理器加载对象(控件和组件)的可本地化属性。
我看到过人们构建自己的方法来应用Form及其控件的本地化属性的解决方案。我所见过的所有解决方案通常都归结为递归地遍历Form及其控件的Controls集合,并应用翻译。不幸的是,.NET Forms本地化并不那么简单,这种方法无法涵盖所有情况,特别是当您使用某些第三方控件时。例如:
然后我就能轻松解决其他所有问题了。
不幸的是,我没有找到如何做到这一点,所以在SO上发了一个问题,询问如何实现。
P.S. 我也可以接受其他符合以下要求的本地化解决方案: 1. 可以在不重新部署应用程序的情况下更改翻译 2. 当创建表单/用户控件时,开发人员不必担心翻译
谢谢。
编辑: Larry提供了一本书的很好的参考,其中有些代码部分地解决了我的问题。在那个帮助下,我能够创建自己的组件,用自定义的 MyResourceManager 替换了默认的ComponentResourceManager。 以下是一个名为Localizer的组件的代码示例,它将ComponentResourceManager替换为自定义的MyResourceManager,以便其他人也能从中受益:
为了让开发人员不必承担本地化问题,对我而言最好的解决方案是在VS设计器中将表单标记为可本地化。这将在表单.resx文件中放置所有可本地化属性值。
现在我的问题是如何从数据库提供翻译。一旦表单被标记为可本地化,VS表单设计器将把所有可本地化内容放入表单.resx文件中。设计器还会修改标准的designer.cs InitializeComponent方法,以便实例化ComponentResourceManager,然后使用该资源管理器加载对象(控件和组件)的可本地化属性。
我看到过人们构建自己的方法来应用Form及其控件的本地化属性的解决方案。我所见过的所有解决方案通常都归结为递归地遍历Form及其控件的Controls集合,并应用翻译。不幸的是,.NET Forms本地化并不那么简单,这种方法无法涵盖所有情况,特别是当您使用某些第三方控件时。例如:
this.navBarControl.OptionsNavPane.ExpandedWidth = ((int)(resources.GetObject("resource.ExpandedWidth")));
//
// radioGroup1
//
resources.ApplyResources(this.radioGroup1, "radioGroup1");
...
this.radioGroup1.Properties.Items.AddRange(new DevExpress.XtraEditors.Controls.RadioGroupItem[] {
new DevExpress.XtraEditors.Controls.RadioGroupItem(resources.GetString("radioGroup1.Properties.Items"), resources.GetString("radioGroup1.Properties.Items1")),
new DevExpress.XtraEditors.Controls.RadioGroupItem(resources.GetString("radioGroup1.Properties.Items2"), resources.GetString("radioGroup1.Properties.Items3"))});
我看到的所有解决方案都无法完成上述组件所需的翻译。
由于VS已经生成了在需要时提供翻译的代码,我的理想解决方案是以某种方式替换ComponentResourceManager为自己派生的类。 如果我可以只替换InitializeComponent中的以下行:
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
使用
System.ComponentModel.ComponentResourceManager resources = new MyComponentResourceManager(typeof(Form1));
然后我就能轻松解决其他所有问题了。
不幸的是,我没有找到如何做到这一点,所以在SO上发了一个问题,询问如何实现。
P.S. 我也可以接受其他符合以下要求的本地化解决方案: 1. 可以在不重新部署应用程序的情况下更改翻译 2. 当创建表单/用户控件时,开发人员不必担心翻译
谢谢。
编辑: Larry提供了一本书的很好的参考,其中有些代码部分地解决了我的问题。在那个帮助下,我能够创建自己的组件,用自定义的 MyResourceManager 替换了默认的ComponentResourceManager。 以下是一个名为Localizer的组件的代码示例,它将ComponentResourceManager替换为自定义的MyResourceManager,以便其他人也能从中受益:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.CodeDom;
using System.ComponentModel.Design;
namespace LocalizationTest
{
[Designer(typeof(LocalizerDesigner))]
[DesignerSerializer(typeof(LocalizerSerializer), typeof(CodeDomSerializer))]
public class Localizer : Component
{
public static void GetResourceManager(Type type, out ComponentResourceManager resourceManager)
{
resourceManager = new MyResourceManager(type);
}
}
public class MyResourceManager : ComponentResourceManager
{
public MyResourceManager(Type type) : base(type)
{
}
}
public class LocalizerSerializer : CodeDomSerializer
{
public override object Deserialize(IDesignerSerializationManager manager, object codeDomObject)
{
CodeDomSerializer baseSerializer = (CodeDomSerializer)
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
return baseSerializer.Deserialize(manager, codeDomObject);
}
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeDomSerializer baseSerializer =
(CodeDomSerializer)manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
if (codeObject is CodeStatementCollection)
{
CodeStatementCollection statements = (CodeStatementCollection)codeObject;
CodeTypeDeclaration classTypeDeclaration =
(CodeTypeDeclaration)manager.GetService(typeof(CodeTypeDeclaration));
CodeExpression typeofExpression = new CodeTypeOfExpression(classTypeDeclaration.Name);
CodeDirectionExpression outResourceExpression = new CodeDirectionExpression(
FieldDirection.Out, new CodeVariableReferenceExpression("resources"));
CodeExpression rightCodeExpression =
new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("LocalizationTest.Localizer"), "GetResourceManager",
new CodeExpression[] { typeofExpression, outResourceExpression });
statements.Insert(0, new CodeExpressionStatement(rightCodeExpression));
}
return codeObject;
}
}
public class LocalizerDesigner : ComponentDesigner
{
public override void Initialize(IComponent c)
{
base.Initialize(c);
var dh = (IDesignerHost)GetService(typeof(IDesignerHost));
if (dh == null)
return;
var innerListProperty = dh.Container.Components.GetType().GetProperty("InnerList", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy);
var innerList = innerListProperty.GetValue(dh.Container.Components, null) as System.Collections.ArrayList;
if (innerList == null)
return;
if (innerList.IndexOf(c) <= 1)
return;
innerList.Remove(c);
innerList.Insert(0, c);
}
}
}