Globalization remarks
Based on a novel by Michael Ketting
The MultiLingualResources
attribute and classes
You can specify more than one MultiLingualResources
attribute for a class. Each attribute associates the class with a resource file.
If you derive more classes from the attributed class, you can combine the attributes along the line of ancestry. Resources for sub-classes extend the resources of their base-classes. Identical resource-IDs in sub-classes override those in the base-class.
Implementation of IObjectWithResources
The page, or user control, shall implement IObjectWithResources
as follows:
using Remotion.Globalization; IResourceManager IObjectWithResources.GetResourceManager() { return this.GetResourceManager(); } protected virtual IResourceManager GetResourceManager() { Type type = this.GetType(); if (MultiLingualResourcesAttribute.ExistsResource (type)) return MultiLingualResourcesAttribute.GetResourceManager (type, true); else return null; }
Usually this facility is provided in a common base-page, or a common base-user control. The derived pages or user controls only the MultiLingualResources
attribute is specified. If a base-class can be used, the implementation of IObjectWithResources
is stored in the corresponding code-behind files.
Accessing resources
For accessing resources we recommend the ResourceManagerUtility
:
using Remotion.Globalization; using Remotion.Web.UI.Globalization; IResourceManager rm = ResourceManagerUtility.GetResourceManager (this);
The utility iterates over the control hierarchy, starting at the control passed as a parameter, going up to the page if necessary. If IObjectWithResources
is implemented as part of the control or the page, the IResourceManager
will be queried and cached. Finally, the IResourceManager
objects of the control hierarchy will be combined and passed as a single instance of IResourceManager
.
Querying a resource entry (identified by Res1
in this example) works like this:
string valueRes1 = rm.GetString ("Res1");
Defining resource-IDs
For unique resource-IDs we recommend enumerating them, as in this example:
using Remotion.Globalization; namespace MyProject { class MyPage : System.Web.UI.Page { [ResourceIdentifiers] public enum ResourceIdentifiers { ID1, ID2 } } }
The enum can be part of a page or a user control or can be defined as "stand-alone".
Accessing resource-IDs
The typical place where to put resources is into resource files identified by a MultiLingualResources
attribute. The demand outlined above for pages or user controls accessing the resource applies to resource-IDs as well: They shall be accessed via IResourceManager
and ResourceManagerUtility
.
using Remotion.Globalization; using Remotion.Web.UI.Globalization; IResourceManager rm = ResourceManagerUtility.GetResourceManager (this);
The resource entry itself is identified by the enum value:
string valueID1 = rm.GetString (ResourceIdentifiers.ID1);
In the resource file itself the entry is identified by
- its namespace
- a class name
- an enum value
- (e.g.
MyProject.MyPage.ID1
)
If an enum is not within a page, the following string is used as a substitute: MyProject.ResourceIdentifiers.ID1
. In other words, the resource-ID is composed of
- its namespace
- the enum type
- the enum value
You can derive a list of all resource-IDs defined with a ResourceIdentifiersAttribute
in a module with the resource summary utility. Xquestion
Defining global resources
If resources are required outside an ASP.NET page, you can use the global ResourceManagerProvider
. The ResourceManagerProvider
is a singleton where you can register your implementations of IResourceManager
or IObjectWithResources
.
Registering global resources
The mechanics should be obvious from the following sample snippets. For IObjectWithResources
implementations:
using Remotion.Globalization; ResourceManagerProvider.Register (IObjectWithResources objectWithResources);
or, for IResourceManager
implementations:
using Remotion.Globalization; ResourceManagerProvider.Register (Type type, IResourceManager resourceManager);
The ResourceManagerProvider
caches the registered IResourceManager
|s in a static hash-table, using the passed type parameter as key. Therefore
you should only register IResourceManager
s that don't change between post-backs. What's more, there should be no dependencies between those
IResourceManager
objects, because hashing makes the order of evaluation unpredictable. For ASP.NET we recommend to register global resources through the HttpApplication
instance:
using Remotion.Globalization; [MultilingualResourcesAttribute ("MyApplication.Globalization.Global")] public class Global : System.Web.HttpApplication, IObjectWithResources { protected void Application_Start(Object sender, EventArgs e) { ResourceProvider.Register (this); } // IObjectWithResources Implementation }
Accessing global resources
The ResourceManagerProvider
gives you the registered IResourceManager
implementations as a combined IResourceManager
object. Due to hashing, their order is unpredictable. Here is a sample code snippet:
using Remotion.Globalization; IResourceManager rm = ResourceManagerProvider.GetResources ();
The resource entry itself is identified by a string or an enum value, just as with page- or control-local resources:
string valueRes1 = rm.GetString ("Res1");
or
string valueID1 = rm.GetString (ResourceIdentifiers.ID1);
By default, the ResourceManagerUtility
includes the ResourceManagerProvider
in the returned IResourceManager
instance. The global resources have the lowest priority of those, i.e. resources of a page or control can override them.
Multilingual ASP.NET applications
ID-dependent resources
You can define resources for each control individually, for example the Text
property for a label. This requires the ResourceDispatcher
facility. You specify the resource-IDs for a uniquely identified control on the page, for example:
auto:FirstNameLabel:Text
The best place where to dispatch a resource is in the pre-render phase of the page. Sample code:
IResourceManager rm = ResourceManagerUtility.GetResourceManager (this); ResourceDispatcher.Dispatch (this, rm);
Defining resources for properties of the page itself is also possible, the page is simply called this
, as in
auto:this:Title
Shared resources
You can assign a resource to multiple controls (e.g. a validator message) by simply using its ID in the property's value. Examples:
FirstNameRequiredValidator.ErrorMessage = "$res:RequiredValidationError"; LastNameRequiredValidator.ErrorMessage = "$res:RequiredValidationError";
It is usually more convenient to set the property in Visual Studio's designer; just use the resource-ID instead of the message itself.
In the resource file itself the prefix $res
is redundant and not allowed. For the example above, the ID is written as RequiredValidationError
in the resource file.
This feature is implemented by the controls themselves, not the resource manager. However, the controls in Remotion.Web
and Remotion.ObjectBinding.Web
do support this feature.
re-motion controls
The controls in the Remotion.Web.*
and Remotion.ObjectBinding.Web
assemblies come with standart text for English and German. For those languages you need to localize only the field identifiers of your application.
A good place for localizing strings for other languages is in the resources for the BasePage
. A better place is in the globally defined resources. The IDs of the desired resources can be determined with the resource summary utility.
Selecting a language
For enabling the correct language for resources you must set the thread's CurrentUICulture
. CurrentCulture
is only used for formatting (dates, numbers, etc.) The culture should be set during the load phase (or earlier), otherwise complete coverage of all affected stages by the correct language cannot be assured. Sample code:
using System.Threading; Thread.CurrentThread.CurrentCulture = // CultureInfo Object Thread.CurrentThread.CurrentUICulture = // CultureInfo Object
There are two ways to find out which language to select for the user:
- Provide options in the application (hyperlinks, user settings).
- Inspect the
UserLanguages
collection of thePage.Request
object. It holds the language selected in the user's browser.
If you don't set the culture in your code, the framework inspects the globalization
section in the Web.config
file. If no entry is found, the setting in the process is used as the default culture.