|
Localization is an important part of global software distribution. Localizing software opens up new markets around the world. We will demonstrate in this tutorial how to use localized strings in a simple C# WinForm application.
Localization Overview
Localization is the process of converting software user interfaces to a user’s local culture. For the most part, localization involves converting text strings into the language of the local culture, but it can also involve time, date, and number formats, for example.
In this tutorial, we will build a simple WinForm application that takes a user’s full name as input, and displays a greeting message box to the user. The application will support three separate languages, and easily allow for future languages with its framework.
Our application will go from this:

To this:

With just a simple radio button check.
Please note: I am not multi-lingual in the human language sense of the term. Therefore, I have used the fish to translate from English into the other two languages. There will probably be some semantically incorrect translations, so please forgive me.
Step 1: Create a New WinForm Application
Go ahead and create a new C# Windows Application under New | Project. Call this project "LocalizationSample". You should now have the IDE form editor open with a blank form. Rename "Form1.cs" to "LocalizationSampleForm.cs". We are going to replace the form code with the form code I used for this application in the next step.
Step 2: Replace Blank Form Code With Code Specified
Replace the code in LocalizationSample.cs with the following code:
using System;
using System.ComponentModel;
using System.Collections;
using System.Windows.Forms;
using System.Resources;
using System.Globalization;
using System.Threading;
namespace LocalizationSample
{
public class LocalizationSampleForm : Form
{
private TextBox textFirstName;
private TextBox textLastName;
private Label labelFirstName;
private Label labelLastName;
private Label labelChoose;
private Button buttonGreetMe;
private RadioButton radioEnglish;
private RadioButton radioFrench;
private RadioButton radioSpanish;
private Container components = null;
public LocalizationSampleForm()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.textFirstName = new System.Windows.Forms.TextBox();
this.labelLastName = new System.Windows.Forms.Label();
this.labelFirstName = new System.Windows.Forms.Label();
this.buttonGreetMe = new System.Windows.Forms.Button();
this.radioFrench = new System.Windows.Forms.RadioButton();
this.labelChoose = new System.Windows.Forms.Label();
this.textLastName = new System.Windows.Forms.TextBox();
this.radioEnglish = new System.Windows.Forms.RadioButton();
this.radioSpanish = new System.Windows.Forms.RadioButton();
this.SuspendLayout();
this.labelLastName.Location = new System.Drawing.Point(20, 58);
this.labelLastName.Name = "labelLastName";
this.labelLastName.Size = new System.Drawing.Size(165, 16);
this.labelFirstName.Location = new System.Drawing.Point(20, 26);
this.labelFirstName.Name = "labelFirstName";
this.labelFirstName.Size = new System.Drawing.Size(165, 16);
this.textFirstName.Location = new System.Drawing.Point(176, 22);
this.textFirstName.Name = "textFirstName";
this.textFirstName.Size = new System.Drawing.Size(209, 20);
this.textFirstName.TabIndex = 1;
this.textFirstName.Text = "";
this.textLastName.Location = new System.Drawing.Point(176, 56);
this.textLastName.Name = "textLastName";
this.textLastName.Size = new System.Drawing.Size(209, 20);
this.textLastName.TabIndex = 2;
this.textLastName.Text = "";
this.buttonGreetMe.Location = new System.Drawing.Point(83, 92);
this.buttonGreetMe.Name = "buttonGreetMe";
this.buttonGreetMe.Size = new System.Drawing.Size(234, 23);
this.buttonGreetMe.TabIndex = 3;
this.buttonGreetMe.Click += new System.EventHandler(this.buttonGreetMe_Click);
this.labelChoose.Location = new System.Drawing.Point(26, 132);
this.labelChoose.Name = "labelChoose";
this.labelChoose.Size = new System.Drawing.Size(122, 14);
this.radioEnglish.Checked = true;
this.radioEnglish.Location = new System.Drawing.Point(151, 129);
this.radioEnglish.Name = "radioEnglish";
this.radioEnglish.Size = new System.Drawing.Size(70, 24);
this.radioEnglish.TabIndex = 4;
this.radioEnglish.TabStop = true;
this.radioEnglish.CheckedChanged += new System.EventHandler(this.radioEnglish_CheckedChanged);
this.radioFrench.Location = new System.Drawing.Point(231, 129);
this.radioFrench.Name = "radioFrench";
this.radioFrench.Size = new System.Drawing.Size(70, 24);
this.radioFrench.TabIndex = 5;
this.radioFrench.CheckedChanged += new System.EventHandler(this.radioFrench_CheckedChanged);
this.radioSpanish.Location = new System.Drawing.Point(311, 129);
this.radioSpanish.Name = "radioSpanish";
this.radioSpanish.Size = new System.Drawing.Size(75, 24);
this.radioSpanish.TabIndex = 6;
this.radioSpanish.CheckedChanged += new System.EventHandler(this.radioSpanish_CheckedChanged);
this.AcceptButton = this.buttonGreetMe;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(414, 177);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.radioSpanish,
this.labelChoose,
this.radioFrench,
this.radioEnglish,
this.buttonGreetMe,
this.textLastName,
this.textFirstName,
this.labelLastName,
this.labelFirstName});
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.Name = "LocalizationSampleForm";
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new LocalizationSampleForm());
}
private void radioEnglish_CheckedChanged(object sender, System.EventArgs e)
{
if (radioEnglish.Checked)
{
}
}
private void radioFrench_CheckedChanged(object sender, System.EventArgs e)
{
if (radioFrench.Checked)
{
}
}
private void radioSpanish_CheckedChanged(object sender, System.EventArgs e)
{
if (radioSpanish.Checked)
{
}
}
private void buttonGreetMe_Click(object sender, System.EventArgs e)
{
}
}
}
|
Step 3: Adding Localized Resource Files
Now that we have the basic form for our application, we can now add localized resource files to our project. From the menu select File | Add New Item. Under "Local Project Items" select "Resources" then "Assembly Resource File" and type in "LocalizationSample.resx".
The file that was just added is going to be the neutral culture resource file (in the case of this sample, plain english). This file will be compiled into a .resource file and embedded in our executable.
Now that we have our neutral culture resource file, lets type in some values:
Value: |
Name: |
Type your first name here: |
labelFirstName |
Type your last name here: |
labelLastName |
Choose a language: |
labelChoose |
Greet me! |
buttonGreetMe |
English |
radioEnglish |
French |
radioFrench |
Spanish |
radioSpanish |
Please specify a first name. |
textFirstNameMB |
Please specify a last name. |
textLastNameMB |
Hello, {0} {1}, nice to meet you! |
greetingMessage |
Localization Sample - By Peter Huene |
formTitle |
Please Note:There are no spaces between the brackets and the numbers in "greetingMessage". Also, feel free to put your name in the formTitle.
Once you are done entering in the name-value pairs, it is time to add the localized versions of this file. Add a new Assembly Resource file, but this time use the name "LocalizationSample.fr-FR.resx". Note the "fr-FR" in the file name. This is the culture identifier for French (in France).
Now, add the following name-value pairs to this file:
Value: |
Name: |
Tapez votre prénom ici: |
labelFirstName |
Tapez votre dernier nom ici: |
labelLastName |
Choisissez un langage: |
labelChoose |
Saluez-moi! |
buttonGreetMe |
Anglais |
radioEnglish |
Français |
radioFrench |
Espagnols |
radioSpanish |
Veuillez indiquer un prénom. |
textFirstNameMB |
Veuillez indiquer un dernier nom. |
textLastNameMB |
Bonjour, {0} {1}, gentil de vous rencontrer! |
greetingMessage |
Échantillon De Localisation - Par Peter Huene |
formTitle |
Once you are done entering in those name-value pairs, it is time to add the Spanish localized version. Add a new Assembly Resource file, but this time use the name "LocalizationSample.es-ES.resx". You guessed correctly, "es-ES" is the culture identifier for Spanish (in Spain).
Now, add the following name-value pairs to this file:
Value: |
Name: |
Pulse su nombre aquí: |
labelFirstName |
Pulse su nombre pasado aquí: |
labelLastName |
Elija un lenguaje: |
labelChoose |
¡Salúdeme! |
buttonGreetMe |
Ingles |
radioEnglish |
Frances |
radioFrench |
Español |
radioSpanish |
Especifique por favor un nombre. |
textFirstNameMB |
Especifique por favor un nombre pasado. |
textLastNameMB |
¡Hola, {0} {1}, agradable satisfacerle! |
greetingMessage |
Muestra De la Localización - Por Peter Huene |
formTitle |
It is interesting to note that when you compile your application and check your bin\Debug or \bin\Release directory that there are now two sub-directories, namely "fr-FR" and "es-ES". These sub-directories contain what are called satellite resource assemblies. These assemblies contain no executable code, just the localized string resources.
Step 4: Adding the ResourceManager and CultureInfo objects
Under your private control variable definitions, add the following:
private ResourceManager m_ResourceManager = new ResourceManager("LocalizationSample.LocalizationSample",
System.Reflection.Assembly.GetExecutingAssembly());
private CultureInfo m_EnglishCulture = new CultureInfo("en-US");
private CultureInfo m_FrenchCulture = new CultureInfo("fr-FR");
private CultureInfo m_SpanishCulture = new CultureInfo("es-ES");
|
The constructor to the ResourceManager object takes a string that is the root name of the resource to manage, and looks in the specified assembly object (in our case, the executing assembly). Note that the root name of the resource is not "LocalizationSample" but "LocalizationSample.LocalizationSample". The reason for this is that the IDE appends the default namespace to the .resource file when it is compiled (you can verify this in your obj\Debug or obj\Release directories). The default namespace can be changed in the Project properties.
The CultureInfo objects represent information about a culture. These objects will be used to change the current user interface culture.
Step 5: Updating the UI
Lets add the following method to our form class:
private void UpdateUI()
{
labelFirstName.Text = m_ResourceManager.GetString("labelFirstName");
labelLastName.Text = m_ResourceManager.GetString("labelLastName");
labelChoose.Text = m_ResourceManager.GetString("labelChoose");
buttonGreetMe.Text = m_ResourceManager.GetString("buttonGreetMe");
radioEnglish.Text = m_ResourceManager.GetString("radioEnglish");
radioFrench.Text = m_ResourceManager.GetString("radioFrench");
radioSpanish.Text = m_ResourceManager.GetString("radioSpanish");
this.Text = m_ResourceManager.GetString("formTitle");
}
|
and also add the following line after InitializeComponent() in the form constructor:
If you compile and run now, you will see that English now appears where there used to be a blank user interface.
Step 6: Responding to a Change in Language
Add the following code to the if statement in the radioEnglish_CheckedChanged method:
Thread.CurrentThread.CurrentUICulture = m_EnglishCulture;
UpdateUI();
|
Now do the same for both radioFrench_CheckedChanged and radioSpanish_CheckedChange but instead use their appropriate cultures.
When we set the current thread's current UI culture and call UpdateUI(), the resource manager's GetString method uses the current UI culture to localize the string. It does this transparently, and pulls the resources out of satellite resource assemblies if needed.
Step 7: Implement the buttonGreetMe_Click Method
Add the following code to the buttonGreetMe_Click method of our form:
if (textFirstName.Text == "")
{
MessageBox.Show(this, m_ResourceManager.GetString("textFirstNameMB"), this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
textFirstName.Focus();
return;
}
else if (textLastName.Text == "")
{
MessageBox.Show(this, m_ResourceManager.GetString("textLastNameMB"), this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
textLastName.Focus();
return;
}
string message = String.Format(m_ResourceManager.GetString("greetingMessage"), textFirstName.Text, textLastName.Text);
MessageBox.Show(this, message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
The first if statement checks for an empty first name field, and prompts the user (with a localized prompt) to enter in a first name. Likewise for the else if clause. If both fields contain some text, then we get the localized greeting message from the resource manager and format it using the first and last name values from the text boxes. We then display the greeting in a message box.
Now we're practically done, but lets do one more step. If a satellite resource assembly for a given culture doesn't exist, lets disable the radio button.
Step 8: Disable Unsupported Language Radiobutton
To do this, we need to simply check with the resource manager to see if it can obtain the resource set. Add the following code to your forms constructor (somewhere after InitializeComponent()):
if (m_ResourceManager.GetResourceSet(m_FrenchCulture, true, false) == null)
{
this.radioFrench.Enabled = false;
}
if (m_ResourceManager.GetResourceSet(m_SpanishCulture, true, false) == null)
{
this.radioSpanish.Enabled = false;
}
|
That's it! Now if a user deletes the satellite resource assembly, the user won't be able to switch to that language.
Conclusion
By now you should understand the process of localization and how to localize string resources in C#.
Feel free to e-mail me with any Questions/Comments/Flames.
About The Author
I think my picture just about sums it up :).
|
|