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