Globalization And Localization With Razor Web Pages

4.34 (35 votes)

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 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 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">
        <meta charset="utf-8" />
            <form method="post">
                <select name="lang">
                    <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>
                <input type="submit" value="@TestResource.Submit" />

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";
        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);
<p><img src="images/@TestResource.FlagImage" /></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.


You might also like...

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


- Ankit

its good

- Carole

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

- Mike


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


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, 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


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

Recent Comments

ojorma 17/06/2017 09:24
In response to Razor Pages - The Elevator Pitch
Finally I can say goodbye to webforms...

Mau 06/06/2017 08:58
In response to Razor Pages - The Elevator Pitch
I am missing the razor view code. Thank you for your article. Design pattern should improve the of...

Obinna Okafor 26/05/2017 16:16
In response to Razor Pages - The Elevator Pitch
Thank you for this wonderful piece, Mike. I need more of these...Keep them coming - anything more...

Cyrus 26/05/2017 06:00
In response to Razor Pages - The Elevator Pitch
There are some concern about razor pages performance. Is it faster or slower than MVC? would you a a...

Cyrus 26/05/2017 05:44
In response to Razor Pages - Understanding Handler Methods
well done, thank you....

Satyabrata Mohapatra 23/05/2017 11:41
In response to Razor Pages - Understanding Handler Methods
Nice and easy !! Great post....

Cyrus 16/05/2017 19:55
In response to Razor Pages - Getting Started With The Preview
There is something wrong related to microsoft.dotnetcore.mvc.taghelpers! if you remove it from page...

Cyrus 16/05/2017 10:18
In response to Razor Pages - Getting Started With The Preview
well done mike, it was very useful, I really appreciate that....

Satyabrata Mohapatra 16/05/2017 07:21
In response to Razor Pages - Getting Started With The Preview
Finally!!!! web pages in core!!! Super excited !!!! Thank u sir for sharing.....Awaiting on...

Daniele 14/03/2017 10:24
In response to Working With Zip Files In ASP.NET MVC
is it possible give to the user a progress bar of the zipping process? Thanks in advance. ...