2/26/11

C#- Change form language at runtime.

Recently I was trying to create multi language application in C#.

I found a lot of useful information in net.

You can always create a resource file and use it for localization:

Localization with resource file.
It's quite simple, but you always need to change text properties in form constructor... Not useful if we have a lot of forms.

Better solution is to localize your form in a designer.
Localization in Designer
It is easier to localize forms and all code is created by designer.

But how we can change language at run time? All procedures described before enables localization changing when new form is created.

I'll describe language change at run time in case when forms are localized using Designer.

In order to change application language we need to:
1. set a proper CultureInfo

            mCI = new CultureInfo(language);
            Thread.CurrentThread.CurrentCulture = mCI;
            Thread.CurrentThread.CurrentUICulture = mCI;

If you want to know all possible CultureInfos you can check it by:
CultureInfo[] a = CultureInfo.GetCultures(CultureTypes.AllCultures);

2. Take Form ComponentResourceManager.

ComponentResourceManager resources = new ComponentResourceManager(inputForm.GetType());

3. Change language.
using System.Globalization;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;

public void ApplyLanguageToForm(Form inputForm, string language)
        {
            CultureInfo mCI = new CultureInfo(language);
            Thread.CurrentThread.CurrentCulture = mCI;
            Thread.CurrentThread.CurrentUICulture = mCI;

            ComponentResourceManager resources = new ComponentResourceManager(inputForm.GetType());
            foreach (Control c in inputForm.Controls)
            {
                resources.ApplyResources(c, c.Name, mCI);
            }
        }

Unfortunately in this approach, you need to iterate through all controls, menus, labels etc... Code above iterates just through controls, so some of your form elements will not be changed.

In my opinion best approach is to use reflection.

4. Using reflection to change language at run time.

Unfortunately I haven't found any complete solution, but reflection simplifies code.

I've attached class which changes all controls placed on form, any drop down items, menu strips, or owned forms. Please notice that in ApplyLanguageToForm you can pass active form. If it's a modal dialog form, all owner controlls will be reloaded.

Here is an example of how it works:








And code of LanguageChange class:

using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace Lang
{
    public class LanguageChange
    {
        private CultureInfo mCI = null;

        public void ChangeLanguage(string language)
        {
            mCI = new CultureInfo(language);
            Thread.CurrentThread.CurrentCulture = mCI;
            Thread.CurrentThread.CurrentUICulture = mCI;
        }

        public void ApplyLanguageToForm(Form inputForm)
        {
            Form mainForm = inputForm;
            while (mainForm.Owner != null)
            {
                mainForm = mainForm.Owner;
            }
            ApplyLanguage(mainForm, null);
        }

        private void ApplyLanguage(object value, ComponentResourceManager resources)
        {
            if (value is Form)
            {
                resources = new ComponentResourceManager(value.GetType());
                resources.ApplyResources(value, "$this");
            }
            Type type = value.GetType();

            foreach (PropertyInfo info in type.GetProperties())
            {
                switch (info.Name)
                {
                    case "Items":
                    case "DropDownItems":
                    case "Controls":
                    case "OwnedForms":

                        if (info.PropertyType.GetInterface("IEnumerable") != null)
                        {
                            IEnumerable collection = (IEnumerable)info.GetValue(value, null);
                            if (collection != null)
                            {
                                foreach (object o in collection)
                                {
                                    PropertyInfo objNameProp = o.GetType().GetProperty("Name");
                                    ApplyResourceOnType(resources, o, objNameProp);
                                }

                            }
                        }
                        break;
                    case "ContextMenuStrip":
                        object obj = (object)info.GetValue(value, null);
                        if (obj != null)
                        {
                            ApplyLanguage(obj, resources);
                            resources.ApplyResources(obj, info.Name, mCI);
                        }
                        break;
                    default:
                        break;
                }
            }

        }

        private void ApplyResourceOnType(ComponentResourceManager resources, object o, PropertyInfo objNameProp)
        {
            switch (o.GetType().Name)
            {
                case "ComboBox":
                    for (int i = 0; i < ((ComboBox)o).Items.Count; i++)
                    {
                        ((ComboBox)o).Items[i] = resources.GetString(GetItemName(o, objNameProp, i), mCI);
                    }
                    break;
                case "ListBox":
                    for (int i = 0; i < ((ListBox)o).Items.Count; i++)
                    {
                        ((ListBox)o).Items[i] = resources.GetString(GetItemName(o, objNameProp, i), mCI);
                    }
                    break;
                // Other classes with string items
                default:
                    if (objNameProp != null)
                    {
                        string name = objNameProp.GetValue(o, null).ToString();
                        ApplyLanguage(o, resources);
                        resources.ApplyResources(o, name, mCI);
                    }
                    break;
            }
        }

        private string GetItemName(object o, PropertyInfo objNameProp, int i)
        {
            string name = String.Format("{0}.{1}",
                objNameProp.GetValue(o, null).ToString(),
                "Items");
            if (i != 0) name = String.Format("{0}{1}", name, i);
            return name;
        }
    }
}

16 comments:

  1. How do you manage translations in resource file? Imagine that you have hundreds of translated sentences and your application has changed, I mean some labels has changed, some labels has been removed or added. Is there a tool similar to QT Linguist?

    ReplyDelete
  2. I do not know a tool QT Linguist so I can't tell if there is any similar tool.

    About managing resource files:
    If you open resource file in Visual Studio you will get a grid view with all elements. Resource files are xml files, so new language can be a copy of base language file. All you need to do is to translate it and give it a proper name.

    I hope it suits your needs.

    ReplyDelete
  3. Nice code, working fine!

    ReplyDelete
  4. Hi I think your's code is the best I found! Can you share the project of video?

    Cristian Italy

    ReplyDelete
    Replies
    1. Hi Cristian,

      Thank you for your comment.
      Unfortunatelly I do not have the project shown on the video :( It was made just for this article and the most important part of it is included in the post. If you have questions please ask then and I'll try to answer.

      Rafał

      Delete
    2. Hi Rafal and thank for your's reply,

      In your's code I dont' understand how use the class...

      Cristian

      Delete
    3. like this?

      TEST_FORM.LanguageChange cL = new TEST_FORM.LanguageChange();
      cL.ChangeLanguage("en-US");

      cL.ApplyLanguageToForm(this.FindForm());

      Delete
    4. Cristian,

      Remember to prepare your form as described in a link "Localization in Designer" mentioned in the article.

      In a main form create an instance of LanguageChange class. I made a parameter.

      private Lang.LanguageChange lanChanger = new Lang.LanguageChange();

      Notice that I've chnaged the namespace name.

      When you click a button, select language from a drop down list etc... (it is up to you)- make:

      lanChanger.ChangeLanguage("en-US");
      //or
      lanChanger.ChangeLanguage("pl-PL");
      //or
      lanChanger.ChangeLanguage("it-IT");

      and invoke

      lanChanger.ApplyLanguageToForm(this);

      this -> a main form of your app. The one which holds also a modal windows.


      If you want to get all strings which can be used in "ChangeLanguage" try:

      CultureInfo[] tmp = CultureInfo.GetCultures(CultureTypes.AllCultures);

      Regards,
      Rafał

      Delete
  5. Hi Rafael sorry for the big delay...

    Now work, but I add this in your's class:

    foreach (object o in collection)
    {
    PropertyInfo objNameProp =
    o.GetType().GetProperty("Name");

    if (objNameProp != null)
    {
    string name =
    objNameProp.GetValue(o, null).ToString();
    ApplyLanguage(o, resources);
    resources.ApplyResources(o, name, mCI);
    }
    }

    Thanks!

    ReplyDelete
    Replies
    1. Thank you for finding and fixing a bug. Code in article was updated.

      Delete
  6. I tried this but I can't change default menu context of textbox. Example: Items: Copy, Paste, Delete...
    Do you have solution for this problem?

    Thanks and regards.

    ReplyDelete
    Replies
    1. Hi Ronie,

      I do not think you can change a default context menu of text box. Maybe you can try to add your own context menu and invoke default methods?

      Delete
  7. Hi!
    Thanks for you. It's the first solution that worked for me! :) (I'm talking about "LanguageChange" class)

    However it isn't translating items from the ComboBox. Is it possible to do it ?

    ReplyDelete
    Replies
    1. Hi Paweł,
      I haven't found a complete solution but I was able to manage ComboBox and ListBox. (unfortunately they base class does not have property Items, which interests us)
      I've modified the code by adding private method "ApplyResourceOnType" was added.
      If you need any String Items based collection you can support them adding entries where I've placed the comment:
      // Other classes with string items

      Delete
  8. For an easier management of resx files localization, you should check out this online translation app https://poeditor.com/ ...

    ReplyDelete