Click here to Skip to main content
Click here to Skip to main content

WPF Localization Tutorial -- In Real Production Environment

By , 13 Jun 2010
Rate this:
Please Sign up or sign in to vote.

Introduction

First, I would suggest that you read this article: GlobalizeLocalizeAWPFPro.aspx.

And after you've finished it, you may have got a rough idea of what you need to do to localize a WPF application/library. But I had some problems when following this article so I want to give some supplement here.

Generally speaking, to localize a WPF app/lib, we have various ways, among which using a tool named locbaml.exe is the most popular one. You can find this tool within this article. Other ways have also been mentioned in the Microsoft official site, they may tell you to use static resources (e.g. "{x:static resource_namespace.propertyname}" ) or dynamic resources (e.g. "{ Dynamicresources propertyname}"). But you will encounter complicated trouble shooting scenarios when using static resources (you cannot see the resource strings using Visual Studio XAML designer, and sometimes your Visual Studio will crash when debugging the application), also, the coding style is a bit annoying/tedious in this case since you need to refer to the resource property name instead of the real value.

And for the 2nd case, using dynamic resources, I cannot see any advantage of this resolution -- it will introduce a resource dictionary file, which is also an XAML file, that defines a whole bunch of resource strings. And you refer to this dictionary file in your Windows XAML. In this case, you will also use locbaml to localize the resource dictionary files, so why not localize the Windows XAML directly? (well, the idea behind the resource dictionary may be to separate the UI with the resources files that it uses. But since we can disassemble an XAML UI to get its CSV resource file, I don't think we really need this methodology.)

So, trust me, please just ignore them – they basically have no value to beginners and what they can do is just confuse you.

We choose Locbaml as the localization solution because we want to localize the application once it has been built. And at design/developing time, we have no idea what the product will be localized to. So, the input of a locbaml is a built-up application, which is mainly the EXE file or DLL file if the application is a class library. But however, unfortunately, since some information which will be used by locbaml are not embedded to the main assembly file, we need one extraordinary file—which probably you are not familiar with (I was not when I started). Remember this file is only internally used by .NET compiler and builder, and is also needed by locbaml. The file name in most cases is your application name plus “g.resources”. So, if you have an application named sample, this file is named sample.g.resources. You can find it from your application source code, it resides in <application name>\obj\debug|release\<application name>.g.resources.

Okay. So now you have copied the main assembly file plus the g.resource file, and also don’t forget the locbaml.exe file, to a single folder. We are ready to go.

Sorry, hold on, I still need to bother you with one important point: if your project file has some dependencies, you must place it within the same folder where locbaml is located, or put them into the GAC. Locbaml will need them when extracting resource information from the main assembly (and in real production environment, it's quite rare that our project has no project dependencies, so this information is actually very important).

Locbaml simply has two functionalities: parse a built-up EXE or DLL file by extracting the resource information into a CSV file format; and the other around: generate a resource file (binary format) from an edited CSV file.

And you use it in the following way (suppose we have an application named sample, so we have got sample.exe, sample.g.resources and locbaml in a folder ):

  • Step 1: locbaml /parse sample.exe
    After this command gets executed, you will get a sample.csv file.
  • Step 2: Open up sample.csv file using Notepad, edit it to your localized language
  • Step 3: Create a child folder named “tmp” for example, then run:
    locbaml /generate sample.g.resources /cul:<your culture name. 
    	e.g. zh-cn for chinese> /out:tmp\

    Then you will get sample.g.resoures file within tmp folder. Locbaml is not intelligent enough so we need to rename this file to sample.g.zh-cn.resources by ourselves

  • Step 4: Use Al.exe to link the resource files to an satellite assembly. First create a child folder and name it using our culture name. e.g. a older named zh-cn will be used to hold satellite DLL for Chinese. Run:
    Al.exe /culture:zh-cn /embed:tmp\sample.g.zh-cn.resources 
    	/out:zh-cn\sample.resources.dll 

Done. Basically, we have finished all the stuff.

But since we are talking about production environment developing, we need to mention that:

  1. You won't only have WPF windows in your application; in most cases you will have some other resources. Please copy them from the <application>\obj\debug|release\ and remember to embed them as well in Al.exe (step 4).
  2. The naming convention is extremely important. Both locbaml and AL(assembly linker) will take the name as qualifier (name space) to the variables in the resource files, so, please pay attention that: DO NOT change the file names. Use file names as they should be and think this as an important/rigid rule.
  3. AL.exe actually has another parameter /keyfile:<your key file>.
    This parameter is used when your main assembly (application binary) is strong named, or signed. And in this case, another parameter /template:<your main assembly file> is also needed.You need to sign your resource file using the same key file (*.snk) that is used to sign your main assembly, otherwise your satellite DLL won't get revoked (the public token must match for main assembly and satellite DLL).
    Signing your assembly with a strong name (you get the key file through sn.exe –k <keypari file name>) or removing the strong name from your assembly won't change the *.resource files you have got. But you need to run al.exe accordingly to reflect the changes to your satellite DLL.
  4. Feel free to remove all the .csv files and .resources files after you have got the satellite DLL.

Okay. Now we have finished the first part. We have successfully localized our sample application to a localized version. But the next question is:
How can I verify it? How do I know if I've done all the things correctly?

Basically you can check it using 2 ways:

  1. Put your main assembly and your satellite DLLs in a machine that has the destination language operation system running. You CANNOT simulate it by changing your language and regional setting to be the destination language.

    And I know you won't like this solution, as I don’t either –it's too expensive.

  2. The other resolution is to adding the following code in your main assembly source code:
    System.Globalization.CultureInfo ci = 
    	new System.Globalization.CultureInfo("zh-cn"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = ci; 
    System.Threading.Thread.CurrentThread.CurrentUICulture = ci; 

    Your thread will inherit the culture info from its parent thread or initializing process, which will inherit from the OS finally. But by adding the above code, you ‘ve told your running thread that you want to load the resource files that of Chinese version. This is the same as putting your binaries to a Windows with Chinese OS installed.

However, you still need to pay attention that:

  • The thread which will run the above code must be the same thread that will display the WPF windows
  • This must be done before the WPF window has been constructed. So you cannot do it within the WPF window constructor. Try to find the place where your WPF gets created and put the above code there.

Ok. That’s mostly all about it. I've attached the files that I used when I was researching all of this. The sample file is the same one as is offered by the article I mentioned at the beginning, but the content is slightly different. I hope you can have fun reading them.

Thanks for your time reading through it. thanks. Wink | ;-)

****************************************
I would strongly suggest you not read the below parts unless you are very interested in performance issues.
*******************************
After this article had been finished, I've been told by people that we can actually add <UICulture> element to the WPF application (or user control library) project file (.csproj). This way, the resource file won't get generated to the main assembly, but will be placed to a satellite DLL and placed in a satellite DLL folder. And in this case, we won't need to parse the main assembly, but will do it on the satellite DLL (which is essentially a resource DLL).

See what Microsoft says to <UICulture> element:

" In the project file, the developer sets <UICulture>en-US</UICulture> so that on build, a language neutral main assembly gets generated with a satellite .resources.dll containing all localizable resources. Alternately, one could keep the source language in the main assembly because WPF localization APIs support extraction from the main assembly. "

I then figure out that *** this is only true for neutral XAML resources***.
For neutral resources of resx type, they will **always be embedded to main assembly**.

And in this case, you may also need to configure the NeutralResourcesLanguage attribute due to some complicated reasons. So, let's get started with what NeutralResourcesLanguage attribute is and what it can do.

[assembly: NeutralResourcesLanguage("en-US", 
           UltimateResourceFallbackLocation.Satellite)] 

The above sentence simply says: my neutral resource language for this assembly is actually en-us, and please use en-US satellite DLL for all the cultures that are not currently supported.

[assembly: NeutralResourcesLanguageAttribute("en-us",
             UltimateResourceFallbackLocation.MainAssembly)]

This simply says: my neutral resource language for the assembly is actually en-US, and for cultures that are not supported, please use the main assembly instead. NOTE that the first parameter matters even if you set the second parameter to mainAssembly. There must be some unrevealed magic here which .NET will use to load resource files (remember I've said that this part is performance related). I'll give you an example here: if you have configured your <UICulture> to be "en-us", and set your neutral language of your assembly to en-us as well, then if you set fallback location to MainAssembly, you will experience an ERROR in runtime. But if you then change the neutral language to some other languages, e.g. "de" or "zh-cn", then everything works fine.The reason underneath is really mysterious Wink | ;-) (but obviously .NET runtime will compare something with the neutral language value here).

This attribute, combined with <UICulture> can make things quite complicated!
Note that the NeutralResourcesLanguage attribute class also supports .NET 2.0 version. And <UICulture> is introduced within WPF which only works on .NET 3.0 and 3.5. So, think about the case that a WPF application also contains a normal Windows form.

if your main assembly contains both a win form and a WPF window, and you have set <UICulture> to en-US for example, and set

[assembly: NeutralResourcesLanguage("en-US", 
UltimateResourceFallbackLocation.Satellite)]
, then, when your application runs in en-US environment, the WPF window will get loaded successfully while win form window will throw an error. Why? because <UICulture> only tells Visual Studio to embed XAML resources to satellite DLL instead of the main assembly, and for all the resx resources, they are still embed to main assembly. So you will get an error says system cannot find resource files for win form. In this case, you need to change UltimateResourceFallbackLocation to Mainassembly because of following reasons:

  • When loading XAML resources, since system can detect the en-US satellite DLL for XAML resources, WPF windows can be loaded successfully.
  • When loading win forms, since en-US satellite DLL only contains resources for XAML, it cannot find form1.en-US.resources. And en-US is already configured to be the fallback language, so resource file for form1 cannot be found.

Changing the UltimateResourceFallbackLocation to be Mainassembly, and also remember to change the neutral language to some other language (the reason is explained in the above section) may fix the issue, both XAML resources and RESX resources can be found then -- in this case, the fallback only happens for win form resource files. But think about the case when this application runs on a French machine, where both XAML and winform resources need fallback, what would happen???

Ridiculous!!! I would say, this is a Microsoft bug and I've yet to see any complaints or comments on this!

Yeah, you will need to create a duplicate RESX file in en-us version. Or a better solution, compile the RESX file to en-us satellite DLLs as well. Do it by renaming your *.resx files to *.en-us.resx. After this has been done, change your neutral language and fallback location back to en-us again.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

bChen@sh
Software Developer
China China
I've been a developper for almost 5 years. I've my developping background mostly on .net and c++. I've also got chance to written tens of thousands of lines sql procedures/ and funcctions Wink | ;-)

Comments and Discussions

 
Generalthe reason why specifying NeutralResourcesLanguageAttribute is a performance enhancement is that,, after we tell .net framework what kind of languate the neutral language is, PinmemberBank Chen15-Feb-11 18:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 13 Jun 2010
Article Copyright 2010 by bChen@sh
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid