In my last post, I discussed using the Yahoo! User Interface Library to start designing an ASP.NET web site. In my next post, I'll continue that discussion. But first, there's a little housekeeping chore that I want to discuss related to maximizing the amount of people that can view your web site. ASP.NET, and in general the entire .NET platform and Windows, all have rich support for globalization and localization. I like to think of globalization and localization as the creation of multicultural applications and web sites. Using globalization and localization, applications, or in this case web sites, can be used across multiple languages and cultures while respecting the specific cultural requirements, and without changing any code.
Enabling Globalization in an ASP.NET web site
Globalization is enabled in an ASP.NET web site or application through the Web.config file that contains the globalization settings for the web site. The Web.config file uses the system.web/globalization element to configure the globalization support for your application. The configuration for my web site looks like this:
As you can see in the configuration settings above, the system.web/globalization element has several attributes that control the behavior of the web site:
- culture: Specifies the default culture for processing incoming web requests. This value has to be a specific culture, in the form of -, such as en-US for U.S. English or es-MX for Spanish in Mexico. The culture attribute affects how values such as numbers, dates, and times are formatted when displayed on the web page.
- enableBestFitResponseEncoding: Indicates whether the best-fit character encoding for a response is enabled. The default value for this setting is false, so I left it that way. I'm guessing that it isn't a best practice.
- enableClientBasedCulture: If this attribute is set to true, then the Culture and UICulture settings for a page will be based on the value provided by the web browser in the AcceptLanguage header field of the web browser.
- fileEncoding: Specifies the default encoding for .aspx, .asmx, and .asax files in the web application. This value is used by the ASP.NET compiler to determine how to read web site files.
- requestEncoding: Specifies the encoding that will be used for the content that is received over HTTP.
- responseEncoding: Specifies the encoding that will be used for the content that is sent in an HTTP response.
- responseHeaderEncoding: Specifies the encoding that will be used for the HTTP headers sent back in the response.
- uiCulture: Specifies the default culture or language to use for the web pages. This value can be a generic or specific culture. For example, en would be a generic English web page, but en-US or en-GB would be specific to U.S. English or U.K. English, respectively. A specific culture may be used if there are vocabulary or other differences between how two different cultures use the same language. For example, going back to high school Spanish, I recall there being differences in how Spanish is used in Spain and how Spanish is used in Mexico.
According to the article How to: Set the Culture and UI Culture for ASP.NET Web Page Globalization on MSDN, it's not considered a best practice to set the enableClientBasedCulture attribute to true. The reasoning is that users may be in a location such as a library or Internet cafe and may be using a browser with different settings than they're used to. In this case, the article is recommending that the web site store the user's culture preferences and set them automatically when the page loads. I'll do this onto my web site's backlog and revisit it in a future post after I've stored the user's profile information. For now, however, I'm setting the culture and uiCulture attributes to auto, which is similar to setting enableClientBasedCulture, in that ASP.NET will automatically choose the first language that is specified in the current browser settings.
Testing globalization support using Internet Explorer
Before we get into actually globalizing the web site, I'm sure that there's a question out there about how we can actually test the globalization in action. After all, I'm running Windows using U.S. English. I'm not going to load a Japanese version of Windows to do my testing. Actually, Internet Explorer, and I'm sure the other web browsers do as well, make it very easy to add and change cultures for testing web sites. The language settings can be found in Internet Explorer 7 from the Tools -> Internet Options menu.
From the Internet Options dialog, choose the Languages button at the bottom of the dialog. This button has been circled in the picture above.
In the Language Preferences dialog (above) you can see all of the configured languages and cultures that your browser will send to web sites that you visit. If you click the Add button you can add addition cultures, such as es-MX for testing Spanish (Mexico). When testing how your web pages look in different cultures, you can reopen this dialog and move a different culture into the top spot. After setting a new top culture, return to your web site and you should see your web site using the culture that you specified.
Using .NET resources
In the Microsoft .NET platform, globalizing an application to provide language-specific text is done through the use of XML resources, or .resx files. During development, developers will store text, images, and other items that may be culture-specific into the .resx files and will refer to them at runtime inside of their code. The .NET platform, including ASP.NET, will locate these resources and will insert the text or other globalized object into the output of the ASP.NET page or other application UI such as a window or form.
In ASP.NET specifically, there are two kinds of resources: global and local resources. A global resource is specific and global to an entire web site or web application. For example, a company name or web site title, and possibly copyright text may be a global resource. A local resource is local to a specific page or user control in ASP.NET. For example, the title or description of a web page may be a local resource. The text of the page may be a local resource if the text is provided in different languages.
In an ASP.NET web site or application, there are specific locations for global and local resources. Global resources are stored in the ~/App_GlobalResources folder at the root of the web site or application. This is the only place where ASP.NET will look for global resources. At compile time, the ASP.NET compiler will compile all of the resource files in this location and will add the resources to the compiled ASP.NET application for global use.
Local resources are stored in the App_LocalResources folder. Unlike the global resources, there can be many App_LocalResources folders. Each sub-folder that you use in your web site may have its own App_LocalResources folder with local resources specific to that sub-folder. You may be asking why this is? Look no further than the Default.aspx page, which is used as the default document for ASP.NET applications. Let's use the following example for a sample web site:
- ~/Default.aspx
- ~/Admin/Default.aspx
- ~/News/Default.aspx
- ~/Blogs/Default.aspx
- ~/Blogs/Michael/Default.aspx
- ~/Blogs/Bob/Default.aspx
In this example web site, we have multiple sub-folders. Each folder has its own Default.aspx page. It should be expected that each page is going to have a different title. If we have only a single App_LocalResources folder, it's going to be hard to tell which Default.aspx page the local .resx file refers to. Therefore, each of these sub-folders can have their own App_LocalResources folder with their own resource. Here's how the web site will look:
- ~/Default.aspx
- ~/App_LocalResources/Default.aspx.resx
- ~/Admin/Default.aspx
- ~/Admin/App_LocalResources/Default.aspx.resx
- ~/News/Default.aspx
- ~/News/App_LocalResources/Default.aspx.resx
- ~/Blogs/Default.aspx
- ~/Blogs/App_LocalResources/Default.aspx.resx
- ~/Blogs/Michael/Default.aspx
- ~/Blogs/Michael/App_LocalResources/Default.aspx.resx
- ~/Blogs/Bob/Default.aspx
- ~/Blogs/Bob/App_LocalResources/Default.aspx.resx
Given this, the resource files in the App_LocalResources folder are local to and apply only to the files in the immediate parent folder of the App_LocalResources folder.
Preparing ASP.NET pages for globalization
ASP.NET pages have rich support for globalization. Most ASP.NET controls also have rich support for globalization that often require absolutely no code at all to implement. ASP.NET defines a special attribute that pages and most controls support named meta:resourcekey. This attribute, when applied to a page or control, specifies the prefix for resources that apply to the page or control.
Using the pages that I built in my last post, the home page could look like this:
<%@ Page Language="C#" MasterPageFile="~/TemplateMasterPage.master" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" meta:resourcekey="Page" Title="ImaginaryRealities Software Company" %>
What's different in this version of the page versus the previous version is that in the <%@Page%> element at the top of my page, I've added the meta:resourcekey attribute with a value of Page and the Title attribute containing the title of my home page. I also replaced the text Main body content and Secondary body content with controls that now contain that same text. You'll notice that I have also defined meta:resourcekey attributes for each of these controls.
What I have now is a web page that is ready to be globalized. The next step is to prepare this page to be translated. Fortunately, Visual Studio provides a menu option to help you by taking an ASP.NET page that has been set up like mine has and automatically generating the neutral language resource for you. It can be found by switching your ASP.NET page from Source view to Design view, then choosing the Tools -> Generate Local Resource menu option from the menu bar at the top if the Visual Studio window. After choosing this command, I automatically get a file named ~/App_LocalResources/Default.aspx.resx. This is the neutral language resource for my application.
What is a neutral resource? I haven't discussed that yet. Earlier, I briefly introduced you to a generic culture, such as en for English. I've also mentioned a couple of times specific cultures, such as en-US for U.S. English, en-GB for U.K. English, or es-MX for Spanish in Mexico. So let's say that I globalize my site for these languages and cultures (which we'll do in a moment), but then someone from Beijing, China, visits my web site. I don't have a Chinese translation for my web site. What happens then is that ASP.NET will default to what's known as the neutral culture, or whatever I determine my neutral culture should be. In my case, since I am a U.S. citizen and live in the United States, my neutral culture of choice is still going to be U.S. English. So Chinese visitors will see U.S. English, but visitors from Mexico will see Spanish, since that's a culture that I support.
Back to globalizing my web page, we last generated the neutral language resource file for the page. The resource file for the above ASP.NET/HTML fragment will look like the following:
As you can see in the screen shot, my new resource file has localizable properties for the page and the controls that I defined that match the values that I provided in the meta:resourcekey attributes that I attached to my controls. The new resource file has three string resources. Page.Title contains the text for the tag on the web page. MainBodyContent.Text contains the text that displays in the main body content area for the page, and SecondaryBodyContext.Text contains the text that displays in the secondary content area.
For instructional purposes, I'm going to create two additional resource files: Default.aspx.en.resx for English language users, and Default.aspx.es.resx for Spanish users. I'm also going to cheat and use the Google Translation service to do the translating for me. My resources should look like the following:
| Property/Language |
English |
Spanish |
| MainBodyContent.Text |
Main body content (en) |
Órgano principal del contenido
|
| SecondaryBodyContent.Text |
Secondary body content (en) |
Secundaria órgano contenido |
I'm also going to modify the English resources to have a suffix of (en) so that you know that the resources are being served from the English language resource and not from the neutral language resource.
Testing globalization
Earlier in this post, I described to you how you can configure the languages and cultures in Internet Explorer to test different cultures and languages. However, to simplify testing and demonstration of the globalization capabilities of ASP.NET, I modified by base WebsitePage class and implemented the Page.InitializeCulture method. The InitializeCulture method is called very early in the page lifecycle before the page itself has been initialized. I'm going to use this method to look at the query string for the web page and look for attributes named culture or uiculture. If one or both of these attributes are specified in the query string, then I'll use the culture that is provided to set the Page.Culture, Page.UICulture, Thread.CurrentCulture, and Thread.CurrentUICulture properties with the provided culture information, which will then cause my web page to be translated. Here's the updated code for the WebsitePage class:
//------------------------------------------------------------------------
//
// Copyright (c) 2008 ImaginaryRealities Software Company
//
//
// Implements the WebsitePage abstract base class that is used as the base
// class for all web pages on the web site.
//
//------------------------------------------------------------------------
namespace ImaginaryRealities.Website.UI.Framework
{
using System.Globalization;
using System.Threading;
using System.Web.Configuration;
using System.Web.UI;
using Wintellect.Diagnostics;
///
/// Abstract base class for web pages in the website.
///
[CodeOwner("Michael F. Collins, III", "michael@imaginaryrealities.com")]
public abstract class WebsitePage : Page
{
///
/// Initializes a new instance of the WebsitePage class.
///
protected WebsitePage()
{
this.InitializePageWidth();
}
///
/// Gets the CSS class name for the web page.
///
///
/// The value of this property is the name of the CSS class that
/// represents the specific web page.
///
public abstract string PageCssClassName
{
get;
}
///
/// Gets the identifier that is used to specify the width of the
/// web page.
///
///
/// The value of this property is the identifier that is used to
/// specify the width of the content area of the web page.
///
public string PageWidth
{
get;
protected set;
}
///
/// Sets the page and thread culture and UI culture values based on
/// values provided in the request's query string.
///
protected override void InitializeCulture()
{
var culture = this.Request.QueryString["culture"];
if (!string.IsNullOrEmpty(culture))
{
this.Culture = culture;
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(culture);
}
var uiCulture = this.Request.QueryString["uiculture"] ?? culture;
if (string.IsNullOrEmpty(uiCulture))
{
return;
}
this.UICulture = uiCulture;
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(uiCulture);
}
///
/// Initializes the property by loading the
/// default page width from the website configuration.
///
private void InitializePageWidth()
{
var configurationSection = (WebsiteConfigurationSection)
WebConfigurationManager.GetSection("website");
this.PageWidth =
configurationSection != null
? configurationSection.PageWidth
: "doc";
}
}
}
Now, if I were to type in the following URL, I should see the the screen shown below: http://www.imaginaryrealities.com/beta/Default.aspx?culture=en-US:
If I used this next URL, I should see the Spanish translation for the web site: http://www.imaginaryrealities.com/beta/Default.aspx?culture=es-MX:
The following URL would give me the Italian translation for the web site: http://www.imaginaryrealities.com/beta/Default.aspx?culture=it-IT:
Wait? That doesn't look like Italian? It's not, actually. Remember, I only defined an English and Spanish translation for the web site. Since Italian is not a supported language, ASP.NET reverted back to the neutral language translation for the web site, which is going to be U.S. English. You can tell that the neutral language resource is being used from the title text in the Window's title. I added (en) and (sp) to the translated titles to show you that the page title is coming from a resource translation. The title without the suffix is being served from the neutral language resource.
If you'll also remember from the last post, I am adding CSS classes to the main BODY tag of the HTML which refer to the language and culture of the user using the web site. If you open the home page in a web browser and specify the culture in the query string, then choose View Source from your browser's menu, you'll be able to see in the BODY tag that the culture that you specify is also specified as CSS classes. For example, viewing the web site with the culture=en-US query string attribute set will specify la-en and lo-US in the BODY's CSS classes. Specifying the query string culture=es-MX will add la-es and lo-MX to the BODY tag's CSS classes. You'll also notice that if you specify a culture, such as Italian (culture=it-IT) to the query string, you'll get the neutral language translation of the web site, but the BODY tag's CSS classes will have la-it and lo-IT because that is the culture that was specified and that the page is running in, even if the page is serving the neutral language translations.
Conclusion
In this post, I introduced you to globalizing ASP.NET applications. I explained how to set up your pages and controls for globalization using the meta:resourcekey attribute. I also explained the difference between the App_GlobalResources and App_LocalResources directories. I then demonstrated a web site with English and Spanish translations. I also modified my base WebsitePage class to allow the page's culture to be set using the request query string. There are additional aspects to globalization such as ASP.NET markup syntax that I didn't discuss in this post, but we'll visit in a future post.