Click here to Skip to main content
15,887,585 members
Articles / Programming Languages / C#
Article

Globalization of Windows Applications in 20 Minutes Using C#

Rate me:
Please Sign up or sign in to vote.
4.90/5 (66 votes)
1 Sep 2006CPOL6 min read 338.8K   9.3K   209   76
Describes the essential features required to enable multiple languages in a Windows application using resource files.

Image 1

Globalization of Windows Application in 20 Minutes

Prelude

There was once a time when multiple language support for a Windows application used to be a three to six months call, but with the advent of .NET, not anymore. Here is a 20 minutes crash course for globalization / localization of a Windows application. I think you'll find it as useful as I do.

Globalization, in simple terms, means enabling an application so that it works in different nationals, similar to how a global company operates in different countries. For a Windows application, globalization means it is intended for worldwide distribution. There are two aspects of globalization:

  • Internationalization: Enabling the application to be used without language or culture barriers, i.e., language and culture information comes from a resource rather than being hard coded in the application.
  • Localization: Translating and enabling the product for a specific locale. Based on a resource file, the application is translated into a language and culture.

Target

  • Modular design: Code Once Use Everywhere (COUE), this is the prime feature which is needed when you globalize an application. All anyone should do is convert any user interface output/message box /labels text etc., to something like frmMain.RM.GetString("10001"). No culture information or resource manager initialization need to be done again in forms or anywhere else.
  • Default language: Saving and retrieving the default language selected by the user in the Registry.
  • Features: How to take care of date/time, multiple forms, images etc.
  • Reusability: Minimum effort when you add a new form to the application.
  • Extensibility: Support for multiple languages like French, Spanish, and English, using resource files for each.

To hold your interest, here is how it looks:

Image 2

Time starts now

The first thing we need will be three resource files for three languages English, French, and Spanish. I have used Google translate here[^] to accomplish this:

  1. English file name: resource.en-US.txt.
  2. Image 3

  3. Spanish file name: resource.es-ES.txt.
  4. Image 4

  5. French file name: resource.fr-FR.txt.
  6. Image 5

Using the Resource generator (Resgen) in the Visual Studio .NET 2003 Command Prompt, we will create three resource files which can be understood by the application, here is how:

Image 6

We are done with the resource files which will be used by the application and will look something like this (below) with the extension .resources for each text file:

Image 7

Put these three .resources files in a Resource folder in the executable path.

Functionality

First run

When the application runs for the first time, we check for the Registry entry language of the application in [HKEY_CURRENT_USER\SOFTWARE\CODE PROJECT\GLOBALIZATION SAMPLE], and returns "en-US" if there is no entry yet. This value is set for the string strCulture of the application.

GetStringRegistryValue in the RegistryAccess class helps us get this:

C#
static public string GetStringRegistryValue(string key, string defaultValue)
{
    RegistryKey rkCompany;
    RegistryKey rkApplication;
    rkCompany = Registry.CurrentUser.OpenSubKey(SOFTWARE_KEY, 
                false).OpenSubKey(COMPANY_NAME, false);
    if( rkCompany != null )
    {
        rkApplication = rkCompany.OpenSubKey(APPLICATION_NAME, true);
        if( rkApplication != null )
        {
            foreach(string sKey in rkApplication.GetValueNames())
            {
                if( sKey == key )
                {
                return (string)rkApplication.GetValue(sKey);
                }
            }
        }
    }
    return defaultValue;
}

Globalize application

Once we have the strCulture, we call the GlobalizeApp function:

C#
// Resource path
private string strResourcesPath= Application.StartupPath + "/Resources";
// string to store current culture which is comon in all the forms
private string strCulture= "en-US";
//resourcemanager which retrivesthe strings
//from the resource files
private static ResourceManager rm;

private void GlobalizeApp()
{
    SetCulture();
    SetResource();
    SetUIChanges();
}
private void SetCulture()
{
    CultureInfo objCI = new CultureInfo(strCulture);
    Thread.CurrentThread.CurrentCulture = objCI;
    Thread.CurrentThread.CurrentUICulture = objCI;
    
}
private void SetResource()
{
    rm = ResourceManager.CreateFileBasedResourceManager
        ("resource", strResourcesPath, null);

}
private void SetUIChanges()
{
    ...
}

The GlobalizeApp function sets the culture information of the current thread, sets the Resource manager to the respective resource file, and SetUIChnages does all the user interface translations.

Modular design: Code Once Use Everywhere

This, as I said already, is an important feature because when an application expands or grows with time, you should be ready to change a new string with just one statement replacement. For this, I have created a public resource manager in frmMain:

C#
public static ResourceManager RM
{ 
  get 
  { 
   return rm ; 
   } 
}

So, when the main form loads, you set the culture and the resource file information to the public resource manager. And, in the new added form or anywhere you add a message box or label, you can call the resource manager like this:

C#
this.Text = frmMain.RM.GetString("0006");
label1.Text = frmMain.RM.GetString("0008");

Translations

SetUIChanges describes how the translations are done:

  • Texts are directly translated from the resource file
  • Images have to be taken care for using multiple images
  • DateTime etc., which are Windows specific does not need to be translated at all (isn't that cool?)

The code-behind

C#
private void SetUIChanges()
{
    if (String.Compare(strCulture,"en-US")==0)
    { 
        picTop.Image = picE.Image;
    }

    if (String.Compare(strCulture,"es-ES")==0)
    { 
        picTop.Image = picS.Image;
    }

    if (String.Compare(strCulture,"fr-FR")==0)
    { 
        picTop.Image = picF.Image;
    }
    label1.Text=rm.GetString("0001");
    label2.Text=rm.GetString("0002");
    label3.Text=rm.GetString("0003");
    btnSubmit.Text=rm.GetString("0004");
    btnCancel.Text=rm.GetString("0005");
    this.Text = rm.GetString("0000");
    lblselect.Text = rm.GetString("0009");

    lbltime.Text = DateTime.Now.ToLongDateString().ToString(); 
}

For images, I have used three hidden PictureBox controls as shown below:

Image 8

Saving the default culture in the Registry

The code-behind:

C#
static public void SetStringRegistryValue(string key, string stringValue)
{
    RegistryKey rkSoftware;
    RegistryKey rkCompany;
    RegistryKey rkApplication;

    rkSoftware = Registry.CurrentUser.OpenSubKey(SOFTWARE_KEY, true);
    rkCompany = rkSoftware.CreateSubKey(COMPANY_NAME);
    if( rkCompany != null )
    {
        rkApplication = rkCompany.CreateSubKey(APPLICATION_NAME);
        if( rkApplication != null )
        {
            rkApplication.SetValue(key, stringValue);
        }
    }
}

Acknowledgement

My humble acknowledgement to my boss who gave me a 6 days deadline, for globalization of an application we have been working on for a year.

What's wrong with the .Resx approach

i got a number of emails asking why not use the .resx approach for each form. Well, here are a few of the reasons. I prefer a single resource file compared to multiple .resx files for each form for three simple reasons:

  1. Maintainability:
  2. Assuming you are taking the .resx files approach:

    Take a simple scenario. By mistake, you have a wrong translation for the "Submit" button, say for the German language. The original translation is "Einreichen", but you initially missed the last n and now, you have "Einreiche" instead of "Einreichen" for Submit buttons throughout your application.

    What you can do to resolve this:

    1. You have to go to each form and change the resource file of the form.
    2. Compile the EXE again, creating the German DLL, and redistribute the whole EXE with setup including the new DLL.

    On the other hand, if you use a single resource file as in this article, "Submit" buttons in all the forms translate into something like ResourceManager.GetString("101").

    If the translation is wrong, just-

    1. Update the initial German text file.
    2. Resgen it and create a resource file.
    3. Overwrite your existing resource file with the updated resource file.

    You are done. Redistribution needs just the lightweight resource file and your EXE will automatically update the Submit buttons everywhere.

  3. Extensibility:
  4. If you have to add another language, say Latino, with the .resx file approach, you have to go to each form and create a resx file for Latino, and compile and create a Latino DLL.

    With the Single Resource file approach, you just have to create another text file with the Latino translation as shown in the example above, Resgen it, and add a menu option for Latino, and you are done. You can have a Latino menu option even earlier, and add the resource file later; you won't even need to re-compile.

  5. Dynamic UI changes:
  6. With resource files, you can have a dropdown menu instead of the radio button in the example, and change the complete UI on the fly to whichever language you fancy. With the .resx and DLL approach, you have to start the application with that localized DLL.

    I think you might be able to dynamically change the UI, but it will be a much more complicated process.

    Another not that important reason is, the Resource file approach creates lightweight .Resources files whereas .resx creates a DLL for each language.

    If you want to go by the standard approach, you can definitely get better results, but will not be as fast as this approach.

And thanks

For coming so far. I hope this 20 minutes was worth it, and give me your comments/ suggestion to improve this.

In action (French)

Image 9

Article history

  • August 20 2006: First published.
  • September 01 2006: Added comparison with .Resx approach.

License

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


Written By
Founder Teamcal AI
United States United States

Comments and Discussions

 
GeneralRe: Nice!! Pin
Raj Lal15-Sep-06 11:18
professionalRaj Lal15-Sep-06 11:18 
Questionaddin dynamic values to/in the translation? (Parameters) Pin
russland89-Sep-06 2:47
russland89-Sep-06 2:47 
AnswerRe: addin dynamic values to/in the translation? (Parameters) Pin
Raj Lal11-Sep-06 8:20
professionalRaj Lal11-Sep-06 8:20 
AnswerRe: addin dynamic values to/in the translation? (Parameters) Pin
twesterd20-Mar-07 21:42
twesterd20-Mar-07 21:42 
GeneralTwo problems Pin
Mihai Nita1-Sep-06 7:35
Mihai Nita1-Sep-06 7:35 
AnswerRe: Two problems Pin
Raj Lal1-Sep-06 10:21
professionalRaj Lal1-Sep-06 10:21 
GeneralRe: Two problems Pin
Mihai Nita1-Sep-06 19:36
Mihai Nita1-Sep-06 19:36 
GeneralRe: Two problems Pin
Raj Lal2-Sep-06 13:12
professionalRaj Lal2-Sep-06 13:12 
Mihai Nita wrote:
This was the point. You MUST ALLWAYS use a different string.


WHY ? Can you explain it ,
if you can do it with one common string in one file for 20 forms
Why do you want to maintain that string for each of the instance in 20 different files ?
There is a clear Maintenability advantage in the first approach i think

Mihai Nita wrote:
You feel like you save 30 cents, but it might cost you 5 dolars


I mean you agree you save atleast 30 cents but you don't explain HOW it might cost you 5 dollars.


Mihai Nita wrote:
Yes, it ResGen is an old beast. But the mechanism is designed for stand alone strings (error messages), not to take them and "push" them in forms.
This is abuse/misuse.
If I come up with a way to store messages in a C header file, is a home-brew "solution" (and a bad one), even if the .h files are not new.


i will suggest you dig more on ResGen
ResGen does the samething what you are doing with .Resx files there is not a single difference
.Resx approach you are making the resource for EACH forms and .Resource file is a Single File for the whole application

THERE IS NO DIFFERENCE AT ALL ResGen can also create .resx files

you can start here to look msdn[^]



Mihai Nita wrote:
You mean you still keep separate resx files for each language? Then what is the gain?

Mihai Nita wrote:
I am talking scalable as number of languages. Please read my post. 3 languages is nothing.
Let's talk again when you get to 30.


Ok let me explain you what is the GAIN in more simple way

For simplicity assume you have Winform application with 20 forms
and you have to make it a multiple language application FOR 30 language ok ?

Using .resx Approach ,
You will create 30 .Resx files for each Form
So a total of 30 x 20 = 600 .resx files you have to manage

And using .Resource approach which is actually using totally same technology
you create ONLY 30 Resource files for each language
and actually each files is a keyword value pair text files which can be much more easily managed

Mihai Nita wrote:
It seems I don't fully understand your method.
You mean you still have separate resx files for the languages? This is the only way to "customize" them and to cover languages that require different fonts or mirroring.
If this is the case, then what is the advantage of this approach?


I think you might wanna just try the sample application in the article and try to create a similar one with .Resx files approach, and let me know what you think after that

nice brainstorming
cheers
Raj


GeneralRe: Two problems Pin
Mihai Nita3-Sep-06 21:03
Mihai Nita3-Sep-06 21:03 
GeneralRe: Two problems Pin
Mihai Nita3-Sep-06 21:05
Mihai Nita3-Sep-06 21:05 
GeneralRe: Two problems Pin
Raj Lal5-Sep-06 9:25
professionalRaj Lal5-Sep-06 9:25 
GeneralRe: Two problems Pin
Mihai Nita5-Sep-06 20:50
Mihai Nita5-Sep-06 20:50 
GeneralRe: Two problems Pin
twesterd20-Mar-07 21:28
twesterd20-Mar-07 21:28 
GeneralRe: Two problems Pin
Mihai Nita20-Mar-07 22:27
Mihai Nita20-Mar-07 22:27 
GeneralRe: Two problems Pin
twesterd20-Mar-07 23:22
twesterd20-Mar-07 23:22 
GeneralRe: Two problems Pin
Mihai Nita21-Mar-07 18:22
Mihai Nita21-Mar-07 18:22 
GeneralRe: Two problems Pin
twesterd21-Mar-07 21:06
twesterd21-Mar-07 21:06 
GeneralRe: Two problems Pin
Mihai Nita9-May-07 6:11
Mihai Nita9-May-07 6:11 
GeneralVery nice approach! Pin
Rabbit1729-Aug-06 5:07
Rabbit1729-Aug-06 5:07 
GeneralRe: Very nice approach! Pin
Raj Lal29-Aug-06 6:14
professionalRaj Lal29-Aug-06 6:14 
GeneralRe: Very nice approach! Pin
Rabbit1731-Aug-06 9:18
Rabbit1731-Aug-06 9:18 
GeneralRe: Very nice approach! Pin
Raj Lal31-Aug-06 9:40
professionalRaj Lal31-Aug-06 9:40 
GeneralRe: Very nice approach! Pin
Sk8tzz5-Sep-06 20:32
Sk8tzz5-Sep-06 20:32 
GeneralRe: Very nice approach! Pin
Raj Lal7-Sep-06 8:25
professionalRaj Lal7-Sep-06 8:25 
QuestionWhat not using embedded resources? Pin
ahmed.morali22-Aug-06 2:16
ahmed.morali22-Aug-06 2:16 

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.