Localisation / Localization in C# ASP.NET

Localisation is something that naturally forms the part of a websites life as it grows & develops. With things like language packs, currency & date formats specific to a user’s locale, a site can reach a far wider audience than it could using the standard en-us interface. 

However it’s not just for those of us who want to make a localised site. You can (and should) also use this method to replace the text for all your labels on your site. This way, you further split out the design from the code of your website. 

.NET makes this process very easy indeed, and with a little bit of forward planning today, you can save yourself an expensive redevelopment task in the future to get your site working across borders. 

Localising your site based on the method I’m going to describe has a number of failsafe features: 

  • If the key doesn’t exist in your resource file, then it will return the key as the value. Hence if you send down “Welcome to my site” as the key, which isn’t found, it will then render this as the value.
  • If the resource file doesn’t exist for the locale of the user, the system will default to the fallback resx defined in the Translate.message class.
  • Once implemented, you can simply add more resx’s to the resource project (ie support more languages), without having to write a single additional line of code!

In this example, I’m going to use the following locales: 

  • en-au (Australian English – my corner of the world)
  • zh-cn (PRC Chinese – what I’ve been studying for a few years)

This example also follows on in a sense from yesterday’s article on Embedded Web Resources, where we’ll be putting our language files inside a dll for safe keeping.  

The first thing you need to do is create two new resource files, embedding them in the resources dll, named according to their contents, ie: 

  • en-au.resx
  • zh-cn.resx

Resource files are simply an Xml file containing three columns – Name, Value and Comment. It pretty much acts like a Dictionary, where you have a Key field (Name), a Value field, but also a comment field should you feel the need to be a proactive documenter. 

Add a few entries your resource files. Mine ended up looking like this: 

 

As with most things code, making things simple & easy is the key. So you want to have a look at a quick way to retrieve values out of these resource files. 

The first thing you want to do on your website is determine what language the user’s browsing in. To do this, I’m going to create a “Translate.cs” class in my resources dll, and add the following: 

 

public static class Translate 

    public static string GetLanguage() 

    { 

        return HttpContext.Current.Request.UserLanguages[0]; 

    } 

  

    public static string Message(string key) 

    { 

        ResourceManager resMan = null

        if (HttpContext.Current.Cache["resMan" + Global.GetLanguage()] == null

        { 

            resMan = Language.GetResourceManager(Global.GetLanguage()); 

            if (resMan != null) HttpContext.Current.Cache["resMan" + Global.GetLanguage()] = resMan; 

        } 

        else 

            resMan = (ResourceManager)HttpContext.Current.Cache["resMan" + Global.GetLanguage()]; 

  

        if (resMan == null) return key; 

  

        string originalKey = key; 

        key = Regex.Replace(key, “[ ./]“, “_”); 

  

        try 

        { 

            string value = resMan.GetString(key); 

            if (value != null) return value; 

            return originalKey; 

        } 

        catch (MissingManifestResourceException

        { 

            try 

            { 

                return HttpContext.GetGlobalResourceObject(“en_au”, key).ToString(); 

            } 

            catch (MissingManifestResourceException mmre) 

            { 

                throw new System.IO.FileNotFoundException(“Could not locate the en_au.resx resource file. This is the default language pack, and needs to exist within the Resources project.”, mmre); 

            } 

            catch (NullReferenceException

            { 

                return originalKey; 

            } 

        } 

        catch (NullReferenceException

        { 

            return originalKey; 

        } 

    } 

  

This is actually a pretty simple class. GetLanguage() does just that – gets the locale code of the language the user is browsing in (eg: “en-us”, “en-au” etc). The Message function simply accepts a string, which lines up to the “Name” column within your .resx files (note that in the resx files, the name cant contain spaces, dots or forward-slashes, so I replace them with underscores in this example). 

This also makes use of the Language class, which is home-brewed and sits in the same location as your resx files: 

public static class Language 

    public static List<string> GetSupportedLanguages() 

    { 

        HttpContext context = HttpContext.Current; 

        if (context != null

            if (context.Cache["SupportedLanguages"] != null

                return context.Cache["SupportedLanguages"] as List<string>; 

  

        string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames(); 

        List<string> languages = new List<string>(); 

  

        for (int i = 0; i < names.Length; i++) 

            if (Path.GetExtension(names[i]).Equals(“.resources”, StringComparison.OrdinalIgnoreCase)) 

                languages.Add(Path.GetFileNameWithoutExtension(names[i])); 

  

        if(context != null) context.Cache["SupportedLanguages"] = languages; 

        return languages; 

    } 

  

    public static ResourceManager GetResourceManager(string languageCode) 

    { 

        foreach (string name in GetSupportedLanguages()) 

        { 

            string[] arrLanguageCode = name.Split(‘.’); 

            string supportedLanuageCode = arrLanguageCode[arrLanguageCode.Length - 1]; 

  

            if (supportedLanuageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase)) 

            { 

                ResourceManager resMan = new ResourceManager(name, Assembly.GetExecutingAssembly()); 

                return resMan; 

            } 

        } 

        return null

    } 

  

 

GetSupportedLanguages() uses reflections to pull your resx files out of the resources dll. GetResourceManager() returns a ResourceManager, which is essentially the C# interface to those resource files. 

The final step is really just a simple operation to thread your resource files into your interface. So before where you might have had: 

<span class=”label”>User:</span> 

You now would put: 

<span class=”label”><%=Translate.Message(“User”) %>:</span> 

When it comes time to render the document, the web server sends the key “User” to the Translate.Message function, which looks it up in the resource file that matches the user language, and then puts in the text held in the “Value” column of that resource file. 

Thus far, this is the best I can come up with for getting multiple languages across your interface, as it provides a very pluggable interface for maintaining language packs. You can adapt this methodology to work easily with Winform applications. 

Localising your site from the beginning will save you a lot of time refactoring your code in the future, and also split out the text of your interface (which essentially should be treated as data, rather than structure / code). Localisation also builds in a lot of movement for growth in your site, and goes a long way towards customising a site per-user.