blue chalkboard different languages written in white and LANGUAGES in yellow

ASP.NET Localization: Working with Multi-Language Web Applications

Techniques for setting the current language in an internationalized web application

On the way to localizing an application, internationalization is only the first step. In general, internationalization is about designing and implementing the application so that it can support multiple languages. As I explained last month in "Expand Your ASP.NET Web Application Localization Options," adapting an application to a particular culture is referred to as the process of localization. Assuming that you have all localizable resources in place for a number of languages, how would you move to the next step? In particular, what are your requirements for localizing an application? Does localization mean that your application supports multiple languages but each language is configured at setup time? Or, instead, does localization mean that the user can switch between a few predefined languages? Or, finally, is yours an auto-adapting application that figures out the language to use from some context? Let's find out more about building support for multiple national languages into an ASP.NET web app and several ways to approach this task.

Multilingual Applications

A very common scenario for localizable applications is a multilingual application. A multilingual application is an application deployed with multiple localized assemblies, but it is configured to use only one set of resources at a time. The user is not allowed to change the user interface (UI) language, and the application always uses the same language and regional settings for its entire lifetime.

You set up a multilingual application by writing the correct information in the globalization section of the web.config file, as shown in the following example:

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

In a multilingual scenario, you might want to make an effort to isolate resources in a separate assembly and minimize the number of deployed assemblies.

Auto-Adapting Applications

An auto-adapting application is an application that supports multiple languages but decides the culture to use at runtime, sometimes based on user-provided information. An auto-adapting application supports a number of predefined cultures and falls back to the neutral culture when the detected locale doesn't match any of the supported cultures. The neutral culture is the native default language of the application.

The defining aspect of an auto-adapting application is how the application determines the culture to use. There are at least a couple of ways to do this. Sometimes the application just grabs the list of accepted languages from the HTTP header that the browser sends with each request. One of these languages is picked up -- typically it is the first language that appears in the list. Another approach is based on geo-localization. In this case, the application looks up the IP address of the requesting browser and chooses the culture accordingly. Let's learn more about ways to implement the first approach.

In ASP.NET, you use the Culture and UICulture properties to get and set the current culture. You can set the culture on a per-request basis. In ASP.NET MVC, for example, you can set the culture from within the constructor of each controller class or in the constructor of certain base controller classes.

In particular, the property Culture governs all application-wide settings, such as dates, currency, and numbers. The property UICulture governs the language being used to load resources. These string properties are publicly available from the view classes in both the ASPX and Razor view engines. The two properties have the empty string as their default value, which means that the default culture is the culture that is set on the web server. If culture properties are set to auto, then the first preferred language that's sent by the browser through the Accept-Languages request header will be picked up. You enable auto-detection of culture by adding a line to the web.config file, as follows:

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

Most of the time, though, what you really want is the ability to set the culture programmatically and the ability to change it on the fly as the user switches to a different culture by clicking an icon or using a culture-specific URL.

Changing the Language on the Fly

Changing the application's culture programmatically is easy once you define the policies you'll be using to retrieve the culture to set. The culture to set can be read from a database table or perhaps from the ASP.NET cache. The culture can also be a value you retrieve from the passed URL. Finally, it can even be a parameter you get via geo-location -- that is, by looking at the IP address the user is using to connect. In any case, at some point you know the magic string that identifies the culture to set. How do you apply that on the fly?

The following snippet presents the code you need to force the ASP.NET runtime to use a particular culture:

var cultureString = "...";    // i.e., en-us
var cultureInfo = CultureInfo.CreateSpecificCulture(cultureString);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;

In the .NET Framework, the culture is set on a per-thread basis -- so you pick up the ASP.NET current thread and set both the CurrentCulture and CurrentUICulture properties. Note that the two culture properties work independently. Most of the time, both properties hold the same value; however, sometimes they might be set to different values. For example, you can switch the language of text and messages according to the browser's configuration while leaving globalization settings (such as dates and currency) constant.

It is important to note that the culture must be set for each request. This is because in ASP.NET each request runs on its own thread and the thread is picked up from the ASP.NET thread pool on demand.

In ASP.NET Web Forms, the Page class offers an overridable method to initialize the culture. In ASP.NET MVC, you can set the culture thread in a number of ways. For example, you can embed the preceding code in each of your controller classes. You can even take this approach one step further and move the culture code to a base controller class, as shown in Figure 1.

public class MyBaseController : Controller
{
     public MyBaseController()
     {
        var cultureString = GetCultureToApply();
        var cultureInfo = CultureInfo.CreateSpecificCulture(cultureString);
        Thread.CurrentThread.CurrentCulture = cultureInfo;
        Thread.CurrentThread.CurrentUICulture = cultureInfo;
     }
}

As an alternative to making changes to the controller class, you can achieve the same result by using a custom action invoker or a global action filter. In both cases, you write the code once and attach it to all controllers in a single step. An action invoker is a component that operates within the boundaries of the controller and is responsible for orchestrating the execution of the given action. An action invoker object can be injected into the controller class -- even into any instance of controller classes -- through the ActionInvoker property of the system's Controller class. The property is of type IActionInvoker, and the interface can be resolved dynamically in ASP.NET MVC 3.

In the end, using an action invoker to set the culture programmatically is recommended if you already have an injection mechanism in place; you use an Inversion of Control (IoC) component or an ASP.NET MVC dependency resolver. Otherwise, using the ActionInvoker mechanism forces you in any case to add code directly into your controller class. Let's examine an alternative approach that is much less code-intrusive: using a global filter for localization. Figure 2 shows a possible implementation for an action filter that retrieves the culture string from a cookie and sets it on the current thread.

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public class CultureAttribute : ActionFilterAttribute
{
    private const String CookieLangEntry = "language";

    public String Name { get; set; }
    public static String CookieName
    {
        get { return "_Culture"; }
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var culture = Name;
        if (String.IsNullOrEmpty(culture))
            culture = GetSavedCultureOrDefault(filterContext.RequestContext.HttpContext.Request);

        // Set culture on current thread
        SetCultureOnThread(culture);

        // Proceed as usual
        base.OnActionExecuting(filterContext);
    }

    public static void SavePreferredCulture(HttpResponseBase response, String language,
                                            Int32 expireDays=1)
    {
        var cookie = new HttpCookie(CookieName) { Expires = DateTime.Now.AddDays(expireDays) };
        cookie.Values[CookieLangEntry] = language;
        response.Cookies.Add(cookie);
    }

    public static String GetSavedCultureOrDefault(HttpRequestBase httpRequestBase)
    {
        var culture = "";
        var cookie = httpRequestBase.Cookies[CookieName];
        if (cookie != null)
            culture = cookie.Values[CookieLangEntry];
        return culture;
    }

    private static void SetCultureOnThread(String language)
    {
        var cultureInfo = CultureInfo.CreateSpecificCulture(language);
        Thread.CurrentThread.CurrentCulture = cultureInfo;
        Thread.CurrentThread.CurrentUICulture = cultureInfo;
    }
}

The CultureAttribute class offers methods to read and write a culture string to a custom cookie. The action filter class overwrites the OnActionExecuting method, meaning that it might kick in just before any controller method runs. An action filter can operate on a given method or on all methods of a given controller class. Since ASP.NET MVC 3, an action filter can also operate globally on all methods of all controllers within an application. For this to happen, though, the action filter must be registered as a global filter. The code in Figure 3 shows how to register the filter as a global filter.

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
       RegisterGlobalFilters(GlobalFilters.Filters);
       :
    }

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new CultureAttribute());
    }
    ...
}

In Application_Start (or in a method you invoke from there), you add a newly created instance of the action filter (CultureAttribute in this case) to the system's collection of global filters. That's all you need; now with this infrastructure in place, you can add links to your pages (typically, the master page) to switch languages on the fly.

Final Touch on Views

It is common in multi-language applications to have links at the top of each page to change the language -- you often implement this with clickable flag buttons. Here's an example of markup you might use to create these links:

@Html.ActionLink("Espanol", "Set", "Language", new { lang = "es" }, null)
@Html.ActionLink("English", "Set", "Language", new { lang = "en" }, null)

Users who click on such links will execute an action method that I usually store in a specific controller, such as the LanguageController class shown in Figure 4.

public class LanguageController : Controller
{
    public void Set(String lang)
    {
        // Set culture to use next
        CultureAttribute.SavePreferredCulture(HttpContext.Response, lang);

        // Return to the calling URL (or go to the site's home page)
        HttpContext.Response.Redirect(HttpContext.Request.UrlReferrer.AbsolutePath);
    }
}

The action method simply stores the newly selected language in a custom cookie (or in any other store you might choose) and redirects to the same page. On the new access, the effect of the global action filter is to read the just-stored language setting from the cookie and switch to the selected culture.

Expand Your Multi-Language Options

Localization is a fundamental attribute of any application, and .NET provides great support for UI text localization. In this article and in "Expand Your ASP.NET Web Application Localization Options," I've shown you several methods that expand your options for adding multi-language support to ASP.NET apps beyond what .NET offers. Using the techniques and utilities I've presented here and in the previous article lets you change a web application's language and culture on the fly, as well as makes it much easier to localize other common resources such as images, style sheets, and views.

Dino Esposito is a trainer and consultant specializing in web, social, and mobile integrated architecture and effective code design principles and practices. He's the author of Programming Microsoft ASP.NET 4 and Programming Microsoft ASP.NET MVC3 (Microsoft Press).

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish