Although many (perhaps even most) commercial software users speak and read English, not all do. Those for whom English is a second language often prefer to use software that works in their own language. If you want to ship versions of your software that support more than one language, you need to think about internationalization and localization.
Designing for InternationalizationWindows has recognized the need for multi-language support for years , enabling users to set their locale and culture information on the Control Panel, and then using that information wherever possible in the user interface. Your application almost certainly leverages that power without knowing it. For example, when you convert a DateTime object to a string by calling its ToShortDateString() method, the format of the string depends on the user's culture settings. The same date might be represented as 03/04/2003 or 04/03/2003, depending on the user's location and settings. Similarly the number 1.234 in North America is written as 1,234 in Europe, and you don't have to write code to support this: When a number is converted to a string the local format rules are applied. Support for different formats for numbers and dates is useful and convenient , but what about that form with buttons that read Add or Delete? What about your error messages and your prompts? There are many places within an application that reflect the language in which an application was developed. Building a WinForms application makes it simpler to support multiple languages and localization rules without writing a lot of code. To build an application that supports multiple languages, you actually create multiple assemblies. The first contains all your executable code and one set of resources (text properties of controls and the like) prepared in the original language you used when developing the application. Satellite assemblies contain only the resources, translated into a different language or locale. The Framework checks the user's culture setting and loads the appropriate satellite assembly. If there is no satellite assembly corresponding to the user's setting, the original resources in the main assembly are used. Localizing a Windows ApplicationTo see how to localize a WinForms application, drag another button onto Form1 of the AdvancedUI project. Change the button's name to Hello and the text to Hello World . Double-click the button to add a handler, but don't add any code to the handler yet. Build the project and make sure it runs.
In the designer, select the entire form, and use the Properties window to change the Localizable property to True . Change the Language property of the form to French (Canada) . Click the button and change the Text property to Bonjour Tout Le Monde . Lengthen the button to accommodate the text. Build and run the application. Unless your copy of Windows is French Canadian, you should see Hello World on the button. To simulate owning a French-Canadian copy of Windows, add this line to the Form1 constructor, before the call to InitializeComponent() : System::Threading::Thread::CurrentThread->CurrentUICulture = new System::Globalization::CultureInfo("fr-CA"); When you set your UICulture with this line of code, the application displays the French version of the button. You can see how simple it is to support multiple languages on your buttons and other form components with this technique. Follow these steps:
Of course, this is only part of the story. What about your error messages and other strings? You don't set these in the designer, so you can't localize them this way. Instead you can keep them in a resource file, and create a different version of the resource file for each locale. Adapting an existing application to get its strings from a resource file is quite a bit of work. That's why internationalization starts at the beginning of a project. Rather than adjusting the rest of the AdvancedUI project to use a resource file, this section shows you how to use a resource file in a new section of code. Make sure that the Language property of Form1 is set to Default , and build the application. Right-click the AdvancedUI project in Solution Explorer and choose Add, Add New Item. Select an Assembly Resource File and name it Strings . The file is XML and it opens in the data view for you to edit. There are five columns in the resource file:
Enter Hello for the name and Hello World for the value of the resource. Save the resource file. Add another resource file named Strings.fr-CA.resx. The name is important: The part immediately before the .RESX extension must match the language and locale combination for which you are localizing. The first part must match the resource file you already created. Make sure you provide the .RESX extensionbecause there is a dot in the filename, the wizard will not add the extension for you if you omit it. (It thinks .fr-CA is the extension.) In the data view of the French Canadian resource file, enter a resource named Hello with a value of Bonjour Mes Amis . Save the resource file. Open the code for Form1.h and scroll to the bottom, where the empty Hello_Click handler was added earlier. Edit the handler so it reads like this: private: System::Void Hello_Click(System::Object * sender, System::EventArgs * e) { Resources::ResourceManager* rm = new Resources::ResourceManager("AdvancedUI.ResourceFiles", Reflection::Assembly::GetExecutingAssembly()); String* greeting = rm->GetString("Hello"); Windows::Forms::MessageBox::Show(greeting); } This code starts by creating a ResourceManager . The two parameters that are passed to the constructor are the resource file and the assembly. When you add .RESX files to your project, the build process converts them to a single resource file called < projectname >.ResourceFiles.resources . The first part of this name, < projectname >.ResourceFiles , is what you pass to the constructor to identify the resource file. The assembly to pass is the one that is currently executing, available from a static method of the Assembly class. Once a ResourceManager instance is created, you use it to access resources such as strings. In a large application you might make the ResourceManager reference a member variable of the form, create it in the form Load event, and just use it in various button handlers. The GetString() method looks up a string, and takes the name as a parameter. This code just puts up a message box with that string. Build and run the application. If your UICulture is still set to French Canadian by the extra line in the constructor, you will see the Bonjour Tout Le Monde button, and when you click it, the message box will read Bonjour Mes Amis. Comment out the line in the constructor, build and run the application, and you should see Hello World on the button and in the message box. If you wanted to make a version of this application in another language, you would add another .RESX file, but make no changes to the code. That's how the .NET Framework can make localization feasible even for small applications and small development teams . Just remember that you must plan for internationalization from the beginningfinding every hard-coded string in your application and replacing it with a call to GetString() will be very tedious . Being LocalizableIt's natural to wonder why you have to set the Localizable property of the form to True . Shouldn't forms always be localizable? Making a form localizable means that you no longer make any assumptions about user interfaces, not even assuming that text runs left to right instead of right to left. The number of properties for each component that is set in InitializeComponent goes up dramatically when a form is localizable. For example, when you first add the Hello button to this application, these lines are added to InitializeComponent() : this->Hello->Location = System::Drawing::Point(112, 224); this->Hello->Name = S"Hello"; this->Hello->TabIndex = 5; this->Hello->Text = S"Hello World"; this->Hello->Click += new System::EventHandler(this, Hello_Click); When the Localizable property of the form is set to True , this block of code changes to this->Hello->AccessibleDescription = resources->GetString(S"Hello.AccessibleDescription"); this->Hello->AccessibleName = resources->GetString(S"Hello.AccessibleName"); this->Hello->Anchor = (*__try_cast<__box System::Windows::Forms::AnchorStyles * > (resources->GetObject(S"Hello.Anchor"))); this->Hello->BackgroundImage = (__try_cast<System::Drawing::Image * > (resources->GetObject(S"Hello.BackgroundImage"))); this->Hello->Dock = (*__try_cast<__box System::Windows::Forms::DockStyle * > (resources->GetObject(S"Hello.Dock"))); this->Hello->Enabled = (*__try_cast<__box System::Boolean * > (resources->GetObject(S"Hello.Enabled"))); this->Hello->FlatStyle = (*__try_cast<__box System::Windows::Forms::FlatStyle * > (resources->GetObject(S"Hello.FlatStyle"))); this->Hello->Font = (__try_cast<System::Drawing::Font * > (resources->GetObject(S"Hello.Font"))); this->Hello->Image = (__try_cast<System::Drawing::Image * > (resources->GetObject(S"Hello.Image"))); this->Hello->ImageAlign = (*__try_cast<__box System::Drawing::ContentAlignment * > (resources->GetObject(S"Hello.ImageAlign"))); this->Hello->ImageIndex = (*__try_cast<__box System::Int32 * > (resources->GetObject(S"Hello.ImageIndex"))); this->Hello->ImeMode = (*__try_cast<__box System::Windows::Forms::ImeMode * > (resources->GetObject(S"Hello.ImeMode"))); this->Hello->Location = (*__try_cast<__box System::Drawing::Point * > (resources->GetObject(S"Hello.Location"))); this->Hello->Name = S"Hello" ; this->Hello->RightToLeft = (*__try_cast<__box System::Windows::Forms::RightToLeft * > (resources->GetObject(S"Hello.RightToLeft"))); this->Hello->Size = (*__try_cast<__box System::Drawing::Size * > (resources->GetObject(S"Hello.Size"))); this->Hello->TabIndex = (*__try_cast<__box System::Int32 * > (resources->GetObject(S"Hello.TabIndex"))); this->Hello->Text = resources->GetString(S" Hello.Text"); this->Hello->TextAlign = (*__try_cast<__box System::Drawing::ContentAlignment * > (resources->GetObject(S"Hello.TextAlign"))); this->Hello->Visible = (*__try_cast<__box System::Boolean * > (resources->GetObject(S"Hello.Visible"))); this->Hello->Click += new System::EventHandler(this, Hello_Click); As you can see, you should turn this property on only when you plan to localize your application. |