Click here to Skip to main content
15,902,635 members
Articles / Programming Languages / XML
Tip/Trick

Multilanguage solution by using Reflection

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
27 Mar 2013CPOL2 min read 9.6K   256   8   1
Choose the UI language at runtime and using XML to save the infomation.

Introduction 

This article introduces a way to provide your application some globalization feathers, like different languages. The ui language can be change at runtime. And the language setting file is simple and can be edited by a non-programmer.  For programmers, it's simply to use--just calling a couple of functions. It's applied to .NET WinForm apps, not tried on other platforms.

Demo program-English version (change language by selecting the combobox's items):  

Image 1

 Demo program-Chinese(simplified) version:   

Image 2

Background 

The Microsoft way to resolve multi-language problem is using the resource files. But I discard this method for two reasons: It's not editable by non-professionals, and the resource file cannot be added or edited once the app is published.

Using the code

It's very simple to use the code:

First, you need name you control, and set their properties in the XML file (one language with a XML). If you want to rename you control, you need to modify you XML file too. Second, add a language selection combobox in you form, and write codes like these:

C#
private void cbLang_SelectedIndexChanged(object sender, EventArgs e)
{
    MultilangComboHelper.UpdateSelection(langSetter, cbLang,this);
    MultilangComboHelper.UpdateSelection(langSetter, cbLang, frmAbout);
} 

The MultilangComboHelper is just a static helper class:

C#
static class MultilangComboHelper
{
    public static void FetchChooser(LanguageSetter langSetter, ComboBox cbox)
    {
        LanguageSetter.LanguageDesc[] langDesc = langSetter.EnumLanguages();
        cbox.Items.Clear();
        cbox.DataSource = langDesc;
        cbox.DisplayMember = "DisplayName";
        cbox.ValueMember = "FileName";
    }
    public static void UpdateSelection(LanguageSetter langSetter, ComboBox cbox, Control control)
    {
        if (cbox.SelectedItem == null) return;
        string lang = (cbox.SelectedItem as LanguageSetter.LanguageDesc).Language;
        langSetter.SetLanguage(lang);
        langSetter.UpdateUILanguage(control);            
    }
}

LanguageSetter is the core class, it's main function is parse the XML file, and travel a control's sub controls, using reflection to find control's properties, then set the control's properties to the corresponded value in XML.

C#
public class LanguageSetter
{
    private const string SPLIT_CHAR = ".";
    private List<LanguageDesc> _langFile;
    private string _LangPath;
    public XmlNode _RootNode = null;
    static private void SetPropertyValue(object classInstance, string propertyName, object value)
    {
        Type myType = classInstance.GetType();
        PropertyInfo myPropertyInfo = myType.GetProperty(propertyName, 
          BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        if (myPropertyInfo != null)
        {
            if (myPropertyInfo.PropertyType.Name.StartsWith("Int"))
                myPropertyInfo.SetValue(classInstance, int.Parse(value.ToString()), null);
            else
                myPropertyInfo.SetValue(classInstance, value, null);
        }
    }

    static private object SetItemsPropertyValueByIndex(object classInstance, 
      int index, string propertyName, object propertyValue)
    {
        object[] postedParams = new object[] { index };
        Type[] myTypeArr = new Type[1];
        myTypeArr.SetValue(typeof(int), 0);
        object obj2 = classInstance.GetType().GetProperty("Item", 
          myTypeArr).GetValue(classInstance, postedParams);
        if (string.IsNullOrEmpty(propertyName))
        {
            //How about ListBox/ComboBox's items
        }
        else if (obj2.GetType().GetProperty(propertyName) != null)
            obj2.GetType().GetProperty(propertyName).SetValue(obj2, propertyValue, null);
        return obj2;
    }

    static private object SetItemsPropertyValueByName(object classInstance, 
      string itemName, string propertyName, object propertyValue)
    {
        object[] postedParams = new object[] { itemName };
        Type[] myTypeArr = new Type[1];
        myTypeArr.SetValue(typeof(string), 0);
        object obj2 = classInstance.GetType().GetProperty("Item", 
          myTypeArr).GetValue(classInstance, postedParams);
        if (obj2 != null)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                //How about ListBox/ComboBox's items
            }
            else if (obj2.GetType().GetProperty(propertyName) != null)
                obj2.GetType().GetProperty(propertyName).SetValue(obj2, propertyValue, null);
        }
        return obj2;
    }

    public class LanguageDesc
    {
        public string FileName { get; set; }
        public string Language;
        public string DisplayName { get; set; }
        public string Version;
        public string Company;
        public string Author;
        public DateTime CreateTime;
        public DateTime ModifyTime;
    }

    public LanguageSetter(string path)
    {
        if (!Directory.Exists(path))
            throw new Exception("Path not exists: " + path);
        _LangPath = path;
    }

    public LanguageDesc[] EnumLanguages(string path)
    {
        List<LanguageDesc> result = new List<LanguageDesc>();
        XmlDocument xmlDoc = new XmlDocument();
        DirectoryInfo dInfo = new DirectoryInfo(path);
        foreach (FileInfo fi in dInfo.GetFiles("*.xml"))
        {
            LanguageDesc lDesc = new LanguageDesc();
            result.Add(lDesc);
            lDesc.FileName = fi.FullName;
            xmlDoc.Load(fi.FullName);
            XmlElement root = xmlDoc.DocumentElement;
            XmlNode xNode = root.SelectSingleNode("language");
            if (xNode != null) lDesc.Language = XmlHelper.GetNodeText(xNode);
            xNode = root.SelectSingleNode("displayName");
            if (xNode != null) lDesc.DisplayName = XmlHelper.GetNodeText(xNode);
            xNode = root.SelectSingleNode("version");
            if (xNode != null) lDesc.Version = XmlHelper.GetNodeText(xNode);
        }
        _langFile = result;
        return result.ToArray();
    }

    public LanguageDesc[] EnumLanguages()
    {
        return EnumLanguages(this._LangPath);
    }

    public void SetLanguage(string language)
    {
        string fileName = Path.Combine(_LangPath, language + ".xml");
        if (!File.Exists(fileName))
        {
            if ((_langFile == null) || (_langFile.Count == 0))
            {
                EnumLanguages();
            }
            if ((_langFile != null) && (_langFile.Count > 0))
            {
                fileName = _langFile[0].FileName;

            }
            if (!File.Exists(fileName))
                throw new Exception("Cannot find the specified language file " + 
                   "or it's replacement: " + fileName);
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            SetLanguage(xmlDoc.DocumentElement);
        }
        else
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            SetLanguage(xmlDoc.DocumentElement);
        }
    }

    public void SetLanguage(XmlNode xNode)
    {
        if (xNode == null) return;
        _RootNode = xNode;
    }

    public void UpdateUILanguage(Control BaseControl)
    {
        if (_RootNode == null) return;
        //travel BaseControl's sub controls, and set it's proprities.
        System.Reflection.FieldInfo[] fieldInfo =
            BaseControl.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | 
              System.Reflection.BindingFlags.Instance);
        if (BaseControl is Form)
        {
            string key = BaseControl.GetType().Name;
            XmlNode ctlNode = _RootNode.SelectSingleNode(string.Format("//item[@key='{0}']", key));
            if (ctlNode != null)
            {
                if (!string.IsNullOrEmpty(XmlHelper.GetNodeText(ctlNode)))
                    SetPropertyValue(BaseControl, "Text", XmlHelper.GetNodeText(ctlNode));
                foreach (XmlNode nod1 in ctlNode.SelectNodes("prop"))
                {
                    SetProp(BaseControl, nod1);
                }
            }
        }
        foreach (Control ctl in BaseControl.Controls)
        {
            if (ctl is UserControl)
            {
                UpdateUILanguage(ctl);
            }
        }
        for (int i = 0; i < fieldInfo.Length; i++)
        {
            DoUpdateUILanguage(BaseControl, fieldInfo[i]);
        }
    }

    // Get a control's key in xml, like:controlname.sub controlname
    protected virtual bool GetControlKey(Control BaseControl, FieldInfo fieldInfo, out string key)
    {
        key = string.Empty;
        object target = null;
        target = fieldInfo.GetValue(BaseControl);
        if (target == null) return false;
        PropertyInfo pInfo = target.GetType().GetProperty("Name");
        if (pInfo == null) return false;
        string xx = (string)pInfo.GetValue(target, null);
        if (!string.IsNullOrEmpty(xx))
        {
            key = BaseControl.GetType().Name + SPLIT_CHAR + xx;
            return true;
        }
        return false;
    }

    public virtual void DoUpdateUILanguage(Control BaseControl, FieldInfo fieldInfo)
    {
        if (_RootNode == null) return;
        string key = string.Empty;
        object target = null;
        target = fieldInfo.GetValue(BaseControl);
        if (GetControlKey(BaseControl, fieldInfo, out key))
        {
            if ((target != null) && (!string.IsNullOrEmpty(key)))
            {
                XmlNode ctlNode = _RootNode.SelectSingleNode(string.Format("//item[@key='{0}']", key));
                if (ctlNode != null)
                {
                    if (!string.IsNullOrEmpty(XmlHelper.GetNodeText(ctlNode)))
                        SetPropertyValue(target, "Text", XmlHelper.GetNodeText(ctlNode));
                    foreach (XmlNode nod1 in ctlNode.SelectNodes("prop"))
                    {
                        SetProp(target, nod1);
                    }
                }
            }
        }
    }

    private void SetProp(Object target, XmlNode nod1)
    {
        //use xml element's text set control's text property;
        string nodeText = XmlHelper.GetNodeText(nod1);
        if (!string.IsNullOrEmpty(nodeText))
        {
            SetPropertyValue(target, nod1.Attributes["name"].Value, nodeText);
        }
        else
        {
            object x = GetPropertyValue(target, nod1.Attributes["name"].Value);
            if (x == null) return;
            int i = 0;
            XmlNodeList xNodeList = nod1.SelectNodes("prop");
            foreach (XmlNode nod2 in xNodeList)
            {
                string name = "";
                if (nod2.Attributes["name"] != null) name = nod2.Attributes["name"].Value;
                string propName = "Text";
                string itemName = name;
                string propValue = XmlHelper.GetNodeText(nod2);
                object obj2 = SetItemsPropertyValueByName(x, itemName, propName, propValue);
                if (obj2 != null)
                {
                    XmlNodeList xNodeList2 = nod2.SelectNodes("prop");
                    foreach (XmlNode nod3 in xNodeList2)
                        SetProp(obj2, nod3);
                }
                i++;
            }
        }
    }

    public object GetPropertyValue(object classInstance, string propertyName)
    {
        Type myType = classInstance.GetType();
        PropertyInfo myPropertyInfo = myType.GetProperty(propertyName, 
          BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        if (myPropertyInfo == null)
            return null;
        else
            return myPropertyInfo.GetValue(classInstance, null);
    }
    public string GetValue(string key)
    {
        if (_RootNode != null)
        {
            XmlNode xNode = _RootNode.SelectSingleNode(string.Format("//item[@key='{0}']", key));
            if (xNode != null)
                return XmlHelper.GetNodeText(xNode);
            else
                return key;
        }
        else
            return key;
    }
}

The XML file's structure is like this:

Image 3

Before the setting is the description:  

  • language: code representing the language, for example: en-US: us English, zh-CN: simplified Chinese.
  • displayName: shows in the language chooser.

Every item represents a object, the key is a string and the value can be referenced in your program by using GetValue method:

C#
private void btnOK_Click(object sender, EventArgs e)
{
    MessageBox.Show(this.langSetter.GetValue("You click OK!"));
} 

Every control that automatically set by LanguageSetter must have the save name as key in the XML. There are two types of top control: form, usercontrol. Sub controls in the top control must have the parent's name as it's key's prefix, followed by a dot and the control's self name.

Points of Interest  

There is a interesting thing of listview: column you set by ID does not effect, you must do it manually. I spend a lot of time to fix this.

C#
lvRegStatics.Columns[0].Name = "colNumber";
lvRegStatics.Columns[1].Name = "colMonth";
lvRegStatics.Columns[2].Name = "colRegCount";

History  

  • 26 March 2013 - First publishing, Version 1.0.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralWhat about Tooltip ? Pin
Z@clarco2-Feb-15 2:51
Z@clarco2-Feb-15 2:51 
Very good article !!

But I'm asking "and Tooltip Text ?"
and DataGridview Header Text ?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.