Working in Europe has introduced me to the world of localization and skinning web applications. Not only do we have to support several different locales for the same web application, but in some cases, depending on the host url, the site should be skinned completely differently. A common reason for needing to do this is if your e-commerce website also sells products under the guise of a different company in certain locales. Obviously, skinning sites involves a lot of css styling, but you may also need to provide different values for titles, labels, urls, etc, for each skin. You really don’t want to have to build and deploy several copies of your website to do this, and luckily, you don’t have to!
Localization in .NET using resource files
In .NET, localization is generally done via resource files. For each language / locale you wish to support, you should have a SiteA.resx file, e.g.
- SiteA.resx (This is the fallback resource file, in case no other more specific resource files match the current culture)
- SiteA.es.resx (Spanish)
- SiteA.de.resx (German)
- SiteA.nl.resx (Dutch)
- SiteA.nl-BE.resx (Belgian Dutch)
If this is all you need, then .NET makes localization pretty straight forward. When you build your solution, .NET automatically creates a strongly-typed resource class with the same name as your resource files (without the extensions). In my example above, .NET would have created a class called SiteA in the Resources namespace, with public readonly string properties for each key in my resources files.
When you access one of the properties in this resource class, as long as you have set your current culture correctly, .NET will take care of picking exactly which resource file to read the values from. How it picks which resource file to use is done via a fallback mechanism which is handled by the convention-based naming. Scott Hanselman provides a pretty good explanation of how this works here.
Localizing skinned sites using resource files
If you also want to use resource files to provide different localized values for each of your skinned sites, you’ll need to create a set of resource files for each skin, e.g. SiteB.resx and SiteC.resx:
From these resource files, .NET creates three separate strongly-typed resource classes for you to use:
However, you cannot edit these classes manually, they do not inherit from a common base class nor do they implement a common interface, so how do you elegantly switch between skins without having some massive switch statement everywhere you need to read a value from the resource files?
As with any programming problem, I’m sure there are many ways to do. I’m going to explain how I solved this particular problem in an ASP.NET MVC 3 web application.
ActionFilterAttributes and a LocalizedResourceManager
First of all, I needed something to intercept every action in my application and figure out which culture to use. In my application, I allow the user to change the language of the site by setting a ‘lang’ parameter in the querystring. If that parameter isn’t set, then I look in the user’s browser languages for the main supported language. If you wanted to, you could also read and write a user’s chosen culture from a cookie, but I didn’t need to implement this. I also needed to figure out which skin to display based on the host url that the user requested.
I was able to do this by sub-classing the ActionFilterAttribute class and overriding it’s OnActionExecuting method:
You’ll see that on line 18 of the LocalizationAttribute, I resolve the site that the user has requested. I do this by reading the host url. Once I know which site the user has requested, I initialize my LocalizedResourceManager by setting it’s CurrentSiteName property.
The LocalizedResourceManager is very simple – it only exposes one method, GetResource(string), to which you pass the name of the resource key that you wish you read out of your resource files. Internally, this method calls HttpContext.GetGlobalResourceObject(string, string) to actually read the property out of the resource files. This method here is really the crux of my solution.
HttpContext.GetGlobalResourceObject(string, string) takes two string parameters: className and resourceKey. In essence, this method allows you to pick which resource class to use and then uses the normal fallback mechanism to figure out which localized resource file to actually read the value from based on which culture is currently set.
All that’s left to do now is to register the LocalizationAttribute as a global filter in the Application_Start() method of Global.asax.cs. I also provide a new route which takes a lang parameter so that the user can change which language they wish to see the site in.
And finally, to make use of this setup, all I have to do is call the static LocalizedResourceManager.GetResource(string) method from within my controllers or views, passing it the resource key name for which I would like to get a localized (both locale and skin-wise) value. By this time, the ActionFilterAttribute will have done it’s magic by setting a current culture and the LocalizedResourceManager.CurrentSiteName property, thereby telling the LocalizedResourceManager which resource class to use and letting .NET use it’s fallback mechanism to pick the right localized resource file as per the current culture.