Dot-Net

.NET WinForms 本地化 - 替換 ComponentResourceManager

  • March 23, 2018

在我目前的項目(.NET Windows 窗體應用程序)中,我要求 .NET Windows 窗體應該本地化,但本地化元素(只是翻譯,而不是圖像或控制位置)應該來自數據庫,以便啟用最終使用者可以根據需要修改控制項的可本地化屬性(只是標題/文本)。為了讓開發人員擺脫本地化問題的負擔,對我來說最好的解決方案是在 VS 設計器中簡單地將表單標記為可本地化。這會將所有可本地化的屬性值放在表單 .resx 文件中。

現在我的問題是如何從數據庫中提供翻譯。表單被標記為可本地化的那一刻,VS 表單設計器將放置所有可以本地化的表單 .resx 文件。設計器還將修改標準的 Designer.cs InitializeComponent 方法,以便實例化 ComponentResourceManager,然後使用該資源管理器載入對象(控制項和組件)的可本地化屬性。

我已經看到人們建立了自己的方法來將本地化屬性應用於 Form 及其控制項的解決方案。我見過的所有解決方案通常歸結為遞歸地遍歷 Form 及其控制項的 Controls 集合併應用翻譯。不幸的是,.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));

然後我可以毫無問題地解決其他所有問題。

不幸的是,我沒有找到如何做這樣的事情,所以我在這裡問一個關於如何做的問題。

PS 我也會接受任何其他符合要求的本地化解決方案: 1. 無需重新部署應用程序就可以更改翻譯 2. 開發人員在創建表單/使用者控制項時不應該關心翻譯

謝謝你。

編輯:拉里提供了一本很好的參考書,其中的程式碼部分解決了我的問題。借助該幫助,我能夠創建自己的組件,該組件替換了 InitializeComponent 方法中的預設 ComponentResourceManager。這是一個名為 Localizer 的組件的程式碼,它用自定義的 MyResourceManager 替換了 ComponentResourceManager,以便其他人也可以從中受益:

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

       }
   }


}

我是 Visual Studio 開發人員本地化工具的作者(為了全面披露)。我強烈建議獲取 Guy Smith-Ferrier 的書“.NET Internationalization, The Developer’s Guide to Building Global Windows and Web Applications”的副本。無論如何,我相信那是正確的書(肯定是正確的作者),但是您需要檢查一下,因為我已經很久沒有看過它了(也許從那以後他甚至寫了一些新的東西)。Guy 是 MSFT MVP 和本地化專家。他向您展示瞭如何準確地完成您正在嘗試的事情,在他的例子中,通過創建一個您可以拖動到每個表單的托盤區域的組件。然後該組件將允許您替換“ComponentResourceManager” 用你自己的(他的設計中有幾個類)。然後,您可以從任何來源(包括數據庫)讀取您的字元串。IIRC,他自己的例子甚至使用了數據庫。您可能無需購買他的書就可以線上找到程式碼,因為我認為他甚至可以在自己的網站上提供程式碼。您還可以在著名的購書網站上找到他的書中的免費段落,因為即使您不使用他的技術(在其他地方很難找到),這些資訊也是無價的。請注意,我曾經(很久以前)測試過他的程式碼,並且它像宣傳的那樣工作。您還可以在著名的購書網站上找到他的書中的免費段落,因為即使您不使用他的技術(在其他地方很難找到),這些資訊也是無價的。請注意,我曾經(很久以前)測試過他的程式碼,並且它像宣傳的那樣工作。您還可以在著名的購書網站上找到他的書中的免費段落,因為即使您不使用他的技術(在其他地方很難找到),這些資訊也是無價的。請注意,我曾經(很久以前)測試過他的程式碼,並且它像宣傳的那樣工作。

引用自:https://stackoverflow.com/questions/15222528