Introduction

This article is the first in of two covering a basic bilingual website in ASP.NET MVC3. I have split the article into two parts because I feel it will become too long winded and complicated in one step. The two parts are:

  1. Creating a simple globalized MVC3 application
  2. Routes based globalization for an MVC3 application

This article is a beginner’s introduction to using internationalized resources, the code is written with the intention of being clear, rather than production quality. I will use Arabic as the second language, this is because the website I did the original work for uses Arabic, and it drives out some left-to-right and right-to-left support that would otherwise be missed. If you speak Arabic, apologies for any mistakes, I do speak a little, but have used Google Translate for most of the text!

The second article will cover route-based internationalization and will be more advanced. Both articles will assume a basic knowledge of how MVC works (especially the relevant components: View, Controller, and RouteHandler) and how .NET handles localisation and culture. The examples will focus on the default aspx view engine rather than razor, but will provide information where the Razor markup is different. The mechanism can be extended to allow multi-lingual websites with a little effort.

To run the code you must have VS2010 installed (and .net 4 :-)) as well as the MVC3 framework.

Background

This article springs from a proof of concept website I wrote for my current employer using MVC3. Prior to this had a little to no experience of ASP.NET MVC 3. My employer has a requirement for their website to be available in English and Arabic, this article documents and shares of what was achieved during that process. There are a few articles covering localisation in MVC3 on the web, but none I could find that achieved exactly what I wanted, which was a bilingual website with a routing strategy similar to that of MSDN (as explained in part 2).
That said I must acknowledge a heavy debt to these articles:

When implementing localisation in my application, I found the process to be far less polished and intuitive than plain old ASP.NET. There is no way of generating the resx files automatically, and extra steps must be taken to make the contents available to the view. I also could not find Microsoft best practises for localisation and globalization in MVC3 available on the web.

The Code

In principle, the process of getting resources to work with MVC3 is fairly straight forward, but less so than for a normal ASP.NET application:

  1. Create the resx file to hold the text.
  2. Make the resx contents publically available.
  3. Repeat Steps 1 & 2 for the other supported language and then for each view.
  4. Get the text from the resx in the view or controller (or view model!).
  5. Set the culture from the browser default.

In this explanatory aticle and code, I have used the master page (or Layout for the Razor version) as an example. The same methodology applies to the child views, I have included a simple example of this in the download projects not reflected in the article.

Creating the Resx Files

I will start by assuming the application has a master page (or a layout for Razor) , and I will describe the process of getting it ready for localisation. Normal Views work in the same manner, so everything that is applied here can be applied to a view. Let’s take the initial mark-up in the Master Page "view" to be something simple like:

<body>
    <h1>Welcome</h1>
    <div>
        <asp:ContentPlaceHolder ID="MainContent" runat="server">
        </asp:ContentPlaceHolder>
    </div>
</body>

An equivalent Razor Layout could look Like:

<body>
    <h1>Welcome</h1>
    <div>
        @RenderBody()
    </div>
</body>

We need to transfer the heading text (“Welcome”) to a resource file. Unlike ASP.NET we cannot just create the resource from the context menu. My advice is to mirror your view folder, my master page is in a folder called Views/Shared, so I will create a directory structure Resources/Shared. The MasterPage is called “MasterPage.Master” (Razor users please see the note in red at the end of this section) so right-click the resources folder the follow the options:

Adding a new Item: Right click the folder, select 'Add' then 'New Item'

Select the General tab, Resources File and change the filename:

Adding a resource file: Select the General Tab then Resource File

Once you have done this you need to move the text into the resource file, giving it a sensible name, in this case “Heading” and enter the text ("Welcome"). Now set the access modifier to public, without this step the view will be unable to access it:

The contents of the resx file: a name 'Heading' with a value 'Welcome'

Typically, you would repeat this for each section of text to be localized, but I have only entered one value to keep the example deliberately simple. The next step is to copy the resx file into the same directory as itself (remember to save beforehand!), and rename it for the culture you wish to support. In my case I am using Arabic, so “MasterPage.resx” has a companion MasterPage.ar.resx. Note that the copied file carries over the public access modifier so there is no need to re-set it. Now all we need to do is to repeat the process creating the resx files for the remaining views. Once finished we should have a “mirrored” View and Resources folders, with one resx file for each language per view:

The resource file contents showing the views and the matching pair of resource files under a mirrored file structure

Note for Razor Users: As Razor uses layout file called "_Layout.cshtml", by default, rather than MasterPage.Master, I have reflected this in the downloadable project, the resx files are called "Layout.resx" and “Layout.ar.resx”, as opposed to "MasterPage.resx" & "MasterPage.ar.resx" for the aspx engine, continuing the strategy of mirroring the "view" and its resources.  What applies for the MasterPage and its resources applies for the equivalent Razor ones too.The namespace is also different, InternationalizedMvcApplicationRazor rather than InternationalizedMvcApplication. The code snippets in the article reflect these differences, but in the interests of clarity I have not done so in the main text.

Adding the Text to the View

Getting the text from the resx file to the view through the mark-up is reasonably easy, we are now effectively getting a property on a class. In the Master Page we replace the word “Welcome” with the magic incantation:

<body>
    <h1><%= InternationalizedMvcApplication.Resources.Shared.MasterPage.Heading %></h1>
    <div>
        <asp:ContentPlaceHolder ID="MainContent" runat="server">
        </asp:ContentPlaceHolder>
    </div>
</body>

For Razor the markup is:

<body>
    <h1>@InternationalizedMvcApplicationRazor.Resources.Shared.Layout.Heading %>
</h1>
    <div>
        @RenderBody()
    </div>
</body>

Notice that the value is accessed like a property on the class (in fact the resources are compiled down to a dll): InternationalizedMvcApplication.Resources.Shared is a namespace (following the directory structure), MasterPage (or Layout for razor)is the class name (following the resx filename) and Heading is the property name (following the name key we gave it). As the resources are publically available to classes in the application, you can access them programmatically too:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using InternationalizedMvcApplication.Resources.Shared;
//using InternationalizedMvcApplicationRazor.Resources.Shared; //For Razor
 

namespace InternationalizedMvcApplication.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            //Note using InternationalizedMvcApplication.Resources.Shared; for brevity
            ViewBag.InternationalizedHeading = MasterPage.Heading; //Layout.Heading for razor
            return View();
        }

    }
}

Note that, in the examples, the View does not access the ViewBag, but it can be accessed in the normal ways if wanted.

Working Out Which Culture to Use

The final step is to define which culture to use, in this article I will use the browser’s default language for simplicity, and then extend the mechanism in the second article. One thing you should do in production code is to provide a way of overriding the browser’s default culture, it might not be the one the user wants, in an Internet Café when abroad for example.

Once the culture is defined, it works in much the same way as standard ASP: if the culture specific resx is available it uses values from that; if no resx is available it falls back to the default culture (remember that I specified .ar.resx as the file extension for Arabic , but English only required .resx as the default will be English). Just like standard ASP.NET if an individual entry is missing, it falls back to the default. You can also extend the name to provide variants for British (as opposed to the default US) English or Jordanian (as opposed to the default Saudi) Arabic.

Unlike ASP.NET we cannot override the page’s InitializeCulture method: there is no code behind, which is the preferred method for many developers. We can add this to global.asax

protected void Application_BeginRequest(object sender, EventArgs e)
{
  //Note everything hardcoded, for simplicity!
  if (Request.UserLanguages.Length == 0)
      return;
  string language = Request.UserLanguages[0];
  if (language.Substring(0, 2).ToLower() == "ar")
    Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("ar");
}

There is, however, a neater mechanism, I have left this code block commented in the sample code as I will not continue to use it. The mechanism I have adopted is to add the following into the web.config, under system.web

<globalization culture="en-GB" uiCulture="auto:en-GB" />

Now the UI Culture will be set to the default culture from the browser, falling back on British English. In both the programmatic example and the configuration based one, I have not set the main application culture, you may need to do this.

Testing and Directional Support

Now we can test what we have done: running the application yields:

The Page rendered in English

Now we swap the default language, to do this in IE9:

  1. Select the Tools icon, then Internet Options
  2. Click the Language Button, under appearance. You get this screen:
    The IE9 Language Selection Screen
  3. If not already added (I have in this screen) Click "Add" and add any Arabic language variant
  4. Select the version of Arabic you want to use and click "Move up" until it is at the top of the list.
  5. OK out of the dialog screens.

Now, the browser's default language is Arabic, a refresh on our page shows:

The Page rendered in Arabic, but the text is formatted left to right

This has been a good test! Although we have Arabic text, the page is still working left to right. This is easily fixed, the steps are similar to creating the original resx files:

  1. Create Common.resx resource file in the /Resources folder.
  2. Add TextDirection as a name and set its value to ltr (Not rtl this file defines the direction for English!)
  3. Make the Access Modifier of the resx file public and save.
  4. Make a copy of the resx file and rename its extension .ar.resx
  5. Remember to change the direction value to rtl in the Arabic resource :-)

Now we can place the following in any tag of the view HTML:

dir="<%= InternationalizedMvcApplication.Resources.Common.TextDirection %>"

Simlarly for the Razor Engine:

dir="@InternationalizedMvcApplicationRazor.Resources.Common.TextDirection"

In the sample code this has been placed into the root HTML tag, making the whole page either right to left or left to right. Sadly the intellisense does not work here, so you will have to rely upon memory. Running once more provides

The Page rendered in Arabic, but the text correctly formatted right to left

Remember to swap the language back to English, and check the text is running left to right. We now have a basic English/Arabic bilingual website.

Conclusion

We have created a simple, globalized MVC3 application, this is not production quality (e.g. we have no override mechanism) but what we have can be easily adapted for production. The principles are similar to, but less smooth than standard ASP.NET practise:

  1. Create Resources for each culture.
  2. Make them Public (note that this is the nth time I have mentioned this: I often forget!).
  3. Get the text from the resource in the mark-up.
  4. Set the UI culture (at least, you may need to set the main culture) on the server to the browser’s default coming in on the request
  5. Depending upon language, you may need to add bidirectional support.

In the next article we will keep using the browser’s default culture so the page will display initially in that language, but we will make it overridable by clicking a link. The language can then be selected by via it’s url. For example English will be: http://localhost/ (default) or http://localhost/en whereas Arabic will be http://localhost/ar. The URL will ignore totally subdivisions of the language (e.g. Saudi Arabic or Jordanian Arabic). URL based selection fits the MVC pattern better than parameterised URLs or cookies, it will also help search engines rank your site on a per-language basis.

If you have any comments or feedback, please feel free to ask. I hope that there will be suggestions for neater mechanisms, though I think the one here is pretty light-weight.

History

If you edit this article, please keep a running update of any changes or improvements you've made here.

Date Revision
12th April 2011 Initial Article
31st April 2011 Added Razor version and details
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"