LocalizedTextProvider Component
By Fons Sonnemans (10 August 2003)
Download
LocalizedText.zip
Introduction
I have read that the .NET framework has great features to localize your application.
I had to use them recently to create an English, Dutch, French and German "version"
of one of my applications. I found out they worked well but it was very time consuming
to set all the text properties of all controls for each language. Even worse you
have to translate common terms like: OK, Cancel, New, Open, Close, etc. on every
form if you want to use the designer. You can, of course, write code to fetch them
all from one resource file. But I don't like to write code, especially if you
don't have to.
I came up with a solution for this problem, called the LocalizedTextProvider component.
This component is actually an extender which, adds a LocalizedText property to a
Control or MenuItem. When you add this component to a WinForm it will allow you
to set the LocalizedText property ('Misc' category) of all controls on your form.
You can select a Key from a predefined list. This list contains all keys from the
string resources in the component. I have filled the list using the
International Word List from the 'Microsoft Official Guidelines for User
Interface Developers and Designers' website.

Example: the LocalizedText property for the Cancel button is set to Microsoft.Cancel
The Text for the Cancel button will automatically be translated to 'Annuleren' for
Dutch users, 'Abbrechen' for German users and 'Annuler' for French users. You can
test this by setting the CurrentUICulture property of the CurrentThread to a Dutch,
German or French CultureInfo.
[STAThread]
static void Main()
{
Thread.CurrentThread.CurrentUICulture =
new System.Globalization.CultureInfo("nl");
Application.Run(new Form1());
}
LocalizedTextProvider class
The LocalizedTextProvider class is derived from System.ComponentModel.Component
and implements the System.ComponentModel.IExtenderProvider interface. This makes
it an extender. The ProvideProperty attribute on the class specifies the name of
the property (LocalizedText) that it offers to other components. The CanExtend method
specifies whether this object can provide its extender properties to the specified
object. In this case a Control or MenuItem. The GetLocalizedText() and SetLocalizedText()
methods retrieve and store the resource key into a Hashtable. The UpdateText() method
uses this key to retrieve the string from the resource file.
[ProvideProperty("LocalizedText",
typeof(Component))]
public class LocalizedTextProvider
: System.ComponentModel.Component, System.ComponentModel.IExtenderProvider, ISupportInitialize {
private System.ComponentModel.Container components
= null;
private Hashtable
_localizedTextList = new
Hashtable();
internal
const string ResourceKey
= "ReflectionIT.Windows.Forms.Resources.Microsoft";
internal
readonly ResourceManager ResourceManager =
new ResourceManager(
ResourceKey, System.Reflection.Assembly.GetExecutingAssembly());
internal
const string DefaultText
= "(None)";
public LocalizedTextProvider(System.ComponentModel.IContainer container) {
container.Add(this);
InitializeComponent();
}
public LocalizedTextProvider()
{
InitializeComponent();
}
#region Component Designer generated
code
private
void InitializeComponent()
{
components
= new System.ComponentModel.Container();
}
#endregion
bool IExtenderProvider.CanExtend(object
target)
{
return
(target
is Control) |
(target
is MenuItem);
}
[Category("Misc")]
[Editor(typeof(LocalizedTextEditor),
typeof(System.Drawing.Design.UITypeEditor))]
[DefaultValue(LocalizedTextProvider.DefaultText)]
[Description("The LocalizedText for this control")]
public string
GetLocalizedText(Component
component) {
if
(_localizedTextList.ContainsKey(component))
{
if
(_localizedTextList[component]
!=
null) {
return (string)(_localizedTextList[component]);
}
}
return
LocalizedTextProvider.DefaultText;
}
public
void SetLocalizedText(Component
component, string
value)
{
_localizedTextList[component]
= value;
if
(value
!= null
&& value !=
LocalizedTextProvider.DefaultText)
{
UpdateText(component);
}
}
private
void UpdateText(Component
component) {
string
s = GetLocalizedText(component);
if
(s !=
null &&
s.Length > 0) {
if
(component is Control)
{
((Control)component).Text
= ResourceManager.GetString(s);
}
else {
if (component
is MenuItem) {
((MenuItem)component).Text
= ResourceManager.GetString(s);
}
}
}
}
#region Implementation of ISupportInitialize
public void
BeginInit() {
}
public
void EndInit()
{
foreach
(Component component
in _localizedTextList.Keys)
{
UpdateText(component);
}
}
#endregion
}
The GetLocalizedText() method has a Editor attribute which specifies the LocalizedTextEditor used
to change a property. The LocalizedTextEditor class is derived from PeterBlum.UITypeEditorClasses.BaseDropDownListTypeEditor.
This base class makes it very easy to create your own editor. You only have to override
the method FillInList() and fill the listbox. I have downloaded it from
Peter's website, thanks Peter.
public class LocalizedTextEditor
: PeterBlum.UITypeEditorClasses.BaseDropDownListTypeEditor {
private static
ArrayList _keys;
static LocalizedTextEditor()
{
_keys
= new ArrayList();
Assembly a
= Assembly.GetExecutingAssembly();
System.IO.Stream rs
= a.GetManifestResourceStream(LocalizedTextProvider.ResourceKey +
".resources");
if
(rs
!= null) {
ResourceReader
rr =
new ResourceReader(rs);
foreach
(DictionaryEntry d in
rr)
{
if (d.Value
is string)
{
_keys.Add((string)d.Key);
}
}
}
}
protected
override void FillInList(ITypeDescriptorContext
pContext, IServiceProvider
pProvider, ListBox
pListBox) {
pListBox.Items.Add(LocalizedTextProvider.DefaultText);
foreach
(string
key in _keys) {
pListBox.Items.Add(key);
}
pListBox.Sorted =
true;
}
}
Conclusion
Localization isn't hard but it is a lot of work. I hope my LocalizedTextProvider
component makes it easier for you. You will probably have to extend the resource
files with extra keys and or languages. Have a try.
Any suggestions and feedback for improving this article is most welcome. Send your
suggestions and feedback to Fons.Sonnemans@reflectionit.nl