Localizing a Hybrid Application

I'm blogging to:

KEXP

Live Stream

Have you ever done one of those things where you search all over the house looking for your glasses and after hours of searching you walk past a mirror and see that they are sitting on your head!  Makes you feel kinda stupid doesn't it?  Well, that's how I felt when I finally figured out how to localize a hybrid application yesterday.  I'd been struggling with this for some time and just couldn't get it to work and then finally it dawned on me which led me to utter the ultra-famous Homer Simpson phrase...D'oh!

Here's the situation...You are building a Windows Forms application that you want to contain some WPF content as well.  Now you also want to localize this application so that it can be used in multiple locales.  Normally, you would want to create a main assembly for your default language and satellite assemblies for the localized content.

So you set off on the journey, a regular Bilbo Baggins leaving theShire behind as you head toward your destiny.

To frame this discussion, we will develop a useless little application purely for illustrative purposes.  Lets create a Windows Forms app called SimpleLocTest that contains a single form.  On that form, let's put a label and set its text to some English text...we'll use "English". 

Next we'll use the Windows Forms designer to localize our form.  Of course there are other ways to localize the form, but this way is easy.  To do this, set the Localizable property of the form to TRUE and change the Language property of the form to say Spanish (Spain).  Now change the text of the label to contain some Spanish text, yep we're going to use "Spanish".  Yeah, I'm clever like that.

Okay, that takes care of localizing the Windows Forms stuff, now let's work on the WPF stuff.  First we need to add an ElementHost control to our form to host the content.  Next we will add a WPF UserControl item to our project.  Do this by adding a New Item to your project and choosing WinFx UserControl from the dialog.  This will create a UserControl1.xaml and a UserControl1.xaml.cs file in your project.

Next let's modify the xaml of our UserControl to simply add a WPF Button to it.

<

UserControl x:Uid="UserControl_1" x:Class="SimpleLocTest.UserControl1"
   xmlns=https://schemas.microsoft.com/winfx/2006/xaml/presentation
   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Hello"/>
</Grid>
</UserControl>

Build and run the application and you should see all Englsh text.

Okay, now we want to set ourselves up for the case where we are running on a Spanish localized version of the OS.  We'll simulate this by changing the UICulture of our application's thread so that it thinks we are running on a Spanish OS.  To do this, add the following line of code to the form's constructor BEFORE the call to InitializeComponent():

System.Threading.

Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("es-ES");

Next we need to go through the localization procedure for the WPF content.  To do this, we will follow the steps oulined in the WinFx SDK documentation for Localizing an Application under the WPF node in the docs.

The first step is to add

 <UICulture>en-US</UICulture>

to our .csproj file.  Wanna know a cool and easy way to hand modify your .csproj file while it is loaded in the Visual Studio IDE?  Just right-click on the project name in the Solution Explorer and choose Unload Project from the context menu.  Then right-click again on the project name and choose Edit SimpleLocTest.csproj from the context menu.  Now you can use the XML editor to work on the project file while still in the IDE.

So we'll add this UICulture tag to to the top ProperyGroup which will mean it is for the debug configuration since we'll stay in the debug mode all the way through.  Of course you can add it to any of the configurations.

<

PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{828F16E3-20A4-4EC4-A8F4-CD95B8ED44C9}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SimpleLocTest</RootNamespace>
<AssemblyName>SimpleLocTest</AssemblyName>
<UICulture>en-US</UICulture>
</PropertyGroup>

Adding this tag will tell WPF that we want to generate satellite assemblies.  Now save the file and then right-click again on the project name and choose Reload Project from the context menu.

Next we need to a Uids to our XAML file.  This makes it easy to identify each element and makes it easy for them to be localized.  To do this, you simple use MSBuild to build the application using the following syntax:

msbuild /t:updateuid SimpleLocTest.csproj

After running this command, your XAML file will now contain Uids.

<

UserControl x:Uid="UserControl_1" x:Class="SimpleLocTest.UserControl1"
   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Uid="Grid_1">
<Button x:Uid="Button_1" Content="Hello"/>
</Grid>
</UserControl>

Okay, rebuild the application. 

Time to do battle with an Orc or two.  Now we need to user the LocBaml tool to extract the resources that we can localize.  Go to the obj\debug directory of our project.  Run the following:

LocBaml /parse SimpleLocTest.g.en-US.resources /out:foo.csv

This will read in the resources that were generated as a result of adding the UICulture tag to our project file.  Note that these are the English resources.  This command will create a .CSV file that will allow you to use an editor like Excel or Notepad to edit the strings to change them to your localized language.

Edit foo.csv.  You will find the string "Hello".  Change this to "Hola" and save the file back out over itself.  Next you need to merge the Spanish resources back into the English resources by running LocBaml again:

LocBaml /generate /trans:foo.csv SimpleLocTest.g.en-US.resources /out:c:\ /cul:es-ES

This will generate a file called SimpleLocTest.g.es-ES.resources in the location c:\.  You can specify a different output directory, but I'm lazy at typing long paths so I just put it at the root.  Now I need to copy that file from c:\ to where the rest of my resources live just for convenience sake.  So copy c:\SimpleLocTest.g.es-ES.resources to our obj\debug directory.

Next we need to merge the Windows Forms Spanish resources with the WPF Spanish resources to create our Spanish satellite assembly.  To do this, we use the linker (Al.exe):

Al.exe /out:SimpleLocTest.resources /culture:es-ES /embed:SimpleLocTest.Form1.es-ES.resources /embed:SimpleLocTest.g.es-ES.resources

This will generate a file called SimpleLocTest.resources.dll in the obj\debug directory.  We need to copy this file to the bin\debug\es-ES\ directory writing over the file of the same name that is already there.  Note that the file that is there is a satellite assembly that only contains Windows Forms Spanish resources that gets generate as part of the build process.  We can get rid of that file because we need the merged satellite assembly file.

Now let's RUN the application, DONT REBUILD IT or you will write over the Spanish satellite assembly that we just worked so hard to build.

You should now see the Spanish text for both the Windows Forms label and the WPF button:

THE PRECIOUS!  Okay, we made it unscathed!  And as you can see we were able to create a single Spanish satellite assembly that contains both Windows Forms and WPF resources.  Kinda like "...one satellite assembly to rule them all..."