LocalizedTextProvider Component

By Fons Sonnemans, 10-8-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]
staticvoid Main()
{
    Thread.CurrentThread.CurrentUICulture =new System.Globalization.CultureInfo("nl");
    //Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("fr");
    //Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de");

    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))]
publicclass LocalizedTextProvider : System.ComponentModel.Component, System.ComponentModel.IExtenderProvider, ISupportInitialize {
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.Container components=null;

    private Hashtable _localizedTextList=new Hashtable();
    internal conststring ResourceKey ="ReflectionIT.Windows.Forms.Resources.Microsoft";
    internal readonly ResourceManager ResourceManager =new ResourceManager( ResourceKey, System.Reflection.Assembly.GetExecutingAssembly());
    internal conststring DefaultText ="(None)";

    /// <summary>
    /// Required for Windows.Forms Class Composition Designer support
    /// </summary>
    public LocalizedTextProvider(System.ComponentModel.IContainer container){
        container.Add(this);
        InitializeComponent();
    }

    /// <summary>
    /// Required for Windows.Forms Class Composition Designer support
    /// </summary>
    public LocalizedTextProvider() {
        InitializeComponent();
    }

    #region Component Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent(){
        components=new System.ComponentModel.Container();
    }
    #endregion

    /// <summary>
    /// Specifies whether this object can provide its extender properties to the specified object.
    /// </summary>
    /// <param name="target">The Object to receive the extender properties.</param>
    /// <returns>true if this object can provide extender properties to the specified object; otherwise, false</returns>
    bool IExtenderProvider.CanExtend(objecttarget) {
        return(target is Control)|(target is MenuItem);
    }

    /// <summary>
    /// Get the key of the LocalizedText for the given component
    /// </summary>
    /// <param name="component">Control or MenuItem</param>
    /// <returns>Key of the LocalizedText</returns>
    [Category("Misc")]
    [Editor(typeof(LocalizedTextEditor), typeof(System.Drawing.Design.UITypeEditor))]
    [DefaultValue(LocalizedTextProvider.DefaultText)]
    [Description("The LocalizedText for this control")]
    publicstring GetLocalizedText(Component component){
        if(_localizedTextList.ContainsKey(component)) {
            if(_localizedTextList[component]!= null){
                return(string)(_localizedTextList[component]);
            }
        }

        return LocalizedTextProvider.DefaultText;
    }
    
    /// <summary>
    /// Set the LocalizedText for the given component
    /// </summary>
    /// <param name="component">Control or MenuItem</param>
    /// <param name="value">Key of the LocalizedText</param>
    public void SetLocalizedText(Component component,stringvalue) {
        _localizedTextList[component]=value;

        if(value !=null&&value!= LocalizedTextProvider.DefaultText){
            UpdateText(component);
        }        
    }

    /// <summary>
    /// Update the text for the component
    /// </summary>
    /// <param name="component">Control or MenuItem</param>
    private void UpdateText(Component component){
        string s = GetLocalizedText(component);

        if(s !=null&& s.Length > 0){
            if(componentis Control) {
                ((Control)component).Text = ResourceManager.GetString(s);
            }else{
                if(componentis MenuItem){
                    ((MenuItem)component).Text = ResourceManager.GetString(s);
                }
            }
        }
    }

    #region Implementation of ISupportInitialize

    publicvoid BeginInit(){
        // ignore
    }

    /// <summary>
    /// Update the Text for the all components, overwrites the manually set Text
    /// </summary>
    public void EndInit(){
        foreach(Component componentin_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.

/// <summary>
/// TypeEditor for LocalizedText used by the LocalizedTextProvider
/// </summary>
public class LocalizedTextEditor : PeterBlum.UITypeEditorClasses.BaseDropDownListTypeEditor {

    privatestatic ArrayList _keys;

    /// <summary>
    /// Static Constructor: Create a list of all Keys from the ResourceFile
    /// </summary>
    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 inrr) {
                if(d.Value isstring){
                    _keys.Add((string)d.Key);
                }
            }
        }
    }

    /// <summary>
    /// Add all keys to the ListBox
    /// </summary>
    /// <param name="pContext"></param>
    /// <param name="pProvider"></param>
    /// <param name="pListBox"></param>
    protected overridevoid FillInList(ITypeDescriptorContext pContext, IServiceProvider pProvider, ListBox pListBox){
        pListBox.Items.Add(LocalizedTextProvider.DefaultText);
        foreach(string keyin_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


Leave a Comment

Leave a Comment
Name
Comment
7 + 6 =

0 Comments

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.