This post is going to mirror the Microsoft post How to: Localize an Application but I’ll try to add something of value to it.
Getting Started
Let’s create a WPF Application, mine’s called Localize1. In the MainWindow add one or more controls – I’m going basic at this point with the following XAML within the Window element of MainWindow.xaml
<Grid>
<TextBlock>Hello</TextBlock>
</Grid>
According to the Microsoft “How To”, we now place the following line in the csproj
<UICulture>en-US</UICulture>
So locate the end of the <PropertyGroup> elements and put the following
<PropertyGroup>
<UICulture>en-GB</UICulture>
</PropertyGroup>
See AssemblyInfo.cs comment on using the UICulture element also
Obviously put the culture specific to your default locale, hence mine’s en-GB. Save the altered csproj and ofcourse reload in Visual Studio if you have the project loaded.
The inclusion of the UICulture will result (when the application is built) in a folder en-GB (in my case) with a single satellite assembly created, named Localize1.resources.dll.
Next we’re going to use msbuild to generate Uid’s for our controls. So from the command line in the project folder of your application run
msbuild /t:updateuid Localize1.csproj
Obviously replace the project name with your own. This should generate Uid’s for controls within your XAML files. They’re not very descriptive, i.e. Grid_1, TextBlock_1 etc. but we’ll stick with following the “How To” for now. Ofcourse you can implement your own Uid’s and either use msbuild /t:updateuid to generate any missing Uid’s or ignore them – and have Uid’s for those controls you wish to localize only.
We can also verify that Uid’s exist for our controls by running
msbuild /t:checkuid Localize1.csproj
At this point we’ve generated Uid’s for our controls and msbuild generated as part of the compilation a resource DLL for the culture we assigned to the project file.
We now need to look at generating alternate language resource.
How to create an alternate language resource
We need to download the LocBaml tool or it’s source. I had problems locating this but luckily found source on github here.
So if you don’t have LocBaml already, download and build the source and drop the locbaml.exe into your bin\debug folder. Now run the following command
locbaml.exe /parse en-GB/Localize1.resources.dll /out:Localize1.csv
You could ofcourse copy the locbaml.exe to the en-GB folder in my example if you prefer. What we’re after is for locbaml to generate our Localize1.csv file, which will be then add translated text to.
Here’s what my csv file looks like
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:Localize1.MainWindow.$Content,None,True,True,,#Grid_1;
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:System.Windows.Window.Title,Title,True,True,,MainWindow
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:System.Windows.FrameworkElement.Height,None,False,True,,350
Localize1.g.en-GB.resources:mainwindow.baml,Window_1:System.Windows.FrameworkElement.Width,None,False,True,,525
Localize1.g.en-GB.resources:mainwindow.baml,TextBlock_1:System.Windows.Controls.TextBlock.$Content,Text,True,True,,Hello
If you view this csv in Excel you’ll see 7 columns. These are in the following order (decriptions copied from the How To document)
- BAML Name. The name of the BAML resource with respect to the source language satellite assembly.
- Resource Key. The localized resource identifier.
- Category. The value type.
- Readability. Whether the value can be read by a localizer.
- Modifiability. Whether the value can be modified by a localizer.
- Comments. Additional description of the value to help determine how a value is localized.
- Value. The text value to translate to the desired culture.
For our translators (if we’re using an external translator to localize our applications) we might wish to supply comments regarding expectations or context for the item to be localized.
So, go ahead and translate the string Hello to an alternate culture, I’m going to change it to Bonjour. Once completed, save the csv as Localize1_fr-FR.csv (or at least in my case translating to French).
Now we want to get locbaml to generate our new satellite assembly for the French language resources, so again from the Debug folder (where you should have the generated csv from the original set of resources as well as the new fr-FR file) create a folder named fr-FR (or whatever your new culture is).
Run the locbaml command
locbaml.exe /generate en-GB/Localize1.resources.dll /trans:Localize1_fr-FR.csv /out:fr-FR /cul:fr-FR
This will generate a new .resource.dll based upon the Localize1.resource.dll but using our translated text (as specified in the file Localize1_fr-FR.csv). The new DLL will be written to the fr-FR folder.
Testing our translations
So now let’s see if everything worked by testing our translations in our application.
The easiest way to do this is to edit the App.xaml.cs and if it doesn’t have a constructor, then add one which should look like this
public App()
{
CultureInfo ci = new CultureInfo("fr-FR");
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
}
you’ll obviously requires the following using clauses as well
using System.Globalization;
using System.Threading;
We’re basically forcing our application to use fr-FR by default when it starts. If all went well, you should see the TextBlock with the text Bonjour.
Now change the Culture to one which you have not generated a set of resources for, i.e. in my case I support en-GB and fr-FR, so switching to en-US and running the application will have an undesirable affect, i.e. an IOException occurs, with additional information “Cannot locate resource ‘mainwindow.xaml'”. This is not very helpful, but basically means we do not have a “fallback” or “neutral language” resource.
Setting a fallback/neutral language resource
We, obviously don’t want to have to create resource files for every possible culture. What we need is to have a fallback or neutral language resource which is used when a culture is not supported via a translation DLL. To achieve this, open AssemblyInfo.cs and locate the commented out line which includes NeutralResourcesLanguage or just add the following either way
[assembly: NeutralResourcesLanguage("en-GB", UltimateResourceFallbackLocation.Satellite)]
obviously replace the eb-GB with your preferred default language. Run the application again and no IOException should occur and the default resources en-GB are used.
What about strings in code?
Well as the name suggests, locbaml is really localizing our BAML and when our WPF application starts up, in essence it loads our XAML with the one’s stored in the resource DLL.
So the string that we’ve embedded in the MainWindow.xaml, is not separated from the XAML (i.e. it’s embedded within the TextBlock itself). So we need to move our strings into a shared ResourceDictionary file and reference them from the UI XAML. For example in our App.xaml let’s add
<ResourceDictionary>
<system:String x:Uid="Header_1" x:Key="Header">TBC</system:String>
</ResourceDictionary>
Now, change our MainWindow.xaml to
<TextBlock Text="{StaticResource Header}" />
This allows us to use FindResource to get at the string resource using the standard WPF FindResource method, as per
var headerString = Application.Current.FindResource("Header");
This appears to be the only “easy” way I’ve found of accessing resources and requires the resource key, not the Uid. This is obviously not great (if it is the only mechanism) as it then requires that we maintain both Uid and Key on each string, control etc. However if we ensure strings are stored as string resources then this probably isn’t too much of a headache.
References
https://msdn.microsoft.com/en-us/library/ms745650.aspx
https://msdn.microsoft.com/en-us/library/ms788718(v=vs.110).aspx