Dashboard > Spring.NET > ... > Spring.Web > Spring.Web Localization
  Spring.NET Log In View a printable version of the current page.  
  Spring.Web Localization
Added by Aleksandar Seovic, last edited by Mark Pollack on Nov 27, 2005  (view change)
Labels: 
(None)

Warning

This documentation has been moved to docbook format and now resides on the Spring.NET website. If you feel the need to edit this document please send and email to the Spring.NET developer list.

If you are new to ASP.NET Globalization and Localization, you may wish to review Globalization Architecture for ASP.NET
and Localization Practices for ASP.NET 2.0 by Michele Leroux Bustamante.

While the .NET framework has excellent support for localization, the support within ASP.NET 1.x is somewhat incomplete.

Each page in a typical ASP.Net project has a resource file associated with it, but those resources are never used. ASP.Net 2.0 will change that and allow you to use local resources for the page. In the meantime, we built support for that into Spring.Web so you can start using the new features immediately.

Spring.Web supports several different approaches to localization within web application, which can be mixed as appropriate. Both push and pull mechanism are supported, as well as fallback to globally defined resources when a local resource cannot be found. Spring also provides support for user culture management and image localization, which are described in the later sections.

Automatic Localization Using Localizers ("Push" Localization)

The idea is that you should be able to specify localization resources in the resource file for the page and have them applied automatically to UI controls by the framework. For example, you could define a page like this:

UserRegistration.aspx
<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %>
<%@ Page language="c#" Codebehind="UserRegistration.aspx.cs" 
    AutoEventWireup="false" Inherits="ArtFair.Web.UI.Forms.UserRegistration" %>
<HTML>
    <body>
        <spring:Content id="mainContent" contentPlaceholderId="main" runat="server">
            <div align="right">
                <asp:LinkButton ID="english" Runat="server" CommandArgument="en-US">English</asp:LinkButton>&nbsp;
                <asp:LinkButton ID="serbian" Runat="server" CommandArgument="sr-SP-Latn">Srpski</asp:LinkButton>
            </div>
            <TABLE>
                <TR>
                    <TD>
                        <asp:Label id="emailLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="email" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="passwordLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="password" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="passwordConfirmationLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="passwordConfirmation" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="nameLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="name" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="street1Label" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="street1" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="street2Label" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="street2" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="cityLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="city" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="stateLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="state" Runat="server" Width="30px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="postalCodeLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="postalCode" Runat="server" Width="60px"/></TD>
                </TR>
                <TR>
                    <TD>
                        <asp:Label id="countryLabel" Runat="server"/></TD>
                    <TD>
                        <asp:TextBox id="country" Runat="server" Width="150px"/></TD>
                </TR>
                <TR>
                    <TD colspan="2">
                        <asp:Button id="saveButton" Runat="server"/>&nbsp;
                        <asp:Button id="cancelButton" Runat="server"/></TD>
                </TR>
            </TABLE>
        </spring:Content>
    </body>
</HTML>

If you look at the .aspx code carefully, you will notice that none of the Label or Button controls have Text property defined. The values of the Text property for these controls are stored in the local resource file using '$this.<controlId>.<propertyName>' syntax to identify the resources:

UserRegistration.aspx.resx
<root>
  ...
  <data name="$this.emailLabel.Text">
    <value>Email:</value>
  </data>
  <data name="$this.passwordLabel.Text">
    <value>Password:</value>
  </data>
  <data name="$this.passwordConfirmationLabel.Text">
    <value>Confirm password:</value>
  </data>
  <data name="$this.nameLabel.Text">
    <value>Full name:</value>
  </data>
  <data name="$this.street1Label.Text">
    <value>Street 1:</value>
  </data>
  <data name="$this.street2Label.Text">
    <value>Street 2:</value>
  </data>
  <data name="$this.cityLabel.Text">
    <value>City:</value>
  </data>
  <data name="$this.stateLabel.Text">
    <value>State:</value>
  </data>
  <data name="$this.postalCodeLabel.Text">
    <value>ZIP:</value>
  </data>
  <data name="$this.countryLabel.Text">
    <value>Country:</value>
  </data>
  <data name="$this.saveButton.Text">
    <value>$messageSource.save</value>
  </data>
  <data name="$this.cancelButton.Text">
    <value>$messageSource.cancel</value>
  </data>
</root>

The last two resource definitions require some additional explanation.

In some cases, you would like to apply a global resource automatically – in this example, it makes perfect sense to define values for Save and Cancel buttons globally, as they will probably be used throughout the application.

Example above shows you how you can do that by definining a resource redirection expression as the value of your local resource, by prefixing global resource name with '$messageSource'. This will tell localizer to use 'save' and 'cancel' as lookup keys and retrieve actual values from the global message source.

Important thing to remember is that you only need to define resource redirect once, in the invariant resource file – any lookup for them will simply fall back to the invariant culture, and result in a global message source lookup using correct culture.

To view the .resx file for a page, you may need to enable "Project/Show All Files" in Visual Studio. When "Show All Files" is enabled, the .resx file appears like a "child" of the code-behind page.

When Visual Studio creates the .resx file, it will include a xds:schema element and several reshead elements. Your data elements will follow the reshead elements. When working with the .resx files, you may want to chose "Open With" from the context menu and select the "Source Code" text editor.

Working with Localizers

In order to apply these resources automatically, you will need to define a localizer that should be used by the page – localizer will inspect the resource file when the page is first requested, cache the resources that start with '$this', and apply the values to the controls before the page is rendered.

Localizer is simply an object that implements Spring.Globalization.ILocalizer interface by extending Spring.Globalization.AbstractLocalizer and overriding abstract method LoadResources, which should load and return a list of all the resources that should be automatically applied from the resource store.

Spring.NET ships with one localizer, Spring.Globalization.Localizers.ResourceSetLocalizer, that retrieves a list of resources to apply from the local resource file. In the future, we will provide localizers that read resources from the XML file or flat text file that contains resource name-value pairs, which will allow you to store resources within the files in your web application instead of as embedded resources in the assembly. Of course, if you would rather store them in a database, you can simply write your own ILocalizer implementation that will load a list of resources to apply from your database.

You would typically configure localizer within abstract base definition for your pages:

Localizer Configuration
<object id="localizer" type="Spring.Globalization.Localizers.ResourceSetLocalizer, Spring.Core"/>

<object id="basePage" abstract="true">
    <description>
        Convenience base page definition for all the pages.
        
        Pages that reference this definition as their parent
        (see examples below) will automatically inherit following properties.
    </description>
    <property name="Localizer" ref="localizer"/>
    ...
</object>

Of course, nothing prevents you from defining different localizer for each page in the application, and you can always override the one specified above, or if you do not want any resources to be applied automatically you can completely ommit localizer definition.

One last thing to note is that Spring.NET UserControls will by default inherit localizer and other localization settings from the page they belong to, but you can override that behavior using dependency injection.

Applying Resources Manually ("Pull" Localization)

While automatic localization as described above works great for many form-like pages, it doesn't work nearly as well for the controls defined within any iterative controls (because the IDs are not fixed) and in cases where you need to display the same resource multiple times within the same page (think header columns for outgoing and return flights tables within travel application, for example).

In these situations, you should use pull mechanism for localization, which boils down to a simple GetMessage call:

Manual Localization Example
<asp:Repeater id="outboundFlightList" Runat="server">
  <HeaderTemplate>
    <table border="0" width="90%" cellpadding="0" cellspacing="0" align="center" class="suggestedTable">
      <thead>
        <tr class="suggestedTableCaption">
          <th colspan="6">
            <%= GetMessage("outboundFlights") %>
          </th>
        </tr>
        <tr class="suggestedTableColnames">
          <th>
            <%= GetMessage("flightNumber") %>
          </th>
          <th>
            <%= GetMessage("departureDate") %>
          </th>
          <th>
            <%= GetMessage("departureAirport") %>
          </th>
          <th>
            <%= GetMessage("destinationAirport") %>
          </th>
          <th>
            <%= GetMessage("aircraft") %>
          </th>
          <th>
            <%= GetMessage("seatPlan") %>
          </th>
        </tr>
      </thead>
      <tbody>
  </HeaderTemplate>
  ...

GetMessage method is available within both Spring.Web.UI.Page and Spring.Web.UI.UserControl classes, and it will automatically fall back to a global message source lookup if local resource is not found.

Localizing Images within Web Application

Spring.NET provides an easy and consistent way to localize images within web application.

Unlike text resources, which can be easily stored within embedded resource files, XML files, or a database, images in a typical web application are usually stored as files on the file system. Using combination of directory naming convention and custom ASP.NET control, Spring allows you to localize images within the page as easy as text resources.

Spring Page class exposes ImagesRoot property, which is used to define root directory where images are stored. Default value is 'Images', which means that it expects to find 'Images' directory within application root, but you can set it to any value you like using Dependency Injection.

In order to localize images, you need to create folder for each localized culture under the ImagesRoot:

Sample Folder Hierarchy
/MyApp
   /Images
      /en
      /en-US
      /fr
      /fr-CA
      /sr-SP-Cyrl
      /sr-SP-Latn
      ...

Once you have appropriate folder hierarchy in place, all you need to do is put localized images in the appropriate folders and make sure that different translations of the same image are named the same.

In order to place image on the page, you will need to use <spring:LocalizedImage> control:

LocalizedImage Usage example
<%@ Page language="c#" Codebehind="StandardTemplate.aspx.cs" AutoEventWireup="false" Inherits="SpringAir.Web.StandardTemplate" %>
<%@ Register TagPrefix="spring" Namespace="Spring.Web.UI.Controls" Assembly="Spring.Web" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
  <body>
    ...

    <spring:LocalizedImage id="logoImage" imageName="spring-air-logo.jpg" borderWidth="0" runat="server" />

    ...
  </body>
</html>

This control will find the most specific directory that contains an image with the specified name using standard localization fallback rules and user's culture. For example, if the user's culture is 'en-US', it will look for spring-air-logo.jpg in Images/en-US, then in Images/en and finally, if not found, in the root Images directory (which for practical purposes serves as an invariant culture folder).

Global Resources

Global resources are defined for the application context using reserved messageSource object definition, which you can add to your Spring configuration file:

Sample Message Source Configuration
<object id="messageSource" type="Spring.Context.Support.ResourceSetMessageSource, Spring.Core">
    <property name="ResourceManagers">
        <list>
            <value>MyApp.Web.Resources.Strings, MyApp.Web</value>
        </list>
    </property>    
</object>

The global resources are cached within the Spring ApplicationContext and are accessible through the Spring IMessageSource interface.

The Spring Page and UserControl classes have a reference to their ApplicationContext and to associated message source, and will automatically redirect resource lookups to a global message source if local resource cannot be found.

Currently, ResourceSetMessageSource is the only message source implementation that ships with Spring, but we will add XML and flat file-based message source implementations in the future. Of course, if you need to read resources from some other data store, you can easily create your own IMessageSource implementation by inheriting from AbstractMessageSource and overriding few storage-related abstract methods.

User Culture Management

In addition to global and local resource management, Spring.Web also adds support for user culture management by exposing it through UserCulture property on the Page and UserControl classes.

The UserCulture property will simply delegate culture resolution to an implementation of Spring.Globalization.ICultureResolver interface. You can specify which culture resolver to use by configuring CultureResolver property of the Page class in the Spring config file:

Sample CultureResolver Configuration
<object id="BasePage" abstract="true">
    <property name="Master">
        <ref object="MasterPage"/>
    </property>
    <property name="CultureResolver">
        <object type="Spring.Globalization.Resolvers.CookieCultureResolver, Spring.Web"/>
    </property>
</object>

CultureResolver Implementations

Several useful implementations of ICultureResolver ship as part of Spring.Web, so it is unlikely that you will have to implement your own culture resolver. However, if you do have such a requirement, implementation should be fairly straight forward as there are only two methods that you need to implement.

Spring.Globalization.Resolvers.DefaultWebCultureResolver

This is default culture resolver implementation. It will be used if you don't specify culture resolver for the page, or if you explicitly configure it as DefaultWebCultureResolver. Latter case would also allow you to specify a culture that should always be used by setting value of the DefaultCulture property on the resolver.

This resolver will first look at the DefaultCulture property, and return its value if it's not null. If it is, it will fall back to request header inspection, and finally, if no 'Accept-Lang' request headers are present it will return UI culture of the currently executing thread.

Spring.Globalization.Resolvers.RequestCultureResolver

This resolver works in a similar way to default resolver, with the exception that it always checks request headers first, and only then falls back to the value of the DefaultCulture property or current thread.

Spring.Globalization.Resolvers.SessionCultureResolver

This resolver will look for culture information in the user's session, and return it if it finds one. If not, it will fall back to the behavior of the default resolver (DefaultCulture property -> request headers -> current thread).

Spring.Globalization.Resolvers.CookieCultureResolver

This resolver will look for culture information in a cookie, and return it if it finds one. If not, it will fall back to the behavior of the default resolver (DefaultCulture property -> request headers -> current thread).

CookieCultureResolver will not work if your application uses localhost as the server URL, which is a typical setting in a development environment.

In order to work around this limitation you should use SessionCultureResolver during development and switch to CookieCultureResolver before you deploy application in a production.

Changing Culture Within Web Application

In order to be able to change the culture, you will have to use one of the culture resolvers that support culture changes, such as SessionCultureResolver or CookieCultureResolver. You could also write your own resolver that will persist culture information in a database, as part of user's profile.

Once that requirement is satisfied, all you need to do is to set UserCulture property to a new CultureInfo object before the page is rendered. In our .aspx example, there are two link buttons that can be used to change the user's culture. In the code-behind, this is all we need to do to set the new culture:

UserRegistration.aspx.cs
protected override void OnInit(EventArgs e)
{
    InitializeComponent();

    this.english.Command += new CommandEventHandler(this.SetLanguage);
    this.serbian.Command += new CommandEventHandler(this.SetLanguage);

    base.OnInit(e);
}

private void SetLanguage(object sender, CommandEventArgs e)
{
    this.UserCulture = new CultureInfo((string) e.CommandArgument);
}

Site running on a free Atlassian Confluence Open Source Project License granted to Spring Framework. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators