Introduction
Xamarin.Forms is Xamarin’s new cross-platform UI framework that allows you to build user interfaces for iOS, Android and Windows Phone using XAML.
In the previous part of this series you saw how to create a tabbed page or carousel page by binding to a collection of ViewModels. You learned how to implement a quasi-data-template selector to materialize viewmodels using MVVM.
In this third part of the series you see how to use the same .resx files across all three platforms: Windows Phone, iOS, and Android. You see how to link a .resx file correctly into your Xamarin.iOS project. You learn how to set up T4 templates and link to provided T4 include files to generate Android friendly resources. Finally, you see how to use the x:Static markup extension to consume your localized resources from XAML.
Before you begin, if you haven’t already, I recommend reading the first article in the series before tackling this one.
Articles in this Series
Source Code for Calcium and Example Apps
The source code for Calcium for Xamarin.Forms and the examples is located at https://calcium.codeplex.com/SourceControl/latest
There are various solutions throughout the repository. The one you are interested in is located in the \Trunk\Source\Calcium\Xamarin\Installation directory, and is named CalciumTemplates.Xamarin.sln.
The structure of the CalciumTemplates.Xamarin solution is shown in Figure 1. You can see the example 'template' projects have been highlighted. These are the projects that contain much of the example source code that is presented within this article series.
Figure 1. Structure of CalciumTemplates.Xamarin Solution
Sharing Localized Resources between Projects
When building your app, it’s best to consider localizability upfront rather than trying to retrofit it later. In this article you see how to localize your app using the same .resx files across all three platforms: Windows Phone, iOS, and Android. Some time ago I wrote about adding localizability support for Android apps. We take a glimpse back to some of that work and we also cover new ground with consuming resx files in iOS and using x:Static markup extension to allow us to consume localized resources directly from a XAML file. We begin by looking at Resource directory name conventions and at integrating a .resx file into an iOS project.
One thing to be mindful of when sharing a .resx file from a Windows Phone app is that the directory name "Resource" collides with the built-in Xamarin.Android resource system. One of the first things I do when beginning a new Xamarin solution is to move the AppResources.resx file located in the Resources directory in the Windows Phone project, and place it into another directory. I use CommonResources, but name it what you like. I then link the AppResources.resx file into my iOS project; within an identically named directory.
When creating a localized version of the default .resx file, this file needs to be linked in to your iOS project, and the file’s Build Action set to BundleResource. You may run into challengers with the designer file not sitting beneath the resx file properly. You can either unload both your Windows Phone project and your iOS project and edit the iOS .csproj by hand to align the file, or, as I recommend, use a tool like VSCommands to nest the designer files correctly. If you get into a pickle, unload and peruse the downloadable sample iOS project to see how it should look. Your iOS project needs to resemble the ItemGroups presented in Listing 1. Once you get this set up correctly, you should be good to go.
Listing 1. iOS project with Resx Files
<ItemGroup>
<Compile Include="..\CalciumSampleApp.WindowsPhone\CommonResources\AppResources.Designer.cs">
<Link>CommonResources\AppResources.Designer.cs</Link>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.resx</DependentUpon>
</Compile>
<Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" />
<None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="..\CalciumSampleApp.WindowsPhone\CommonResources\AppResources.fr.resx">
<Link>CommonResources\AppResources.fr.resx</Link>
</BundleResource>
<EmbeddedResource Include="..\CalciumSampleApp.WindowsPhone\CommonResources\AppResources.resx">
<Link>CommonResources\AppResources.resx</Link>
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
<CustomToolNamespace>CalciumSampleApp</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
Android is a rather different story. For Android, consuming a .resx file requires MacGyver like use of T4. In this section you’ll see how to use T4 templates to transform your .resx files into Android friendly resource files.
To begin, within your Android create a place to store your Localization templates. Be mindful to keep these away from the Resources directory in your Android project. You can create a directory structure like that shown in Figure 2.
Figure 2. Android Localization Directory Structure
The following steps walk you through how to create this structure and setup your T4 templates to support localizability:
Step 1. In your Android project create a subdirectory to store your T4 templates. In the downloadable sample solution I created a ResourcesModel/T4Templates subdirectory. You can call it whatever you like, but you may have to play with the file paths somewhat if you stray from this naming scheme.
Step 2. Place the two text files: ResxToAndroidAccessorClass.ttinclude and ResxToAndroidXml.ttinclude from the downloadable sample project into the ResourcesModel/T4Templates/. Include them in your project and set their Build Action to None.
Step 3. Right click on the ResourcesModel/T4Templates/ directory in Visual Studio and select Add New Item, and select Text File. Before clicking Add, change the name of the file to “AppResources.tt”.
Step 4. Paste the following text into your newly created AppResources.tt file:
<#@ include file="ResxToAndroidAccessorClass.ttinclude " #>
<#@ output extension=".cs" #>
<#@ template language="C#" hostSpecific="true" #>
<#
Process(Path.GetDirectoryName(Host.TemplateFile) + "../../../../CalciumSampleApp.WindowsPhone/CommonResources/AppResources.resx", "CalciumSampleApp");
#>
Step 5. Replace "../../../../CalciumSampleApp.WindowsPhone/CommonResources/AppResources.resx" with the path to the .resx file in your Windows Phone project. And replace "CalciumSampleApp" with the namespace you are using for your resources. This must be the same across the board for each project. Having a namespace mismatch prevents you from consuming the strongly typed resources from your Shared project. When you press Ctrl+S to save the AppResources.tt file, if your paths are correct, you should be able to open the AppResources.cs file to reveal your Android compatible strongly typed resource properties.
Step 6. You’re almost done. In Visual Studio create a Values directory in the Resources directory of you Android project. This is where you’ll generate your string resources.
Step 7. Just like in Step 3, create a new T4 template named “Strings.tt” and paste the following text into the file:
<#@ include file="..\..\ResourcesModel\T4Templates\ResxToAndroidXml.ttinclude" #>
<#@ output extension=".xml" #>
<#@ template language="C#" hostSpecific="true" #>
<#
Process(Path.GetDirectoryName(Host.TemplateFile)
+ "../../../../CalciumTemplateApp.WindowsPhone/CommonResources/AppResources.resx");
#>
Step 8. Once again, modify "../../../../CalciumTemplateApp.WindowsPhone/CommonResources/AppResources.resx" to correspond to the location of your .resx file in your Windows Phone project. Saving the template will cause it to spit out an XML file with your default language resource strings. Set the Build Action of the resulting String.xml file to AndroidResource. If you fail to set the Build Action, the Resource.Designer.cs file is not populated with the strongly typed resource names and your project won’t build.
Step 9. Create directories within the Resources directory that correspond to your other localized resx files. For example, the downloadable sample code has a resx file that is localized to French. Thus, there is a Values-fr directory. Repeat 7, 8, and 9 for the subsequent localized resx files.
Keep in mind that when you modify your string resources, you need to write click on each of the T4 templates and select Run Custom Tool. Alternatively, there is a “Transform all T4 Templates” toolbar item present in the Build toolbar. To display the Build toolbar, right click on the toolbar area and ensure that Build is checked. In addition there exists several custom tools out there that are capable of processing T4 templates based on various conditions. Take a look at the Visual Studio extensions gallery for more information.
You can find more about this work in greater detail in another article at http://danielvaughan.org/post/Generating-Localized-Resources-in-Mono-for-Android-Using-T4.aspx
Consuming Localized Strings in a Xamarin Project
A key advantages of using a proxy class to display string resources in your app, is that you can update the UI immediately to reflect changes to the user’s preferred language setting in your app. Calcium for Windows Phone handles this nicely. You can see an example of it in action by switching the Language setting in Surfy for Windows Phone. The UI updates immediately, no restart required; bang there it is. This makes for a frictionless user experience, because often users won’t understand what “Please restart the app for changes to take effect” means.
While Windows Phone allows this approach to work seamlessly, it is, however, difficult to achieve with Xamarin’s current binding infrastructure. I investigated a number of approaches but without success. Without the ability to explicitly set the source property of a binding expression there’s no way to target a resource proxy that can raise property changed events.
The good news is, however, that Xamarin Forms supports the x:Static markup expression, which makes binding to localized resources easy. If you’re familiar with WPF you’ll know how useful it is. This is something that has always been noticeably absent from Silverlight for the web, Windows Phone and WinRT. The x:Static markup expression allows you to retrieve the value of a static property, in this case our generated localized resource strings.
To display a localized string in Xamarin Forms XAML place a XML namespace definition at the top of the file like so:
xmlns:local="clr-namespace:CalciumSampleApp;assembly=CalciumSampleApp"
The “local” part is an alias you can use within the file. The clr-namespace and assembly names identify the location of types that will be associated with the alias.
The following excerpts how to employ the x:Static markup expression to display a localized string in an .resx file using a Label element:
<Label Text="{x:Static local:AppResources.AppTitle}" />
This is more concise than using a proxy object to display the strings, and in fact as Xamarin improves its tooling around XAML we may see compilation errors raised if the path to a resource cannot be found; as is the case with WPF. At the moment, that tooling isn’t there for Xamarin Forms. But, I have high hopes.
Conclusion
In this article you saw how to localize your app using the same .resx files across all three platforms: Windows Phone, iOS, and Android. You saw how to link a .resx file correctly into your Xamarin.iOS project. You learned how to configure the provided T4 include files to generate Android friendly resources. Finally, you saw how to use the x:Static markup extension to consume your localized resources from XAML.
In the next article, Part 4: Creating a Cross-Platform Application Bar with Xamarin Forms and Calcium, you learn how to create a cross-platform Application Bar component using the Xamarin Forms native rendering API with Menu Item support that works on iOS, Android, and Windows Phone; and which offers more features than the built-in Windows Phone Application Bar
I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better.
History
October 2014