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: 79312

10 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
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and I end up deleting quite a lot. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Requests to fix your code (post a question to forums.asp.net instead, please)
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

Wayne Hudson 4/20/2015 9:14 PM
In response to Inline Editing With The WebGrid
Thank you very much for your articles. They've helped me a lot. How would you handle inline such...

Dan 4/20/2015 5:01 PM
In response to Sessions in ASP.NET 5
Can I ask how these sort of options are affected by the IIS configuration options when hosted in be...

Shaheen 4/20/2015 3:36 PM
In response to Conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value
Perfect! Saved me from wasting hours....

Hisham Abdullah Bin Ateya 4/19/2015 7:21 AM
In response to Sessions in ASP.NET 5
FYI the Session.GetInt & Context.Session.SetInt have been renamed to Context.Session.GetInt32 &...

Philipp 4/18/2015 12:15 PM
In response to Creating Reusable Components For ASP.NET Razor Web Pages
Thank you very much for your articles. They are always good and interesting to read. In your last a...

Magnus 4/17/2015 10:13 AM
In response to Sessions in ASP.NET 5
One thing that took me a while to figure out, is that the UseSession() must come before UseMVC() in...

kaleem 4/16/2015 9:14 AM
In response to ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS
hi this is very helpful ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS if to...

cranston mason 4/16/2015 8:49 AM
In response to Managing Checkboxes And Radios In ASP.NET Razor Web Pages
How do you include a checkbox at the View module and send its values to controller to be processed?...

vamsi 4/15/2015 5:40 AM
In response to Sessions in ASP.NET 5
good article...

James Chaney 4/14/2015 8:44 PM
In response to 7 C# 6.0 Features That Every ASP.NET Developer Should Know About
Question on #5 - I don't see where this gets you out of a NullReferenceException if returns null is...