Globalization And Localization With Razor Web Pages

Globalization is the process of preparing your site so that it is accessible to as wide an audience as possible. This is largely achieved by presenting content in the native language of the visitor. This article explores how you can approach this task within the ASP.NET Web Pages framework.

ASP.NET already provides a localization framework, which is based on Cultures and Resources. Web Pages have a Culture property and a UICulture property. The first of these controls how culture-sensitive formatting is managed for dates, currencies, numbers and so on. The second property works with .NET Resource files. More of these in a second. Culture and UICulture values are defined using a standard set of values described on MSDN. These values are typically composed of two lower-case letters defining the language, and two upper case letters defining the locale. For example, English language is defined as "en". Britain is defined as "GB". British English is therefore defined as "en-GB", while American English is defined as "en-US". These culture codes are important, as they are used by the ASP.NET Globalization framework to locate the correct resource file.

If you want to present different version of the same content to users based on their language, you need a way of organising multiple versions of the same content. One way in which you can do this in the .NET world is through Resource files, which are XML files ending in .resx. Resource files contain name/values, which represent strings to be used in web pages. WebMatrix doesn't (currently) offer a way to generate resource files, which means you have two options: download and use the free version of Visual Web Developer (or use Visual Studio if you have it), or create text files and use the resgen.exe tool that comes with the Windows SDK to convert them to valid .resx files. In the following example, I used Visual Studio. You can also use a database as a resource store rather than resource files. You will see both approaches used in this example.

The sample site used to illustrate the concepts discussed here is a simple one:

It displays some content which is determined by the language chosen by the user from the dropdown list at the top. The English version is shown above, and this is the French version:

Once a new empty site has been created, it needs to be opened in Visual Web Developer or Visual Studio, which is achieved by clicking the Launch button in the ribbon bar. Right click on the project name in solution Explorer and choose Add New Item, and then from the dialog that results, choose Resource file.

I named mine TestResource.resx, but you can call it anything you like. When you click the Add button, you will be prompted to save the file to a folder called App_GlobalResources. Agree to this option, as App_GlobalResources is a special ASP.NET folder which is expected to house resource files. You'll see how this convention works shortly. Resource files themselves are little more than convoluted name/value pairs. The Resource File Editor within VS/VWD offers an easy way for you to input your name/value pairings. Here's a screenshot showing the sample site resource file:

In the Name column, you see what are effectively labels. These will actually become methods that return strings when the resource file is compiled on first run. The strings that these methods return are the corresponding Value entries. When the resource file is compiled, it becomes a class named after the file name - in this case TestResource.

At the moment, you have one resource file which represents the default culture for the site. You need additional resource files for other languages. These need to be named carefully, as the framework expects to find valid culture codes as part of the file name. A French resource file can be created by copying the English one, and renaming the result TestResource.fr-FR.resx. Notice the culture code fr-FR appearing between the file name and its extension. Any errors in the culture code will lead to compiler objections to the effect that "The namespace 'Resources' already contains a definition for 'TestResource'", where TestResource is the name of the resource file. A valid German version of the resource file would be named TestResource.de-DE.resx. Leave the Name column entries as they are and replace the Value entries with French and German versions respectively. So now you should have three different resource files:

There are other files in the image, but they can wait for a bit. Next, you need a database. It's a simple one-table database. The table has four columns - and Id column (int IDENTITY), a column called section, which defines the location of the content, the content itself - stored in an ntext field, and a column called Culture. This column will hold the culture code for each row of data. The content itself is entered three times for the Default page - once for each of the languages represented by the Resource files.

Back to the other files in the earlier image... Here's the _Layout.cshtml page:

@using Resources;
<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <div>
            <form method="post">
                @TestResource.SelectLanguage
                <select name="lang">
                    <option></option>
                    <option value="en-GB" @(Culture == "en-GB" ? "selected=\"selected\"" : "")>English</option>
                    <option value="fr-FR" @(Culture == "fr-FR" ? "selected=\"selected\"" : "")>French</option>
                    <option value="de-DE" @(Culture == "de-DE" ? "selected=\"selected\"" : "")>German</option>
                </select>
                <input type="submit" value="@TestResource.Submit" />
            </form>
        </div>
        @RenderBody()
    </body>
</html>

There is a using statement at the top of the file that makes the resources class available via its namespace (which is automatically generated on compilation). The generated namespace is always Resources. The layout page contains a drop down which acts as a language selector. This will be available on any other page that makes use of the layout file. You can see the first reference to one of the TestResource class' methods - SelectLanguage. This returns the appropriate string based on the culture of the page. And that is driven by the choice made using the select list. Notice also that the Page's Culture property is referenced simply by the property name.

The culture is set within the _PageStart.cshtml file:

@{
    Layout = "~/_Layout.cshtml";
    if(!Request["lang"].IsEmpty()){
        Culture = UICulture = Request["lang"];
    }
}

In fact, both the Culture property and the UICulture property are set here.

The final page is the Default page itself:

@using Resources;
@{
    var db = Database.Open("Localization");
    var sql = "SELECT Content FROM Content WHERE Section = 'Default' AND Culture = @0";
    var content = db.QueryValue(sql, Culture);
}
<h1>@TestResource.Welcome</h1>
<p><img src="images/@TestResource.FlagImage" /></p>
<p>@TestResource.Intro</p>
<p>@content</p>
<p>Currency: @(10000.ToString("c"))</p>
<p>Date: @DateTime.Now</p>

This page also makes the Resources namespace available via a using statement. You can see a number of examples of the TestResource class methods being referenced. One of those examples returns a file name, which is used as part of the src attribute to an image. The others return strings to be rendered verbatim. The database query uses the value of the page's Culture property to obtain the correct version of text for the current culture. Finally, two examples showing the Culture property at work - dictating the way that currency and dates are formatted for display.

This article has explored the basics behind localizing a Razor Web Pages site. You have seen two approaches - Resource files and database. Resource files compile to dlls when the application first runs. For this reason, they are best used for content that changes infrequently. Since they are easiest to work with when you have specialist tools, they are not suitable for storing user-provided values. Databases are better for user-supplied content and that which may change on a regular basis.

A sample site containing all the code is available as a GitHub repo.

 

Date Posted:
Last Updated:
Posted by:
Total Views to date: 81816

12 Comments

- Ankit

its good

- Carole

Looks like what I need, but including the project file would've been nice.

- Mike

@Carole,

The sample containing the code for the article is provided in a link which can be found at the end of the article - just above the Twitter and ratings features. It's been there all the time...

- Kalle

We are setting up a multilingual website for our newly created Malta Centre and your tutorial has been very helpful. We encountered two problemes, however: 1. Setting culture in the way you suggest works for the current page and its componenents (e.g. Layout), however it doesn't persist across pages. I worked around this using a session variable, which works but is not perhaps the most elegant method; 2. - and more serious - since the culture is set by posting back to the same page, it interferes with additional forms on the same page, which is very annoying. Have you any suggestions?
Thanks in advance, Kalle

- Mike

@Kalle,

I think I would use a cookie to store the language preference. That can be retained across visits so you don't have to ask the visitor again in the future. The language selector should not interfere with other forms being posted. I would imagine you have a design flaw somewhere. If you post details of your actual issue to the forums at www.asp.net, someone should be able to help you resolve the issue.

- oleg_harp

Mike, thanks for your work!
What's about SEO? Your pages have the same name but different content. Would it be better to give them different names, for example fr/Default, de/Default and so on?

- waqar

excellent article

- Amit Rohilla

It is okay, it is good for static data, but i want to know how to translate dynamic data unto any language using culture??

- al-ma

you can use the following method with cookies to make the language selection persistent.

@{

Layout = "~/_SiteLayout.cshtml";
if (!Request["lang"].IsEmpty())
{
//this runs when you post the language change so I save the language to a cookie named lang
Response.Cookies["lang"].Value = Request["lang"];
Culture = UICulture = Request["lang"];
}
else if (Response.Cookies.Get("lang").Value != null)
{
//this will only run if there was no post request with lang and if there is a cookie lang set
Culture = UICulture = Response.Cookies.Get("lang").Value;
}

}

Btw Mike cheers for the great effort with these turorials! I got your book as well and pretty much taught myself the basics in a weeks time with very little previous experience

- Nandhagopal M

Nice articles

- Epameinondas

Useful Information, but you don't mention how to use local resources and how to access entries in them using Razor syntax. I can't find this kind of Information anywehere on the Web: all I find is outdated Information about how to do it in Web Forms.

- Mike

@Epameinondas

Local Resources only work with Web Forms - files that end in .aspx, .ascx or .master.
Share
Additional Info

Recent Comments

Manie Verster 5/28/2015 9:39 AM
In response to MVC 5 with EF 6 in Visual Basic - Implementing Inheritance
Sorry Mike but on my project when run it does not show the hire date and/or enrollment date. I the I...

Joseph Casey 5/27/2015 3:17 PM
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Probably should have read the title. Couldn't use it with Visual Studios 2013. =[...

jean 5/27/2015 8:41 AM
In response to iTextSharp - Drawing shapes and Graphics
hey Mike, I read your arcticle about drawing on PDF with ITextSharp, it was really interesting !...

Alexandros L 5/26/2015 10:50 PM
In response to Entity Framework 6 Recipe - Hierarchical Data Management
You are a live saver.. thank you so much......

saurabh rao 5/26/2015 1:03 PM
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Hey Mike ! Great article ...the code worked flawlessly for me. Didn't have any issues executing it ....

Vijay 5/25/2015 2:12 PM
In response to How To Send Email In ASP.NET MVC
Thanks...

RR 5/25/2015 8:58 AM
In response to Web Pages - Efficient Paging Without The WebGrid
@MIke.. is it possible to combine pagination and filter (change category ) in the script? Thanks...

Irfan Khan 5/25/2015 6:19 AM
In response to Highlighting Keywords Found In Search Results
This is truely helpful. Thank you for the brilliant tip....

Manoj Kulkarni 5/25/2015 4:46 AM
In response to Custom TagHelpers in ASP.NET MVC 6
Thank you for nice article. Really helpful....

Sean 5/21/2015 4:20 AM
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Thank you for the tutorial. Saving to the database works, but the save FilePath is not working. It...