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 the Page.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.