This blog post was authored by Dan Zucker, a program manager on the Windows Phone team.
– Adam
This is Part 1 of a two part series. Part 1 gives you a deeper understanding of the standard pattern for localizing a Windows Phone 8 XAML app along with a lesson learned or two. In Part 2, I describe how the powerful Multilingual App Toolkit , which has been released for the Windows Phone SDK 8.0, can make the end-to-end process of localizing your app substantially easier.
So you’ve decided to localize your planned Windows Phone app. Perhaps you realized (or already knew) that making your app available in other languages means your app can reach a much bigger market with fewer competitors. You’ve read Localization best practices for Windows Phone and How to build a globalized app for Windows Phone and other content in the Globalization and localization for Windows Phone section of MSDN.
You are ready to factor globalization and localization into your app design. How can you best take advantage of the Windows Phone SDK 8.0?
What’s new?
The first thing to be aware of is that the new projects and templates for XAML apps provide several new helpful features:
- Projects start you out with a neutral language resource file in place (AppResources.resx).
- Templates contain commented sample code for binding resource strings in XAML.
- Templates contain the LocalizedStrings helper class already configured to provide easy code access to the resources that match the current culture of an app.
- Templates also contain sample code for localizing the app bar, including accessing resource strings in code-behind.
- Templates contain initialization code and locale-specific parameter resources that insure that fonts are rendered correctly for all languages (xml:lang and traditional flow direction are explicitly set for the RootFrame—and if that’s not the pattern you want it’s easy to modify).
- Adding a Supported Culture from the Project Properties in Visual Studio will cause a new resource file with locale-specific name and app language initialization parameters in place to be created.
STANDARD LOCALIZING STEPS
With the list of new Windows Phone 8 localization features in mind, I created a hard-coded English language version of an illustrative sample app I called the Humanitarian Reader. Before I dive into the custom localizing code in my app, I’ll review the standard steps I took to localize the hard-coded version.
Getting into a bind (in a good way)
As I mentioned above, I first created the hard-coded English language version of a sample app. Next, I bound the XAML text elements to string resources. I did this by copying each hard-coded string in the app’s XAML that I wanted localized to a new row in the string table of my AppResources.resx file. I gave it a unique name. Then, I caused the XAML element to refer to this resource by adding the unique name and a standard binding clause in place of the hard-coded value. The original XAML appears as follows:
- <TextBlock Text=”Application Title” Style=”{StaticResource PhoneTextNormalStyle}"/>
The updated XAML is bound to the localized resource:
- <TextBlock x:Name=”AppTitleTextBlock”
- Text=”{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}“
- Style=”{StaticResource PhoneTextNormalStyle}"/>
Then I searched through the project’s code-behind for each place my code modified a text attribute of a UI element. I replaced the hard-coded value with a reference to the resource string for each element. Therefore, the original code appears as follows:
- ApplicationBarMenuItem about_appBarMenuItem =
- new ApplicationBarMenuItem(“menu item”);
The updated code has been modified to refer to the localized resource:
- ApplicationBarMenuItem about_appBarMenuItem =
- new ApplicationBarMenuItem(AppResources.AppBarAboutMenuItem);
Adding languages
Be sure to read Part 2 of this blog (coming soon), where I will describe how the powerful Multilingual App Toolkit, soon to be released for Windows Phone 8, can be used to make the following steps even easier.
Adding languages to a Windows Phone 8 project in Visual Studio was simple. I navigated to the project’s property page and selected the languages I wanted from the Supported Cultures list.
I added Arabic (Saudi Arabia), Chinese (Simplified), and Spanish (Spain), whose locale codes are ar-SA, zh-Hans, and es-ES, respectively. When I saved the project, Visual Studio created and initialized a new copy of the AppResources.resx file for each locale.
How the resource file is initialized
As I mentioned, the initialization of a newly created resource file uses the locale-based .resx file name. For example, selecting “Arabic (Saudi Arabia)” adds the resource file AppResources.ar-SA.resx. The newly created resource file is prepopulated with the existing resources from the AppResources.resx file.
Included in each resource file are two very special resources named ResourceLanguage and ResourceFlowDirection. These two resources are used when the InitializeLanguage method is called from the App.xaml.cs constructor. The ResourceLanguage and ResourceFlowDirection values are checked automatically to ensure they match the culture of the resource file actually loaded at run time.
The ResourceLanguage value is initialized with the locale name of the resource file and is used to set the RootFrame.Language value. The purpose here is to ensure that the user sees the right font for cases like East Asian languages, where Unicode character ranges overlap and the system needs xml:lang to render the correct character. For instance, without specifying both character code and language, rendering of a Japanese language app on a phone with Chinese set as its language may show a Chinese character in the middle of a Japanese string.
The ResourceFlowDirection value is set to the traditional direction of that resource language. In our example (AppResources.ar-SA.resx), the reading order is “RightToLeft”.
Note: While ResourceLanguage and ResourceFlowDirection are part of a localization pattern we believe you will find beneficial, you can always modify the resource and code to align with your design style.
Using machine translation to visualize localized text
Now I have a localized app minus the translation of the text. My next step is to use Microsoft Translator to get a rough draft for each language. This task was reduced to a few clicks and almost no wait by using Satish Chandra’s Resource Translator plug-in for Visual Studio 2012 (again, see Part 2 of this blog for even more powerful tools coming soon!). When installed, this tool shows up in the shortcut menu for .resx files.
(Note that this tool wouldn’t process my .resx files while they were in a subfolder. Solution: temporarily move .resx files to the project root, translate, and move them back to the Resources folder.)
With machine translated text, I was able to see that the Arabic translation was going to take more space then I had allowed for the English text, and I adjusted the size of the UI element. I could also easily see that the display language, flow, and navigation of the app changed as expected; you can think of it as fulfilling some of the function of the best practice known as pseudolocalization.
Human translation
Machine translation is truly amazing these days. This is especially true when the technology can ask an author which of several possible alternate meanings is intended. With these advances, the quality and consistency of machine translation will likely soon become good enough for the purposes of many projects. Having said that, it really is a good idea to involve a human who is fluent in both the original and the target translation languages. It may be a long time before software can match a human for specific domain knowledge required for your app or for understanding of the cultural and political sensitivities of a region.
I was able to crowd source my translations (Microsoft is a VERY international place and my coworkers are very generous with their time!). I simply sent my .resx file to my translation volunteers and replaced my machine-translated strings with the human-translated strings.
Finding and affording the right translator is a task that keeps many people from localizing in the first place, but this is getting easier. There is an ever-growing variety of translation options on the web, ranging from free crowd sourcing up to large commercial houses. At the risk of repeating myself, see Part 2 of this blog to find out what Microsoft is doing to make this workflow easier.
CUSTOM LOCALIZATION
The XAML and code-behind needed for localizing an app will, of course, vary based on its features and overall design. In the case of the Humanitarian Reader, I wanted the app to do two basic things: use the user’s selected Phone Language in Settings when it is first launched, and allow the user to select a different language on-the-fly at run time.
Letting the system decide what language to display
The key method that maintains the app’s language at run time is called SetUILanguage, which takes a culture name (such as “en-US”) as a parameter. To always follow the user’s Phone Language selection, I simply had to stay out of the way of the system, so at launch I initialize the UI language using the current culture:
- // Render the page using the current culture
- SetUILanguage(CultureInfo.CurrentUICulture.Name);
The user Phone Language selection is one of the four translated language resources, along with flow direction and font selection. If the user has selected any other language, the system will fall back to the app’s neutral language—US English in this case.
Designating some text to always remain in the same language
The Application Bar menu is one place in the app where I did NOT want some strings to localize. In my design, the user selects their display language via the Application Bar menu. I wanted the fluent reader of each language to see the menu items for their language localized in that language regardless of what language the app UI was displaying at the moment.
Note in the following illustration that the Arabic, Chinese, English, and Spanish menu items stay consistent across display languages while the “about” item localizes.
To accomplish this, I simply left the values of the language selection items hard-coded in the BuildLocalizedApplicationBar method that creates the localized ApplicationBarMenuItems on launch and whenever display language is changed:
- // Build the localized ApplicationBar
- private void BuildLocalizedApplicationBar()
- {
- // Set the page’s ApplicationBar to a new instance of ApplicationBar.
- ApplicationBar = new ApplicationBar();
- // Create new menu items with hard-coded, translated values for the language selections.
- //These do not localize.
- ApplicationBarMenuItem ar_appBarMenuItem = new ApplicationBarMenuItem(“العربية“);
- ApplicationBar.MenuItems.Add(ar_appBarMenuItem);
- ar_appBarMenuItem.Click += new EventHandler(ar_appBarMenuItem_Click);
- ApplicationBarMenuItem zh_appBarMenuItem = new ApplicationBarMenuItem(“中文“);
- ApplicationBar.MenuItems.Add(zh_appBarMenuItem);
- zh_appBarMenuItem.Click += new EventHandler(zh_appBarMenuItem_Click);
- ApplicationBarMenuItem en_appBarMenuItem = new ApplicationBarMenuItem(“english”);
- ApplicationBar.MenuItems.Add(en_appBarMenuItem);
- en_appBarMenuItem.Click += new EventHandler(en_appBarMenuItem_Click);
- ApplicationBarMenuItem es_appBarMenuItem = new ApplicationBarMenuItem(“español“);
- ApplicationBar.MenuItems.Add(es_appBarMenuItem);
- es_appBarMenuItem.Click += new EventHandler(es_appBarMenuItem_Click);
The “about” item is resource bound because that label needs translation to the user’s choice of language.
- // Add an ?about? menu item that is localized from app resources.
- ApplicationBarMenuItem about_appBarMenuItem =
- new ApplicationBarMenuItem(AppResources.AppBarAboutMenuItem);
- ApplicationBar.MenuItems.Add(about_appBarMenuItem);
Enabling the user to change the language on the fly
When a user taps a language in the Application Bar menu, the SetUILanguage method is once again called with the locale name of the selected language used as a parameter in the Click handler. So if the user has tapped “中文” (Simplified Chinese), the following code sets the language:
- // App Bar menu item handler to change the app language to Chinese (PRC).
- private void zh_appBarMenuItem_Click(object sender, EventArgs e)
- {
- SetUILanguage(“zh-CN”);
- }
The SetUILanguage method first resets the CurrentUICulture of the app to the locale supplied in the call.
- // Set this thread’s current culture to the culture associated with the selected locale.
- CultureInfo newCulture = new CultureInfo(locale);
- Thread.CurrentThread.CurrentCulture = newCulture;
- Thread.CurrentThread.CurrentUICulture = newCulture;
From this point on, any resource-bound text rendered by the app will use the resources of the specified locale. The next action is to use the parameters in the locale’s resource to set the FlowDirection and Language of the RootFrame, which causes any new UI rendered by the app to follow these settings.
- // Set the FlowDirection of the RootFrame to match the new culture.
- FlowDirection flow = (FlowDirection)Enum.Parse(typeof(FlowDirection),
- AppResources.ResourceFlowDirection);
- App.RootFrame.FlowDirection = flow;
- // Set the Language of the RootFrame to match the new culture.
- App.RootFrame.Language = XmlLanguage.GetLanguage(AppResources.ResourceLanguage);
The one hitch is that MainPage.xaml has already been rendered, so SetUILanguage needs to do a couple of things to cause the currently displayed elements to be refreshed in the new language. The first is to fetch each translated resource string for its XAML element and shift the element’s language to match the locale supplied in the SetUILanguage call:
- // Modify the language of each page UI element and render it in the new language.
- AppTitleTextBlock.Language = XmlLanguage.GetLanguage(locale);
- AppTitleTextBlock.Text = AppResources.ApplicationTitle;
- PageTitleTextBlock.Language = XmlLanguage.GetLanguage(locale);
- PageTitleTextBlock.Text = AppResources.PageTitle;
- MissionTextBlock.Language = XmlLanguage.GetLanguage(locale);
- MissionTextBlock.Text = AppResources.MissionText;
- GoToNewsTextBlock.Language = XmlLanguage.GetLanguage(locale);
- GoToNewsTextBlock.Text = AppResources.GoToNews;
- DisclaimerTextBlock.Language = XmlLanguage.GetLanguage(locale);
- DisclaimerTextBlock.Text = AppResources.DisclaimerText;
Changing FlowDirection
The other task is to check the current flow direction and shift elements around if it has changed from one direction to the other.
Changing FlowDirection at the app RootFrame level causes the layout of all XAML elements in the application to immediately switch flow direction. If the direction changes to RightToLeft (RTL) then, without any more work on your part, text in the controls reads right to left and justifies according to RTL rules (text that is flush left when FlowDirection is LTR changes to flush right). Also, the layout of controls relative to each other shifts.
Note: Without any additional code, the logo, app title, and page title will switch orientation appropriately.
This built-in localization support truly simplifies your localization efforts. I hope you’ll agree. However, there’s still one thing remaining: Changing flow direction also changes the direction of navigation. Study the illustration above and you’ll realize that in this case I do want the arrow image to both change orientation on the page AND to flip to a mirror image. I needed an LTR arrow pointing to the right for the next page, an RTL arrow pointing to the left for that purpose, and code to switch them on FlowDirection switch:
- //Change next page arrow image depending on FlowDirection
- bool isFlowRTL = AppResources.ResourceFlowDirection == “RightToLeft” ? true : false;
- if (isFlowRTL)
- {
- GoToNewsImage.Source = new BitmapImage(new Uri(“Assets/rtlGoToNews.png”,
- UriKind.RelativeOrAbsolute));
- }
- else
- {
- GoToNewsImage.Source = new BitmapImage(new Uri(“Assets/ltrGoToNews.png”,
- UriKind.RelativeOrAbsolute));
- }
Mapping RSS source language to the app’s current culture
The last thing the SetUILanguage method does is to map the current culture of the app to the correct three-letter language code used to form the URL of the appropriate RSS feed:
- // Set the RSS language variable to match the language of the new culture
- switch (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.ToString())
- {
- case “ar”: RSSLocale = “ara”; break;
- case “zh”: RSSLocale = “chi”; break;
- case “en”: RSSLocale = “eng”; break;
- case “es”: RSSLocale = “spa”; break;
- }
FlowDirection and the web browser navigation UI
The Article view page of the app displays the RSS feed whose links open the Browse page. The Browse page contains a web browser for displaying the destination HTML pages as well as back and forward browse arrow navigation elements. This is another case where the navigation direction flip-flops when switching FlowDirection. For RTL flow, left equals forward and right equals backward, instead of the opposite for LTR.
In this case, I modified my web control navigation routine to take a direction parameter. The BrowserNav method is called from the Click handler of the browse arrows, and the value of dir is conditionally based on the current flow direction. I initialized a page Boolean variable using the current FlowDirection:
- bool isFlowRTL = AppResources.ResourceFlowDirection == “RightToLeft” ? true : false;
Then I used it to determine the direction value used in the BrowserNav call:
- private void Button_Click_LeftBrowseArrow(object sender, RoutedEventArgs e)
- {
- string dir = isFlowRTL ? “forward” : “backward”;
- BrowserNav(dir);
- }
- private void Button_Click_RightBrowseArrow(object sender, RoutedEventArgs e)
- {
- string dir = isFlowRTL ? “backward” : “forward”;
- BrowserNav(dir);
- }
INTERESTING IMPLICATIONS OF SERVICE-PROVIDED CONTENT FORMAT
Finally, remember that content format counts. Here are a couple of issues I found, despite having carefully studied the content format of the RSS feed that the app consumes.
Browser content and FlowDirection
As I wrote earlier, setting FlowDirection for the app RootFrame affects all elements in the tree, including the WebBrowser control. In many cases this may not have an impact, but be aware that web content may already have its own FlowDirection rendered by the browser. This was the case with the destination pages displayed in my app. So, to my surprise, setting the RootFrame.FlowDirection caused the FlowDirection of the web content I displayed to be the opposite of the intended.
Because properties such as FlowDirection can be overridden at any point in the hierarchy, the fix was simple: hard-code the FlowDirection of the WebBrowser control in XAML:
- <phone:WebBrowser Grid.Row=“1” Name=“webBrowser1” FlowDirection=“LeftToRight”
- Navigated=“webBrowser1_Navigated” Margin=“0,17,0,28” Grid.RowSpan=“2”
- BorderThickness=“1”/>
RSS data format and an XMLReader globalization gotcha
There was another interesting and instructive collision of feed data format and app design choice: The Article view page displays the content of the RSS feed as a linked headline and a block of descriptive text. The RSS feed is parsed and rendered by using the XMLReader class; although it is not fully supported in Windows Phone, it is offered in our sample code as a quick and easy way to make an RSS feed app. And, it works fine for English.
However, as it happens, the format of the RSS feed I used has a quirk. The RSS 2.0 spec calls for a 3-letter month code. In the RSS data, the Arabic and Chinese feeds provide that code translated in a way that XMLReader could not digest as part of a date object. The feed contains a well-formed date, but it is in a separate namespace that is not visible to XMLReader. The answer: it may be best to stick with the flexible and fully supported Linq to XML for localized RSS feed parsing.
Well that’s all for this blog, and to repeat myself yet again, read Part 2 to learn how I used the Multilingual App Toolkit (released for Windows Phone SDK 8.0) to implement localization for the Humanitarian Reader Windows Phone 8 app.