Share via


Design and Implementation Guidelines for Web Clients

Retired Content
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

 

patterns & practices Developer Center

Microsoft Corporation

November 2003

Applies to:
   Microsoft .NET Framework
   ASP.NET

Summary: This chapter describes how globalization and localization requirements affect the development of your presentation layers.

Contents

In This Chapter

Understanding Globalization and Localization Issues

Using Cultures

Formatting Data

Creating Localized Resources

Summary

In This Chapter

This chapter describes how to address globalization and localization issues in Web applications. The chapter includes the following sections:

  • Understanding Globalization and Localization Issues
  • Using Cultures
  • Formatting Data
  • Creating Localized Resources

Applications might be used in multiple geographical locations and cultures; this can bring many challenges to software architects, particularly when designing the presentation layer. This chapter describes how to address these challenges so that your applications can support as broad a customer base as possible. This chapter also describes how to design and implement your applications so that they can be easily extended to support new cultures when necessary.

For a summary of best practices and recommendations discussed in this chapter, see "Best Practices for Developing World-Ready Applications" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconbestpracticesforglobalapplicationdesign.asp).

Understanding Globalization and Localization Issues

Globalization is the term given to the process of making sure that an application does not contain any internal dependencies on a particular culture; for example, it is best for a globalized application to not have any hard-coded number formats that can differ across multiple countries, or to assume a particular sorting mechanism for strings. A globalized application can correctly accept, process, and display a worldwide assortment of scripts, data formats, and languages.

Localization is the process of adapting an application, and in particular the user interface, to suit a specific culture. Localization typically involves tasks such as translating strings into different natural languages, resizing user interface elements to fit on the screen, and regenerating images for specific cultures.

To simplify localization efforts and minimize costs, deal with globalization and localization issues during the design phase of a project. Failure to correctly identify such requirements at design time can lead to expensive and inferior attempts at localization later in development.

There are several issues you must take into account:

Globalization and localization are particularly important in Web applications because users can potentially access these applications from anywhere in the world. By providing culture-sensitive user interfaces and application logic, you increase the reach of the application and improve the user's experience when they use the application.

Effective globalization and localization helps reduce the effort you expend to develop world-ready Web applications and also simplifies maintenance and extensibility after the application has been deployed.

Additional Information

For a summary of general best practices for globalization and localization, see "Best Practices for Globalization and Localization" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsent7/html/vxconbestglobalizationpractices.asp).

For advice on how to test your application for globalization and localization, see "Testing for Globalization and Localization" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsent7/html/vxcontestingforglobalizationlocalization.asp).

Using Cultures

The various rules for languages and countries, such as number formats, currency symbols, and sort orders, are aggregated into a number of standard cultures.

Each culture is identified by a culture ID as defined in RFC 1766; each culture ID is made up of a neutral culture code indicating the language of the culture and an optional specific culture code indicating the country represented by the culture. Neutral culture codes are written in lowercase, for example "en" represents English, while "fr" represents French. Specific culture codes are appended in uppercase, allowing specific language and country combinations such as "en-US" (English in the United States), "en-GB" (English in the United Kingdom), "fr-FR" (French in France) and "fr-CA" (French in Canada) to be represented.

The .NET Framework supports all cultures defined in RFC 1766 in addition to an invariant culture for culturally-insensitive data; an invariant culture uses an empty string as the culture ID and is defined as English language with no specific country. A culture is represented programmatically in the .NET Framework using the System.Globalization.CultureInfo class.

Identifying the Current Culture

On any particular computer running Windows, the system's current culture is determined by the system regional and language settings. By default, .NET Framework applications inherit the current culture from these settings and use it when performing tasks such as formatting numbers, sorting strings, and displaying currency.

The .NET Framework actually uses a combination of two cultures to handle different aspects of localization:

  • Current culture–Determines how various data types are formatted, such as numbers and dates.
  • Current user interface culture–Determines which localized resource file the resource manager loads. For more information about how to create localized resource files, see "Creating Localized Resources" later in this chapter.

These two aspects of localization are represented by the CurrentCulture and CurrentUICulture static properties of the CultureInfo class:

  • CultureInfo.CurrentCulture–Returns the Thread.CurrentCulture property; this property indicates the culture of the currently executing thread.
  • CultureInfo.CurrentUICulture–Returns the Thread.CurrentUICulture property; this property indicates the user interface culture of the currently executing thread.

The following code shows how to get the culture and user interface culture of the currently executing thread.

using System.Globalization;
using System.Threading;
…
string currentCulture   = CultureInfo.CurrentCulture.Name;
string currentUICulture = CultureInfo.CurrentUICulture.Name;
  

In addition to retrieving the current culture and user interface culture, it is also possible to change these settings to influence how information is presented to the user. The following section describes how to use an alternative culture.

Using an Alternative Culture

In most Windows-based applications (and on smart-device applications), it is reasonable to assume the system regional and language settings indicate the culture that an individual application uses. However, in some cases you might want to allow the user to select an alternative culture in your application, regardless of the underlying system settings.

For Web applications, the requirement to programmatically use an alternative culture is even more pronounced. The underlying system settings indicate the regional and language settings of the server hosting the application, not necessarily those of the user accessing it.

There are various ways to set the culture and user interface culture in an ASP.NET Web application, depending on your intended scope:

  • Set the culture and user interface culture in Web.config–Use this approach if you want to set the default culture and user interface culture for all the pages in a Web application. The following fragment from a Web.config file illustrates this technique.

    <configuration>
       <system.web>
          <globalization culture="en-US" uiCulture="de-DE"/>
       </system.web>
    </configuration>
    
    
  • Set the culture and user interface culture in the @ Page directive–Use this approach if you want to override the default culture and user interface culture for a specific page in a Web application. The following fragment from an .aspx file illustrates this technique.

    <%@ Page Culture="en-GB" UICulture="Fr-FR" … %>
    
    
  • Set the culture and user interface culture programmatically–Use this approach if you want to select which culture and user interface culture to use at run time.

    Note   You cannot change a thread's culture in semi-trusted code; changing the culture requires a SecurityPermission with the SecurityPermissionFlag,ControlThread set. Manipulating threads is dangerous because of the security state associated with threads. Therefore, this permission should be given only to trustworthy code, and then only as necessary.

    The following code in an ASP.NET Web page retrieves the user's language preferences from the Request.UserLanguages property and uses the culture and user interface culture for the preferred language.

    using System.Globalization;
    using System.Threading;
    
    // Set the culture to the browser's accept language
    Thread.CurrentThread.CurrentCulture = 
        CultureInfo.CreateSpecificCulture(Request.UserLanguages[0]);
    
    // Set the user interface culture to the browser's accept language
    Thread.CurrentThread.CurrentUICulture = 
        new CultureInfo(Request.UserLanguages[0]);
    
    

Note   You cannot change the CurrentCulture or CurrentUICulture properties when programming with the .NET Compact Framework. If you have to support per-application localization on smart-client devices such as Pocket PCs, you must use a CultureInfo object to store the user-selected culture internally, and use it explicitly whenever loading resource files or formatting data.

Formatting Data

Different cultures format data in different ways. An example of this is the formatting of numbers in different currencies, but there are many other data type-specific issues that must be considered when using multiple cultures:

  • Localizing string data
  • Localizing numeric data
  • Localizing date and time data

The following sections describe each of these localization issues.

Localizing String Data

Different cultures use different character sets to represent string values. Generally, a globalized application stores all strings internally as Unicode and converts them to the appropriate character set code-pages only if necessary to display them (for example by sending a code-page specific array of bytes to a browser). You can use the static GetEncoding method of the System.Text.Encoding class to retrieve an Encoding object for a specific code-page, and then use its GetBytes method to convert Unicode strings to a code-page specific byte array.

Another aspect of dealing with localized string data is that sort orders are culture-specific. For example, an application that sorts the names Ändrè and Andrew produces different results depending on the culture used (in German cultures, Ändrè is at the beginning; while in Swedish cultures, Andrew is first). The static Sort method of the Array class automatically sorts according to the Thread.CurrentThread.CurrentCulture setting.

You can also compare strings using the static Compare method of the String class; this allows a CultureInfo object to be passed so that the sort order from a culture other than the current culture can be used. Alternatively, the CultureInfo class has a CompareInfo object property; this provides a Compare method similar to that of the String class.

Localizing Numeric Data

You can display localized number formats using the ToString method of the base numeric types (such as int, long, and double) or any derived class that implements the IFormattable interface.

With the ToString method, you can pass a format parameter to indicate the kind of numeric formatting required (such as currency or decimal) to format the number according to the Thread.CurrentThread.CurrentCulture setting.

The following example formats a double value as a currency by passing the "c" format string to ToString; if the current culture is en-US, the result string is formatted as "$12,345,678,910.11."

double number = 12345678910.11;
string resultString = number.ToString("c"); 
  

You can also pass a CultureInfo object in the provider parameter of the ToString method to format the number for an alternative culture. The following example formats a double value as a currency using the "fr-FR" culture. The result string is formatted as "12 345 678 910,11 €."

double number = 12345678910.11;
string resultString = number.ToString("c", new CultureInfo("fr-FR")); 
 
  

For a complete list of the numeric format strings, see "Standard Numeric Format Strings" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconstandardnumericformatstrings.asp).

Localizing Date and Time Data

Date and time data presents three challenges in respect to localization:

  • Formatting dates
  • Using different calendars
  • Handling time zones
  • The following sections describe how to address each of these issues.

Formatting Dates

To display dates in an appropriate localized format, you can use any of the ToXxxxString methods (such as ToShortDateString or ToShortTimeString) provided by the DateTime structure; these methods automatically format the date according to the Thread.CurrentThread.CurrentCulture setting.

Formating Dates and Times for a Particular Culture

The following example sets the current culture to "fr-FR," and then formats a DateTime object as a short and long date string, followed by a short and long time string. Sample results are shown in each case.

Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
DateTime dt = DateTime.Now;

string shortDateString = dt.ToShortDateString();  // "31/12/2003"
string longDateString  = dt.ToLongDateString();   // "mercredi 31 décembre 2003" 

string shortTimeString = dt.ToShortTimeString();  // "09:32"
string longTimeString  = dt.ToLongTimeString();   // "09:32:57"
  

Customizing Date and Time Formatting for a Particular Culture

If you want more control over how the date or time is formatted, you can use the DateTimeFormat property of a CultureInfo object to obtain a DateTimeFormatInfo object for that culture. DateTimeFormatInfo has properties and methods that enable you to specify how to format items such as long dates, short dates, long times, and short times.

The following example modifies the long date pattern for the current culture, so that long dates appear in the format [year—monthName—day].

DateTimeFormatInfo fmt = Thread.CurrentThread.CurrentCulture.DateTimeFormat;
fmt.LongDatePattern = @"\[yyyy-MMMM-dd\]";

string longDateString = DateTime.Now.ToLongDateString();  // "[2003-décembre-31]"
  

Parsing Culture-Specific Date and Time Strings

To read a date from a string into a DateTime object, you can use the static Parse method of the DateTime structure. By default, the Parse method assumes that the date is formatted according to the Thread.CurrentThread.CurrentCulture setting, but you can also pass a CultureInfo object to read dates in formats for an alternative culture.

The following example parses a date string that has been formatted using the "fr-FR" culture.

DateTime dt = DateTime.Parse(aFrenchDateString, new CultureInfo("fr-FR"));
  

Using Different Calendars

Although most people are familiar with the Gregorian calendar, some cultures provide alternative calendars that your application might have to support. A globalized application typically displays and uses calendars based on the current culture.

The .NET Framework provides a Calendar class that you can use to perform calendar-related operations. Each culture has a default calendar (available from the Calendar property of the CultureInfo object) and a collection of optional calendars (available from the OptionalCalendars property of the CultureInfo object). The .NET Framework includes the following Calendar implementations: GregorianCalendar, HebrewCalendar, HijriCalendar, JapaneseCalendar, JulianCalendar, KoreanCalendar, TaiwanCalendar, and ThaiBuddhistCalendar.

Enumerating Calendars for a Culture

The following example retrieves the default calendar and any optional calendars for the current culture. Note that GregorianCalendar has a CalendarType property; this indicates the language version of the GregorianCalendar.

// Display details for the default calendar for the current culture
Calendar calendar = Thread.CurrentThread.CurrentCulture.Calendar;
Console.Write("Default calendar:  {0}", calendar);
if (calendar is GregorianCalendar)
  Console.Write(", subtype: {0}", ((GregorianCalendar)calendar).CalendarType);

// Display details for all the optional calendars for the current culture
Calendar[] calendars = Thread.CurrentThread.CurrentCulture.OptionalCalendars;
foreach (Calendar cal in calendars)
{
  Console.Write("\nOptional calendar: {0}", cal);
        
  if (cal is GregorianCalendar)
    Console.Write(", subtype: {0}", ((GregorianCalendar)cal).CalendarType);
}
  

The following sample output shows the default calendar and optional calendars for the "ja-JP" culture.

Default calendar:  System.Globalization.GregorianCalendar, subtype: Localized
Optional calendar: System.Globalization.JapaneseCalendar
Optional calendar: System.Globalization.GregorianCalendar, subtype: USEnglish
Optional calendar: System.Globalization.GregorianCalendar, subtype: Localized
  

Interpreting Dates and Times for a Culture

The following example shows how to interpret dates and times correctly for a particular calendar. The culture is set to "he-IL" (Hebrew in Israel), and the calendar is set to the HebrewCalendar. A DateTime object is then created using the "he-IL" culture, and the HebrewCalendar is used to retrieve the year according to the Hebrew calendar. The year is also obtained directly by using the DateTime.Year property; note that the DateTime structure always returns date information in the GregorianCalendar.

// Set the current culture to "he-IL"
CultureInfo he = new CultureInfo("he-IL");
Thread.CurrentThread.CurrentCulture = he;
      
// Use the Hebrew calendar for date-and-time formatting
he.DateTimeFormat.Calendar = new HebrewCalendar();
      
// Create a DateTime, using HebrewCalendar rules
DateTime dt = new DateTime(5763, 11, 4, he.DateTimeFormat.Calendar);
      
// Retrieve the year, using HebrewCalendar rules
Console.WriteLine("Hebrew year: {0}", he.DateTimeFormat.Calendar.GetYear(dt));
      
// Retrieve the year, using GregorianCalendar rules
Console.WriteLine("Gregorian year: {0}", dt.Year);
  

The preceding example produces the following output.

Hebrew year: 5763
Gregorian year: 2003
  

Displaying Calendar Controls for a Culture

When you add a Calendar control to an ASP.NET Web page, the calendar is displayed using the current culture. Figure 7.1 shows how a Calendar control appears in the "en-US" culture.

Ff647353.f07diforwc01(en-us,PandP.10).gif

Figure 7.1. Calendar control in an ASP.NET Web form, using the "en-US" culture

To display the calendar control in a culture-specific fashion, add the following code to the Page_Load method (or another appropriate location) in your Web application.

// Set the culture to "Hebrew in Israel"
CultureInfo cultureInfo = new CultureInfo("he-IL");
Thread.CurrentThread.CurrentCulture = cultureInfo;
      
// Use the Hebrew calendar for formatting and interpreting dates and times
cultureInfo.DateTimeFormat.Calendar = new HebrewCalendar();
  

Figure 7.2 shows how the Calendar control appears in the "he-IL" culture.

Ff647353.f07diforwc02(en-us,PandP.10).gif

Figure 7.2. Calendar control in an ASP.NET Web form, using the "he-IL" culture

The Calendar control in Figure 7.2 illustrates two aspects of localization. First, the month name and year number are displayed in Hebrew because the "he-IL" culture is being used. Second, the days of the month are displayed in Hebrew numerals because a HebrewCalendar object has been assigned to the cultureInfo.DateTimeFormat.Calendar property.

Handling Time Zones

Some applications might be used in multiple time zones. For example, a scheduling application might allow a user to schedule an event while in one time zone and automatically update the event details when the user moves to another time zone. To handle this kind of functionality, the .NET Framework supports universal time, a neutral time zone that can be used for the internal storage of date/time values.

You can use the ToUniversalTime and ToLocalTime methods of the DateTime structure to convert date/time values between the local time for the current system time zone and universal time. Additionally, the static Parse and ParseExact methods of the DateTime structure allow you to specify a styles parameter of DateTimeStyles.AdjustToUniversal to automatically read a date in a local time zone and adjust it to universal time. This makes it easier to accept input in the local DateTime format and to use universal time internally.

Persisting Dates and Times Using the Invariant Culture

When persisting a date as a string, it is a good idea to use the invariant culture to format it—this ensures that a consistent format is always used internally, regardless of the culture used for presenting dates to the user. For example, you can use the following code to persist a date/time value so that it can be adjusted for local time zones.

// Function to save DateTime value
private void SaveDateTime(DateTime localDateTime)
{
    // Convert the value to universal time
    DateTime udtDate = localDateTime.ToUniversalTime();

    // Format the date string using the invariant culture
    string dataToSave = udtDate.ToString(CultureInfo.InvariantCulture);

    // Save the data
    StreamWriter f = new StreamWriter(@"C:\SavedDate.txt");
    f.WriteLine(dataToSave);
    f.Close();
}

// Function to load a saved time
private DateTime LoadDateTime()
{
    // Load the data
    StreamReader f = new StreamReader(@"C:\SavedDate.txt");
    string loadedData = f.ReadToEnd();
    f.Close();

    // Create a DateTime object based on the string value formatted for 
    // the invariant culture
    DateTime udtDate = DateTime.Parse(loadedData,CultureInfo.InvariantCulture);

    // Convert the value to local time and return it
    return udtDate.ToLocalTime();
}
  

With this approach, you can handle all internal date/time storage neutrally, only applying culture-specific formats and local time zones when required.

Creating Localized Resources

You can create localized collections of string and binary data that the .NET Framework common language runtime loads automatically depending on the Thread.CurrentThread.CurrentUICulture setting. This approach is typically used to create localized user interfaces, although it can also be used to manage any set of strings or binary data that require localization in your application.

To localize a user interface, avoid hard-coding localizable strings or images in your ASP.NET Web Forms or Windows Forms; store this data in localized resource files instead. Each application has a default set of resources; this set is used when no culture-specific resource file is available.

This section focuses on how to create custom resource files for ASP.NET Web applications and for other situations where you want to localize non-user interface data. For information about how to localize Windows Forms, see "How to Localize Windows Forms" in Appendix B in this guide.

Creating Custom Resource Files

If your application is not a Windows Forms-based application, or if you want to localize non-user interface data, you can create custom resource files. Custom resource files are compiled as satellite assemblies; you use the System.Resources.ResourceManager class to load the appropriate resources for the Thread.CurrentThread.CurrentUICulture setting.

Creating Resource Files

You can create resource files in three different ways:

  • Text (.txt) files–Text files contain string resources in name=value pairs.

    You cannot directly embed a .txt file in an assembly; you must convert the .txt file into a .resources file using the Resource File Generator, Resgen.exe.

  • .resx files–.resx files are XML files that contain references to strings and objects. When you view a .resx file, you can see the binary form of embedded objects (such as pictures) as long as the binary information is in the resource manifest.

    You cannot directly embed a .resx file in an assembly; you must convert the .resx file into a .resources file using the Resource File Generator, Resgen.exe.

  • .resources files–.resources files contain references to strings and objects. You can create .resources files programmatically by using methods in the System.Resources.ResourceWriter class. You can also generate .resources files from .txt and .resx files by using the Resource File Generator, Resgen.exe. You can read .resources files programmatically by using methods in the System.Resources.ResourceReader class

For more information about these three types of resource files and how to use the Resgen.exe tool, see "Creating Resource Files" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconcreatingresourcefiles2.asp).

Visual Studio .NET allows you to add XML .resx resource files to a project and automatically generates and embeds a binary version of the file when the project is compiled. You can use the resource editing window in Visual Studio .NET to add string and encoded binary resources to a resource file.

You must create a default resource file using the naming format ResourceFileName.resx; this file is used when no specific resource file can be found for the current user interface culture. For example, an assembly might contain a default resource file named MyResources.resx. Resource files for specifically supported cultures must be named using the format ResourceFileName.Culture_ID.resx. For example, a resource file for the "English in the UK" culture might be named MyResources.en-GB.resx.

Create the same list of resources in each resource file. It is a good idea for each resource file to use the same name property for each resource, with a localized value. For example, you might have a resource with the name welcomeMessage; this resource has the value "Welcome" in a MyResources.en-GB.resx file and the value "Bienvenue" in a MyResources.fr-FR.resx file.

When the solution is compiled, the default resource file is embedded in the application assembly and a satellite assembly for each culture-specific resource file is created and linked to the main assembly.

At run time, the common language runtime loads the most appropriate resources for the current user interface culture. If a specific culture resource file (that is, a resource file for a specific language and geographical location such as "en-GB") is available for the current user interface culture, it is loaded. If not, the runtime looks for an appropriate language culture (such as "en") and loads that. If no language culture resource file is available, the runtime uses the default resource file embedded in the main assembly. Note that this "closest match" approach allows you to make your localization language- and location-specific (for example, by having different resources for English speakers in the U.K., English speakers in the U.S., French speakers in Canada, and French speakers in France) or just language-specific (for example, by having different resources for English speakers and French speakers).

Retrieving Localized Resources

To retrieve the appropriate resource at run time, you can use the System.Resources.ResourceManager class. To create a ResourceManager object to load resources from satellite assemblies (or the main assembly if the default resource file is required), use the constructor that allows you to specify baseName and assembly parameters:

  • The baseName is the root name of the resource files, including the namespace. For example, the baseName for the MyResources.resx and MyResources.en-GB.resources resource files in an assembly that uses the namespace "MyApplication" is "MyApplication.MyResources."
  • The assembly parameter is used to specify the main assembly for the resources. This is generally the assembly that the code to create the ResourceManager object resides in, and you can specify the current assembly by passing this.GetType().Assembly to the constructor.

The following sample code shows how to create a ResourceManager object.

using System.Resources;
…
ResourceManager resMan = 
  new ResourceManager("MyApplication.MyResources", this.GetType().Assembly);
  

You can retrieve resources by using the GetString and GetObject methods of the ResourceManager class and passing the name of the resource to be retrieved. For example, the appropriate welcomeMessage string resource for the current culture (as defined in the CultureInfo.CurrentUICulture object) can be retrieved using the following code.

string welcomeMessage = resMan.GetString("welcomeMessage");
  

The GetString method is overloaded to allow you to pass a CultureInfo object and therefore retrieve a resource for an alternative culture.

Additional Information

For more information about how to retrieve data from all kinds of resource files, see "Best Practices for Developing World-Ready Applications" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconbestpracticesforglobalapplicationdesign.asp).

For additional information about how to use resources to localize the layout and presentation of an ASP.NET Web page, see "Enterprise Localization Toolkit" on MSDN (https://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/entloctoolkit.asp). The Toolkit enables you to extend the .NET Framework foundation, and it includes code to manage resource strings through a database, localize complex data types, and build resource files automatically.

Summary

Globalization and localization are important factors in Web applications. By careful thought, you can design your user interface in a suitably generic manner so that it can be easily localized for different cultures. You can also take advantage of the extensive support provided by the .NET Framework class library to write code that is sensitive to the current culture.

Start | Previous | Next

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

© Microsoft Corporation. All rights reserved.