Localization


In an ASP.NET Web application, the page developer is ultimately responsible for localizing the application by appropriately specifying the culture , or locale, associated with the pages in the application. As a component developer, you can support the page developer by enabling localization of your controls. To support localization, you should use as few hard-coded strings as possible in the rendering generated by your controls because these strings are impossible to localize. Instead, your controls should expose string properties to which the page developer can assign localized values. If you do use fixed strings in the rendering of your controls, you should package those strings as resources and then provide localized versions of those strings via satellite assemblies . Satellite assemblies are resource-only assemblies that contain resource data for a specific culture. We will show how you can create satellite assemblies later in this section. In addition to localizing any strings you use at run time, you should localize strings that are visible to the page developer through the user interface of a design-time tool.

The System.Globalization.CultureInfo class is an object that encapsulates preferences specific to a culture, such as the name of the locale (for example, fr-FR, which stands for French that is used in France), the calendar information, and the date and number formatting conventions. Each thread of execution in a managed application has two CultureInfo objects associated with it: a Culture instance, which affects formatting, sorting, and so on; and a UICulture instance, which determines the selection of the satellite assembly whose resources are to be used. These properties default to the locale of the operating system, which might not always be desirable for Web applications because a Web server's culture isn't necessarily significant to the user of the application. These properties can be set in code, as shown here:

 Thread.CurrentThread.CurrentCulture=newCultureInfo("fr-FR"); Thread.CurrentThread.CurrentUICulure=newCultureInfo("fr-FR"); 

In an .aspx page, the page developer can also specify the culture in a declarative manner within the Page directive, as shown here:

 <%@PageCulture="fr-FR" UICulture="fr-FR" %> 

The page automatically sets the CurrentCulture and CurrentUICulture properties of the thread being used to process the request by using the previous code. As a result, any APIs used by the page developer and by controls on the page that depend on culture-specific information automatically use the culture specified on the page.

Using Resources in Controls

In this section, we will implement a GreetingLabel control that mimics a Label and incorporates localization to render a localized version of the commonly used "Hello, World" greeting. In addition, GreetingLabel demonstrates how you can localize metadata attributes that contain string values that are displayed in the UI of a design-time environment.

Figures 17-1 and 17-2 show the same page requested for the U.S. English (en-US) and France French (fr-FR) cultures. The GreetingLabel contained in the page renders text containing the localized greeting.

Figure 17-1. U.S. English greeting shown by requesting http://localhost/BookWeb/Chapter17/GreetingLabelTest.aspx

graphics/f17hn01.jpg

Figure 17-2. France French greeting shown by requesting http://localhost/BookWeb/Chapter17/GreetingLabelTest.aspx?culture=fr-FR

graphics/f17hn02.jpg

Notice that the culture to be used is specified as an argument on the query string of the URL in this sample page. In a more realistic application, a page developer might decide to automatically detect it (by using the HTTP_ACCEPT_LANGUAGE server variable), or might allow the user to pick a locale, or might use other profile information of the user to determine the culture.

Listing 17-1 contains the page used to demonstrate the GreetingLabel control. Notice that this page does not contain any code to localize the control. The code in the page simply sets the culture properties of the current thread.

Listing 17-1 GreetingLabelTest.aspx
 <%@Pagelanguage="c#" %> <%@ImportNamespace="System.Globalization" %> <%@ImportNamespace="System.Threading" %> <%@RegisterTagPrefix="msp" Assembly="MSPress.ServerControls" Namespace="MSPress.ServerControls" %> <html> <scriptrunat="server"> publicvoidPage_Init(objectsender,EventArgse){ stringculture=Request.QueryString["culture"]; if(culture==null){ culture= "en-US"; } CultureInfonewCulture=newCultureInfo(culture); Thread.CurrentThread.CurrentCulture=newCulture; Thread.CurrentThread.CurrentUICulture=newCulture; Literal1.Text=newCulture.Name; } protectedvoidButton1_Click(objectsender,EventArgse){ stringtext=TextBox1.Text.Trim(); if(text.Length!=0){ GreetingLabel1.UserName=Server.HtmlEncode(text); GreetingLabel1.Visible=true; } } </script> <head> <title>GreetingLabelSample</title> </head> <body> <formmethod="post" runat="server" ID="Form1"> <p> [CurrentCulture:<asp:Literalrunat="server" id="Literal1"/>] <br/> Enteryourname: <br/> <asp:TextBoxid="TextBox1" runat="server" Columns="40"/>&nbsp; <asp:Buttonid="Button1" runat="server" Text="OK" onclick="Button1_Click"/> <hr> <msp:GreetingLabelid="GreetingLabel1" runat="server" Visible="false"/> </p> </form> </body> </html> 

Listing 17-2 contains the code for the GreetingLabel control.

Listing 17-2 GreetingLabel.cs uses localized strings in its rendering and localized metadata.
 usingSystem; usingSystem.ComponentModel; usingSystem.Web.UI; usingSystem.Web.UI.WebControls; namespaceMSPress.ServerControls{ publicclassGreetingLabel:WebControl{ [ Bindable(true), DefaultValue(""), LocalizedCategory("Greeting"), LocalizedDescription("GreetingLabel_UserName") ] publicvirtualstringUserName{ get{ strings=(string)ViewState["UserName"]; return(s==null)?String.Empty:s; } set{ ViewState["UserName"]=value; } } protectedoverridevoidRenderContents(HtmlTextWriterwriter){ stringgreetingFormat= AssemblyResourceManager.GetString("GreetingLabel_Format"); if(greetingFormat==null){ writer.Write(UserName); } else{ writer.Write(greetingFormat,UserName); } } } } 

GreetingLabel demonstrates how a control can use resources that are embedded in the control's assembly or its satellite assemblies. Because GreetingLabel uses a fixed formatting string to generate the greeting text, it loads the value of the string from its resources by using the GetString method of AssemblyResourceManager (shown later in the section). This utility class provides access to resources associated with the control's assembly. Placing the string value into resources allows the control to load localized versions of the greeting format.

GreetingLabel exposes a UserName property that is associated with localized versions of the DescriptionAttribute and CategoryAttribute metadata. Rather than using these attribute types directly, the control uses derived implementations ( LocalizedDescriptionAttribute and LocalizedCategoryAttribute ) of these attributes that contain the logic to load their values by using AssemblyResourceManager . We will cover their implementation later in this section.

Embedding and Accessing Resources

Resources are defined in .resx files in an XML format. The .resx file is converted into a binary .resources format and embedded into an assembly as part of the compilation process. At run time, the embedded resources can be accessed by using the System.Resources.ResourceManager class.

Resources can be classified into two broad categories: culture-neutral and culture-specific . Culture-neutral resources (typically U.S. English) are embedded into the main assembly and are used when a culture-specific resource is not found as part of a fallback mechanism. Culture-specific resources are embedded into satellite assemblies. Each satellite assembly is associated with one particular culture.

Listings 17-3 and 17-4 show the relevant portions of the .resx files that contain resource strings used by the GreetingLabel control.

Listing 17-3 Resources.resx contains the culture-neutral set of resources.
 <root>  <dataname="GreetingLabel_UserName"> <value>Thepersontowhomthegreetingisaddressed.</value> </data> <dataname="Category_Greeting"> <value>Greeting</value> </data> <dataname="GreetingLabel_Format"> <value>Hello,{0}!</value> </data> </root> 
Listing 17-4 Resources.fr-FR.resx contains the France-French versions of the same set of resources.
 <root>  <dataname="GreetingLabel_UserName"> <value>Lapersonnequilasalutationestadresse.</value> </data> <dataname="Category_Greeting"> <value>Salutation</value> </data> <dataname="GreetingLabel_Format"> <value>Bonjour,{0}!</value> </data> </root> 

Microsoft Visual Studio .NET automatically converts these .resx files into .resources during its compilation process. The compiler then embeds the culture-neutral resources into the main assembly and compiles the culture-specific resources into a separate satellite assembly. You can obtain the same results by using the tools provided in the .NET Framework SDK and performing the following steps:

  1. Compile the .resx files into .resources files by using the resgen .exe tool:

     resgenResources.resxMSPress.ServerControls.Resources.resources resgenResources.fr-FR.resxMSPress.ServerControls.Resources.fr- FR.resources 
  2. Compile the main assembly, MSPress.ServerControls.dll, by using csc.exe, and include the culture-neutral resources in MSPress.ServerControls.Resources.resources by using the compiler's /res option:

     csc/t:library/out:MSPress.ServerControls.dll /r:System.dll/r:System.Web.dll/r:System.Drawing.dll /r:System.Design.dll /res:MSPress.ServerControls.Resources.resources, MSPress.ServerControls.Resources.resources *.cs 
  3. Generate the satellite assembly for the fr-FR culture, MSPress.ServerControls.resources.dll, by using the al.exe tool:

     al/t:lib/embed:MSPress.ServerControls.Resources.fr-FR.resources /culture:fr-FR/template:MSPress.ServerControls.dll /out:MSPress.ServerControls.resources.dll 

To use the satellite assembly in a Web application, you need to place it inside a subfolder of the application's bin directory that has the same name as the culture it corresponds to, such as "fr-FR." For example, if the BookWeb application is rooted at C:\Inetpub\wwwroot\BookWeb and its bin directory is C:\Inetpub\wwwroot\BookWeb\bin, the path containing resources for the France-French culture will be C:\Inetpub\ wwwroot \BookWeb\bin\fr-FR.

Note

Visual Studio .NET can generate satellite assemblies for a Web control project but does not automatically copy the satellite assemblies into a Web project that depends on the Web control project, even though it does copy the main assembly containing the implementation of the control into the bin directory of the Web application. Therefore, you must create the appropriate directory structure and copy the satellite assembly manually.


The ResourceManager class selects the appropriate resources based on the culture specified by the UICulture property of the current thread. ResourceManager implements a fallback mechanism. For example, when the UICulture is "fr-FR" (France-French), it first attempts to load the "fr-FR" version of the resources from a satellite assembly. If it fails to find the correct version, it attempts to locate a less specific "fr" (French) version of the resources. If it fails to find that version as well, it falls back on the culture-neutral version of resources that are embedded in the main assembly. This fallback logic implemented by ResourceManager is transparent to an application.

You can use the ResourceManager class to access resources directly in your control. However, as shown in Listing 17-2, we used the AssemblyResourceManager class instead. The AssemblyResourceManager utility class caches an instance of the ResourceManager class to provide optimized access to resources associated with the assembly, as Listing 17-5 shows.

Listing 17-5 AssemblyResourceManager.cs
 usingSystem; usingSystem.Diagnostics; usingSystem.Globalization; usingSystem.Reflection; usingSystem.Resources; 
 namespaceMSPress.ServerControls{ internalsealedclassAssemblyResourceManager{ privatestaticResourceManager_resources; privateAssemblyResourceManager(){ } privatestaticvoidEnsureResources(){ if(_resources==null){ try{ _resources= newResourceManager("MSPress.ServerControls.Resources", typeof(AssemblyResourceManager).Assembly); } catch{ } } } publicstaticobjectGetObject(stringname){ returnGetObject(null,name); } publicstaticobjectGetObject(CultureInfoculture, stringname){ EnsureResources(); if(_resources!=null){ return_resources.GetObject(name,culture); } returnnull; } publicstaticstringGetString(stringname){ returnGetString(null,name); } publicstaticstringGetString(CultureInfoculture, stringname){ EnsureResources(); if(_resources!=null){ return_resources.GetString(name,culture); } returnnull; } } } 

The AssemblyResourceManager class has the following key characteristics:

  • Is a thin wrapper around a ResourceManager object. AssemblyResourceManager instantiates its underlying ResourceManager once and caches it in a static variable to optimize the performance associated with resource loading. The implementation loads the resources by their name, "MSPress.ServerControls.Resources." ResourceManager contains the logic to load localized resources when needed by loading satellite assemblies with resources with names such as "MSPress.ServerControls.Resources.fr-FR."

  • Is marked as internal ”in other words, assembly scoped ”because the class is meant for use only by other classes within the same assembly that need to access resources embedded in their common assembly.

  • Provides access to strings and arbitrary objects serialized into resources. Strings are by far the most commonly stored resources and are accessed by using the GetString method. However, .resx files and resources files also allow other objects (such as images) to be stored. You can access these objects by using the GetObject method.

Although this utility class is optional and ResourceManager can be used directly, we strongly recommend that you use this class in your own assembly to benefit from the caching mechanism that it implements.

Localizing Metadata Attributes

Some metadata attributes such as DescriptionAttribute and CategoryAttribute are used to specify string values that are presented to the user in the UI of a design-time environment. If you want your control to fit in naturally with a localized version of the design-time environment, you need to enable localization of metadata by creating your own assembly-scoped, derived versions of these attribute classes that extract their values from the resources associated with your assembly. The GreetingLabel class shown in Listing 17-2 makes use of these derived, localizable metadata attributes for its UserName property.

Listing 17-6 shows the implementation of LocalizedDescriptionAttribute .

Listing 17-6 LocalizedDescriptionAttribute.cs
 usingSystem; usingSystem.Diagnostics; usingSystem.ComponentModel; 
 namespaceMSPress.ServerControls{ [AttributeUsage(AttributeTargets.All)] internalsealedclassLocalizedDescriptionAttribute: DescriptionAttribute{ privatebool_localized; publicLocalizedDescriptionAttribute(stringresourceKey): base(resourceKey){ } publicoverridestringDescription{ get{ //Localizethedescriptionthefirsttimeitis //accessedbyusingthecurrentdescriptionasthe //nameoftheresourceandreplacingitwiththe //stringvalueretrievedfromtheresourcemanager. if(_localized==false){ _localized=true; try{ stringresourceKey=base.Description; stringlocalizedDescription= AssemblyResourceManager.GetString(resourceKey); Debug.Assert(localizedDescription!=null,  "Resourcenotfound:'" + resourceKey+ "'"); if(localizedDescription!=null){ base.DescriptionValue= localizedDescription; } } catch{ } } returnbase.Description; } } } } 

The implementation of LocalizedDescriptionAttribute exhibits the following details:

  • The class derives from DescriptionAttribute so that the property browser can use it to retrieve description text for properties and events.

  • LocalizedDescriptionAttribute is marked as sealed. Sealed metadata attributes are much more performant.

  • The class is marked as internal, or assembly scoped, because it loads assembly-specific resources and is therefore usable only by classes within the same assembly.

  • The constructor accepts the key to the resource value, rather than the resource value itself. Attribute constructors can take as their parameters only those values that are constant at compile time.

  • When the Description property getter is invoked the first time, the attribute implementation loads the resource value. To do so, the implementation uses the GetString method of AssemblyResourceManager based on the resource key that was specified in the constructor.

Listing 17-7 shows the implementation of LocalizedCategoryAttribute .

Listing 17-7 LocalizedCategoryAttribute.cs
 usingSystem; usingSystem.Diagnostics; usingSystem.ComponentModel; namespaceMSPress.ServerControls{ [AttributeUsage(AttributeTargets.All)] internalsealedclassLocalizedCategoryAttribute: CategoryAttribute{ publicLocalizedCategoryAttribute(stringresourceKey): base(resourceKey){ } protectedoverridestringGetLocalizedString(stringvalue){ stringlocalizedValue=base.GetLocalizedString(value); if(localizedValue==null){ localizedValue= AssemblyResourceManager.GetString("Category_" +value); } 
 Debug.Assert(localizedValue!=null,  "Resourcestring:'Category_" +value+ "'notfound."); return"localizedValue; } } } 

The implementation of LocalizedCategoryAttribute exhibits the following details:

  • The class derives from CategoryAttribute so that the property browser can use it to retrieve category information for properties and events.

  • As with LocalizedDescriptionAttribute , the class is marked as both sealed and internal ”in other words, assembly scoped.

  • The constructor takes in the nonlocalized name of the category, which is constant at compile time. All attribute constructors are restricted to using constant values for their parameters.

  • LocalizedCategoryAttribute overrides the GetLocalizedString method to return the localized name of the category. The class first invokes the corresponding method of its base class, CategoryAttribute . The base class is responsible for localizing standard category names such as "Appearance" and "Behavior" so that they are consistent across all components . If the base class does not support a particular category name, LocalizedCategoryAttribute looks up the localized name of the category in the assembly's resources by using the AssemblyResourceManager class. By convention, the resource key used for a category name is the string "Category_" followed by the nonlocalized name.

When you need to localize the controls you develop, you must first define the culture-neutral string resources that you embed into the main assembly. Next you must create your own assembly-specific implementations of AssemblyResourceManager and localized metadata attributes based on the code shown in this section. You can then add new satellite assemblies containing localized values of the same string resources for each culture you decide to add support for.



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net