Introduction
This article is the first one of two covering a basic bilingual website in ASP.NET MVC 3. 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:
- Creating a simple globalized MVC3 application
- 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 (available here) 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 MVC 3 framework.
Background
This article springs from a proof of concept website I wrote for my current employer using MVC 3. Prior to this, I had 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 what was achieved during that process. There are a few articles covering localisation in MVC 3 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 practices for localisation and globalization in MVC 3 available on the web.
The Code
In principle, the process of getting resources to work with MVC3 is fairly straightforward, but less so than for a normal ASP.NET application:
- Create the RESX file to hold the text.
- Make the RESX contents publically available.
- Repeat steps 1 and 2 for the other supported language and then for each view.
- Get the text from the RESX in the View or Controller (or View Model!).
- Set the culture from the browser default.
In this explanatory article 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 and follow these options:
Select the General tab, Resources File, and change the filename:
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:
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 “mirrored” Views and Resources folders, with one RESX file for each language per view:
Note for Razor Users: As Razor uses a 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" and "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 file name), 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;
namespace InternationalizedMvcApplication.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.InternationalizedHeading = MasterPage.Heading;
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 requires .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 English (as opposed to the default US) or Jordanian Arabic (as opposed to the default Saudi).
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)
{
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:
Now we swap the default language; to do this in IE 9:
- Select the Tools icon, then Internet Options.
- Click the Language button, under Appearance. You get this screen:
- If not already added (I have in this screen), alick "Add" and add any Arabic language variant.
- Select the version of Arabic you want to use and click "Move up" until it is at the top of the list.
- OK out of the dialog screens.
Now, the browser's default language is Arabic, a refresh on our page shows:
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:
- Create the Common.resx resource file in the /Resources folder.
- Add TextDirection as a name and set its value to ltr (not rtl; this file defines the direction for English!).
- Make the Access Modifier of the RESX file public and save.
- Make a copy of the RESX file and rename its extension .ar.resx.
- 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 %>"
Similarly 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:
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 MVC 3 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 practices:
- Create Resources for each culture.
- Make them public (note that this is the nth time I have mentioned this: I often forget!).
- Get the text from the resource in the mark-up.
- 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.
- Depending upon the 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 via its URL. For example, English will be: http://localhost/ (default) or http://localhost/en whereas Arabic will be http://localhost/ar. The URL will totally ignore 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.
For those interested: part 2 is available here.
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.
- 12th April 2011: Initial article.
- 31st April 2011: Added Razor version and details.
- 5th June 2011: Added reference to second article.
- 15thJune 2011: Added obvious links to second article.