Tuesday, June 26, 2007 6:53 PM
The other day I stumbled over a non intuitive feature of localization. Turns out that you need to include any key you are going to use in a local specific file in the culture neutral file as well. Otherwise - declarative control localization using the meta:resourcekey="myKey" property syntax would not work as expected..
Create a file named Default.aspx
Create an asp:HyperLink tag, and set some properties:
Now create a App_LocalResources folder, and in it create a resx file Default.aspx.resx and in it have the single string:
Key = "HelloWorld.Text"
Value = "Hello World!"
(Note that the resource has the exclamation point. This proves that the app is using the resource value rather than the Text property in the tag at runtime.)
Run the application. Nothing fancy, and as expected you get a link, it says what it says in the culture neutral resource file (Default.aspx.resx)
Now copy the file Default.aspx.resx and make a file named Default.aspx.en-AU.resx. Open the new file, and edit the value to say "G'day Mate!". In the web .config add a node:
Run again and you get what you expect, the resource value from the Default.aspx.en-AU.resx
At this point, add a key to the localized resource file to have a ToolTip:
Key = "HelloWorld.ToolTip"
Value = "Go Joey"
Run again, but no tool tip is displayed, hover over the link as you may. Debug through and try to inspect the expression GetLocalResourceObject("HelloWorld.ToolTip") - it does return "Go Joey", but doesn't render. That's peculiar!
Now go back to the Default.aspx.resx file and add the ToolTip key and any value you want as well.
Run it again and - drum roll please - you get the tool tip.
Curious about this, I surmised that somehow the compiler emits properties or methods that are based on the culture neutral resource file. ILDASM confirms it. If you open the generated assembly (c:\windows\Microsoft.net\framework\v2.0.50727\Temporary Internet Files\....) you see that when you include the key HelloWorld.ToolTip in Default.aspx.resx, you get something along the lines of:
actually emits a method ___BuildControlGreetings(). Opening the IL shows that in that method the ToolTip resource value is fetched and the property of the Greeting control is set to that value:
IL_003c: ldstr "HelloWorld.ToolTip"
IL_0041: call instance object [System.Web]System.Web.UI.TemplateControl::GetLocalResourceObject(string)
IL_0046: call class [mscorlib]System.Globalization.CultureInfo [mscorlib] System.Globalization.CultureInfo::get_CurrentCulture()
IL_004b: call string [mscorlib]System.Convert::ToString(object,
IL_0050: callvirt instance void [System.Web]System.Web.UI.WebControls.WebControl::set_ToolTip(string)
But if you remove the resource key and value from the culture neutral file, the IL would not be emitted. That's a pretty interesting discovery. The resource key gets compiled into inline code which makes the appropriate resource fetching call and populate the control's property. This is a pretty efficient implementation because the property is set explicitly, and no reflection is used at runtime. The overhead of GetLocalResourceObject() is based on the internals of the provider, but in general it boils down to a hash lookup. Recall that GetLocalResourceObject() during debugging did indeed return the desired string? Well, that proved that the specific satellite assembly was correctly compiled and present. But that was no use because the runtime ___BuildControlxxx simply won't include it.
In conclusion, make a sticky note to always have the culture neutral file include a superset of all keys you may want to localize. The default mechanism provides a fallback in case a specific local key is not specified to go to the less specific or culture neutral file. But there is no "fall forward" - all keys must be specified in the culture neutral file or the method to get a localized resource would simply be missing!