Localisation/Globalisation isn’t my subject and it’s a gnarly old topic so I generally leave it to folks like Guy who has a specific interest in that area, knows his onions and wrote the book! π
However, a couple of things sparked my interest here.
One was because a customer was asking me about it earlier this week and I hadn’t really looked at it other than to expect it to work much like the regular .NET Framework.
Then I saw Tim’s post over here and that sparked more interest so I thought I’d take a dig in and see if I could get a bit of understanding.
Generally, when I think of globalisation/localisation I think of CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture and how one drives the formatting for dates, times, decimals and so on and the other drives resource lookup.
This is explained up here for Silverlight;
but straight away I was foxed by the feature.
I wrote a little code to see how CurrentCulture and CurrentUICulture were reported in a “Hello World” Silverlight application and I got;
Culture = English (UK)
UI Culture = English (US)
I was interested in this, so I changed the culture of my browser to French and re-ran my Silverlight control. I got;
Culture = English (UK)
UI Culture = English (US)
Hmm! So, I went and hacked the hosting HTML page to force the culture with;
and then Silverlight reports;
Culture = French (France)
UI Culture = French (France)
by default culture and uiculture are set to “auto” and, clearly, by default these are not getting their value from the browser but are, instead, getting it from the Operating System. Note – there’s documentation up here which says;
“The .NET Framework for Silverlight provides data for the invariant culture, but it retrieves information about all other cultures from the operating system. This means that the information that is available to a specific culture may differ across operating systems or even across versions of the same operating system. In some cases, data may even be unavailable, in which case data from the invariant culture will be used instead. The developer should make no fixed assumptions about the values of particular properties or particular objects returned by specific cultures”
So, the culture information is not coming from the browser. It’s coming from the OS. In Windows Vista I find that changing my format for dates/times/etc as in;
changes the value reported for Culture in Silverlight whereas changing the display language;
affects the CurrentUICulture.
What if I wanted to pick up the culture from the browser rather than from the Operating System?
I’m not sure whether there’s a consistent way of doing this from script in the browser so I wondered whether it might be better to do it server-side when the page is requested. However, when I look at the ASP.NET Silverlight control it doesn’t look to have settings for culture/uiCulture so I threw something in to my ASPX page based around an object tag instead with some server-side generated bits;
<div style="height: 100%;"> <object id="foo" data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"> <param name="Culture" value="<%=System.Threading.Thread.CurrentThread.CurrentCulture.Name %>" /> <param name="UICulture" value="<%=System.Threading.Thread.CurrentThread.CurrentUICulture.Name %>" />
and then as long as I’ve got my web config setting up to set culture and uiculture based on browser settings as in;
<globalization culture="auto" uiCulture="auto"/>
then when this renders down to the browser it might render something like;
and then I can pick up the settings from the browser and get the routed into the Silverlight control instance.
Ok…that gets us to the point where I can either have culture/uiCulture set by;
- The browser
- The operating system
- The programmer
And CurrentCulture’s going to be used for numeric formatting, date formatting, comparisons and so on – all as detailed in the documentation page.
What about resources loaded based on CurrentUICulture? How does that work?
Starting with the easiest example that I can try, I put a button onto a form as in;
<StackPanel x:Name="LayoutRoot" Background="White"> <Button Content="Click Me" Margin="10"/> </StackPanel>
Now, I want that “Click Me” to come from a resource file. So, I add a resource file called myResources.resx to my application and create a single string resource called btnText with a value of “Click Me From Resources”. I set the code generation “Access Modifier” of the resource file to be public and that’ll cause me a class called myResources with a static property called btnText on it.
Then what I want to do is;
<UserControl x:Class="SilverlightApplication1.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SilverlightApplication1"> <UserControl.Resources> <local:myResources x:Key="resources" /> </UserControl.Resources> <StackPanel x:Name="LayoutRoot" Background="White"> <Button Content="{Binding Source={StaticResource resources},Path=btnText}" Margin="10"/> </StackPanel> </UserControl>
That’d be great except the code generation tool generates the myResources class with an internal constructor π¦
Usually, this doesn’t matter but it does here because I want the XAML system needs to construct an instance of myResources.
Guy already spotted this and has a resource generator which makes a class with a public constructor so I downloaded that and used it and ended up with a public constructor. That means that I can use my example above.
If I want to now add French resources I can make a new resource file named (e.g.) myResources.fr.resx and add that to the project with a btnText string in it.
From there, I’d expect that things would “just work” but if you’ve looked through the SDK documentation then you’ll know that I now need to;
- Unload the Silverlight project in Visual Studio.
- Edit it in the XML editor.
- Add to the <SupportedCultures> element value a semi-colon separated list of strings naming the cultures that I support (i.e. for me “en;fr“).
With that in place ( and using the previous page that sets the UI culture based on the browser’s locale ) I can browse to my page and set my language to be english/french and get to see the right resources loaded for that language.
Taking that one step further, what if my one application is using a control from another assembly? I can apply the same approach in that I can;
- Add a new Silverlight Class Library project
- Add a UserControl to that project (say myControl)
- Add resources to my library project – say controlResources.resx and controlResources.fr.resx
- Set my resources to build using Guy’s tool which gives me a class controlResources
- Add a resource to my resources (e.g. a string called controlString1)
- Add an instance of controlResources to my UserControl’s XAML definition giving it a key (of say ctrlResources)
- Add a TextBlock to the UI of my UserControl and bind the text of the text block to the property controlString1 of the resource identified as ctrlResources
If I then use an instance of my new UserControl on my main UI ( by adding a reference to the class library ) then that all works the way I’d expect in that changing the UI culture sets the resource culture for both my main UI and the control used on that UI.
So, that all works but it’s interesting to look at what’s going on from the point of view of deploying my code.
Right now, with the simple application that I’ve got which has a main UI with English/French resources and uses a control which has a UI with English/French resources. What’s present in the XAP though is;
and then in the fr folder is;
and my English resources are built into the 2 “top level” DLLs and my French resources are built into the assemblies in the fr folder but they’re all in the XAP.
That is, all the resources that I need for the application have been built into the XAP which means that my XAP is going to grow in size as I add additional languages and resources and if my user’s were (say) 99% English or 99% French then they are paying for the download of all these additional resources in my XAP file when they don’t actually need them.
I might prefer to leave all these additional resources back up on the server and dynamically load them for the user if they need them – I’ll try and follow up with a look at that in another post.