Windows Forms Globalization

Posted January 8th @ 6:21 pm by aaron

It’s amazing how the process of making an application localizable can be both simple and confusing at the same time. At a basic level, the Visual Studio designer makes it very easy. Set the "Localizable" property on your form to True and you’re good to go. It’s very convenient.

On the other hand, there are a lot of challenges like poorly named properties, confusing defaults, and possibly unexpected behavior. You really need to understand what’s going on under the covers.

For instance, if you’ve looked into .NET localizability at all, you probably know that by default resources are loaded using the CultureInfo retrieved from the System.Threading.Thread.CurrentThread.CurrentUICulture property. That’s why most guidance for manually changing the default application UI language says to set that property to a value of your choice. And well you should - it’s the only way to override the default language settings for your Windows Forms.

See, the Windows Forms designer automatically generates all sorts of useful code that you can’t (and shouldn’t) touch. Here’s an example.

image

Notice the ComponentResourceManager? It’s a local variable in the InitializeComponent method. The only way to alter it’s behavior is to alter the current thread’s UI culture information.

So that’s great, and even better it’s easy. When your application starts, you can load culture/language information from whatever persistence mechanism you prefer, and essentially "set it and forget it", since all Controls should be created on the same thread.

image

But there’s one problem with this: it only applies to UI elements (Controls).

It’s also common to load objects outside the context of the UI, perhaps with the intention of later presenting them to the user, or persisting to a file, or something else "user visible". For this, you can add an arbitrary number of resx files that contain images, strings, serialized objects, etc.

image

With Visual Studio 2005 and higher, these resources are available as strongly-typed properties, giving you compile-time checking and ease of use.

image

But here’s where our problem comes in. Overriding the default UI culture on the main thread applies only to that thread. If you load any resources from a background thread of any kind, they will be the resources for the default windows UI language.

The strongly-typed class ("Resources", in my example) offers us some relief, however, in the form of a static "Culture" property on the auto generated class. If you never touch the property, resources will be loaded using the current thread value. But if you assign your own value, you can explicitly control which resources are loaded.

Consider this debugging example where I’m stopped in the execution of a background thread. I’ve created a global static "Localization" class with a "CurrentCulture" property that I use to maintain the current UI language for my application. You can compare and contrast the values set for the current thread and the auto generated Resource static class, which my application has initialized. (I’m running the English UI for Windows, by the way.)

image

So, why am I posting this? Because I myself was unsure about how to correctly accomplish the goal of enabling users to select their own UI language until I noticed the auto generated "Culture" property on the Resources class. And it took me embarrassingly long to find it. Hopefully this post keeps somebody else from wasting an evening, or more.

In summary, it’s actually simple once you understand all the details. To enable a specific "non-default" language in your application:

  • Set the System.Threading.Thread.CurrentThread.CurrentUICulture property to the appropriate culture, and
  • Initialize all auto generated resource classes (or any custom uses of the ResourceManager class) to the appropriate culture, before loading any resources.

1 Comments

  1. Matt
    February 14, 2008 at 16:17

    Aaron, thanks for catching that…I miss spoke during the recording, it’s definitly a little more nerve racking than I thought it would be. After going back and reviewing the code (it’s been over a year since I touched another of the globalization pieces of this application) we do something either very similar or exactly what you have posted above…and don’t feel bad about how long it took you to find it…from what I remember I went through the same head banging process to figure it out the first time.

Leave a comment

OpenID Login

Standard Login

Options:

Size

Colors