When you go on a vacation, you pack a suitcase with clothes, toiletries, and other personal belongings. You could carry the stuff with you as opposed to using your suitcase, but it would be very awkward (not to mention the fact that you only have limited number of pockets!). This suitcase serves as an invaluable resource for your trip; without it, your vacation would be a disaster.
ASP.NET allows you to "pack a suitcase" for your application, so to speak. Any custom information (such as variables, messages, text blurbs, images, and so on) can be packed away in a resource file. Your pages can then easily access the resources in these files whenever they are needed.
| || |
Resource files are typically used for localizing your applications. Localization is the process of modifying the output of your applications so that they are compatible with different cultures and languages. You can create a resource file for each different culture that will be using your application. For instance, you can store a welcome message in your resource file. If an English-speaking person accesses the page, you would display "Hello!" If a French person views it, you would display "Bonjour!" By storing each language's messages in a different resource file, you don't have to worry about creating a new ASP.NET page for every new language. It's much easier simply to create a new resource file.
ASP.NET makes localization and resource grouping a simple process. These processes allow you to modify your content output without having to change any code, thus separating code from content again. In the next couple of sections, you'll take a look at determining who's accessing your pages and how to adjust for them, and then how to pack your resources into files.
Localizing Your Application
Ideally, when localizing your Web application, you want to be able to automatically detect the locations of your users so that you can set the appropriate output languages and other culture-related info. Unfortunately, due to the nature of the Web, there is no standard way to easily determine the geographical location of a Web surfer.
Luckily, there are a few ways around this. You can determine the primary language the visitor is using in his Web browser. Typically, although not always, this indicates the language and culture the user is most comfortable with, and you can adjust accordingly. This information is provided by the Request.UserLanguages object, which gathers the languages used from the browser, and sorts them according to the priority the user has set. Listing 19.9 shows an example of detecting the primary user language.
Listing 19.9 Using the Request Object to Determine User Language Preferences
1: <%@ Page Language="VB" %> 2: <%@ Import Namespace="System.Globalization" %> 3: 4: <script runat="server"> 5: sub Page_Load(Sender as Object, e as EventArgs) 6: lblMessage.Text = Request.UserLanguages(0).ToString 7: end sub 8: </script> 9: 10: <html><body> 11: Your primary language is: 12: <asp:Label runat="server"/> 13: </body></html>
| || |
On line 2, you import a new namespace, System.Globalization. This namespace contains many of the objects you'll need to alter your output for different cultures. Note that none of the objects in this namespace is used in this listing, but you'll be using them soon! On line 6, you retrieve and display the user's primary language. The UserLanguages property returns an array of the languages that a visitor uses (as specified by his browser), sorted by priority (common languages and their codes are shown in Table 19.1). UserLanguages(0) returns the default language, or the one with the highest priority. Listing 19.9 produces Figure 19.8 when viewed from the browser.
Figure 19.8. The Request object can be used to determine a user's primary language.
Table 19.1. Commonly Used Languages and Abbreviations
|Language ||Code |
|Arabic (Egypt) ||ar-eg |
|Chinese (Hong Kong) ||zh-hk |
|Dutch (Belgium) ||nl-be |
|English (Australia) ||en-au |
|English (Canada) ||en-ca |
|English (United Kingdom) ||en-gb |
|English (United States) ||en-us |
|French ||fr |
|German ||de |
|Italian ||it |
|Japanese ||ja |
|Korean ||ko |
|Portuguese ||pt |
|Russian ||ru |
|Spanish (Mexico) ||es-mx |
|Spanish (Traditional) ||es |
Now that you've got the user's primary language, you can use it to set the culture information for your ASP.NET page. This will be done using the System.Globalization.CultureInfo object. This object contains information that completely represents the culture indicated by the language used, such as the calendar used, the country names, the date and time format, and so on. Listing 19.10 shows an example of using this object.
Listing 19.10 Using the CultureInfo Object
1: <%@ Page Language="VB" %> 2: <%@ Import Namespace="System.Globalization" %> 3: 4: <script runat="server"> 5: sub Page_Load(Sender as Object, e as EventArgs) 6: dim strLanguage as string = Request. _ 7: UserLanguages(0).ToString 8: 9: lblMessage.Text = "Primary language: " & _ 10: strLanguage & "<br>" 11: 12: dim objCulture as new CultureInfo(strLanguage) 13: lblMessage.Text += "Full name: " & objCulture. _ 14: EnglishName & "<br>" 15: lblMessage.Text += "Native name: " & objCulture. _ 16: NativeName & "<br>" 17: lblMessage.Text += "Abbreviation: " & objCulture. _ 18: ThreeLetterISOLanguageName & "<br>" 19: lblMessage.Text += "Current Time: " & DateTime.Now. _ 20: ToString("D", objCulture) 21: end sub 22: </script> 23: 24: <html><body> 25: <b>Your user information:</b> <p> 26: <asp:Label runat="server"/> 27: </body></html>
| || |
This listing is a modification of the previous one. On line 2, you again import the System.Globalization namespace. On lines 6 and 7, you retrieve the primary language used by the visitor, as you did in Listing 19.9. This is displayed in the label on line 26. On line 12, you create a new CultureInfo object based on the primary language retrieved from line 6. Lines 13 20 display various properties of this culture, including its name, abbreviation, and date, formatted appropriately for the culture. To format the date properly, you use the ToString method with two parameters: the first indicates how the date should be displayed, and the second is the CultureInfo object that provides customization info. Figure 19.9 shows the output from this listing when the primary language is French.
Figure 19.9. The output of the page is customized to the culture of the user, without any code modifications.
The CultureInfo object has a lot of properties that allow you to determine more detailed information. Table 19.1 lists the most common of these properties.
Table 19.1. CultureInfo Properties
|Property ||Description |
|Calendar ||The calendar used by the culture (that is, Gregorian, Korean, and so on). |
|CurrentCulture ||Retrieves the culture used by the server (not by your user's browser). Returns a CultureInfo object. This property is read-only. |
|CurrentUICulture ||Retrieves the culture used by the server. This property is used to specify the culture of resource files. See "Packing Resources into Files" later today. This property is read-only. |
|DateTimeFormat ||Returns a DateTimeFormatInfo object that specifies how to format date and time information for the culture. See the .NET SDK Documentation for more details. |
|DisplayName ||The full name of the CultureInfo object in the language used by the UI. |
|EnglishName ||The full name of the CultureInfo object in English. |
|Name ||Same as DisplayName, but read-only. |
|NativeName ||The full name of the CultureInfo object, in its native language. |
|NumberFormat ||Describes how to display numbers (that is, where to place commas, decimal points, and so on). Returns a NumberFormatInfo object. |
|ThreeLetterISOLanguageName ||The ISO 3166 standard three-letter code for the culture. |
|ThreeLetterWindowsLanguageName ||The Windows version of the three-letter code for the culture. |
|TwoLetterISOLanguageName ||The ISO 3166 standard two-letter code for the culture. |
You can also set the culture information for a page with the Culture attribute of the @ Page directive. The following line sets the culture to German (de):
<%@ Page Language="VB" Culture="de" %>
Whenever you use culture-specific objects in your ASP.NET page, they will use this value as the default instead of what is specified by the server. For example, when the culture is set to German, the code DateTime.Now.ToString("D") produces the output:
Mittwoch, 28. März 2001
When the culture is set to en-us, the same code generates the output:
Wednesday, March 28, 2001
This is an easy way to set the culture at the page level if you know ahead of time what you'll be using.
The CultureInfo.CurrentCulture property is actually a shortcut to the System.Threading.CurrentThread.CurrentCulture object. Both return a CultureInfo object that can be used as you have done here. The difference is that the former is read-only, whereas the latter is not. Therefore, when you want to change the current culture, use the latter object.
Culture is a property of the currently running thread (in other words, the currently running piece of the current application). When setting the culture information, ASP.NET does so for the entire application. Thus, you can access the culture information for current thread, as well as for the current CultureInfo object. (You'll use the System.Threading.CurrentThread object later today.)
In addition to the CultureInfo object, you can use RegionInfo, which provides information such as currency symbol and whether or not the region uses the metric system. The syntax for this object is similar to the CultureInfo object, but it doesn't use languages to present customized information. Rather, it uses country abbreviations. For instance, US for the United States and FR for France. Listing 19.11 shows an example.
Listing 19.11 Displaying Information About a User's Region
1: <%@ Page Language="VB" %> 2: <%@ Import Namespace="System.Globalization" %> 3: 4: <script runat="server"> 5: sub Page_Load(Sender as Object, e as EventArgs) 6: dim objRegion as RegionInfo 7: 8: if Page.IsPostBack then 9: objRegion = new RegionInfo(btRegion.Text) 10: else 11: objRegion = RegionInfo.CurrentRegion 12: end if 13: 14: lblMessage.Text = "Region: " & objRegion.Name & "<br>" 15: lblMessage.Text += "Full name: " & objRegion. _ 16: EnglishName & "<br>" 17: lblMessage.Text += "Currency: " & objRegion. _ 18: CurrencySymbol & "<br>" 19: lblMessage.Text += "Abbreviation: " & objRegion. _ 20: ThreeLetterISORegionName & "<br>" 21: lblMessage.Text += "Metric system: " & objRegion. _ 22: IsMetric 23: end sub 24: </script> 25: 26: <html><body> 27: <form runat="server"> 28: <b>Your information:</b> <p> 29: <asp:Label runat="server"/><p> 30: Change to (i.e. 'US', 'FR', 'JP', etc): 31: <asp:TextBox runat="server" 32: AutoPostBack=true /> 33: </form> 34: </body></html>
| || |
When this page loads for the first time, the default region is loaded into a RegionInfo object, as shown on line 11 with the CurrentRegion property. Lines 14 22 display properties of the region, such as the currency symbol and abbreviation. When a user enters information into the text box on line 31, the page is automatically posted because the AutoPostBack property is set to true. The region code entered by the user is then used to create a new RegionInfo object, as shown on line 9. Figure 19.10 shows the output for the region JP, or Japan.
Figure 19.10. The information is displayed for the specified region.
Table 19.2 lists the common properties of the RegionInfo object.
Table 19.2. RegionInfo Properties
|Property ||Description |
|CurrencySymbol ||The symbol used to represent monetary values in the specified region. |
|CurrentRegion ||Retrieves the region used by the server (not by your users). Returns a RegionInfo object. |
|DisplayName ||The full name of the RegionInfo object in the language used by the UI. |
|EnglishName ||The full name of the RegionInfo object in English. |
|IsMetric ||A Boolean value specifying whether the current region uses the metric system. |
|ISOCurrencySymbol ||The ISO code that represents CurrencySymbol. For example, USD is the ISO code for $. |
|Name ||Same as DisplayName, but read-only. |
|ThreeLetterISORegionName ||The ISO 3166 standard three-letter code for the region. |
|ThreeLetterWindowsRegionName ||The Windows version of the three-letter code for the region. |
|TwoLetterISORegionName ||The ISO 3166 standard two-letter code for the region. |
| || |
Finally, you can specify the encoding of the output of your ASP.NET pages. Encoding represents how characters are represented by the computer; for instance, Unicode or ASCII. ASP.NET, by default, uses Unicode, but encoding varies from region to region.
There are two different ways to specify the encoding for your application: directly in the ASP.NET page or in the web.config file. In the ASP.NET page, simply specify the encoding in the @ Page directive with the ResponseEncoding attribute:
<%@ Page Language="VB" ResponseEncoding="UTF-8" %>
In the web.config file, use the following syntax:
<configuration> <system.web> <globalization fileEncoding="utf-8" /> </system.web> </configuration>
So far, you've examined how to change the output of culture-specific objects, such as the DateTime object. What about plain text? How can you convert plain text into culture-specific languages? Unfortunately, there's no easy way to do so; you have to translate the text manually. When you have multiple languages in your site's audience, this usually means you have to create multiple versions of each page. Fortunately, you don't have to resort to that in ASP.NET; resource files are the answer.
Packing Resources into Files
Resource files are used to store application data apart from an application. You can have multiple versions of a resource file, so that your ASP.NET pages can present different information without requiring any changes in code. Using localization as an example, you can have multiple resource files one for each culture that will view your pages. Each resource file contains the same information, but translated into different languages.
Let's examine a simple ASP.NET page with content that can be extracted into resource files, shown in Listing 19.12.
Listing 19.12 An ASP.NET Page Used for Resource Extraction
1: <%@ Page Language="VB" %> 2: 3: <script runat="server"> 4: sub Page_Load(Sender as Object, e as EventArgs) 5: lblMessage.Text = DateTime.Now.ToString("t") 6: end sub 7: </script> 8: 9: <html><body> 10: <b>Welcome!</b> The time is now: 11: <asp:Label runat="server"/><p> 12: 13: This page demonstrates using resource files with 14: ASP.NET.<p> 15: 16: <font size=1>Don't forget to try this at home!</font> 17: </body></html>
This listing is uncomplicated. It displays some static text as HTML, and the current time in the label on line 11. Figure 19.11 shows the output of this page.
Figure 19.11. A simple ASP.NET page ready for a resource file.
Suppose that you wanted to translate this page into French. You could create a new file with the same format but different text, but why bother? You could create two separate resource files instead. These are pure text files, and can be created with any text editor. Let's make the English version first. Type the following code into your text editor:
Greeting=Welcome! Time=The time is now: Blurb=This page demonstrates using resource files with ASP.NET Disclaimer=<font size=1>Don't forget to try this at home! </font>
Save this file as data.en-us.txt (I'll get to the reason behind the filename in a moment). This file contains one section that contains all your string values. The information is organized into key/value pairs. In the line
Greeting is the name of the resource you'll refer to in your ASP.NET page, whereas Welcome! is the actual value that will be displayed to the user. The French version is as follows:
Greeting=Bonjour! Time=L'heure maintenent est: Blurb=Cette page démonste comment utiliser les files de resource é ASP.NET Disclaimer=<font size=1>N'oublie pas essayez de faire ceci chez soi!</font>
Save this file as data.fr-fr.txt. (Keep each string value on one line! You'll receive an error if there are line breaks in the string.) Notice that the key names haven't changed. They must stay the same or your ASP.NET page won't be able to find the resources.
ASP.NET, however, can't use these text files as they are. You must convert them into a format that ASP.NET understands, using the Resource Generator tool (resgen.exe). This tool converts the format and spits out .resources files. Open a command prompt and navigate to the directory you saved these .txt files in. Type the following commands into the window (don't forget to press Enter after each command):
resgen data.en-us.txt resgen data.fr-fr.txt
After each command, you will see something like the following:
Read in 4 resources from 'data.en-us.txt' Writing resource file... Done.
You will now see two additional files in your directory: data.en-us.resources and data.fr-fr.resources. These files are readable by ASP.NET's resource manager, so let's use them in your pages!
The System.Resources.ResourceManager object is responsible for handling all resource files for ASP.NET. This object finds the resource file that corresponds to the user's culture, and loads all resources within that file. Listing 19.13 shows this object in action.
Listing 19.13 Using the ResourceManager to Load Resource Files
1: <%@ Page Language="VB" %> 2: <%@ Import Namespace="System.Globalization" %> 3: <%@ Import namespace="System.Resources" %> 4: <%@ Import namespace="System.Threading" %> 5: 6: <script runat="server"> 7: sub Page_Load(Sender as Object, e as EventArgs) 8: dim objRM as ResourceManager 9: dim strLanguage as string = Request.UserLanguages(0). _ 10: ToString 11: dim objCulture as new CultureInfo(strLanguage) 12: Thread.CurrentThread.CurrentCulture = new _ 13: CultureInfo(strLanguage) 14: Thread.CurrentThread.CurrentUICulture = new _ 15: CultureInfo(strLanguage) 16: 17: objRM = ResourceManager. _ 18: CreateFileBasedResourceManager("data", _ 19: Server.MapPath("."), Nothing) 20: 21: lblGreeting.Text = objRM.GetString("Greeting") 22: lblTime.Text = objRM.GetString("Time") & " " & _ 23: DateTime.Now.ToString("t") 24: lblBlurb.Text = objRM.GetString("Blurb") 25: lblDisclaimer.Text = objRM.GetString("Disclaimer") 26: 27: objRM.ReleaseAllResources 28: end sub 29: </script> 30: 31: <html><body> 32: <b><asp:Label runat="server"/></b> 33: <asp:Label runat="server"/><p> 34: 35: <asp:Label runat="server"/><p> 36: 37: <asp:Label runat="server"/> 38: </body></html>
| || |
First, note the additional namespaces on lines 3 and 4. System.Resources is needed to use the ResourceManager object; the System.Threading namespace is needed, too (its purpose will be explained in a moment). On line 8, you declare a new ResourceManager just as with any other object. On lines 9 and 10, you retrieve your visitor's primary language from the browser, as you did in Listing 19.9.
Lines 12 15 set the culture information for this page based on the primary language. Recall from the "Localizing Your Application" section earlier today that you can set the culture with the System.Threading.CurrentThread.CurrentCulture property, which takes and returns a CultureInfo object. On line 12, you use the primary language to set the CurrentCulture property to the proper culture. All localizable objects will now use the new culture you've specified (for example, the time, in the French culture, will display 22:06 instead of 10:06 PM).
However, just changing the culture won't cause ASP.NET to retrieve resources from a different resource file. You must set the System.Threading.CurrentThread.CurrentUICulture property to the new culture as well, as shown on lines 14 15. It is this property that ASP.NET uses to determine which resource files to load.
On lines 17 19, you do the actual loading of the resource file with the CreateFileBasedResourceManager method. This method takes three parameters: a prefix to use for your resource files, the path of the resource files, and an optional object used to parse your resources. In the second parameter, you use the Server.MapPath method to return the physical path of the directory where your resource files are stored. (Refer to "The HttpServerUtility Object" in Day 4, "Using ASP.NET Objects with C# and VB.NET," for more information on the MapPath method.) You don't need another object to parse your resource files, so the third parameter is Nothing.
Recall that you named each of your resource files in the format: data.culture.resources. The CreateFileBasedResourceManager method searches for files with the format prefix.culture.resources. Thus, the prefix you specify in the first parameter of this method must be the prefix you used for your resource files' names. In this case, when the culture is en-us (English, United States), this method searches for the file data.en-us.resources. Likewise, if the culture were fr-fr (French), this method would look for the data.fr-fr.resources file. The prefix serves as a logical way to group your resource files.
Finally, on lines 21 25, you use the GetString method of the ResourceManager to retrieve the key/value pairs stored in the resource files. Each of these values is displayed in label controls shown in lines 30 35. When the resource manager opens the resource files, it places a lock on them so that no other application can alter them while they are in use. Call ReleaseAllResources to release the lock on these files, as shown on line 27. Figures 19.12 and 19.13 show the output of this listing when the culture is en-us and fr-fr, respectively.
Figure 19.12. When the culture is set to en-us, ASP.NET uses the data.en-us.resources resource file.
Figure 19.13. When the culture is set to fr-fr, ASP.NET uses the data.fr-fr.resources resource file.
Recall that you can set the culture from the @ Page directive as well:
<%@ Page Language="VB" Culture="fr" %>
If you do this, the code in Listing 19.13 will not pull from the proper resource file. This is because line 9 set the culture from the value retrieved from the user's browser, and not the value in the @ Page directive. To fix this, change lines 9 11 to the following line:
dim strLanguage as string = CultureInfo.CurrentCulture.ToString
No code changes are needed at all. Depending on the user's culture, he will see a different page. Using resource files is akin to storing customized information in a database and retrieving specific data depending on the user's preferences.
Because the culture information should be constant throughout your application, you might consider setting the culture in the Application_BeginRequest method of the global.asax file. This way, the culture is properly set automatically upon every new request to the server.
Also, you could create the ResourceManager object and store it as an application-level variable in the Application_OnStart event to avoid having to re-create it with every new request. For example, the following code could be added to the Application OnStart event in global.asax:
Application("RM") = New ResourceManager("data", _ Server.MapPath("."), Nothing)
You now have one ResourceManager available to your entire application, which can be accessed through the Application("RM") variable.