In my current project (.NET Windows Forms application) I have a requirement that the .NET windows forms should be localized but the localization elements (just the translations, not the images or control positions) should come from the database in order to enable the end users to modify the control localizable properties (just the caption/text) as they wish. To keep the developers unburdened with localization issues the best solution for me would be to simply mark the form as Localizable in the VS designer. This will place all localizable property values in the forms .resx file.
Now my problem is how to provide translations from the database. The moment that the form is marked as Localizable the VS Forms designer will place everything that can be localized is forms .resx file. The designer will also modify the the standard designer.cs InitializeComponent method so that it instantiates ComponentResourceManager and then use that resource manager the load the localizable properties of objects (controls and components).
I have seen solutions where people have build up their own method of applying the localized properties to Form and its controls. All the solutions that I've seen usually boil down to recursively iterating through the Controls collection of Form and its controls and applying translations. Unfortunately the .NET Forms localization is not so simple and this does not cover all scenarios especially if you have some 3rd party controls. For example:
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 I've seen could not do translations such as required by the above components.
Since the VS has already generated the code which provides translation where needed my ideal solution would be to somehow replace the ComponentResourceManager with my own derived class. If I could just replace the following line in 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 any problems.
Unfortunately I did not find how I could do something like that so here I am on SO asking a question on how it could be done.
P.S. I would also accept any other localization solution which matches the requirements: 1. Changing translations should be possible without redeploying the application 2. The developer should not take care about translation when creating forms/user controls
Thank you.
EDIT: Larry provided a great reference to a book which has code which partly solves my problem. With that help I was able to create my own component which replaces the default ComponentResourceManager in the InitializeComponent method. Here is the code for a component called Localizer which replaces the ComponentResourceManager with custom MyResourceManager so that someone else can also 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);
}
}
}