.NET WinForms本地化-替换ComponentResourceManager

8
在我目前的项目中(.NET Windows Forms应用程序),我有一个要求,即.NET窗体应该本地化,但本地化元素(仅翻译,不包括图像或控件位置)应来自数据库,以使最终用户能够根据需要修改控件可本地化属性(仅标题/文本)。
为了让开发人员不必承担本地化问题,对我而言最好的解决方案是在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);

        }
    }


}

我在这里找到了更多关于Localizer如何工作的解释:http://flylib.com/books/en/3.147.1.147/1/ - jm.
2个回答

7
我是Visual Studio开发人员本地化工具的作者(为了充分披露)。我强烈建议获取Guy Smith-Ferrier的书《.NET国际化,开发人员构建全球Windows和Web应用程序指南》。我相信那是正确的书(肯定是正确的作者),但您需要检查一下,因为我很久以前看过它(也许他自那时以来甚至写了一些更新的内容)。Guy是MSFT MVP和本地化大师。他向您展示如何做到您正在尝试的事情,在他的情况下,通过创建一个组件,您可以将其拖放到每个窗体的托盘区域。然后,该组件将允许您使用自己的“ComponentResourceManager”替换它(他的设计涉及几个类)。然后,您可以从任何源读取字符串,包括DB。如果我没记错,他自己的例子甚至使用了DB。您可能会在网上找到代码,而无需购买他的书,因为我认为他甚至可能在自己的网站上提供它。您还可以在值得信赖的图书购买网站上找到他的书中的免费段落,因为即使您不使用他的技术,该信息也是非常有价值的(很难在其他地方找到)。请注意,我曾经测试过他的代码(很久以前),它像广告一样有效。

非常感谢。我会去查看并将您的答案标记为正确。 - Dalibor Čarapić
@larry 我怎么才能用 winforms 替换它? - Smith
太棒了。谢谢。让我去读一整本书以获取一个快速的答案。 - Pangamma
@Pangamma 谁说你必须读完整本书。只需找到相关信息即可。虽然仍然不会简单,但如果你期望对一个固有的模糊且非常规问题(在现实世界中很少有人熟悉,绕过 Visual Studio 中正常的本地化方式)得到“快速”和简单的答案,那么祝你好运能找到更及时的答案。显然,OP认为这本书是一个“很好的参考资料”(见他的“EDIT”),我也同意。 - Larry
@Larry 好的,没问题。 - Pangamma

2
我这样进行了替换:
public class CustomCodeDomSerializer : CodeDomSerializer
{
    public override object Serialize(IDesignerSerializationManager manager, object value)
    {
        for (var i = 0; manager.Context[i] != null; i++)
        {
            var collection = manager.Context[i] as CodeStatementCollection;
            if (collection != null)
            {
                foreach (var statement in collection)
                {
                    var st = statement as CodeVariableDeclarationStatement;
                    if (st?.Type.BaseType == typeof(ComponentResourceManager).FullName)
                    {
                        var ctr = new CodeTypeReference(typeof(CustomComponentResourceManager));
                        st.Type = ctr;
                        st.InitExpression = new CodeObjectCreateExpression(ctr, new CodeTypeOfExpression(manager.GetName(value)));
                    }
                }
            }
        }
        var baseClassSerializer = (CodeDomSerializer)manager.GetSerializer(value.GetType().BaseType, typeof(CodeDomSerializer));
        var codeObject = baseClassSerializer.Serialize(manager, value);
        return codeObject;
    }
}

[DesignerSerializer(typeof(CustomCodeDomSerializer), typeof(CodeDomSerializer))]
public class CustomUserControl : UserControl { }

然后视图应该继承CustomUserControl而不是UserControl(或CustomForm : Form):

public partial class SomeView : CustomUserControl { ... }

然后会生成设计师文件的结果:

private void InitializeComponent()
{
    CustomComponentResourceManager resources = new CustomComponentResourceManager(typeof(SomeView));
    ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接