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:

Example resx files

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.

 

Note: due to the apparent complexity of localisation, I've included a demo project. Download.

posted @ Friday, November 09, 2007 8:45 AM

Print

Comments on this entry:

# re: Localisation / Localization in C# ASP.NET

Left by Michael Moore at 3/25/2008 2:13 AM
Gravatar
Great stuff here. Thank you. Can you tell me a conceptual method for someone in another country being able to maintain or translate for "their" language? i.e. I need a web page that allows someone who speaks German, to translate for me, online, meaning fill the DE resource file online.

# re: Localisation / Localization in C# ASP.NET

Left by Andrew at 3/31/2008 12:20 PM
Gravatar
Hi Michael,

if I were you, I'd approach it in this way:

If a user comes in to translate your site to German, retrieve a copy of your en-us.resx from your satellite dll. As this is an xml file, you can apply an xslt to it to display it as an input form to the user.

They then go an replace the english-values with german-values.

When they hit save, you could get ModuleBuilder (http://msdn2.microsoft.com/en-us/library/system.reflection.emit.modulebuilder.aspx) to write it back into the dll as de.resx

# re: Localisation / Localization in C# ASP.NET

Left by Sandy at 4/2/2008 10:43 PM
Gravatar
Does anyone know the right way to get/set the comment field value?

# re: Localisation / Localization in C# ASP.NET

Left by Markus at 4/19/2008 12:40 AM
Gravatar
Anyone would provide a full working example? there're missing classes here

# re: Localisation / Localization in C# ASP.NET

Left by Andrew at 4/19/2008 9:24 AM
Gravatar
Hey Guys, I've included an example project at the end of the post now.

# re: Localisation / Localization in C# ASP.NET

Left by adkdavid at 5/21/2008 7:53 AM
Gravatar

Ther was enough here to get me straightened out with C# additional resx files and localization. Thanks a TON!

# re: Localisation / Localization in C# ASP.NET

Left by Dom at 8/27/2008 4:07 AM
Gravatar
I'd like to use constructs directly in the aspx file, like this:

<asp:LinkButton Text="<%$ resources: myKey %>" ID="myKeyControl" runat="server" OnClick="myKeyControl_Click"></asp:LinkButton>

But since the resources are in a separate DLL, the compiler doesn't find them and I get :
The resource object with key 'myKey' was not found.

does anyone know how I can get that to work with external resource DLLs?

tx

Your comment:



 (will not be displayed)


 
 
 
Please add 1 and 4 and type the answer here:
 

Live Comment Preview:

 
«November»
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456