Skip to main content
August 8, 2013
PC

Developing world-ready apps in XAML (part 2)



 

This is the second part in a series on creating world-ready apps using XAML. Part 1 covered Font Selection, and Bidi support. In this second part we’ll look at how you can use the resource mechanism to manage and look up localized versions of strings and resources, and how to use the formatting APIs for dates and numbers so they are presented in the right format for your customers.

Resource dictionaries

XAML has the concept of resource dictionaries, which can be used to define styles and other resources. In Windows 8, the templates included a resource dictionary in “Common/StandardStyles.xaml.” In Windows 8.1 Preview, these styles have been integrated into the platform, and are part of generic.xaml. To enable customization of resources, we’ve added a new resource lookup mechanism ThemeResource, which will be re-evaluated when the system theme is switched into high contrast mode.

For example, generic.xaml defines a resource for the default font used in the other styles as:

<FontFamily x:Key="ContentControlThemeFontFamily">Segoe UI</FontFamily>

Which is then referenced in other styles such as:

<!-- Default style for Windows.UI.Xaml.Controls.Button -->
<Style TargetType="Button">
<Setter Property="Background" Value="{ThemeResource ButtonBackgroundThemeBrush}" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForegroundThemeBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="12,4,12,4" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="Template">...</Setter>
</Style>
The theme resources can be replaced at app startup by:
  • Overriding them with new definitions in App.xaml, for example:
<Application
x:Class="MyTestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyTestApp">

<Application.Resources>
<FontFamily x:Key="ContentControlThemeFontFamily">Times New Roman</FontFamily>
</Application.Resources>

</Application>

 

  • Overriding them in code in App.xaml.cs, for example:
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
base.OnWindowCreated(args);
SetResource("ContentControlThemeFontFamily", new FontFamily("Papyrus"));
}

void SetResource(object key, object value)
{
if (this.Resources.ContainsKey(key))
this.Resources[key] = value;
else
this.Resources.Add(key, value);
}

 

  • Loading a resource dictionary from App.xaml.cs, for example:
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
base.OnWindowCreated(args);

ResourceDictionary d = new ResourceDictionary();
d.Source = new Uri(“ms-appx:///Resources/Dictionary1.xaml”);
this.Resources.MergedDictionaries.Add(d);
}

Note: If your app uses multiple windows, each window will have its own resources and UI thread. Therefore, load the resources in OnWindowCreated so they can be initialized for each window.

Note: A copy of generic.xaml is included in the Windows SDK, typically located at “C:Program Files (x86)Windows Kits8.1Includewinrtxamldesign”.

String tables (.resw)

Windows Store XAML projects use resw files to define resource strings into collections of resources that can then be localized. These can then be used for localizing xaml markup or queried for from code.

String tables are specified in resw files called Resources.resw, they can be located anywhere in your project, but the typical location is a folder called “strings.” You can use the qualifier naming patterns described further below to create versions for different languages, regions etc.

Localizing XAML markup

The recommended (and easiest) way to localize XAML is to add x:Uid attributes to the elements whose values you want to localize. For example, take the following from MainPage.xaml:

<Page
x:Class="MyTestApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/XAML/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/XAML"
xmlns:local="using:MyTestApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xml:lang="en-us">

<Grid>
<StackPanel Margin="20">
<TextBlock x:Uid="mp_tb1" x:Name="TextBlock1" Text="Hello World" />
</StackPanel>
</Grid>
</Page>

And if I were to run it, I would see a TextBlock with “Hello World” in it. If I add a Resources.resw file to my Project containing:

Resource editor with a new resource added

Resource editor in Visual Studio

And re-run the app, it shows “Welcome!” This is because as the XAML is loaded, if any elements have an x:Uid attribute, it will match the Uid value to any entries in the string table named using the pattern “uid.property” and add or override the properties in the XAML. So if I added:

Name Value Comment

mp_tb1.FontSize

40  

To the resource file, it will make the FontSize 40px, even though the attribute was not specified in the XAML.

This can be used to replace any attribute in XAML, including in style and resource definitions, for example in MainPage.xaml:

<Page.Resources>
<SolidColorBrush x:Key="TextBoxBrush" x:Uid="test1" Color="Blue" />
<Style TargetType="TextBox">
<Setter Property="Background" Value="{StaticResource TextBoxBrush}" />
<Setter Property="FontSize" Value="20" x:Uid="test2" />
</Style>
</Page.Resources>
Name Value Comment

test1.Color

Green

 

test2.Value

40  

The values for the x:Uid attribute are independent of the element name (x:Name). The x:Uid values are not validated for global uniqueness across a project; however, there is no scoping mechanism, so the same values will be applied to all elements with the same x:Uid.

Looking up strings from code

Values in the string table can be queried from code, for example in MainPage.xaml.cs:

using Windows.UI.Xaml.Controls;
using Windows.ApplicationModel.Resources;

namespace MyTestApp
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();

ResourceLoader loader = ResourceLoader.GetForCurrentView();
string s = loader.GetString("String1");
}
}
}

This is valuable for being able to localize programmatic generated content, particularly when combined with string.Format() in .NET (or the wsprintf() family of APIs in c++) to do string substitution based on a format string. Don’t rely on string concatenation for building strings as the order of substitution can be different in other languages.

Localization tools

It would be remiss of me not to mention the Multilingual app toolkit. Once enabled, you can use the toolkit to create XLIFF files for sending out to localization, and do automatic translations into a subset of languages using bing’s translation service.

Languages dialog from the Multilanguage app toolkit

Languages dialog from the Multilingual app toolkit

It then creates files for each of the languages, and you can use the translation dialog to localize each of the resources:

Multilingual Editor from the Multilingual app toolkit

Multilingual Editor from the Multilingual app toolkit.

Pseudo localization

Sending files off for localization can take time, and while UI is still being iterated on, you may not want to localize until it’s somewhat stabilized and subject to changes. Pseudo localization is a technique whereby you can use a machine algorithm to localize strings, and increase the size and character sets, so that you can test the localization. The pseudo-loc tool takes the default strings and:

  • Creates an identifier so you can see it in the UI
  • Changes the text so you can tell its localized but can still read it
  • Makes the text longer as different languages can have different lengths, for example German is more verbose than English.

You can add “qps-ploc” as a language to Windows (you need to explicitly do a search for it), make it the top priority, and then run your app to see the localization. For example, taking the Hub Template project, which is using string resources, it will look like the following when localized:

Localized Hub template project

The localized Hub Template

File naming qualifiers

The resource manager for Windows Store apps has a really nifty feature for choosing which file to load from a set based on a set of qualifiers in the filename or path.

For example if you have a resource “Strings/Resources.resw,” it will load other files based on qualifiers including:

  • Language and locale
  • DPI
  • Right-to-left
  • High contrast

Other files resources that would be resolved would be:

filenames table

This mechanism can be used for matching to multiple versions of any resource that is loaded from your project, such as images, resw files, xml, html and even XAML files.

Note: Unlike resources in .Net, un-qualified resources don’t act as the default language fallback. So if you specify a resource for one language such as “/fr-fr/MyIcon.jpg” and have a default language of “en-us,” an unqualified file such as “/MyIcon.jpg” won’t be mapped to that language. You need to explicitly create a resource for the fallback language, such as “/en-us/MyIcon.jpg”.

Supply the same sets of resources for each language you support. So if you supply a set of resources for French and German, it’s expected that both will contain an identical uniform set of resource keys (files and string table entry names).

XAML file resources

The recommended mechanism for localizing XAML files is to use x:Uid’s and a string table as described above, but if necessary alternate versions can be loaded using the resource qualifier mechanism:

  • In your project, keep the original XAML file for compilation/code gen etc, together with a single code behind file (*.xaml.cs)
  • Add the localized alternatives for just the XAML, using the naming qualifiers to identify them
    • Keep the compilation type as Page
    • Remove the x:Class from the XAML declaration at the head of the file to prevent code generation and compilation errors.

For example:

Solution Explorer

Solution Explorer showing a project with localized versions of App.xaml for English,
French and Spanish languages.

Resource packs

Resource packs are a new packaging feature for Windows 8.1. The resources for different languages and DPI will be packaged into separate appx files, so the download payload and disk space requirements are reduced.

The option to create resource packs is listed under the Generate app bundle in the Create App Packages wizard in Visual Studio.

 

Select and configure app packages

When selected, and if the app contains multiple resources, it creates an appxbundle rather than appx package file. If you rename it to .zip to view the contents, it looks something like:

An appx bundle file showing resources for French and Spanish languages

An appx bundle file showing resources for French and Spanish languages

Formatting APIs

Different locales and regions can format dates, times, numbers and currencies differently. XAML and the WinRT platform provide controls and APIs that enable you to display and input these values in an appropriate format for the end user.

Dates and times

In the US and Europe, we tend to represent dates using the Gregorian calendar. Other regions have their own calendars, such as the Korean and Hebrew calendars. They can have different combinations of months with different lengths from the Gregorian calendar, and years are measured from different points.

For input the DatePicker control can handle these different calendar types, for example:

<DatePicker CalendarIdentifier="JapaneseCalendar" x:Name="date1" />

For display, the DateTimeFormatter API from WinRT or DateTime.ToString from .Net can be used.

For example, the following code:

public void ShowDates()
{
string[] languages = { "en-us", "en-gb", "fr-fr", "ar-sa", "zh-hans-cn", "hi-in", "ja-jp", "ko-kr", "ru-ru" };
StringBuilder sb = new StringBuilder();

DateTime now = DateTime.Now;

foreach (string lang in languages)
{
sb.AppendLine(string.Format("Language: {0}", lang));
DateTimeFormatter df = new DateTimeFormatter("longdate",new string[] {lang} );
sb.AppendLine(df.Format(now));
df = new DateTimeFormatter("shortdate", new string[] { lang });
sb.AppendLine(df.Format(now));
df = new DateTimeFormatter("longtime", new string[] { lang });
sb.AppendLine(df.Format(now));
}
Reb1.Document.SetText(TextSetOptions.UnicodeBidi, sb.ToString());
}

Will produce the following results:

long short date table

Be cautious when parsing dates from strings as different regions can use different formats for dates. For example, in the US dates tend to be represented as Month/Day/Year, but in the UK its typically Day/Month/Year. The .Net DateTime.Parse method can take culture into consideration when parsing dates from strings.

The main consideration when working with times is the time zone. Users typically expect times to be shown using their local time zone.

Numbers and currencies

Numbers can include regional differences in how they are formatted such as which character to use for the decimal point and whether to separate at thousand boundaries. Not all locales use the Latin numeral system ( 0-9) to represent numbers; different locales can use other numeral systems. Numbers can be formatted using the DecimalFormatter API from WinRT, which has support for these different scenarios.

Currencies include the additional issue of what symbol to use, and where to place the symbol in relation to the number. The CurrencyFormatter API can be used to format currencies.

Note: The formatter doesn’t do currency conversion, so your app needs to take that into account if mapping currencies between regions.

For example, the following code formats numbers:

public void ShowNumbers()
{
string[] languages = { "en-US", "en-GB", "fr-FR", "ar-SA", "zh-CN", "hi-IN", "ja-JP", "ko-KR", "ru-RU" };
StringBuilder sb = new StringBuilder();
double d = 1234567.89;

foreach (string l in languages)
{
string[] t = l.Split('-');
GeographicRegion region = new GeographicRegion(t[1]);

sb.AppendLine(string.Format("Language: {0}", l));
DecimalFormatter df = new DecimalFormatter(new string[] { l }, region.CodeTwoLetter);
df.IsGrouped = true;
sb.AppendLine(df.Format(d));

CurrencyFormatter cf = new CurrencyFormatter(region.CurrenciesInUse[0], new string[] { l }, region.CodeTwoLetter);
cf.IsGrouped = true;
sb.AppendLine(cf.FormatDouble(d));
}
Reb1.Document.SetText(TextSetOptions.UnicodeBidi, sb.ToString());
}

The results are:

decimal formatter table

Wrapping up

The XAML platform and WinRT provide a great set of functionality that you can use without much effort to create world-ready apps. I hope this provides a good primer on how you can make your apps suitable for users around the world.

–Sam Spencer, Senior Program Manager, Windows