Skip navigation
ethernet cord laying on a map

Expand Your ASP.NET Web Application Localization Options

Augment your options for localizing ASP.NET websites

Localization certainly isn't a new challenge for ASP.NET developers. Tools that let you generate culture-specific web pages have existed since the very first version of ASP.NET. Nothing is different in ASP.NET MVC, and nothing has changed. In the end, adding localization capabilities to ASP.NET MVC applications is neither more difficult nor different from doing so using classic ASP.NET Web Forms. So what's the purpose of this article? To be honest, when you undertake the challenge of localizing a non-trivial application, you realize that the tools you get free with the .NET Framework and ASP.NET are only "first-aid" tools. They're definitely helpful tools that you'll use extensively in your project; however, they'll likely be insufficient for an effective localization that goes beyond plain replacement of text in UI web pages. In this article, I'll discuss how to build a few specific tools that can make localization easier in the context of an ASP.NET MVC project.

Internationalization vs. Localization

Internationalization and localization are two apparently similar terms that are sometimes used interchangeably. They aren't synonyms, although both terms refer to the process of making a site available through different national languages. Internationalization is about architecting an application so that it can be easily adapted to various languages and cultures. Localization is about adapting a site to the needs of a specific language and culture. In other words, you can't localize an application that hasn't been architected with localization in mind.

Localizing to a given culture consists of replacing the application resources with another culture-specific set of resources. Internationalization consists of making the entire application's architecture flexible enough to support new cultures at any time and to let users change the culture in use at any time. When the user switches to a different culture, the entire application should refresh accordingly.

Using Localizable Resources

When you think of software localization, typically what comes to mind first is translation of a UI's text. But resources that could require localization span the entire application, not just the UI, and include more than just text to translate. ASP.NET MVC application resources include string literals, files, and views. Files can include script files, Cascading Style Sheets (CSS) files, and images.

Visual Studio makes it easy to isolate string literals to incorporate in the UI. All you do is add a resource file to the project. A resource file (a .resx file) is ultimately an XML file that gets compiled on the fly by Visual Studio. Visual Studio parses the content of the .resx file and creates a designer class that exposes literals as string properties.

As a developer, you have some control over the namespace and the access modifier of the class members. When you add a resource file to the project, you can choose whether to make all its properties public or internal (the default) and decide which namespace will group them. In Visual Studio, the default value of the access modifier is internal; you need to change the value to public if you're compiling resources in their own assembly and linking it to the site.

The bottom line is that a .resx file isn't designed to simply contain text. It can contain other types of resources, such as images, menus, and other auxiliary files. In the context of a web application, though, you might want to handle anything that isn't plain text as a distinct file. So in the end, the .resx file is still used but mostly as a string repository for localizable text. Figure 1 shows the .resx file editor in Visual Studio.

Figure 1: The .resx file editor in Visual Studio
Figure 1: The .resx file editor in Visual Studio

The following code snippet shows how you use localizable text from within the application views.


    @Strings.AppTitle 

In a Razor view, as shown in the previous code example, you use an object with the same name as the .resx file ( Strings in the code snippet and in Figure 1) and access as public properties all the entries you created in the editor. If your application comes with different resource files, the application will automatically pick up the resource file that matches the currently selected locale. So far, so good, and especially, there's nothing really new.

A complex UI, however, might not simply be adapted to a different culture by only replacing string literals. And for resources other than text, you don't have the same facilities that you have for text. How would you, for example, indicate a localizable image or script file?

Localizable Files

In ASP.NET MVC, it's fairly common to use the Url.Content method to reference content files such as images or CSS files. The major benefit of using the Url.Content method is that it transparently converts a relative path to an absolute path. More specifically, the method understands the tilde (~) operator and fixes the path automatically. When used in a URL, the tilde operator indicates the root path of the application and works regardless of how your application is deployed, whether as a root application or a sub-application. For this reason, I always recommend you reference external files through Url.Content.Now wouldn't it be great if the Url.Content method could handle localization, too? Imagine that you call the method passing, say, home.png, and the method transparently checks the current locale and picks up home.fr.png if the current culture is, say, French. How would you write such a method? The original Url.Content method can't be altered; however, writing an extension method that just extends the Url.Content syntax is no big deal. Figure 2 shows how to do so.

public static class UrlExtensions
{
    public static String Content(this UrlHelper helper,
    String contentPath, Boolean localizable=false)
    {
    var url = contentPath;
    if (localizable)
        url = GetLocalizedUrl(helper, url);
    return helper.Content(url);
    }

internal static String GetLocalizedUrl(UrlHelper
    helper, String resourceUrl)
    {
    var cultureExt = String.Format("{0}{1}",
        Thread.CurrentThread.CurrentUICulture
        .TwoLetterISOLanguageName,
        Path.GetExtension(resourceUrl));
    var url = Path.ChangeExtension(resourceUrl,
        cultureExt);

    // Check if localized URL exists and return
        file.XX.jpg (or whatever)
    return VirtualFileExists(helper, url) ? url :
        resourceUrl;
    }

internal static Boolean VirtualFileExists(UrlHelper
    helper, String url)
    {
    var fullVirtualPath = helper.Content(url);
    var physicalPath =
        helper.RequestContext.HttpContext.Server
       .MapPath(fullVirtualPath);
    return File.Exists(physicalPath);
    }
}

Basically, the new Content method works as a thin wrapper around the ASP.NET MVC native Url.Content method. The new method first builds a localized name for the resource, then checks whether such a localized resource exists. If so, the method returns the localized URL; otherwise it returns the original URL. In a Razor view, you use the method as shown in the following example:

The example uses the new method to link a CSS file. Note that the type of the resource you reference (image, style sheet, script) isn't relevant; the Content method just processes URLs and changes the extension if conditions apply.

Culture and UICulture

You might have noticed in Figure 2 that I used the CurrentUICulture property on the Thread class to ascertain the current culture. If you carefully check the properties of the Thread class, you find two similar properties -- CurrentCulture and CurrentUICulture. Are they really different? And what's the difference?

Both properties refer to the current culture set on a given thread, but they address a different set of localizable properties. The CurrentCulture property refers to functions such as the date, time, number of decimals, and currency. The CurrentUICulture property, on the other hand, just filters the resources to be loaded for the thread. As it turns out, CurrentUICulture is precisely the property to use for the purpose of localizing resources.

In Figure 2, I used two letters to identify a culture from the language only. In software, a locale can be identified using language and culture information. For example, you use en-us to indicate the English language and the US culture. You can edit the code in Figure 2 to further extend Url.Content to make it support language and culture and not simply language. Note that the code in Figure 2 shouldn't be considered simplistic because it uses only language information -- whether you also need culture depends on the application.

Localizable Views

Views are another part of the application that might need to be adapted to the current locale. It isn't unusual that, in some languages, you have to tweak one of the views because, for example, a translated UI text is longer than expected. In ASP.NET MVC, you call out the view from within a controller action method. In addition, each view invoked from the controller can include partial views that might need to be localized as well. This means that we need to add localization capabilities for views at two levels: the action method and the extension methods commonly used to link partial views.

The best way to add localization logic to action methods is through an action filter. In the end, all you need to do is determine the name of the view algorithmically and then call it. An action filter will keep the code clean and move localization logic elsewhere where it can be managed separately. Let's focus on HTML extensions to invoke localized partial views.In ASP.NET MVC, you use Html.Partial when you want to receive the HTML markup and write it to the stream yourself. The process of adding localization logic here is nearly the same as shown earlier for resource files. Figure 3 shows the code to add the localization logic.

public static class PartialExtensions
{
    public static MvcHtmlString Partial(this
    HtmlHelper htmlHelper,
         String partialViewName, Object model,
         ViewDataDictionary viewData,
         Boolean localizable = false)
    {
    // Attempt to get a localized view name
    var viewName = partialViewName;
    if (localizable)
        viewName = GetLocalizedViewName(htmlHelper,
        viewName);

    // Call the system Partial method
    return System.Web.Mvc.Html.PartialExtensions
        .Partial(htmlHelper, viewName, model, viewData);
    }

public static MvcHtmlString Partial(this HtmlHelper
    htmlHelper, String partialViewName,
        Boolean localizable=false)
    {
    // Attempt to get a localized view name
    var viewName = partialViewName;
    if (localizable)
        viewName = GetLocalizedViewName
        (htmlHelper, viewName);

    // Call the system Partial method
    return htmlHelper.Partial(viewName, null,
        htmlHelper.ViewData);
    }

public static String GetLocalizedViewName(HtmlHelper
    htmlHelper, String partialViewName)
    {
    var urlHelper = new UrlHelper(htmlHelper.ViewContext
        .RequestContext);
    return UrlExtensions.GetLocalizedUrl(urlHelper,
        partialViewName);
    }
}

The structure of the code is similar to what we saw in Figure 2. The new Partial method checks whether a localized view exists according to the established convention. If so, the method proceeds to call the original Partial method with the localized name; otherwise everything proceeds as usual. The extra step can be controlled through the localizable Boolean parameter. Here's how you use the Partial method in a Razor view:

@Html.Partial("Details", localizable:true)

Changing the view based on the language is an important point in localization. In the end, localizing an application means that you should adapt the entire UI to the specific needs of the culture. Adapting the UI involves primarily text, but also images, scripts, style sheets, and views.

The Way Ahead

Considering localization from the perspective of an entire application with a long expected life span, three aspects need to be addressed: how to make resources localizable, how to add support for a new culture, and how to use (or whether to use) databases as a storage place for localized information. In this article I discussed how to deal with localizable resources. In future articles, I'll address other aspects of localization, such as switching languages on the fly.

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