6.4. Global Applications
If you plan to distribute your applications worldwide, you may
need to prepare different versions of the user interface for different regions.
At a minimum, this involves translating text into the appropriate language. It
may also involve other UI changes. You might need to adapt certain visuals to
local cultural conventions. Or you might find that the original layout doesn't
quite work after translation, because the words are all different lengths
(although WPF's layout system makes it easy to build flexible layouts that will
avoid that last problem).
It is possible to build different versions of your software for
different markets. However, a more common approach is to build a single version
that can adapt to different locales, usually by selecting suitable resource
files at runtime. The ResourceManager infrastructure that WPF uses
makes this fairly straightforward.
 |
Microsoft makes a distinction between localization
and globalization. Localization is the process of enabling an
application to be used in a particular locale, by creating culture-specific
resources such as translated text. Globalization is the process of
ensuring that an application can be localized without needing to be recompiled.
Using ResourceManager helps to globalize your application, because its
runtime resource selection enables a single build of the application to be
localized by supplying suitable resources. For more information on recommended
globalization and localization practices in Windows, see Microsoft's
internationalization site:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vboriInternationalization.asp
(http://shrinkster.com/6m9).
|
|
When a ResourceManager is asked to retrieve a named
resource stream, the first thing it does is determine which culture it should
use. A culture is the combination of a
language and location, and is typically represented as a short string. For
example, en-US means the English language, as spoken in the U.S. The en-GB
culture represents English as spoken in Great Britain. The first two letters
indicate the language and the last two the region. The reason both language and
location are specified is that there are often variations in dialect and idiom
even when two cultures ostensibly share a language. For example, one of the
authors of this book hails from en-GB, and therefore prefers "color"
to be spelled "colour."
The ResourceManager.GetStream method takes a CultureInfo
object as a parameter. If you wish to use the end user's configured culture,
you can retrieve a CultureInfo from the CurrentUICulture property
of Thread.CurrentThread.
Although executables usually have resources compiled in, the ResourceManager
will look for culture-specific resources before resorting to the built-in ones.
It will look in the directory containing the application for a subdirectory
named for the culture. So, if you are running in a French Canadian culture, it
will look for an fr-CA subdirectory containing a file called MyApp.resources.dll,
where MyApp is the name of your application or component. If
that doesn't exist, it will then look for the same file in a directory called fr.
This means that if your translation budget doesn't stretch to producing
different versions for all of the various French-speaking regions of the world,
you can instead provide a single set of French resources that will be used in
any French-speaking region. Only if neither of these subdirectories exists will
it resort to using the built-in resources.
The resource DLLs that the ResourceManager looks for
are called satellite resource assemblies ,
so called because they are small assemblies associated with a larger assembly
nearby.
Note that if you supply a satellite assembly, you are not
required to provide localized versions of all of the resources. It might be
that some of the resources you embed in your main assembly work just fine for
all cultures. For example, the application shown in
Figure 6-6 had an embedded bitmap called Sunset.jpg. The sun
sets in most parts of the world, so while you might need to do something
special for Arctic and Antarctic editions, the basic Sunset.jpg probably
works for most cultures. It would be a bit of a waste of space for every
satellite resource assembly to contain a copy of the same image. Fortunately,
they don't have toif a particular named resource is not present in a satellite
resource assembly, the ResourceManager will fall back on the built-in
resources.
You can think of satellite resource assemblies as containing
just the differences between the built-in resources and those required for the
target culture. Any common resources will live in the main assembly alone. An
assembly in a language-specific but location-generic subdirectory (e.g., in the
fr subdirectory) contains resources that need to be different for the
specified language. And then the fully culture-specific subdirectories (e.g., fr-CA,
fr-FR, fr-BE, etc.) contain only those resources that need to
be adjusted to take into account local idioms. (In this context a "resource" is
a single stream as retrieved by the ResourceManager, rather than an
object retrieved from a ResourceDictionary.)
6.4.1. Building Localizable Applications with XAML
Because XAML is compiled into BAML resources
that are retrieved using a ResourceManager, localizability is an
intrinsic feature of any WPF application built using XAML. However, there is
currently no built-in support for localizing WPF
applications in Visual Studio 2005, so there are a few manual steps involved.
If you build a UI in XAML, localization effectively occurs one
XAML file at a timethe ResourceManager cannot go more fine-grained
than a single BAML resource, so each BAML resource is either localized or not.
Since there is a close relationship between a XAML file and its code-behind
file, however, it is important that the localized BAML resource has the same
essential structure as the original. In principle, you could achieve this by
writing a new XAML file for the localized version and trying to keep its
structure the same. However, there is a more robust way of guaranteeing
consistency.
Instead of authoring a set of XAML files for every culture, you
can write one master set of XAML filesone for each window or page in your
application. Then, for each culture you wish to support, you can use a tool to
generate culture-specific satellite resource assemblies containing localized
resources. You supply the tool with configuration files indicating how the
resources should be modified in order to create the localized versions.
Figure 6-7 illustrates the overall process.
 |
This is a slightly long-winded process. By the time
the final release of WPF ships, this will no doubt be better streamlined and
integrated into Visual Studio 2005. For now, welcome to the wonderful world of
pre-release software.
|
|
First, you must make sure the project is set up to build a
localizable application by specifying a default UI culture. At the time of
writing, there was no way of doing this from within Visual Studio 2005, so you
must edit the .csproj file using a text editor. Add a UICulture
element inside the PropertyGroup element (it doesn't matter where the UICulture
element appears within this section). Set it to the default culture for your
applicationthe culture in which you will create the main resources. This will
cause Visual Studio 2005 to put all of the binary resources into a satellite
resource assembly for this default culture.
Example 6-28 shows some sample markup that achieves this result.
Example 6-28. Specifying a UI culture for your project
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
...
<UICulture>en-US</UICulture>
...
</PropertyGroup>
...
Next, you must add Uids to your XAML. A Uid is a special
attribute on a XAML element indicating content that may require localization.
The localization configuration file containing localization instructions will
use Uids to indicate which elements are being changed.
Example 6-29 shows a TextBlock with a Uid.
Example 6-29. A Uid
<TextBlock x:Uid="TextBlock_1">Hello, world</TextBlock>
You can add these by hand if you want. Or you can generate them
automatically using msbuild. (msbuild is the command-line
tool for building projects. Visual Studio 2005 uses the same build technology,
but msbuild offers more control. For example, it lets you get at this
Uid generation feature of the WPF build system.) To add Uids to your XAML
automatically, run this command:
msbuild /t:updateuid MyProject.csproj
If you have done this already, and have subsequently edited your
XAML, you may want to check that you've not ended up with any duplicated Uids.
You can do this with the following command:
msbuild /t:checkuid MyProject.csproj
Now you can build the project, either by using VS 2005 or by
running msbuild from the command line, passing just the project
filename as a parameter. You should now find that as well as building an EXE or
DLL, your project also adds a satellite resource assembly in a subdirectory.
(The subdirectory will be the one you named when you added the UICulture
element to the project file.)
The next step is to create the configuration file that will
direct the localization process. This file will contain all localized items,
such as translated strings. You can create the skeleton of this file using the LocBaml
command-line tool. This tool examines resource assemblies for BAML streams and
builds a file containing one line for each localizable piece of information in
the file. You can then put your translated strings and whatever else is
required into this file.
Example 6-30 shows
how to run LocBaml to generate the skeleton configuration file.
Example 6-30. Generating a CSV file with LocBaml
LocBaml bin\Debug\en-US\MyApp.resources.dll /out:MyAppResources.csv
This will create a CSV file.
Table 6-1 describes each of the columns in the order in which they
appear in the CSV file. To localize the resource, edit the Value column. (Note
that the CSV file doesn't contain a heading lineit depends entirely on the
column positions.)
Table 6-1. Columns generated by LocBaml
|
Column
|
Description
|
Baml Name
|
Identifies the BAML stream;
the value will be of the form AssemblyManifestStreamName:SubStream
Name.
|
Resource Key
|
Identifies the localizable
resource; the value will be of the form Uid:Element
Type.$Property.
|
Localization Category
|
An entry from the LocalizationCategory
enumeration, indicating what kind of content this is.
|
Readable
|
Indicates whether the resource
is visible for translation.
|
Modifiable
|
Indicates whether this value
can be modified during translation.
|
Comments
|
Localization comments.
|
Value
|
The value of this resource.
|
Example 6-31 shows
a line from one of these configuration files. (It has been split across several
lines here to make it fit. In a real file, this would be on a single line.)
Example 6-31. Example configuration file
HelloApp.g.fr-FR.resources:window1.baml,
TextBlock_1:System.Windows.Controls.TextBlock.$Content,
None,True,True,,Bonjour monde
Once you have translated all of the Values, you can then run LocBaml
again to generate the new resource DLL. You must pass in the path of the
original resource DLL, the path of the CSV file containing translations, a
target directory, and the target culture, as shown in
Example 6-32. (Note that this example has been split across multiple
lines to fit into the book. It should be entered as a single line in practice.)
Example 6-32. Generating a resource DLL with LocBaml
LocBaml /generate bin\Debug\en-US\MyApp.resources.dll /trans:MyAppResource.csv
/out:bin\Debug\en-GB /cul:en-GB
This will generate a new satellite resource assembly in the
specified directory, targeting the chosen culture. (If you want to build
several resource assemblies, create one CSV file for each culture and run LocBaml
once for each file.) If you place the resulting resource assembly in a
subdirectory of the application's directory named after the culture, it will
automatically be picked up at runtime if the application is run with that
particular culture selected.
 |