Enhancing Usability of Windows Presentation Foundation (WPF) and Windows Forms Applications
- 2/15/2011
- Before You Begin
- Lesson 1: Implementing Asynchronous Processing
- Lesson 2: Implementing Globalization and Localization
- Lesson 3: Integrating Windows Forms Controls and WPF Controls
- Case Scenarios
- Suggested Practices
- Take a Practice Test
Lesson 2: Implementing Globalization and Localization
Applications that display data in formats appropriate to a particular culture and that display locale-appropriate strings in the user interface (UI) are considered globally ready applications. You can create globally ready applications with Visual Studio by taking advantage of the built-in support for globalization and localization. In this lesson, you learn how to implement localization and globalization in a Windows Forms application and a WPF application.
Globalization and Localization
Globalization and localization are different processes of internationalization. Globalization refers to formatting existing data in formats appropriate for the current culture setting. Localization, however, refers to retrieving appropriate data based on the culture. The following examples illustrate the difference between globalization and localization:
Globalization In some countries, currency is formatted using a period (.) as a thousand separator and a comma (,) as a decimal separator, whereas other countries use the opposite convention. A globalized application formats currency data with the appropriate thousand separator and decimal separator based on the current culture settings.
Localization The title of a form is displayed in a given language based on the locale in which it is deployed. A localized application retrieves the appropriate string and displays it based on the current culture settings.
Culture
Culture refers to cultural information about the country or region in which the application is deployed. In the .NET Framework, cultures are represented by a culture code that indicates the current language. For example, the following culture codes represent the following languages:
en Specifies the English language
eu Specifies the Basque language
tr Specifies the Turkish language
Culture codes can specify only the language, like the ones shown here, or they can specify both the language and the region. Culture codes that specify only the language are called neutral culture codes, whereas culture codes that specify both the language and the region are called specific culture codes. Examples of specific culture codes are shown in the following list:
en-CA Specifies the English language and Canada as the region
af-ZA Specifies the Afrikaans language and South Africa as the region
kn-IN Specifies the Kannada language and India as the region
You can find a complete list of culture codes in the CultureInfo class reference topic (http://msdn.Microsoft.com/en-us/library/system.globalization.cultureinfo.aspx) in the .NET Framework reference documentation.
Most culture codes follow the format just described, but some culture codes are exceptions. The following culture codes are examples that specify the character sets in addition to other information:
uz-UZ-Cyrl. Specifies the Uzbek language, the Uzbekistan region, and the Cyrillic alphabet
uz-UZ-Latn. Specifies the Uzbek language, the Uzbekistan region, and the Latin alphabet
zh-CHT. Specifies the traditional Chinese language, no region
zh-CHS. Specifies the simplified Chinese language, no region
Changing the Current Culture
Your application automatically reads the culture settings of the system and implements them. Thus, in most circumstances, you do not have to change the culture settings manually. You can, however, change the current culture of your application in code by setting the current culture to a new instance of the CultureInfo class. The CultureInfo class contains information about a particular culture and how it interacts with the application and system. For example, the CultureInfo class contains information about the type of calendar, date formatting, currency formatting, and so on for a specific culture. You set the current culture of an application programmatically by setting the CurrentThread.CurrentCulture property to a new instance of the CultureInfo class. The CultureInfo constructor requires a string that represents the appropriate culture code as a parameter. The following code example demonstrates how to set the current culture to French Canadian:
Sample of Visual Basic Code
System.Threading.Thread.CurrentThread.CurrentCulture = New _ System.Globalization.CultureInfo("fr-CA")
Sample of C# Code
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("fr-CA");
Implementing Globalization
The CurrentThread.CurrentCulture property controls the culture used to format data. When CurrentCulture is set to a new instance of CultureInfo, any data formatted by the application is updated to the new format. Data that is not formatted by the application is not affected by a change in the current culture. Consider the following examples:
Sample of Visual Basic Code
Label1.Text = "$500.00" Label2.Text = Format(500, "Currency")
Sample of C# Code
label1.Text = "$500.00"; label2.Text = (500).ToString("C");
When the culture is set to en-US, which represents the English language and the United States as the region (which is the default culture setting for computers in the United States), both labels display the same string—that is, “$500.00”. When the current culture is set to fr-FR, which represents the French language and France as the region, the text in the two labels differs. The text in Label1 always reads “$500.00” because it is not formatted by the application. The text in Label2, however, reads “500,00 €”. Note that the currency symbol is changed to the appropriate symbol for the locale—in this case, the euro symbol—and the decimal separator is changed to the separator that is appropriate for the locale (in this case, the comma).
Implementing Localization
You can implement localization—that is, provide a user interface (UI) specific to the current locale—by using the built-in localization features of Visual Studio, which enable you to create alternative versions of forms that are culture-specific and automatically manages retrieval of resources appropriate for the culture.
Changing the Current User Interface Culture
The UI culture is represented by an instance of CultureInfo and is distinct from the CultureInfo.CurrentCulture property. The CurrentCulture setting determines the formatting that will be applied to system-formatted data, whereas the CurrentUICulture setting determines the resources that will be loaded into localized forms at run time. You can set the UI culture by setting the CurrentThread.CurrentUICulture property, as shown in the following example:
Sample of Visual Basic Code
' Sets the current UI culture to Thailand System.Threading.Thread.CurrentThread.CurrentUICulture = New _ System.Globalization.CultureInfo("th-TH")
Sample of C# Code
// Sets the current UI culture to Thailand System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("th-TH");
When the current UI culture is set, the application loads resources specific to that culture if they are available. If culture-specific resources are unavailable, the UI displays resources for the default culture.
Note that the UI culture must be set before a form that displays any localized resources is loaded. If you want to set the UI culture programmatically, you must set it before the form has been created, either in the form’s constructor or in the application’s Main method.
Creating Localized Forms
Every form exposes a Localizable property that determines whether the form is localized. Setting this property to True enables localization for the form.
When the Localizable property of a form is set to True, Visual Studio .NET automatically handles the creation of appropriate resource files and manages their retrieval according to the CurrentUICulture setting.
At design time, you can create localized copies of a form by using the Language property. It is available only at design time and assists in the creation of localized forms. When the Language property is set to (Default), you can edit any of the form’s UI properties or controls to provide a representation for the default UI culture. To create a localized version of the form, you can set the Language property to any value other than (Default). Visual Studio will create a resource file for the new language and store any values you set for the UI in that file.
To create localized forms:
Set the Localizable property of your form to True.
Design the UI of your form and translate any UI elements into the localized languages.
Add UI elements for the default culture. This is the culture that will be used if no other culture is specified.
Set the Language property of your form to the culture for which you want to create a localized form.
Add the localized UI content to your form.
Repeat steps 4 and 5 for each localized language.
Build your application.
When CurrentUICulture is set to a localized culture, your application loads the appropriate version of the form by reading the corresponding resource files. If no resource files exist for a specified culture, the default culture UI is displayed.
Localizing a WPF application
Localization in WPF is enabled through satellite assemblies. Localizable elements of your application are segregated into resource assemblies that are loaded automatically, depending on the current UI culture. When a localized application is started, the application first looks for resource assemblies targeted to the specific culture and region (fr-CA in the previous example). If those assemblies are not found, it looks for assemblies targeted to the language only (fr in the previous example). If neither is found, the application looks for a neutral resource set. If this is not found either, an exception is raised. You should localize your application for every language in which you expect it to be used.
You can avoid localization-based exceptions by setting the NeutralResourcesLanguage attribute. This attribute designates the resource set to be used if a specific set of resources cannot be found. The following example demonstrates how to use the NeutralResourcesLanguage attribute:
Sample of Visual Basic Code
<Assembly: NeutralResourcesLanguage("en-US", _ UltimateResourceFallbackLocation.Satellite)>
Sample of C# Code
[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
Localizing an Application
Localization in WPF is a multi-step process. The following procedure is a high-level protocol for localizing a WPF application. Each of the steps is discussed in greater detail later in this lesson.
To localize an application:
Add a UICulture attribute to the project file and build the application to generate culture-specific subdirectories.
Mark localizable properties with the Uid attribute to identify them uniquely. You must perform this step for each XAML file in your application.
Extract the localizable content from your application using a specialized tool (as discussed later in this chapter).
Translate the localizable content.
Create subdirectories to hold satellite assemblies for the new cultures.
Generate satellite assemblies using a specialized tool.
Adding the UICulture Attribute to the Project File
By default, a WPF application is not culture-aware. You can make your application culture-aware by adding the UICulture attribute to the project file and building the application. The UICulture attribute indicates the default culture for the application (usually en-US for applications created and run in the United States). After adding this attribute, building the application generates a subdirectory for the culture in the application directory with localizable content in a satellite assembly.
To add the UICulture attribute to the project file:
Open the project file for your project (<ProjectName>.csproj for C# applications and <ProjectName>.vbproj for Visual Basic applications) with Notepad or a similar text editor.
Locate the first <PropertyGroup> tag. Within that tag, add the following set of XAML tags:
<UICulture>en-US</UICulture>
If you are creating your application in a location other than the United States, or are using a language other than English, adjust the culture code in this tag accordingly.
Save the project file and build your application.
Marking Localizable Elements
The first step in actually localizing your application is to mark elements that are localizable; this includes all strings displayed in the user interface, but many other properties are localizable as well. For example, languages that use different alphabets might require the FontWidth property of visual elements to be localized, and languages that are read from right to left (rather than from left to right, as English is) require localization of the FlowDirection property of visual elements. Images are typically localized; thus, ImageSource properties have to be adjusted to point to the appropriate images. Different languages require the localization of font or other UI element sizes to account for differences in string lengths. Even color combinations can be culturally sensitive and require you to localize the Foreground and Background brushes. Deciding what to localize in an application is often the most difficult part of the entire process, but it is also the most important and should be given a great deal of thought. The point to keep in mind is that localization involves much more than simple translation; it is a complex process that requires sufficient research and planning.
You can mark elements for localization by adding the Uid attribute to the element in XAML. This is an attribute that uniquely identifies an element for the purpose of localization. You can add the Uid attribute as shown here in bold:
<Button x:Uid="Button_1" Margin="112,116,91,122" Name="Button1">Button</Button>
Alternatively, you can use the Msbuild.exe tool to mark every element in your application with the Uid attribute by using the updateuid flag and pointing it to your project file, as shown here:
msbuild /t:updateuid myApplication.vbproj
This tool should be run from the command prompt in Visual Studio, which is available in the Visual Studio Tools subdirectory of your Visual Studio folder on the Start menu.
When localizable resources are extracted from your application, every localizable property of every element marked with the Uid attribute is extracted.
Note that you must mark every element in your application that is in an XAML file and that you want to localize. This includes resources and resources in resource dictionaries.
Extracting Localizable Content
Extraction of localizable content from your application requires a specialized tool. You can download a command-line tool named LocBaml that can extract localizable content, and third-party solutions are also available. To acquire LocBaml, navigate to http://msdn.microsoft.com/en-us/library/ms771568.aspx and download the source files from the link in the LocBaml Tool Sample topic.
The LocBaml tool is not a compiled application. You must compile it before you can use it, and then you must run it as a command-line application from the directory that contains your compiled application and use the /parse switch to provide the path to the resources dynamic link library (DLL). An example is shown here:
locbaml /parse en-US\myApplication.resources.dll
LocBaml outputs a .csv file that contains all localizable properties from all the elements that have been marked with the Uid attribute.
Translating Localizable Content
Content typically is not translated by the developer. Rather, localization specialists are employed to provide translated strings and values for other translatable properties. The .csv file generated by LocBaml provides a row of data pertaining to each localizable property extracted from the application. Each row contains the following information:
The name of the localizable resource
The Uid of the element and the name of the localizable property
The localization category, such as Title or Text
Whether the property is readable (that is, whether it is visible as text in the user interface)
Whether the property value can be modified by the translator (always true unless you indicate otherwise)
Any additional comments you provide for the translator
The value of the property
The final entry in each row, the value of the property, is the property that must be translated by the translator. When translation is complete, the .csv file is returned to you with the translated values in the final column.
Creating Subdirectories
Before satellite assemblies can be created, you must create a subdirectory named for the appropriate culture code to house them. This subdirectory should be created in the directory where your compiled application exists, and it should be named for the culture code for which you are creating satellite assemblies. For example, if you were creating satellite assemblies for French as spoken in Canada, you would name your directory fr-CA.
Generating Satellite Assemblies
After the resources have been translated and the subdirectories have been created, you are ready to generate your satellite assemblies, which hold culture-specific resources for a localized application. If you are using LocBaml, you can generate satellite assemblies by running LocBaml again from the directory in which your compiled application resides and using the /generate switch to generate a satellite assembly. The following example demonstrates a command-line use of LocBaml to generate a satellite assembly:
locbaml /generate en-US\myApplication.resources.dll /trans:myApplication.resources.FrenchCan.csv /cul:fr-CA /out:fr-CA
Let’s break down what this command does. The /generate switch tells LocBaml to generate a satellite assembly based on the indicated assembly, which, in this example, is en-US\myApplication.resources.dll. The /trans switch specifies the .csv file used to generate the satellite assembly (myApplication.resources.FrenchCan.csv in this example). The /cul switch associates the indicated culture with the satellite assembly, and the /out switch specifies the name of the folder, which must match the specified culture exactly.
Loading Resources by Locale
After satellite assemblies have been created, your application automatically loads the appropriate resources for the culture. As described previously, you can change the current UI culture by setting the CurrentThread.CurrentUICulture property to a new instance of System.Globalization.CultureInfo, or you can change culture settings through the system. If the culture changes while an application is running, you must restart the application to load culture-specific resources. If you use code to change the UI culture, you must set UICulture to a new instance of CultureInfo before any of the user interface is rendered. Typically, the best place to do this is in the Application.Startup event handler.
Using Culture Settings in Validators and Converters
Although localizing UI elements is an invaluable part of localization, you must also format data appropriately for the current culture setting. In some cases, this happens automatically. For example, the String.Format method uses the correct decimal and time separators based on the current UI culture. But when you provide formatting for data presented in your user interface or provide validation, your code must take the current culture into account.
The Convert and ConvertBack methods of the IValueConverter interface and the Validate method of the ValidationRule class provide a parameter that indicates the culture. In the case of IValueConverter, the parameter is named culture, and in the Validate method, the parameter is called cultureInfo. In both cases, the parameter represents an instance of System.Globalization.CultureInfo. Whenever you create a validation rule or converter in a localized application, you always should test the culture value and provide culture-appropriate formatting for your data. The following shows an example:
Sample of Visual Basic Code
<ValueConversion(GetType(String), GetType(String))> _ Public Class LanguageConverter Implements IValueConverter ' Note: the Translator class is assumed to be a class that contains a ' dictionary used to translate the provided strings. Dim myTranslator As New Translator Public Function Convert(ByVal value As Object, ByVal targetType As _ System.Type, ByVal parameter As Object, ByVal culture As _ System.Globalization.CultureInfo) As Object Implements _ System.Windows.Data.IValueConverter.Convert Dim astring As String = CType(value, String) Select Case culture.ToString Case "fr-FR" Return myTranslator.EnglishToFrench(astring) Case "de-DE" Return myTranslator.EnglishToGerman(astring) Case Else Return astring End Select End Function Public Function ConvertBack(ByVal value As Object, ByVal targetType _ As System.Type, ByVal parameter As Object, ByVal culture As _ System.Globalization.CultureInfo) As Object Implements _ System.Windows.Data.IValueConverter.ConvertBack Dim astring As String = CType(value, String) Select Case culture.ToString Case "fr-FR" Return myTranslator.FrenchToEnglish(astring) Case "de-DE" Return myTranslator.GermanToEnglish(astring) Case Else Return astring End Select End Function End Class
Sample of C# Code
[ValueConversion(typeof(string), typeof(string))] public class LanguageConverter : IValueConverter { // Note: the Translator class is assumed to be a class that contains a // dictionary used to translate the provided strings. Translator myTranslator = new Translator(); public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string aString = (string)value; switch(culture.ToString()) { case "fr-FR": return myTranslator.EnglishToFrench(aString); case "de-DE": return myTranslator.EnglishToGerman(aString); default: return aString; } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string aString = (string)value; switch(culture.ToString()) { case "fr-FR": return myTranslator.FrenchToEnglish(aString); case "de-DE": return myTranslator.GermanToEnglish(aString); default: return aString; } } }
PRACTICE: Create Localized Forms
In this practice, you create localized forms. You create a form for the default culture that demonstrates date/time display and currency display as well as strings for the default culture. Then you create a localized version of this form that includes German strings. Finally, you create a form that enables you to choose the locale for which you would like to display your localized form and sets the culture appropriately. A completed solution to this practice can be found in the files installed from the companion CD.
EXERCISE Creating Localized Forms
In Visual Studio, create a new Windows Forms application.
From the Project menu, choose Add Windows Form to add a new Form to your project. Name the new form Form2.
In the designer, click the tab for Form2. From the Toolbox, add four Label controls. Set the Text properties as follows:
Label
Text Property Value
Label1
Currency Format
Label2
(nothing)
Label3
Current Date and Time
Label4
(nothing)
Double-click Form2 to open the Form2_Load event handler. Add the following code to the Form2_Load event handler:
Sample of Visual Basic Code
Label2.Text = Format(500, "Currency") Label4.Text = Now.ToShortDateString
Sample of C# Code
label2.Text = (500).ToString("C"); label4.Text = System.DateTime.Now.ToShortDateString();
In the designer, set the Form2.Localizable property to True and set the Language property to German (Germany).
Set the Text properties of Label1 and Label3 as follows:
Label
Text Property Value
Label1
Währung-Format
Label3
Aktuelle Uhrzeit
In the designer, click the tab for Form1.
From the Toolbox, add three Button controls to the form and set their Text properties as shown here:
Button
Button Text Property Value
Button1
United States
Button2
United Kingdom
Button3
Germany
In the designer, double-click the Button1 control to open the Button1_Click default event handler and add the following code:
Sample of Visual Basic Code
System.Threading.Thread.CurrentThread.CurrentCulture = New _ System.Globalization.CultureInfo("en-US") System.Threading.Thread.CurrentThread.CurrentUICulture = New _ System.Globalization.CultureInfo("en-US") Dim aform As New Form2() aform.Show()
Sample of C# Code
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); Form2 aform = new Form2(); aform.Show();
In the designer, double-click the Button2 control to open the Button2_Click default event handler and add the following code:
Sample of Visual Basic Code
System.Threading.Thread.CurrentThread.CurrentCulture = New _ System.Globalization.CultureInfo("en-GB") System.Threading.Thread.CurrentThread.CurrentUICulture = New _ System.Globalization.CultureInfo("en-GB") Dim aform As New Form2() aform.Show()
Sample of C# Code
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-GB"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-GB"); Form2 aform = new Form2(); aform.Show();
In the designer, double-click the Button3 control to open the Button3_Click default event handler and add the following code:
Sample of Visual Basic Code
System.Threading.Thread.CurrentThread.CurrentCulture = New _ System.Globalization.CultureInfo("de-DE") System.Threading.Thread.CurrentThread.CurrentUICulture = New _ System.Globalization.CultureInfo("de-DE") Dim aform As New Form2() aform.Show()
Sample of C# Code
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE"); Form2 aform = new Form2(); aform.Show();
Press F5 to build and run your application. Click each button to see a localized form. Note that the appropriate format for currency and the date are displayed in the localized form and that the new strings are loaded for the German form.
Lesson Summary
Culture refers to cultural information about the country or region in which the application is deployed and is represented by a culture code. Globalization refers to the process of formatting application data in formats appropriate for the locale. Localization refers to the process of loading and displaying localized strings in the UI.
The CurrentCulture setting for the thread determines the culture used to format application data. The CurrentUICulture setting for the thread determines the culture used to load localized resources.
You can create localized forms by setting the Localizable property of a form to True and then setting the Language property to a language other than (Default). A new copy of the form is created for this culture, and localized resources can be added to this form.
You can implement right-to-left display in a control by setting the RightToLeft property to True. You can reverse the control layout of an entire form by setting the RightToLeftLayout and RightToLeft properties of a form to True.
Localization in WPF requires localizable elements to be marked with the Uid attribute, which uniquely identifies localizable elements in your application.
LocBaml is a command-line application available from Microsoft as a downloadable, compilable sample. LocBaml can be used to extract localizable resources from your application and to build satellite assemblies with localized resources.
Methods in IValueConverter and ValidationRule provide a reference to the CultureInfo object to be used in the operation. Whenever culture-specific formatting or validation is required, your code should check the culture to provide the appropriate functionality.
Lesson Review
The following questions are intended to reinforce key information presented in Lesson 2: Implementing Globalization and Localization. The questions are also available on the companion CD if you prefer to review them in electronic form.
Which of the following lines of code should be used to format data appropriately for Germany?
Sample of Visual Basic Code
System.Threading.Thread.CurrentThread.CurrentUICulture = New _ System.Globalization.CultureInfo("de-DE")
Sample of C# Code
System.Threading.Thread.CurrentThread.CurrentUICulture = New System.Globalization.CultureInfo("de-DE");
Sample of Visual Basic Code
Me.CurrentUICulture = New System.Globalization.CultureInfo("de-DE")
Sample of C# Code
this.CurrentUICulture = New System.Globalization.CultureInfo("de-DE");
Sample of Visual Basic Code
System.Threading.Thread.CurrentThread.CurrentCulture = New _ System.Globalization.CultureInfo("de-DE")
Sample of C# Code
System.Threading.Thread.CurrentThread.CurrentCulture = New System.Globalization.CultureInfo("de-DE");
Sample of Visual Basic Code
Me.CurrentCulture = New System.Globalization.CultureInfo("de-DE")
Sample of C# Code
this.CurrentCulture = New System.Globalization.CultureInfo("de-DE");
Given a form that contains a Label control named Label1 and a Button control named Button1, all with default settings, which of the following must you do to display the entire form and all controls in a right-to-left layout with right-to-left text display? (Choose all that apply.)
Set the Label1.RightToLeft property to True.
Set the Button1.RightToLeft property to True.
Set the Form1.RightToLeft property to True.
Set the Form1.RightToLeftLayout property to True.