Many programmers write applications that are used only in their countries. It's easy enough to find plenty of customers for a small application without looking for customers long distance.
However, the world has grown smaller in the past few decades, and it's not too hard to provide programs for people all over the world. Customers can download your software over the Internet and pay for it using online payment systems in a matter of minutes. Web applications that run in a browser are even more likely to be used by people all over the world.
With such a potentially enormous market, it makes sense in some cases to make programs accessible to people in different countries, particularly since C# and Visual Studio make it relatively easy.
In this lesson, you learn how to make a program accessible to customers in other countries with different cultures. You learn how to make multiple interfaces for a program so users can work in their own languages. You also learn how to work with values such as currencies and dates that have different formats in different locales.
A computer's locale is a setting that defines the user's language, country, and cultural settings that determine such things as how dates and monetary values are formatted. For example, the Format Values example program shown in Figure 33.1 (and available in this lesson's downloads) displays the same values in American, British, German, and French locales.
If you look closely at Figure 33.1, you can see that the same values produce very different results in the different locales. For example, the value 1234.56 displayed as currency appears variously as:
Not only do these results use different currency symbols, but they even use different decimal and thousands separators.
Localizing an application involves two main steps: building a localized user interface and processing locale-specific values.
At first this may seem like a daunting task. How do you build completely separate interfaces for multiple locales? Fortunately this is one thing that C# and Visual Studio do really well (at least for Windows Forms applications).
To build a globalized program, start by creating the form as usual. Add controls and set their properties as you want them to appear by default.
After you've defined the program's default appearance, you can localize it for other locales. To do that, set the form's Localizable
property to true
. Then select a new locale from the dropdown list provided by the form's Language
property. Now modify the form to handle the new locale. You can change control properties such as the text they display. You can also move controls around and change their sizes, which is particularly important because the same text may take up a different amount of room in different languages.
At run time, the program automatically checks the computer's locale settings and picks the program's localization that gives the closest match.
Note that many languages have several sub-locales. For example, English comes in the varieties used in India, Ireland, New Zealand, and more than a dozen other locales.
There's also locale listed simply as “English.” If the user's computer is set up for one of the English locales that the program doesn't support, the program falls back to the generic English locale. If the program can't support that locale either, it uses the default locale that you used when you initially created the form.
The Localized Weekdays example program (available in this lesson's code download) is localized for English (the form's default) and German. Figure 33.2 shows the form's English interface and Figure 33.3 shows its German interface.
Having the program check the computer's locale automatically at run time is convenient for the user but it makes testing different locales tricky.
One way to force the program to pick a particular locale so you can test it is to select the locale in code. You must do this before the form is initialized because after that point the form's text and other properties are already filled in and setting the locale won't reload the form.
When you create a form, Visual Studio automatically creates a constructor for it that calls the InitializeComponent
function. Place your code before the call to InitializeComponent
.
The following code shows how the Localized Weekdays program explicitly selects either the English or the German locale:
using System.Threading;
using System.Globalization;
...
public Form1()
{
// English.
//Thread.CurrentThread.CurrentCulture =
// new CultureInfo("en-US", false);
//Thread.CurrentThread.CurrentUICulture =
// new CultureInfo("en-US", false);
// German.
Thread.CurrentThread.CurrentCulture =
new CultureInfo("de-DE", false);
Thread.CurrentThread.CurrentUICulture =
new CultureInfo("de-DE", false);
InitializeComponent();
}
This code contains statements that set the locale to English or German. Simply comment out the one that you don't want to use for a given test.
Setting the CurrentCulture
makes the program use locale-specific methods when processing dates, currency, numbers, and other values in the code. Setting the CurrentUICulture
makes the program load the appropriate user interface elements for the form.
Inside C# code, variables are stored in American English formats. To avoid confusion, Microsoft decided to pick one locale for code values and stick with it.
When you move data in and out of the program, however, you need to be aware of the computer's locale. For example, suppose the program uses the following code to display an order's due date:
dueDateTextBox.Text = dueDate.ToString("MM/dd/yy")
If the date is November 20, 2010, this produces the result “11/20/10,” which makes sense in the United States but should be “20/11/10” in France and “20.11.10” in Germany.
The problem is that the program uses a custom date format that is hard-coded to use an American-style date format. To produce a format appropriate for the user's system, you should use standard date, time, and other formats whenever possible. The following code uses the standard short date format:
dueDateTextBox.Text = dueDate.ToString("d")
This produces “11/20/2010” on an American system and “20/11/2010” on a French system.
You can run into the same problem if you assume the user will enter values in a particular format. For example, suppose you want to get the whole number part of the value 1,234.56 entered by the user. If you assume the decimal separator is a period and just use whatever comes before it as the integer part, then you'll get the answer 1 when a German user enters “1.234,56” and the program will crash when a French user enters the value “1 234.56.”
To avoid this problem, use locale-aware functions such as the numeric classes' Parse
methods to read values entered by the user. In this example, a good solution is to use float.Parse
to read the value and then truncate it as shown in the following code:
value = (int)float.Parse(valueTextBox.Text);
For a list of standard numeric formats, see msdn.microsoft.com/library/dwhawy9k.aspx
.
For a list of standard date and time formats, see msdn.microsoft.com/library/az4se3k1.aspx
.
For more information on parsing strings, see msdn.microsoft.com/library/b4w53z0y.aspx
.
In this Try It, you write the program shown in Figures 33.5 and 33.6, which lets you select foreground and background colors in American English and Mexican Spanish.
In this lesson, you:
RadioButton
s' Click
events.RadioButton
. Use one event handler for all of the foreground buttons and one for all of the background buttons.Tag
properties and then use the Color
class's FromName
method to get the appropriate Color
.RadioButton
s' Tag
properties.RadioButton
s' Click
events.
// Set the foreground color.
private void Foreground_Click(object sender, EventArgs e)
{
// Get the sender as a RadioButton.
RadioButton rad = sender as RadioButton;
// Use the color.
Color clr = Color.FromName(rad.Tag.ToString());
this.ForeColor = clr;
fgGroupBox.ForeColor = clr;
bgGroupBox.ForeColor = clr;
}
This code converts the sender
object into a RadioButton
and uses its Tag
property to get the corresponding color. It then applies that color to the form and the two GroupBox
es.
RadioButton
s to this event handler.RadioButton
s.Localizable
property to true
. Click the Language
property, click the dropdown arrow to the right, and select “Spanish (Mexico).”Text
properties so they have the values shown in Figure 33.6.// Select a locale for testing.
public Form1()
{
// English.
//Thread.CurrentThread.CurrentCulture =
// new CultureInfo("en-US", false);
//Thread.CurrentThread.CurrentUICulture =
// new CultureInfo("en-US", false);
// Spanish.
Thread.CurrentThread.CurrentCulture =
new CultureInfo("es-MX", false);
Thread.CurrentThread.CurrentUICulture =
new CultureInfo("es-MX", false);
InitializeComponent();
}
Dutch.txt
, German.txt
, and English.txt
from the book's website and make a program that can read them. The program should let the user select a file, check the filename to see which locale it should use, and select the correct locale. It should read and parse the values into appropriate data types and then display the values in a DataGridView
control. Hints:
Thread.CurrentThread.CurrentCulture =
new CultureInfo("en-US", false);
File.ReadAllLines
to get the lines and Split
to break each line into fields.The following text shows the values in the file Dutch.txt
:
Potlood ? 0,10 12 ? 1,20
Blocnote ? 1,10 10 ? 11,00
Laptop ? 1.239,99 1 ? 1.239,99
// Set the form's culture.
private void SetFormCulture(Form form, string culture)
{
// Make the CultureInfo.
CultureInfo cultureInfo = new CultureInfo(culture);
// Make a ComponentResourceManager.
ComponentResourceManager resourceManager =
new ComponentResourceManager(form.GetType());
// Apply resources to the form.
resourceManager.ApplyResources(form, "$this", cultureInfo);
// Apply resources to the form and its controls.
SetControlCulture(form, cultureInfo, resourceManager);
}
The SetFormCulture
method creates a CultureInfo
object to represent the desired culture. It then creates a ComponentResourceManager
for the form and uses it to load the form's localized resources. Resources for use by the form are identified by the special name $this
.
The method then calls the following SetControlCulture
method for the form:
// Set the control's culture using the indicated
// CultureInfo and ComponentResourceManager.
private void SetControlCulture(Control control,
CultureInfo cultureInfo,
ComponentResourceManager resourceManager)
{
// Apply resources to the control.
resourceManager.ApplyResources(
control, control.Name, cultureInfo);
// Apply resources to the control's children.
foreach (Control child in control.Controls)
SetControlCulture(child, cultureInfo, resourceManager);
}
The SetControlCulture
method uses the resource manager to load culture-specific resources for the control. The method uses the control's name to identify the resource values to use. (When SetFormCulture
calls this method, it first sets properties for the form. However, the form's resources are stored with the special name $this
, so that first call to ApplyResources
doesn't do anything.)
After making the control reload its resources, the code loops through the control's children and calls SetControlCulture
to reload their resources. This is necessary to handle controls inside containers such as GroupBox
es or TabControl
s.
Copy the program you wrote for Exercise 1, remove the testing code that selects a locale, and add English, Español, and Italiano RadioButton
s to the top of the form. When the user selects one of them, use the SetFormCulture
and SetControlCulture
methods to make the form use the appropriate localization. (Hint: Store the locale name in the RadioButton
s' Tag
properties.)
PictureBox
's Image
property. (I asked people at Microsoft about this and they said, “Gee, we never thought anyone would want to localize that.”)
Copy the program you wrote for Exercise 3 and add a PictureBox
to display an image of the selected country's flag. Add code to the RadioButton
s' Clicked
event handler to display the correct flag. (Hint: Add the flag images to the project's resources by selecting Project Properties, clicking the Resources tab, opening the Add Resource dropdown, and selecting Add Existing File. Then make the code set the PictureBox
's Image
property to a value such as Properties.Resources.MexicanFlag
.)
Resources.resx
.LeftHeader
and set its value to “Foreground Color.”
Public
.xmlns:res=”clr-namespace:WPF_Select_Colors.Properties”
This statement lets you use the name res
to represent the application's properties. (In this example, the application's root namespace is WPF_Select_Colors
.)
GroupBox
use the value of the LeftHeader
resource:<GroupBox Grid.Row=”0” Grid.Column=”0”
Header="{x:Static res:Resources.LeftHeader}
">
To make a resource file for another locale, follow these steps:
Resources.resx
file. Rename it to include the locale identifier as in Resources.es-MX.resx
.When the program runs, it will select the appropriate resource file. You can test the program by setting its CurrentCulture
and CurrentUICulture
in the main window's constructor just as you would for a Windows Forms application.
For this exercise, create a WPF program similar to the program you built for Exercise 1.
Hints:
Color clr = (Color)ColorConverter.ConvertFromString("Red");
Brush brush = new SolidColorBrush(clr);
Background
property.GroupBox
es' Background
properties. Then loop through Children
collections of the controls (probably StackPanel
s) that hold the RadioButton
s and set the children's Background
properties.CurrentCulture
and CurrentUICulture
as usual. Then use code similar to the following to reload all of the localized properties:
fgGroupBox.Header = Properties.Resources.LeftHeader;
redFgButton.Content = Properties.Resources.Red;
greenFgButton.Content = Properties.Resources.Green;
...
You can place all of those statements in a method to make them easier to call.
Copy the program you wrote for Exercise 5 and modify it so it allows the user to change locales at run time (much as you did for Exercise 3).
Image
controls holding the pictures you want to display and then change their Visibility
properties at run time. Copy the program you wrote for Exercise 6 and modify it so it displays appropriate flag images when the user changes locales (much as you did for Exercise 4).18.119.235.79