Anyone who is starting almost any kind of software nowadays should be thinking on internationalization, even more if it is web-based. The effort required to prepare a system for localization (the difference between internationalization and localization is subtle, you can find it on Wikipedia) from the very beginning is minimum and the payoff is huge.

In this post I will first describe in the large how we can approach this problem with ASP.NET MVC, and then I will provide some easy steps to accomplish the task.

General idea of i18n

We will start by setting all the strings that our system uses in a key-value pair file. We will have one of these files for each language we choose to support, for instance the values for the key “colors” would be “colors”, “colours” and “colores” in the files Resources.en-US.resx, Resources.en-UK.resx and Resources.es.resx respectively. After that we only have to know which file to pick, in order to do that we read the preferred language sent by the browser on every Http request. Finally we should give the user the option to change the language, imagine that an English speaker would not be very happy to check his e-mail with the browser preferred language during his vacations in China!

1. Create the resources files

The first thing we are going to do is to create the resources file/s. Right-click on the App_GlobalResources folder, add a new resource file and name it Resources.resx: this would be the default language. By adding this item, another file called Resource.designer.cs is added to the project, it provides direct access to the key-value pairs we define on the resource file/s. In this post we will show how to support English (default) and Spanish, so we add another resource file called Resources.es.resx. Finally we add some string to those files, for instance: “Welcome” associated with “Welcome to MySite!” and “Bienvenido/a a MiSitio!”.

When we access these string within the code the Framework first try to do an exact match with the language. For instance if the language is set to es-AR (Spanish from Argentina) the framework will look first for the more specific resource file (Resources.es-AR.resx in this case), if that file does not exist the one corresponding to the parent language would be chosen (Resources.es.resx) and if that is not found either, then the default resource file is used.

2. Use the browser preferred language

In ASP.NET MVC is very easy to set the current culture to the browser’s preference. We just need to add a single line to the Web.config file: after the <system.web> tag we add:

<system.web>
    <globalization culture="auto" uiCulture="auto"/>

Now we are ready to test what  we have done so far. We just need to change the access modifier of the default resource file from internal to public. Then every string defined on these files could be accessed by just writing:

App_GlobalResources.Resources.Welcome;

3. Set the user preferred language and store it in a cookie

We have two ways to do this: server-side or client-side. In my personal opinion, as we only need to create and store a cookie and reload the site, is not convenient to go to the server. On the other hand, doing this client-side could be a bit more difficult. Luckily, we have jQuery!

First we need to have some links or cute images associated with onclick events:

<a href="#" onclick="changeLang('es-AR')">es-AR</a>
<a href="#" onclick="changeLang('en-US')">en-US</a>

On the <head> section we need to import jQuery (line 1) and the cookie plugin (line 2), which can be found here and a nice demo here.

   1: <script src="/Scripts/jquery-1.2.6.js" type="text/javascript"></script>
   2: <script src="/Scripts/jquery.cookie.js" type="text/javascript"></script>
   3: <script type="text/javascript">
   4:     function changeLang(lang) {

   5:         $.cookie('lang', lang);
   6:         window.location.reload();
   7:         return false;
   8:     }
   9: </script>

In line 5 the jQuery Cookie Plugin is used to store the chosen language in a variable named ‘lang’, and the statement in line 6 tell the browser to reload the site. What for? We will see in the next (and final) step.

4. Eat the cookie (before everybody else!)

As we all know, every cookie which is associated with the current domain is sent with every Http request. It would be great if we could write some code to “catch” each request, see if there is a cookie with the preferred language, and set it.

ASP.NET works with Http Modules which, in short, are pieces of code that can play with the Http request before it reaches to the handlers. Mansoor Ahmed Siddiqui wrote a great post about this, which I strongly recommend.

First we register the module in the Web.config file, so the Framework knows it have to be executed with every request:

<httpModules>
    <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    <!-- this one is our module =) -->
    <add name="CookieLocalizationModule" type="MyProject.CookieLocalizationModule, MyProject"/>
    <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>

Finally the code, which is pretty what you can image: getting the cookie and setting the language, with some more lines the modules require:

public class CookieLocalizationModule : IHttpModule
{
    public void Dispose()
    {
    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        // eat the cookie (if any) and set the culture
        if (HttpContext.Current.Request.Cookies["lang"] != null)
        {
            HttpCookie cookie = HttpContext.Current.Request.Cookies["lang"];
            string lang = cookie.Value;
            var culture = new System.Globalization.CultureInfo(lang);
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
        }
    }
}

And that is all?

Almost. There is one more thing we need to do: internationalize images. Although it is quite an easy task and there are many ways of doing it, this post is long enough so I will write about it later.

This should be enough for lots of applications and many languages. However there exists some issues related with Arabic languages (which are written from right to left) and some Asian ones (which use symbols) that are not at all cover in this post.

Thanks a lot Brian for all the help!
I hope this have been useful. Have a nice day!