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.