Localization of .NET WinForms - replacement of ComponentResourceManager

In my current project (.NET Windows Forms application), I have a requirement that Windows.NET forms be localized, but localization elements (just translations, not images or control positions) must come from the database in order to allow end users to change localized control properties (just the title / text) as you wish. So that developers are not burdened with localization problems, the best solution for me would be to simply mark the Localizable form in the VS designer. This will put all localizable property values โ€‹โ€‹into a .resx file.

Now my problem is how to provide translation from the database. At the moment when the form is marked as Localizable, the developer of VS Forms will place everything that can be localized - this is a .resx file. The designer will also modify the standard designer.cs InitializeComponent method to run the ComponentResourceManager and then use this resource manager to load the localizable properties of objects (controls and components).

I saw solutions in which people created their own method of applying localized properties to a form and its controls. All the solutions that I have seen usually come down to recursively iterating through the Controls collection of the form and its controls and applying translations. Unfortunately, the localization of .NET Forms is not so simple, and this does not apply to all scenarios, especially if you have third-party controls. For instance:

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

All the solutions that I have seen cannot translate, such as those required by the above components.

Since VS has already generated code that provides translation where necessary, my ideal solution would be to somehow replace the ComponentResourceManager with my own derived class. If I could just replace the following line in the InitializeComponent:

 System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); 

with

 System.ComponentModel.ComponentResourceManager resources = new MyComponentResourceManager(typeof(Form1)); 

then I could solve everything else without problems.

Unfortunately, I did not find how I could do something like this, so I am on SO asking a question about how this can be done.

PS I also make any other localization solution that meets the requirements: 1. Changing translations should be possible without redeploying the application 2. The developer should not care about the translation when creating forms / user controls

Thanks.

EDIT: Larry gave a great link to a book that contains code that partially solves my problem. With this help, I was able to create my own component, which replaces the standard ComponentResourceManager component in the InitializeComponent method. Here is the code for a component called Localizer, which replaces the ComponentResourceManager with a custom MyResourceManager so that someone else can benefit from it:

 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); } } } 
+6
source share
2 answers

I am the author of a localization tool for Visual Studio developers (in the interest of full disclosure). I highly recommend reading a copy of Guy Smith-Ferrier's book Internationalization of .NET, Developer's Guide for Creating Global Windows and Web Applications. I believe that the correct book is in any case (a faithful author for sure), but you will need to check, since I looked at it for a long time (and, perhaps, since then he even wrote something new). Guy - MVP MVP and Localization Guru. It shows you how to do what you are trying, in his case, by creating a component that you can drag onto the tray area for each of your forms. Then the component will allow you to replace the "ComponentResourceManager" with your own (several classes are involved in its design). Then you can read the lines from any source, including the database. IIRC, his own example even uses a database. You can probably find the code online without purchasing his book, as I think he can even provide it on his own site. You can also find free excerpts from your book on reputable book buying sites, as the information is invaluable even if you don't use its methods (very difficult to find elsewhere). Please note that I tested his code once a while (a long time ago) and it works as advertised.

+5
source

I made a replacement as follows:

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

Then, the view should inherit CustomUserControl instead of UserControl (or CustomForm : Form ):

 public partial class SomeView : CustomUserControl { ... } 

And then we get the created constructor file:

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

All Articles