Локализация .NET WinForms-замена ComponentResourceManager

в моем текущем проекте (приложение .NET Windows Forms) у меня есть требование, чтобы .NET windows forms были локализованы, но элементы локализации (только переводы, а не изображения или позиции управления) должны поступать из базы данных, чтобы позволить конечным пользователям изменять свойства локализуемого элемента управления (только заголовок/текст), как они хотят. Чтобы разработчики не обременяли себя проблемами локализации, лучшим решением для меня было бы просто отметить форму как Локализуется в дизайнере VS. Это поместит все локализуемые значения свойств в формы .файл resx.

теперь моя проблема заключается в том, чтобы предоставить переводы из базы данных. В тот момент, когда форма помечена как Локализуемая, конструктор VS Forms разместит все, что может быть локализовано, - это формы .файл resx. Конструктор также изменит стандартный конструктор.cs InitializeComponent метод, так что он создает экземпляр ComponentResourceManager и затем использовать этот менеджер ресурсов загрузить локализуемые свойства объектов (элементов управления и компонентов).

Я видел решения, в которых люди создали свой собственный метод применения локализованных свойств к форме и ее элементам управления. Все решения, которые я видел, обычно сводятся к рекурсивной итерации через коллекцию элементов управления Form и ее элементов управления и применению переводов. К сожалению, локализация .NET Forms не так проста, и это не охватывает все сценарии, особенно если у вас есть некоторые 3rd партия контролирует. Например:

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));

затем я все остальное можно было решить без проблем.

к сожалению, я не нашел, как я мог бы сделать что-то подобное, поэтому здесь я так задаю вопрос о том, как это можно сделать.

С. П. Я бы также принял любое другое решение локализации, которое соответствует требованиям: 1. Изменение переводов должно быть возможно без повторного развертывания приложения 2. Разработчик не должен заботиться о переводе при создании форм / пользовательских элементов управления

спасибо вы.

изменить: Ларри дал отличную ссылку на книгу, в которой есть код, который частично решает мою проблему. С этой помощью я смог создать свой собственный компонент, который заменяет ComponentResourceManager по умолчанию в методе InitializeComponent. Вот код для компонента под названием 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);

        }
    }


}

2 ответов


Я автор инструмент локализации для разработчиков Visual Studio (в интересах полного раскрытия). Я настоятельно рекомендую получить копию книги Гая Смита-Ферье ".NET Internationalization, руководство разработчика по созданию глобальных окон и веб-приложений". Я считаю, что это правильная книга в любом случае (правильный автор наверняка), но вам нужно будет проверить, так как прошло много времени с тех пор, как я смотрел на нее (и, возможно, он даже написал что-то новое с тех пор). Парень MSFT MVP и гуру локализации. Он показывает вам, как сделать именно то, что вы пытаетесь, в его случае, создав компонент, который вы можете перетащить в область лотка каждой из ваших форм. Затем компонент позволит вам заменить "ComponentResourceManager" на свой собственный (в его дизайне участвует несколько классов). Затем вы можете прочитать свои строки из любого источника, включая DB. IIRC, его собственный пример даже использует DB. Вероятно, вы можете найти код в интернете без необходимости покупать его книга, так как я думаю, что он может даже предоставить ее на своем собственном сайте. Вы также можете найти бесплатные отрывки из его книги на авторитетных сайтах покупки книг, так как информация бесценна, даже если вы не используете его методы (очень трудно найти в другом месте). Обратите внимание, что я тестировал его код когда-то (давно), и он работает так, как рекламируется.


Я сделал замену таким образом:

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 { }

тогда view должен наследовать CustomUserControl вместо UserControl (или CustomForm : Form):

public partial class SomeView : CustomUserControl { ... }

и затем результат в сгенерированном файле конструктора:

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