Click here to Skip to main content
Click here to Skip to main content

Globalization and localization demystified in ASP.NET 2.0

, 30 Sep 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
This article explains how to globalize an ASP.NET 2.0 website, step by step, with a practical example.

Introduction

Globalization and localization are two important processes which every developer should be aware of while creating global products or applications. Though there are many articles which explain the subject well, I did not find a single resource which explains all important concepts regarding globalization/localization, practically and comprehensively. This article aims to provide practical step-by-step approach to globalizing a web application in ASP.NET 2.0.

Background Theory

Globalization is defined as the process of developing a program or an application so that it is usable across multiple cultures and regions, irrespective of the language and regional differences. For example, you have made a small inventory management program and you live in a region where English is the main language, assume England. Now, if you want to sell your program in a different country, let’s say Germany, then you need to make sure that your program displays and takes input in German language.

Localization is the process of creating content, input, and output data, in a region specific culture and language. Culture will decide date display settings (like, mm/dd/yyyy or dd/mm/yyyy), currency display formats etc. Now, the process by which we can make sure that our program will be localized is known as Internationalization or Globalization. In simpler terms, Globalization can be defined as the set of activities which will ensure that our program will run in regions with different languages and cultures.

So, globalization is related to intrinsic code changes to support such changes like using Resource files etc. Whereas, localization is the process of using a particular culture and regional info so that the program uses the local languages and culture. This means translating strings into a particular local language. This covers putting language specific strings in the resource files. Globalization starts in the main construction phase along with the code development. Localization generally comes later.

Globalizing an ASP.NET 2.0 Website

Let’s start with a simple example. For the purposes of explaining localization and keeping things simple, I have created a new website in ASP.NET and C#, called TestSite (source code of the example is included in this article). I have added a Master Page and a default page. This default page has a TextBox and a Calendar control. The TextBox control has a double number which will represent currency, and we will see how the currency format varies as user selects different languages. The default page looks like this when I run the application:

Default.JPG

I have published the test web application, and you can see the functional version here.

Cultures and Locale

Now, before we move ahead, let me throw some light on cultures and locale.

Languages also depend upon the geographical location. For example, French is spoken in France as well as Canada (besides many other countries). But linguistically speaking, Canadian French is quite different from French spoken in France. Similarly, there are linguistic differences between US English and British English. Therefore, the language needs to be associated with the particular region where it is spoken, and this is done by using locale (language + location).

For example: fr is the code for French language. fr-FR means French language in France. So, fr specifies only the language whereas fr-FR is the locale. Similarly, fr-CA defines another locale implying French language and culture in Canada. If we use only fr, it implies a neutral culture (i.e., location neutral).

How do we define or change the current culture?

There are two properties of the CultureInfo class in the .NET FCL (Framework Class Library) which we can set using the overloaded constructor of the class, and then we can use the class to change the culture of the currently executing thread:

  • UICulture: gets/sets the user interface culture for the currently executing thread. This property helps the runtime to load the resource strings from a specific resource file (which we will see later). This property can take neutral cultures as well as locales. For example:
  • Thread.CurrentThread.CurrentUICulture = new CultureInfo(“fr”);

    Or,

    Thread.CurrentThread.CurrentUICulture = new CultureInfo(“fr-CA”);
  • Culture: gets/sets the region specific culture and formats of currency, dates etc. This needs language as well as location (locale).
  • Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr-A”); //correct as 
        ///we have given locale 
    Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr”); //wrong, will
                                                               // not work

Sometimes we need a culture which does not belong to any language or locale, which is invariant of any region/language. For this, we have the CultureInfo.InvariantCulture property. It is used during the internal system processes which need to be culture independent, or to store data which does not need to be displayed directly to the end user.

Both UICulture and Culture properties can be defined in the Web.Config file under the <GLOBALIZATION>property. They can also be specified at page level. But we don’t want to hard-code these values, and would like to set them dynamically instead. As seen above, we could also get/set these values from the code using the Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture properties. So, we will use these properties in our application.

Switching Locale

Coming back to our application, we need a way to switch the locale. There are two approaches regarding this:

  1. Use the browser settings: In IE, the user can change the culture by going to Internet Options->General->Languages. For this to work, we need to set both the Culture and the UICulture to auto and enableClientBasedCulture = true as:
  2. <GLOBALIZATION culture="auto" uiculture="auto" enableClientBasedCulture="”true”" />
  3. User specified settings: We can give an option for the user to specify and change the culture and the language at runtime. This is the recommended approach, as sometimes the browser itself may not have the user specific culture set (for example, a French tourist might be surfing net in India). Also, sometimes changing Language settings via the browser is blocked.

Going by the second recommended approach, I have created a section on the top (inside a Panel control) in the Master Page where I have a drop-down with these language options which let the users choose a particular locale. For illustration purposes, I have the option of only four languages to choose from: Hindi, US English, British English, and French.

For my application to be globalized, I want that whenever the user selects a locale from the language, the following should happen:

  1. All content should be localized: This means that all strings and text should be displayed in the chosen language and locale.
  2. Each control’s caption/content should also show text in local language.
  3. Date and currency formatting should occur according to the chosen locale.
  4. All messages displayed to the user should be in the local language.

To achieve the above goals, the first thing you need to make sure is to take out content from the code and put it in separate resource files, which are simple XML files in .NET with a .resx extension.

Since this content will vary from language to language, we will have resource files for every culture. Each such file has Name and Value fields (like a Dictionary). Below are the sample entries in two resources, assuming we have to enter a string “Welcome”:

  1. Add a new resource file, and name it as TestSiteResource.resx (you can use any name), and open it in the VS editor. Enter “Banner” in the Name field, and “Test Website for Localization” in the value field. This resource file is the default for American English.
  2. Add another resource file, and name it as TestSiteResources.fr-FR.resx. This file is for French language strings. Add “Banner” in the Name field, and “Examinez le site Web pour le comportement de localisation” in the Value field.
  3. If you want to add Canadian French resources, then you need to create another resource file by the name of TestSiteResources.fr-CA.resx. The middle part of this name defines the locale, and this should be the same as specified by the UICulture property.

  4. These files would be saved in the App_GlobalResources folder in ASP.NET 2.0.

Tip/Trick: If you want that only certain pages show localized strings, you can restrict the localization behavior throughout the application by putting resource files in the App_LocalDirectory folder. This will make localization page specific and not application wide. The naming should be like (assuming you want to localize only a page named MyPage.aspx):

  • MyPage.aspx.resx: this is the default resource file for MyPage.aspx.
  • MyPage.aspx.fr-FR.resx: this will be used when the culture changes to French, but only MyPage.aspx in the application would be localized.

All the above .resx files would be compiled into assemblies at runtime. These assemblies are known by the name of “satellite assemblies”, and have strongly typed wrappers for the .resx files. So, we don’t need to worry about creating resource assemblies ourselves in ASP.NET 2.0. These assemblies are placed in separate folders (by the name of the locale) under the /bin folder, after you have published your website:

BinResFiles.JPG

For non ASP.NET applications, we need to use two tools:

  1. Resource file generator: resgen.exe
  2. Assembly linker (al.exe)

There is lot of detailed information on how to use these tools, on MSDN, and the user can refer to these links:

Now that we have created the resource files for different cultures and languages, we need a way to load them at runtime when the user changes culture dynamically. Fortunately, implementing this in ASP.NET 2.0 is quite easy. See the code below:

String welcome = Resources.TestSiteResources.Welcome;

In this line of code, we are using the Resources namespace which was created automatically by ASP.NET when it compiled the resource files into satellite assemblies, and we used the TestSiteResources class, with the same name as the resource file we created. We then accessed the Welcome property which will give the actual text from the resource file based on the current culture. If we want to localize the text of the Label control lblWelcome, we can set the same using these methods, in ASP.NET 2.0:

  1. Implicit localization: Here, we specify the new meta tags in the control definition and let ASP.NET get the value from the resource files based on the resourcekey attribute:
  2. <asp:Label id=lblWelcome meta:resourcekey="lblWelcome" 
               Text="Welcome" runat="server">
    </asp:Label>

    For this to work, we need to have page specific resource files in the /App_LocalDirectory folder. Implicit localization helps trim down the size of the global resource files, and helps in better overall resource management. Use it when you have largely page specific content.

    You do not need to do anything manually to set these implicit localization properties. Just open your web page in the Design mode, go to Tools->Generate Local Resources. This will automatically create a resource file for your page. You only need to set the values (Control.Property) of different fields for each control in the resource file editor in Visual Studio 2005.

  3. Explicit localization: This works when we have Global resource files. Here, we use Expressions to set the values from the resource files, as:
  4. <asp:Label id=lblWelcome Text="<%$Resources:TestSiteResources, Welcome %>" 
               runat="server"></asp:Label>

    We can set this using the VS IDE. Select the Label control, go to the Properties window, select Expressions->Text. Then, choose Resources from the drop down and enter the class name (TestSiteResources, for this example) and the Resource key (Banner). This is the recommended way to localize the UI controls on a page.

    Sample screenshot

  5. Programmatically accessing strongly typed resource classes as:
  6. lblWelcome.Text = Resources.TestSiteResources.Welcome;

    This will work, but then it needs to be coded for every control in the page. So, use #2 for all the controls, and use this method to access resource strings for other content, if needed. Also, note that controls like the Calendar control have localization in-built. As soon as the UICulture and Culture of the current thread changes, it shows localized content by itself, thanks to ASP.NET!

Incorporating Globalization

In my website, after creating resource files and putting some localized data, I first start using the explicit localization to set the text of the controls such as Labels in my website so that they get their values from the resource files. Since there are four languages, I have created four resource files besides a fifth fallback resource file (with no locale name).

s1.JPG

Notice that the resource files have the locale as their middle names, so I need to set the UICulture to the same named locale in order for ASP.NET to access these resource files.

But the problem is: how should I change the culture dynamically on the postback event? Fortunately, ASP.NET provides a method in the Page class to override: InitializeCulture(). This method executes very early in the page lifecycle (much before any control is generated), and here we can set the UICulture and Culture of the current thread.

Since this method is in the Page class and I do not want to repeat the same code for each web page, I created a BasePage class, and all the aspx pages in my application derive from this BasePage class. But, I faced another problem now. Let me explain:

Going back to the UI design: I had a MasterPage and a Header user control in it (inside a ContentPlaceHolder). I had a default page associated with that MasterPage. The entire site had to be localized dynamically. So, in the header, there was a dropdown from where the user could select a language/culture. In the BasePage’s InitilializeCulture method, I had to get the value of the item the user selected from the drop down, but since it was not initialized as yet (since InitializeCulture() is called much earlier in the page life cycle), I cannot access any control's value. The answer: use the Form collection (from the Response object). Here is the code:

///<span class="code-SummaryComment"><SUMMARY></span>
/// The name of the culture selection dropdown list in the common header. 
/// We need to use this name as we don't have any other
/// control property as the control (dropdown) itself is not initialized yet.
/// So we use the "nested" dropdown name through which we will get the 
/// dropdown's value from the Request.Form[] collection.
/// <span class="code-SummaryComment"></SUMMARY></span>
public const string LanguageDropDownID = "ctl00$cphHeader$Header1$ddlLanguage"; 
/// <span class="code-SummaryComment"><SUMMARY></span>
/// The name of the PostBack event target field in a posted form. You can use
/// this to see which control triggered a PostBack: 
/// Request.Form[PostBackEventTarget] .
/// <span class="code-SummaryComment"></SUMMARY></span>
   public const string PostBackEventTarget = "__EVENTTARGET";

See how I am using the "parentControl:ChildControl" method to access the control from the Form collection. You can access any nested control generated by ASP.NET by adopting this convention. Using this value of the selected item from the Form collection, I set the culture in a switch case statement, as:

    /// <span class="code-SummaryComment"><SUMMARY></span>
    /// Overriding the InitializeCulture method to set the user selected
    /// option in the current thread. Note that this method is called much
    /// earlier in the Page lifecycle and we don't have access to any controls
    /// in this stage, so have to use Form collection.
    /// <span class="code-SummaryComment"></SUMMARY></span>
    protected override void InitializeCulture()
    {
      ///<span class="code-SummaryComment"><remarks><REMARKS></span>
      ///Check if PostBack occured. Cannot use IsPostBack in this method
      ///as this property is not set yet.
      ///<span class="code-SummaryComment"></remarks></span>
        if (Request[PostBackEventTarget] != null)
        {
            string controlID = Request[PostBackEventTarget];

            if (controlID.Equals(LanguageDropDownID))
            {
            string selectedValue = 
                   Request.Form[Request[PostBackEventTarget]].ToString();

            switch (selectedValue)
            {
                case "0": SetCulture("hi-IN", "hi-IN");
                    break;
                case "1": SetCulture("en-US", "en-US");
                    break;
                case "2": SetCulture("en-GB", "en-GB");
                    break;
                case "3": SetCulture("fr-FR", "fr-FR");
                    break;
                default: break;
            }
        }
      }
        ///<span class="code-SummaryComment"><remarks></span>
        ///Get the culture from the session if the control is tranferred to a
        ///new page in the same application.
        ///<span class="code-SummaryComment"></remarks></span>
        if (Session["MyUICulture"] != null && Session["MyCulture"] != null)
        {
            Thread.CurrentThread.CurrentUICulture = (CultureInfo)Session["MyUICulture"];
            Thread.CurrentThread.CurrentCulture = (CultureInfo)Session["MyCulture"];
        }
        base.InitializeCulture();
    }
    /// <span class="code-SummaryComment"><Summary></span>
    /// Sets the current UICulture and CurrentCulture based on
    /// the arguments
    /// <span class="code-SummaryComment"></Summary></span>
    /// <span class="code-SummaryComment"><PARAM name="name"></PARAM></span>
    /// <span class="code-SummaryComment"><PARAM name="locale"></PARAM></span>
    protected void SetCulture(string name, string locale)
    {
        Thread.CurrentThread.CurrentUICulture = new CultureInfo(name);
        Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
        ///<span class="code-SummaryComment"><remarks></span>
        ///Saving the current thread's culture set by the User in the Session
        ///so that it can be used across the pages in the current application.
        ///<span class="code-SummaryComment"></remarks></span>
        Session["MyUICulture"] = Thread.CurrentThread.CurrentUICulture;
        Session["MyCulture"] = Thread.CurrentThread.CurrentCulture;
    }

So the user will see the content in his/her selected language. We need to save the culture selected in a Session or a Cookie variable because if the user moves to some other page in the same application, the thread's culture information would be lost as the new Page class will instantiate from the beginning (HTTP is stateless!). Cookies can be used if you do not want to lose the current thread's Culture on the user's Session expiry. Once we have pulled out all content from the web application, set the Culture and UICulture based on the user choice, and used Resources.TestWebSite.XXXPropertyName, we are ready with our globalization framework. Now, the only thing left is the adding of resource specific data in the resource files. For each culture, we need to have a separate (and appropriately named) resource file. This process is localization. In my web.config file, I have used the following properties:

<globalization responseEncoding"=utf-8” requestEncoding="utf-8” 
               fileEncoding="utf-8" />

Note the encoding attributes: utf-8 (8 bit Unicode Transformation Format) is used since it is variable length character encoding and can represent languages such as Greek, Arabic etc., besides it is ASCII compatible too. For more info on UTF-8 encoding, see this link.

Also, an important point to note is that though we can have the resource files in raw XML form on the deployment server (so that the user can edit them without re-compiling the entire site), the application will re-start if we make any modification in the resource files. This can hamper the performance of the deployed application.

dir Attribute for Language Direction

Many times, we also need to set the direction of the localized text (which is set using the dir attribute of the <html> or the <body> tag). This is necessary because some languages are read from right-to-left (RTL), e.g., Arabic, instead of the standard left-to-right (LTR) like Hindi and English. This can be achieved quite easily by setting the dir attribute to the appropriate value from the .resx file.

First, create a Direction (you can use any name) field in all your resource files, setting its property to RTL or LTR based on individual resource files. For Arabic, the value of this field would be RTL, and for Hindi it would be LTR. Then, set the same in the dir attribute of the <body> tag as:

<body runat="server" dir="> 
<%$ Resources: TestSiteResources, Direction %>"

This will set the right direction as the value will come from the resource file based on the current thread's culture.

Using a Database for Localization

We have seen how to localize the text of the controls and the presentation in the UI. But what about the content stored in a database? This content also needs to be localized, but since it is stored in a DB, we cannot use resource files for the same. We need to create new tables for the same.

Suppose I have a table which stores user comments. The table structure is:

T1.JPG

Now, we want the Comments and the Name fields to be displayed in localized text. But we can’t store all the different language versions of these fields in this same table as it will not be normalized (since there are other fields which don’t need to be localized but will be repeated). Hence, we need to re-organize the table structure and create another table which will hold the localized version of these two fields. First, we need to remove these two fields from this table and create a new table as:

T2.JPG

Here, we have added a new field as CultureID, which is equivalent to LCID, or the Locale Identifier, an integer which indicates a particular culture. We can add culture specific localized data as:

T3.JPG

Now, we can use SQL queries with CultureID (LCID) as one of the parameters to get the localized content. We can also provide a user interface to enter localized data into such tables so that the content can be created in an interactive way.

Summary

I have tried to cover some important aspects of implementing Globalization in ASP.NET 2.0, and we saw that it is easy and simple, but there are a few important points to note:

  1. Do not rely on the web browser’s settings. Give a link on the application (may be in the header) so that the users can select their choice of language by clicking it.
  2. Use Resource files to separate Presentation related data in the GUI. Resource fallback is the approach used by ASP.NET when it is unable to find the resource file for a particular culture. It will first go to the neutral resource file, and then the default or fallback resource file (TestSiteResource.resx).
  3. Use database tables for data or content stored in a DB. You need to create separate tables to store localized content.
  4. If you use sn.exe to create a strong name of your main application assembly, then you need to use the private key from the same set of key pair (generated by sn.exe) to sign your satellite assemblies as well. Strong named assemblies require that satellite assemblies should also be strongly named.

Though I tried my best to cover important topics, in case I missed something, I would appreciate if readers can send in their suggestions on the same.

Happy globalizing!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Vivek Thakur
Founder Axero Solutions LLC
United States United States
Vivek is the co-founder of Communifire, a social business software platform that drives business growth. You can collaborate with anyone, provide better customer support, power your company intranet, build a knowledge base, and launch online communities for anything -- and more -- all in one integrated platform.
 
Link: http://axerosolutions.com

Comments and Discussions

 
GeneralMy vote of 5 Pinmembervishakhakhadse10-Jul-12 21:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 30 Sep 2006
Article Copyright 2006 by Vivek Thakur
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid