Internationalize Your ASP.NET Applications (Part 1 of 3)

The Internet was originally used primarily by English-speakers, but the percentage of non-English speakers is increasing as Internet penetration increases. With huge populations in Asian countries coming online and European countries needing to work more closely together, there’s a growing need for Web sites that cater to visitors from different cultures. This is clearly a good thing, but it creates new challenges for Web site developers.

The .NET Framework includes a variety of features to help you create truly international Web sites, many of which apply to all types of application whether they’re Windows Forms, console or ASP.NET applications. Although ASP.NET cannot help you translate content, it can help you manage content once it has been translated. In this article you’ll see the features that are especially useful to ASP.NET developers in managing this.

This article series uses a sample Web site that provides access to a series of news articles. Unlike most Web sites however, a site visitor can select a language from a drop-down list to see the article in the selected language. The Web site uses the localization features of .NET to locate pre-translated content for the page in the correct language and then display it to the site visitor. .NET provides a variety of extremely useful features to help you, which save a significant amount of time, because there are so many aspects of localization that you need to examine before implementing a successful project.

Language and Culture Considerations
In looking at what needs to look and function differently for each of the cultures and languages in a localized Web site, a few core areas are paramount:

  • Database Content. Larger pieces of information like news stories, articles, product descriptions, and so on. This type of data is most often located in a database, although many Web sites will cache it within HTML files.
  • Graphics. Virtually every site has graphics and although many won’t be affected by changes in language, some will. Certainly every image that contains text will need internationalizing, and also images containing symbols with differing meanings across cultures.
  • Text resources. This refers to those little bits of text that appear over a site that include things like the corporate switchboard number, fax number, single-line copyright statement and the front page welcome message. The majority of Web sites will keep this type of information in the page files itself, whether it’s an aspx file or a flat HTML file.
  • Dates. Different cultures display their dates differently, and it isn’t just the words in long date strings that differ between locales. For example the date “1st Dec 2001” is displayed in England as “1/12/01”, but in the US, it is “12/1/01”?displaying information in a format that is optimized for the viewer is crucial if you want the reader to be comfortable with the site and have access to accurate information.

In addition to these four areas, there are several more that you should consider, but will not be discussed in this article. These include currencies, number format localization (e.g. commas or periods), string sorting and casing, and imperial (pounds and ounces) vs. metric (kilograms and grams) measurement systems.

The translation process is worth a mention briefly as it is often more significant in terms of operational planning than the technology itself. Here are some questions you should answer as you plan your internationalized application:

  • Who will translate the content?
  • What languages will the site support?
  • When content is available only in a limited number of languages, does the site display the content in the default (wrong) language, or hide it from the user?
  • What process or processes will support the translation?
  • Will there be parallel releases of content in different languages, or will each be released as and when it is complete?
  • Which language will be primary a default language used when errors occur or resources cannot be found?

A short warning: After you build the site, the process will become the key focus. Consider it early to avoid problems after you release it. I’ve provided the warning because the translation process used in this article is relatively trivial. I’ve used Altavista’s Babelfish to perform the translations in order to provide some sample content. The site will display the most appropriate content to our audience, but there are some restrictions in the sample application.

Author Note: Throughout this article you will see examples of text in English, Chinese, French and Japanese. I’d like to apologize in advance to the non-English speakers for the quality of the translations. I hope you can forgive me and see the text as secondary to the technology that they are attempting to demonstrate. Babelfish, while convenient, creates some slightly dubious translations at times. I would also like to give a mention to BBC News Online, snippets of whose news items I have used for the English text of some of the test content.

Map Content to Classes
The sample site for this article series is a Web-based content viewer built using ASP.NET that provides news articles in several languages in parallel. The content is stored in several different languages and is displayed to the Web site visitor in the correct language depending on their preference. The site ensures that all article text, page text, dates and images are displayed for the specified culture.

The news site stores its articles in a database, which is common in sites of this type, because it simplifies building management interfaces for editors and journalists. The first step in putting the application together is to build a user interface to display the news stories. The problem lies in controlling which language version is displayed. Here are the simple requirements for the site:

  • The site stores each article in four different languages
  • Users select the language from: English – UK, French – France, Chinese – China and Japanese – Japan.
  • The default language is UK English.
  • The site displays a series of news articles in the selected language (if available).
  • Each article consists of a unique identifier, title, body, and publication date
  • As this is an example, there will be no interface to submit, edit, delete or otherwise manipulate articles. The Test data were inserted directly into the database (SQL Server).

A class called Article represents an instance of an article in a specific language. At the start of the Article.cs class file are some directives for the namespaces you’ll need:

   using System.Data;   using System.Globalization; 

The System.Data namespace provides the DataSet class, and the System.Globalization namespace provides classes you use to manage the localization of information. The most important of these is the CultureInfo class, which represents a culture and which you use to specify the culture to be used to display a specific page.

The Article class has private members used to store the identifier, title, body, publication date and culture (each Article instance represents a piece of content in a specific culture).

   private int articleID;   private string articleTitle = "";   private string articleBody = "";   private DateTime articlePublicationDate;   private CultureInfo articleCulture; 

Next are some properties for the private fields that will be displayed on the page for each article:

   public string Title   {      get   { return articleTitle; }   }      public string Body   {      get   { return articleBody; }   }      public DateTime PublicationDate   {      get   { return articlePublicationDate; }   }      public CultureInfo Culture   {      get    { return articleCulture; }   }      public int ArticleID   {      get   { return articleID; }   } 

Nothing to get excited too about there. The SQL Server 2000 database that holds the articles includes a view called ArticleDetail, which returns a dataset that looks like Figure 1:


Figure 1: The SQL Server ArticleDetail view contains data like this.

Each row in the dataset represents a version of an article in a specific language, and each version includes the article identifier, date, title, body and a culture identification string.

Author Note: Although both Access and SQL Server support Unicode, you may need to configure your development machine to display characters in foreign languages by installing additional language support. To do this in Windows look in Control Panel at the Regional settings and you will find an area marked “Language settings for the system”. You will need to select each language that you want to use. Clearly, which items you need will vary depending on the current configuration of your machine and the languages with which you intend to work.

The Article class has an overloaded constructor that accepts an integer identifying a specific article and a System.Globalization.CultureInfo object that represents the culture, and returns an Article object.

   public Article(int articleID, CultureInfo culture)    {   } 

A SQL query in the constructor uses the two parameters to retrieve the relevant translation of the article.

   string Sql = "SELECT * FROM ArticleDetail WHERE       ArticleID=" + articleID + " AND LanguageID='" +       culture.Name + "'"; 

The constructor passes the SQL query to a private helper method called GetDataSet() that returns the relevant rows. Here’s the Article class code.

   using System;   using System.Data;   using System.Data.SqlClient;   using System.Web;      namespace Multilingual   {      public class Database      {         public static DataSet GetDataSet(string Sql)         {            DataSet DS = new DataSet();            string myPhysicalPath =              HttpContext.Current.Request.              PhysicalApplicationPath;            string connectionString =               "Server=optimism;              Database=sqlservername;              UID=sqlserveraccountname;              PWD=mypassword;";            SqlConnection myConnection = new               SqlConnection(connectionString);            SqlDataAdapter myDataAdapter =               new SqlDataAdapter(Sql, myConnection);            DataSet myDataSet = new DataSet();            myDataAdapter.Fill(myDataSet);            return myDataSet;         }      }   } 

So, the Article class retrieves an article by formulating the SQL statement and passing that to the Database object, which calls the GetDataSet() method:

      DataSet dataSet = Database.GetDataSet(Sql); 

If the query returns no rows then there is no matching article in the specified culture. In this case, this next block of code runs another SQL query to search for an English version (the default language in the application).

      if (dataSet.Tables[0].Rows.Count == 0)      {         Sql = "SELECT * FROM ArticleDetail WHERE            ArticleID=" + articleID + " AND            LanguageID='" + "en-gb" + "'";         dataSet = Database.GetDataSet(Sql);      } 

If, having run the second SQL statement, there are still no rows in the result set then the code throws an exception.

      if (dataSet.Tables[0].Rows.Count == 0)      {         Exception exception = new Exception("Article not available");         throw exception;         } 

If either query returns a row, the constructor sets the values of the private members for the title, body, publication date and culture.

      DataTable dt = dataSet.Tables[0];      articleTitle = dt.Rows[0]["Title"].ToString();      articleBody = dt.Rows[0]["Body"].ToString();      articlePublicationDate = DateTime.Parse         (dt.Rows[0]["PublicationDate"].ToString());      articleCulture = CultureInfo.         CreateSpecificCulture          (dt.Rows[0]["LanguageID"].ToString());   }  

The last line of the constructor uses the CreateSpecificCulture() method to create a new CultureInfo object that represents the culture of the article The CreateSpecificCulture method takes a culture string such as ‘en-US” and returns a CultureInfo object representing the most appropriate culture.

The application displays articles on the page using a server control. The server control has a single property that represents the article number. The control automatically selects the correct culture when it displays the article (more on this later). The server control class starts with the definition of a private member variable to hold the article identifier and a public property to manipulate that value.

   using System.Globalization;   using System.Threading;      public class ArticleControl : System.Web.UI.Control   {      private int articleID;      public int ArticleID      {         get { return articleID; }         set { articleID = value; }      } 

The server control’s CreateChildControls() method creates the HTML tags by adding controls to the page. First it extracts the culture in which the page is executing as a CurrentUICulture object using the Thread object in the System.Threading namespace. You will see how to manipulate this setting later in the article.

   protected override void CreateChildControls()   {      CultureInfo culture =       Thread.CurrentThread.CurrentUICulture; 

The CreateChildControls() method calls the Article class constructor and passes the culture value and the article identifier (from the ArticleID property) to create the Article object that will be displayed on the page.

         Article myArticle =         new Article(articleID,culture); 

The Article object (myArticle) renders the page by creating a series of LiteralControl objects that squirt HTML out to the browser through the HTTPResponse object.

      LiteralControl literal;      literal = new LiteralControl("

" + myArticle.Title + "

"); Controls.Add(literal); literal = new LiteralControl("" + myArticle.PublicationDate.ToLongDateString() + "

"); Controls.Add(literal); literal = new LiteralControl(myArticle.Body + "
"); Controls.Add(literal); }

Using the server control, it’s very easy to insert an article into a page in a Web application. For example:

   <%@ Page language="c#" %>   <%@ Register TagPrefix="NewsSite"       Namespace="Multilingual.Controls"       Assembly="Multilingual" %>                                  

The three-tier design of this site keeps the program logic in the business objects and reserves the .aspx files for the interface and layout. The @Register directive in the Web Form specifies where .NET should look for controls. In this case the @Register directive means “When you come across any server controls, look in the Multilingual.Controls namespace to see if there is a matching class”.

You insert the tag into the page with its ArticleID property set to an ID that matches one of the articles. If you set the ArticleID property to a value that does not match an article, the Article class throws an exception. Figure 2 shows the result of the preceding tag example.


Figure 2: A typical article displayed by adding a tag to a Web Form.

To view different articles you can change the article identifier on the query string. The culture is taken from the thread in which the page code is running. You can change the culture setting using the Culture property in the @Page directive. For example, you can change the first line of the page to include the Culture and UICulture attributes as follows.

   <%@ Page language="c#" Culture="fr-FR" UICulture="fr-FR" %> 

After making the change, the page displays the same article (see Figure 3), but in French (‘fr-FR’ is actually French as spoken in France). Notice that not only has the title and body text switched languages, but the page also displays the date formatted appropriately for French readers. Later you will see how we can dynamically change the culture setting.


Figure 3: The same article displayed in French.

Changing the Culture setting in the @Page directive affects the way the date is displayed, but changing the UICulture setting alters the language of the article itself. The former sets the culture for the display of information, and the latter specifies how the user interface should be shown. Notice also that if the culture is invalid the page throws an exception and that if you enter a culture code for which there is no matching article the page displays the English version.

Many Web applications using .NET hook the front-end directly to the database (rather than using business objects) because of the ease and speed with which it’s possible to create interfaces using server controls like the DataGrid and DataList. This approach works well for most Web sites, however you must be aware that for larger Web sites this architecture is inappropriate and it is necessary to move the data access code into business objects and out of the code behind.

This method demonstrates how to use the @Page directive to specify that a page in the site should always be displayed in a certain language, whatever the culture settings. That’s not always useful. You want to be able to change the language and culture dynamically. The next section discusses a means of providing site visitors with an interface to choose their own culture.

As you’ve seen, using the @Page directive changes the culture for a page for all users. But the application needs to store the culture setting for each user. You could let the user select the language each time they connect to your site and store the result on the server in that user’s Session, but more realistically, you need to store the culture setting between visits. Therefore, the example site has a drop-down list that lets users select their preferred language and then stores the user’s language choice in a permanent cookie that contains a culture string?thus letting you use the selected settings for all future page requests from that user on that machine.

The Application_BeginRequest() method in the global.asax.cs file uses the cookie value. The ASP.NET framework calls this method for each page request. The method reads the cookie value and sets the culture of the thread appropriately. The page code picks up the setting to display the article in the appropriate culture.

First, grab the cookie value to see if it contains a culture string.

   protected void Application_BeginRequest(Object sender, EventArgs e)   {      string culturePref;      if (Request.Cookies["CulturePref"] != null)      {         culturePref = Request.Cookies["CulturePref"].Value;      } 

Next a try-catch block attempts to set the thread culture using the cookie value. The error handling protects against invalid cookie values.

      try      {       Thread.CurrentThread.CurrentCulture =            CultureInfo.CreateSpecificCulture           (culturePref);      }      catch      {         Thread.CurrentThread.CurrentCulture =            new CultureInfo("en-GB");      }      Thread.CurrentThread.CurrentUICulture =         Thread.CurrentThread.CurrentCulture;   } 

The final line of the method sets the CurrentUICulture to the same culture value as the CurrentCulture, because in this application there’s no need for them to be different.

Another server control creates an interface that lets visitors select their language (and therefore set the culture cookie). The control displays a list of four cultures in a drop-down list using a form. This form is not a server-side form (it does not have a runat=”server” attribute) because only one server side form can be used on each page. All the pages in the Web site include the language chooser so it’s important to use a standard HTML form to leave the option of using the server-side form on the site. On submission, the form sends the data to a page called culturechooser.aspx that contains the following code in its Page_Load() method.

   CultureInfo culture =       CultureInfo.CreateSpecificCulture     (Request.QueryString["CultureChoice"].ToString());   HttpCookie cookie = new HttpCookie      ("CulturePref", culture.Name);   cookie.Expires = DateTime.Now.AddYears(100);   Response.Cookies.Add(cookie);   string referrer = Request.UrlReferrer.ToString();   Response.Redirect(referrer); 

The main purpose of this code is to save the culture name as a cookie, but it also redirects back to the page from which the user came. To the user, it looks like the page simply refreshes with the selected language.

You can add the culture chooser server control to the article viewer page using a standard server control statement:

       

After adding the control and refreshing the page, you can see the new drop-down list (see Figure 4).

If you select French, the code sets the cookie redirects your browser back to the article page, which now displays the content in French.

In addition to handling the text in articles stored in the database, the example site also localizes smaller pieces of text on the page?such as the Copyright message?by using resource (resx) files. You can create resource files in Visual Studio .NET, or create resources in text files and convert them to resx files. You can also create resource files programmatically using the System.Resources.ResourceWriter class.

In the example the English-language resource file Resources.resx. ResX files are XML resource files and are very easy to hand edit. Because they’re humanly readable you can translate them directly. They can contain serialized objects as well as textual information. The sample site contains resource strings for a welcome message, a copyright message and for the text above the culture drop-down list. Figure 5 shows how Resources.resx looks in the Visual Studio .NET XML editor:


Figure 5: The Resources.resx file in the Visual Studio .NET XML editor.

Note that there are fields containing the string value, a brief comment describing its purpose, and a name for the string resource. The code uses the name field value to retrieve individual strings from the resource files.

VS.NET stores default-language resources within the main assembly. To create a French equivalent you create another resource file called Resources.fr.resx. The naming is important as Visual Studio .NET uses the filename to maintain the logical relationship between these files and pass that information to the compiler. In this case it uses the “fr” in the filename Resources.fr.resx to determine that this is the French version of Resources.resx. Figure 6 shows the French version.

The French version contains only two strings so I can show you how the site handles a missing resource string.

You can repeat the resource file process for each language. If you now rebuild the project and look in the bin folder of the Web site, you’ll see something very interesting. Although the primary English resource file (Resources.resx) is embedded in the main assembly, Visual Studio.NET creates the other three (French, Chinese and Japanese) as their own assemblies and places them in separate folders named using the culture identifier (see Figure 7).


Figure 7: VS.NET stores primary-language resource files in the main assembly, but creates subfolders to hold resource files for each additional language.

Author Note: The reason I set the Chinese-China culture as zh-CN rather than zh is that for political reasons there is no high-level culture zh.

If you are not using Visual Studio .NET, you can embed the main resource file in the main assembly by using the /res compiler flag. You can create satellite assemblies using the al.exe utility.

After compiling the resources into a useful form, you can access them from code and ensure that you select a string for the most suitable culture. To do that I created another server control called SiteTextControl that you can add to the page wherever you want to display localized text. The server control has a public property to set the name of the string value that you want to access, such as“Copyright”

   public string Name   {      get   { return textName; }      set   { textName = value; }   } 

The CreateChildControls() method renders the control. In this case it just writes the relevant resource text without any HTML tags. To retrieve the text, create a System.Resources.ResourceManager instance that provides access to the resource files. To instantiate the ResourceManager object, pass it the name of the resource file to use, and a reference to the currently executing assembly so it knows where to look for the resource strings.

   ResourceManager res = new ResourceManager      ("Multilingual.Resources",      Assembly.GetExecutingAssembly()); 

To pull out the resource string value, call the GetString() method and supply the textName property that you set using the Name property of the server control.

   string text = res.GetString(textName); 

Finally create a literal control to hold the resource string and add it to the page control structure.

   LiteralControl literal = new LiteralControl(text);   Controls.Add(literal); 

Notice that code doesn’t have to manage the culture. The .NET framework does that for you, because the culture of the thread has already been set by the code in Application_BeginRequest();.

For example, to add the Copyright message to the article page, add a SiteTextControl control to the page and specify the name of the resource display using the Name property.

        

After adding the control, and refreshing the article page the Copyright message appears (see Figure 8).

If you change the culture using the drop-down list, the control displays the Copyright message in the selected language:

Now suppose you wanted to add a caption above the drop-down list to prompt visitors to select their language. Unfortunately, you may remember that the ChooseLanguage string resource exists only in the UK English resource file (Resources.resx). To add the caption you need to make a change to the CreateChildControls() method inside the server control that displays the drop-down list. I added the following code just before the drop-down list was created:

   SiteTextControl text = new SiteTextControl();   text.Name = "ChooseLanguage";   Controls.Add(text);   literal = new LiteralControl("
"); Controls.Add(literal);

If you refresh the site it displays the new drop-down list caption. The site defaults to using the resources in the main assembly when it doesn’t find relevant resources in the satellite assemblies; therefore, if you view a French page (see Figure 9), you’ll see that because the French resource doesn’t exist, the drop-down list caption defaults to UK English. If you add resource strings to translate the drop-down caption into other languages, the page would display those without any other code changes.

A framework like this is relatively simple to implement. After it’s in place it’s important to develop a process for inserting text strings into pages using resources rather than embedding strings directly into your code or ASP.NET page files.

In this article you have seen how to put together a simple Web site that uses some of the core localization features of .NET including the CultureInfo object and resource files. You have seen how to alter the culture setting at the page level and also how to insert localized content into the page from both a database, and from a resource file.

In the next part of this article I’ll show you how to manage the localization of graphics, and also how to select the most suitable culture for Web site visitors automatically when they first arrive at the site.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: