Ach du Lieber is an expression in German. It's what you say when you can't believe something really lame has just happened, usually to you. Something like, you are trying to set up your Silverlight application for proper localization, but you keep running into gotchas that you really would have been expected to have been dealt with better by your development process, and your toolset in particular.

I spend a fair amount of the time exploring the questions that application authors pose in the Silverlight.net forums. It's good intellectual exercise for any of the following:

  • Find out what people are doing with Silverlight apps in the real world.
  • Identifying the parts of the learning curve that could use a guardrail.
  • Playing around with features of Silverlight that I don't know as well because one of my illustrious associates knows way more about that area than I do and thus is writing that part of the MSDN docs and not me.

Something that I posted onto recently was an example of the last case. It was a question about how to use the combination of the older .NET Framework concept of "resources" in a RESX file in the project structure, and the WPF and Silverlight XAML concept of "resources" as a ResourceDictionary with keys. Plus also the concept of data binding with XAML attributes in the mix. All this comes together to form the recommended application localization story when XAML is involved in your UI (just about always!)

I had worked with these concepts piecewise before, but somehow had thusfar avoided building a Silverlight app from scratch that tried to use these techniques in a functioning way. So off I went. And, to simulate the typical app developer experience, I started out by deliberately not reading ANY of the possibly helpful documentation available on MSDN under the Silverlight node. Yes, you can either laugh or cry, but this forge-blindly onward approach to learning basic Silverlight is probably pretty common.

What I did use was some of the cross linked forum posts from the original silverlight.net forum question, and other community-generated material. It turned out that this gave me just enough knowledge to walk RIGHT INTO several of the gotchas that I am about to review for you now.

So, let's start out with resources in the RESX form as promoted heavily by Visual Studio. If you've done any .NET programming, including WinForms, the idea of using the RESX concept/system to hold your application's resources is not new. And you know well enough that strings that go into UI should all be swept up out of whatever design prototype you started with and each such string assigned to be a designated named string resource in your RESX resources. That's about where I stood on this learning curve.

Now in Silverlight or WPF, for a real world app you might be starting from some pretty full-blown XAML that is dropped on you by your designers. Performing the scrub for the placeholder strings and turning each of these into resource references might be part of your role if you are a developer. Or if you are a hybrid designer-developer. But here at the Hello World / Hola, el mundo level of exploration, I just cranked out my own XAML interface with two TextBlocks and a button. We don't need no designers!

    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBlock Name="textBlock1" Text="Hello"/>
        <TextBlock Name="textBlock2" Text="Are you ready to begin?" />
        <Button Content="Yes" Name="button1"/>
    </StackPanel>

Enter the RESX! Now it's time to add the actual RESX resources to the project. To do so: In Solution Explorer, select the project and right-click it. From the menu, choose Add, New Item. From the Add New Item dialog, choose Visual C#, Resources File.

As an initial scrub, this becomes three string resources. In the RESX table editor in VS, after choosing names and then copying out the value strings, you would see this represented as something like:

CommitString Yes
HelloString Hello
TextBodyString Are you ready to begin?

Let's pretend you don't even know how to deal with this yet in a XAML UI, and so for the meantime all you do is put the resource name back into the XAML instead of the resource value. Not a bad idea actually as a workflow, because having that string be there will be advantageous just a little later. So now:

    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBlock Name="textBlock1" Text="HelloString"/>
        <TextBlock Name="textBlock2" Text="TextBodyString" />
        <Button Content="CommitString" Name="button1"/>
    </StackPanel>

At this point you have two ways in which you might stumble forward: make sure that at least your default resources display, or forge ahead and get all the localized resource strings in and try to make them all work. The former approach of making sure my resources are basically working just makes more sense to me as a seat of pants coder, so that's what I did.

Now, just HOW do you get resource strings into XAML?

If you know much about XAML, you probably know about ResourceDictionaries and XAML. ResourceDictionaries are awesome refactoring tools for XAML. Basically, they are useful for getting a XAML declaration out of one part of your project and into another, with the often-used added bonus that you can share a single resource into multiple referencing spots in UI declaration XAML. Seems just about right for dealing with this string resource scenario, right?

Well .... maybe.

The issue here is that in this aspect, the RESX concept and the ResourceDictionary concept are orthogonal at this point. You can get strings out of a RESX, you can get strings out of a ResourceDictionary, but you can't really just blast the strings from a RESX right into a ResourceDictionary as discrete strings.

There is a localization technique available here that uses only ResourceDictionary and doesn't even use RESX. I'll roughly mock it up here, illustrating with my brilliant XAML UI remade:

    <UserControl.Resources >
        <ResourceDictionary xmlns:sys="clr-namespace:System;assembly=mscorlib">
            <sys:String x:Key="CommitString">Yes</sys:String>
            <sys:String x:Key="HelloString">Hello</sys:String>
            <sys:String x:Key="TextBodyString">Are you ready to begin?</sys:String>
        </ResourceDictionary>
    </UserControl.Resources>
    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBlock Name="textBlock1" Text="{StaticResource HelloString}"/>
        <TextBlock Name="textBlock2" Text="{StaticResource TextBodyString}" />
        <Button Content="{StaticResource CommitString}" Name="button1"/>
    </StackPanel>

This does really work. But, to use this apprach for a real localization process, you're going to be giving up a lot of the tool support and built-up knowledge from your localizers about how to localize RESX files. Instead, you'll be handing them XAML files with ResourceDictionaries in them, and count on them to hand you valid XAML back. You'll also have to write application code that swaps resource dictionaries in and out. Or, do per-language builds, overload the generic.xaml concept that is only really meant for control authors, or go through quite a few other steps. I believe you'll discover these hurdles make the initial idea of using straight strings in a ResourceDictionary a lot more complex than it seemed at first. That's why the straight ResourceDictionary technique is not the recommended XAML localization technique for Silverlight.

So, let's back off that and talk about RESX string properties again. You might know that the RESX system hinges on there being generated classes that use the ResourceManager static class to actually serve up resources. Also, for each of those string properties, the generated class contains a static access helper property that the ResourceManager calls. If that's not familiar to you, just open up a generated <resource>.designer.cs file to see what I am talking about.

Hmm. A CLR class that has straightforward property definitions, already generated for you in your assembly and namespace. A need to get property values out of a CLR class and into a XAML UI. What does that suggest? Yes, that's right kids, it is time to data bind. Something like this?

    <StackPanel x:Name="LayoutRoot" Background="White">
        <StackPanel.DataContext>
            <resx:Resources/>
        </StackPanel.DataContext>
        <TextBlock Height="23" Name="textBlock1" Text="{Binding HelloString}"/>
        <TextBlock Height="23" Name="textBlock2" Text="{Binding TextBodyString}" />
        <Button Content="{Binding CommitString}" Height="23" Name="button1"/>
    </StackPanel>

Here it comes. Gotcha!  By default, the generated resources class that contains this logic is generated as internal. Internal access is good enough for a lot of .NET Framework programming models. BUT, it's not good enough for Silverlight XAML. If you want to instantiate that Resources class in XAML, the class has to be public, that's the rules. So, Visual Studio anticipated this need a few versions back, and added a different associated build tool action that makes the generated classes public. To use it, go to the Resources.Resx view. Just above the table, there is an Access Modifier dropdown. Flip that value over to Public. Try again.

Hey. Why didn't that work? Gotcha!  That one hurt.

Turns out there's a flaw in the VS build action logic here; unfortunately changing this tool action will NOT flip the access level of the class constructor from internal to public (at least not for a strongly typed language). A public constructor is another requirement of Silverlight XAML usage of a class. You will have to do this yourself manually in the designer.cs file. Moreover, you will have to redo it every time the action reruns, due to you changing strings in the associated RESX. Ach du Lieber! Fortunately, YOU are the only one that runs into this, not your localizers. Once you have the base resources class set up correctly, it's all that the pure RESX level from then on, you won't need to regenerate, and the localization infrastructure will be correct for all.

Another consideration: what if there is ALREADY data binding going on in the surrounding XAML, such that it is inappropriate for you to "steal" the DataContext for your nefarious localization needs? Gotcha!

Getting around this is conceptually easy. But it does end up making your XAML binding statements more verbose. Here's how it works - don't use the DataContext. Specify the Source explicitly in all bindings that are localization related. Then instantiate a Resources class as a keyed object in the ResourceDictionary either at the page level or application level. This will end up looking something like:

<Application .... xmlns:resx="clr-namespace:MyApp">
    <Application.Resources>
       <resx:Resources x:Key="appresources"/>
    </Application.Resources>
</Application>

    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBlock Name="textBlock1" Text="{Binding HelloString, Source={StaticResource appresources}}"/>
        <TextBlock Name="textBlock2" Text="{Binding TextBodyString, Source={StaticResource appresources}}" />
        <Button Content="{Binding CommitString, Source={StaticResource appresources}}" Name="button1"/>
    </StackPanel>

 At this point, you are a good distance along. All you need to do now is start adding the culture-specific resource files. To do so: From the menu, choose Add, New Item. From the Add New Item dialog, choose Visual C#, Resources File.

Gotcha! (maybe ...) How you name this resource file is VERY IMPORTANT! You should name it with EXACTLY the same initial name as your default resources file. But then, between the name and the resx extension, add a string in the form ".ln-lc" where "ln" is the two letter ISO language code and "lc" is the locale code. For example, if your default resources are named resources.resx, your German resources for Germany should be named "resources.de-de.resx". If you violate this naming pattern, the magic that goes on with the default ResourceManager behavior breaks.

(I got caught by this one - because I am a sloppy typer. Wish list for VS - maybe help a fellow out here by offering an autocomplete, or some kind of a BasedOn option for creating resources rather than always relying on typed name?)

I actually speak some German; I was born in Germany but have lived in the US for all but 18 months of my life. Thus my German is just as sloppy as my typing. So instead of showing you a set of sloppy German strings, I'll just cross map the German culture into one where I can be sloppy without anyone noticing - pirate! (Sadly, you can't just invent the "en-pr" culture to represent this and skate around .NET's legitimacy test when you pass a CultureInfo. Scurvy dogs!)

CommitString Yarrrr!
HelloString Ahoy!
TextBodyString Be ye prepared to walk tha plank, matey?

 Which language actually displays when you view Silverlight content that has localized components generally hinges on the CultureInfo of the UI thread that Silverlight is using. There are several ways that you can tweak your own system so that you can pass a culture that's different than your current default culture when you do localization tests:

* Do it the "user" way, and actually change Windows settings. This turns out to be annoying; especially so if you can't read anything in the Windows UI anymore!

* Use two params that you pass through the Silverlight <object> tag. These params are named "culture" and "uiculture".

 <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
    <param name="source" value="ClientBin/LocTest.xap"/>
...
     <param name="culture" value="de-de" />
     <param name="uiculture" value="de-de" />
</object> 

(I won't go into the differences between these; for more information see MSDN.)

* Tweak the acting cultureinfo at the app level pre startup. Something like:

    public App() {
...
       Thread.CurrentThread.CurrentCulture = new CultureInfo("de-de");
       Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
       InitializeComponent();
    }

Of these I prefer the second way - params for the object tag. The aspx you use is mostly for testing at this stage, so it's fair game to change its parameters and this has low risk of changing something that will be confusing in the eventual deployment.

 And then you test. And ... it doesn't work. You are still displaying the default strings.

Another gotcha! It turns out that merely creating a resource is not enough to inform the SIlverlight application model and build actions that you want it to create satellite resource assemblies. You have to explicitly configure your project to include each culture as a supported culture. I did sort of know that in the back of my head, so went looking in the Project Properties for my Silverlight project in the VS UI. Where I found ... nothing that let me set the cultures, in any of the possible Project Properties dialog tabs. Gotcha!

Unfortunately, you will have to manually add a node to your vbproj or csproj. The node to add looks like this:

    <SupportedCultures>en-us;de-de</SupportedCultures>

Contents of the node should be each ln or ln-lc combo you support, separated by ; . You typically do not put your default culture here.

Whenever you edit a project file outside the VSUI like that, with VS open, VS gives you a prompt asking you what to do. I always seem to mess this up and choose the wrong option, which causes my manual changes to be discarded. The right thing to do is: Save All in VS; manually edit the proj; at the prompt, choose Reload.

Test again. And finally, sweet sweet success!

Now, how about we run through that again? Except this time, as a nice concise checklist. And without the gotchas? Here goes ...

  1. Start with the pre-localized application project.
  2. Select the project in Solution Explorer. Add a New Resources file. For purposes of the steps, call this Resources.resx.
  3. Go through all your XAML files. Identify each attribute where you have a string that needs to be localized. Also identify nonstring attributes that might need to be localized. Make sure also to catch cases where the original XAML authors used an implicit string content property form for instance <Button>Hello</Hello>. You might want to change these over to use explicit attributes. For each such string, define a Name in the RESX table view of your default RESX file. For the Value, use the string (or nonstring) you are replacing. Do this now for ALL possible resources in all XAML files. I say this because otherwise, you'll have to repeat Step 6 every time that you force regeneration of the generated class because you added a property.
  4. In the RESX UI in VS, use the dropdown to change the Access Level to Public.
  5. In Solution Explorer, select the RESX file and right click. Choose Run Custom Tool. This forces the initial generation of the .designer.cs file.
  6. Open the resources.designer.cs file. Find the class constructor for Resources. Change its access level to public. Hopefully you only have to do this once!
  7. Compile the app, so that the generated resource class exists.
  8. Open the app.xaml file in your solution. In the root tag, map an xmlns to point to your application's assembly and default namespace. Something like xmlns:resx="clr-namespace:MyApp".
  9. Inside the perhaps empty <Application.Resources> property element, add a new element starting with your mapped prefix, something like <resx: ... At this point Intellisense will hopefully kick in and suggest your Resources class. Should end up with something like <resx:Resources />.
  10. Give <resx:Resources /> an x:Key attribute. For the value specify a string name. I always just use the class name, since this is probably a singleton, eg <resx:Resources x:Key="Resources" />.
  11. Go back to all the places in XAML files where you defined a named resource in RESX. For each of those cases replace attribute values with bindings that have the RESX name as their path. The bindings should also specify a Source that references your generated class resource from step 10. Something like Text="{Binding someResxName, Source={StaticResource Resources}}".
  12. Build and run under your default culture. If at this point you are missing strings in UI versus what you started with in Step 1, something is wrong ...
  13. Add new RESX resources for each culture you localize into. Be real careful with the naming!
  14. Paste copies of the string table (and possibly other tables) from the default RESX into each of the culture specific RESX files, to establish the set of resources each culture needs.
  15. For testing purposes, faux localize at least a few strings so you can see what you are doing in your tests. For instance, localize into Pirate! Or, how about Surfer, or Yoda? Save All in VS.
  16. MANUALLY edit the CSPROJ file. Add a <SupportedCultures> node. For each of the cultures you created RESX for, add its ln-lc code to the body of <SupportedCultures>, with semicolons between. Save the file in text editor. If prompted back in VS, make sure to reload the proj file.
  17. Choose your favorite technique for forcing culture during testing. For instance add <param name="culture" value="de-de" /><param name="uiculture" value="de-de" /> into the ASPX test file's Silverlight <object> tag.
  18. Build and run.
  19. You should now be able to see your faux localized strings, and your app should speak proper Yoda. If not, debug and recompile you must! Again test!
  20. It might be good for your career if you undo all those Pirate and Yoda translations you did in the other RESXs for testing, and put suitable English starting strings back in. Or, if you're the adventurous sort, leave them in and see how adept your loc team really is at dealing with idioms and cross translation.

I mentioned earlier that there really are MSDN resources that describe all this, which I deliberately did not look at to refresh my memory until ... just now. And here they are, with some annotation:

How to Make XAML Content Localizable

This covers the basic concept of RESX strings+binding to generated class as a StaticResource, and has most of the steps except the test-related ones. Personally, I did have a little trouble following the procedure, perhaps because it was not always clear what part of VS UI you might be in at the time.

Localizing Silverlight-based Applications

Overview that gives same concepts but not a procedure. Also gives some tips and best practices.

Note also that there are other topics in MSDN that are underneath this node in the TOC. These topics cover things like how to localize nonstring resources, something I mention only in passing in my steps.

Silverlight and localizing string data

Not actually on MSDN, but it's Tim Heuer's blog. Good stuff. Was actually my inspiration for writing this article, in particular how he hit several of the same gotchas. However, note that not everything in it is accurate because it was written circa Silverlight 3 for non-RTM tools; that is the curse of blogs sometimes, they too often get frozen in amber and don't get a touchup to reflect the current releases.