World Ready

Around the World with ASP.NET AJAX Applications

Guy Smith-Ferrier

Code download available at:InternationalizingASPNETAJAX2008_01.exe(167 KB)

This article discusses:

  • Globalization support in ASP.NET AJAX
  • Need for support in JavaScript
  • Assembly-based AJAX localization
  • File-based AJAX localization
This article uses the following technologies:
ASP.NET AJAX

Contents

The Assembly-Based Localization Model
How It Works
The File-Based Localization Model
Inside the File-Based Model
Choosing a Localization Model
Localizing the ASP.NET AJAX Framework
Creating a Release Version of the Framework
Globalizing ASP.NET AJAX
La Fin

It is a fact that ASP.NET 2.0 has an excellent localization model for Web applications, and the Microsoft® .NET Framework includes considerable globalization support as well. However, this support is limited to server-side code. ASP.NET AJAX applications include a significant amount of client-side code, and this is not covered by the standard ASP.NET localization model. Here I'll explain the localization and globalization models for ASP.NET AJAX applications and show how you can localize JavaScript resources and add limited globalization support. I'll assume you have some knowledge of ASP.NET and ASP.NET AJAX, along with a basic understanding of internationalizing ASP.NET 2.0 applications.

The Assembly-Based Localization Model

The Microsoft ASP.NET AJAX framework includes two models for localizing JavaScript resources: one that is assembly-based and one that is file-based. Let's look first at the assembly-based model.

The assembly-based model uses resources—regular .NET Framework .resx files—that you would use in any ASP.NET or Windows® Forms application. The resources are embedded in an assembly and ultimately retrieved using the ResourceManager class. To explain this process, I will work through an example—a single button that invokes a JavaScript function that displays a localizable message to the user using an alert. If you plan to follow along and create the example yourself, use the names exactly as indicated here; otherwise, it can be difficult to identify the root of problems if your example fails to produce the correct result.

So let's start by creating an AJAX-Enabled Web Application Project (File | New | Project | ASP.NET AJAX-Enabled Web Application). Note that you will need Visual Studio® 2005 SP1 for this project template (the AJAX Web Site Project can't be used as a substitute for this example because there is no clear access to the resulting assembly). Call the project AJAXEnabledWebApplicationI18N1 (I18N is an abbreviation for Internationalization because Internationalization starts with an "I," has 18 more letters, and ends with an "N"). Add a button to the form and an OnClientClick event to call a JavaScript function named displayAlert:

<asp:Button ID="Button1" runat="server" 
  OnClientClick="return displayAlert();" Text="Button"/>

Now add a JScript file called DisplayFunctions.js, like so:

function displayAlert() {
    window.alert(DisplayFunctionsResources.Greeting);
    return false;
}

The displayAlert function uses window.alert to display DisplayFunctionsResources.Greeting. Note that DisplayFunctionsResources.Greeting doesn't exist yet, but it is the localizable resource, and I refer to it as if it were a strongly typed resource class. I will return to this idea later. Using the Properties Window, set the Build Action for the DisplayFunctions.js file to Embedded Resource. This tells the compiler to embed DisplayFunctions.js as a resource in the resulting AJAXEnabledWebApplicationI18N1.dll assembly.

Next we want to make it easy for the user to set the culture used by the Web application, so we add Culture and UICulture attributes to the page header and set them to auto. Of course, these page attributes are added automatically if you use "Tools | Generate Local Resource" as you would normally do when localizing an ASP.NET application, but I want to add them manually in this example because I want it to be clear that the ASP.NET AJAX localization models do not require you to also use the ASP.NET localization model.

Now add a Resource file and call it DisplayFunctionsResources.resx. Add a resource entry called Greeting and give it a value of "Hello World". At this point, you should be able to see what the reference to DisplayFunctionsResources.Greeting in DisplayFunctions.js will be pointing at. Now add a Resource file called DisplayFunctionsResources.fr.resx and a resource entry called Greeting, and give it the value "Bonjour Le Monde". This resource file uses the standard .NET Framework .resx file-naming convention of including the culture name (here fr for French) before the .resx file extension.

You'll just have to trust me while performing the next step until I explain later how it all works. For now, you need to know that the resources in your Web application (DisplayFunctions.js, DisplayFunctionsResources.resx, and DisplayFunctionsResources.fr.resx) are made available to your page using the WebResource and ScriptResource HTTP handlers. These handlers do not make all resources available automatically; instead, they look for assembly attributes that mark the resources they are allowed to expose. Open the project's AssemblyInfo.cs file and add these assembly attributes to the file:

[assembly: System.Web.UI.WebResource( "AJAXEnabledWebApplicationI18N1.DisplayFunctions.js", "text/javascript")]
[assembly: System.Web.UI.ScriptResource( "AJAXEnabledWebApplicationI18N1.DisplayFunctions.js", 
"AJAXEnabledWebApplicationI18N1.DisplayFunctionsResources", "DisplayFunctionsResources")]

To expose these resources to the page, you need to add the ASP.NET AJAX ScriptManager to the page and configure it to generate links to localizable resources. You do this by setting the ScriptManager's EnableScriptLocalization property to true. In addition, you need to indicate which resources the page can reference by adding a ScriptReference to the ScriptManager. The complete ScriptManager element follows:

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableScriptLocalization="true">
    <Scripts>
        <asp:ScriptReference Name="AJAXEnabledWebApplicationI18N1.DisplayFunctions.js" 
          Assembly="AJAXEnabledWebApplicationI18N1" />
    </Scripts>
</asp:ScriptManager>

The ScriptReference includes the name of the JavaScript resource and a reference to the resource's assembly.

Finally, you can run your completed Web application. Click on the button and you will see the English resource ("Hello World"), assuming that your system's default language is set to English. To see the French resources in Internet Explorer®, open Tools | Internet Options, click on the Languages button, click on the Add button, select French (France), click OK twice, refresh the page, and finally click on the button in your application and the French resource ("Bonjour Le Monde") will be shown.

How It Works

So now you have a working localized ASP.NET AJAX application, but it was built without a good understanding of how the framework actually makes it work. In this section, I will connect the dots. It all begins with web.config. Take a look at the HTTP handlers section and you will find a ScriptResource handler:

<add verb="GET,HEAD" path="ScriptResource.axd" 
    type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, 
    Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>

This ScriptResourceHandler is referenced in your page when you add EnableScriptLocalization="true" to your ScriptManager. If you go ahead and enable script localization in a freshly created, unchanged ASP.NET AJAX-Enabled Web Application, view the source HTML and you will see the lines shown in Figure 1.

Figure 1 WebResource and ScriptResource References

<script src="/WebResource.axd?d=wLnl3WuFAiWB3W8aelty5A2&amp; t=632969072944906146" type="text/javascript"></script>
<script src="/ScriptResource.axd?d= ekhhG997JkU79ensgQhEZepkzUh3YLeLuz9-kWs0_QimDVEmoCSwcFNOHayaCQX6y2v7StlLcqRUUH­TtwG4zJloCtMocAEx2dXPoMNyO-AE1&amp; t=633132543078401746" type="text/javascript"></script>
<script src="/ScriptResource.axd?d= ekhhG997JkU79ensgQhEZepkzUh3YLeLuz9-kWs0_QimDVEmoCSwcFNO­Haya­CQX6y2v7­Stl­Lcq­RUUHTtwG4zJjtbgQARGVzU-hP34jwIti3qwKit4BmBA5oDqgYMBzoU0&amp; t=633132543078401746" type="text/javascript"></script>

The WebResource reference will be there regardless of whether you have enabled script localization, but, since all of the handlers use the same parameters, that's where we'll start.

The key to understanding what ASP.NET AJAX is doing lies in understanding the parameters. In Figure 1, you can see that there are two parameters: d and t. The parameter d represents the data and t is a date/time stamp. The t parameter is simply the DateTime (represented in ticks) of the assembly that contains the resource. A value of 632969072944906146 represents 20th October 2006 02:14:54, which is the date/time stamp for System.Web.dll. The benefit of this parameter is that the result returned by the HTTP handler will be cached by the browser as long as the URI does not change. This means that if the assembly that serves the resource changes, its date/time stamp will change and the ScriptManager will generate a different URI (because there will be a different t parameter value) and the cached value will be replaced.

The d parameter is encrypted so it makes no sense to the naked eye. However, the System.Web.UI.Page class has internal static methods—DecryptString and EncryptString—that allow the Page to build and un-build these strings. The decrypted version of the d parameter (wLnl3WuFAiWB3W8aelty5A2) in WebResource in Figure 1 is s|WebForms.js. The vertical bar is simply used to delimit different values in the string. The "s" indicates that the data is script, and "WebForms.js" is the name of the resource to retrieve. The WebForms.js resource is retrieved from System.Web.dll.

The ScriptResource d parameters make for more interesting reading. The d parameters shown in Figure 1 are encrypted versions of this string

ZSystem.Web.Extensions,1.0.61025.0,31bf3856ad364e35|Mi­crosoftAjax.js|en

and this string:

ZSystem.Web.Extensions,1.0.61025.0,31bf3856ad364e35| MicrosoftAjaxWebForms.js|en

The first character (Z) indicates that the result should be returned in GZip compressed format. The next parameter is the fully qualified assembly name where the resource can be found (the ASP.NET AJAX System.Web.Extensions.dll in this example). The parameter following that is the name of the resource to retrieve (in this instance, MicrosoftAjax.js), and the final parameter is the culture for which it should be retrieved (en for English in this case).

If you view the HTML source for the sample application, you will see a third ScriptResource that is generated as a result of including the ScriptReference element in the ScriptManager. It looks like the following:

<script src="/ScriptResource.axd?d=SIfun5X-G8AZLQEDQrC3E6MU4JIgMj1Y2u4fjk0_keEO3Avx4Kbf3v3a WEvpImtLlh3gxoqTDlXW5ZBtINnuNCTcGSSWUdsCxEJu9l3X fPtkrSmJXLzRqze5gVmu6Xzz0&amp;t=633137261664170000" 
  type="text/javascript"> </script>

The decrypted version of this d parameter is ZAJAXEnabledWebApplicationI18N1|AJAXEnabledWebApplicationI18N1.DisplayFunctions.js|. It indicates that the ScriptResource HTTP handler should retrieve the AJAXEnabledWebApplicationI18N1.DisplayFunctions.js resource from the AJAXEnabledWebApplicationI18N1 assembly. Recall that we set the Build Action of this resource to embedded resource so it will be embedded in the assembly. It is also worth noting that the culture (at the end of the decrypted d parameter) is empty. When the browser's language is set to fr, the culture value in the d parameter is set to fr.

The final pieces of this jigsaw puzzle should fall into place if you consider the Z value at the beginning of the d parameter. Instead of Z for compressed, you might find a u, which indicates that the result should be returned uncompressed. So if you change this value to a u, rebuild the parameter (uAJAXEnabledWebApplicationI18N1|AJAXEnabledWebApplicationI18N1.DisplayFunctions.js|), encrypt it, and then call the ScriptResource HTTP handler directly, like this:

https://localhost:49573/ScriptResource.axd?d=t9iZ-3-wfmF_qcNwBHaQrktF2T3rVGzj376NN7kvehJFET0JsrpDTsYt9i KmZpSmrU1GjtTCo9PIQmuAmMNNkN2Yc_ipVH7JL-jt_hmL9_v8 HtplfhERvz-WsOUxZ-1k0&t=633137261664170000

Then you can see exactly what the handler is returning. Here's the uncompressed, intelligible result:

// JScript File 
function displayAlert() {
    window.alert(DisplayFunctionsResources.Greeting);
    return false;
}
DisplayFunctionsResources = {
    "Greeting": "Hello World"
};

You can see that this is an amalgamation of the original DisplayFunctions.js file plus a JavaScript serialization of the DisplayFunctionsResources.resx. This amalgamation is serialized on request by the ScriptResource HTTP handler. Notice that the name of the JavaScript class is DisplayFunctionsResources. At first, you might think this is the name of the corresponding .resx file. However, this is a deliberate coincidence on my part. Recall the ScriptResource assembly reference from above:

[assembly: System.Web.UI.ScriptResource ("AJAXEnabledWebApplicationI18N1.DisplayFunctions.js", 
  "AJAXEnabledWebApplicationI18N1.DisplayFunctionsResources", "DisplayFunctionsResources")]

The final parameter (DisplayFunctionsResources) is the name of the JavaScript class that the ScriptResource HTTP handler generates. I chose this name because I think it is helpful to use the same name for the class and the file name, as this is the convention used by strongly typed resource classes. However, you can make these names different if you prefer.

When you go ahead and switch the culture to fr, the ScriptResource HTTP handler returns an amalgamation using the DisplayFunctionResources.fr.resx resources (courtesy of the ResourceManager behavior that has existed since the release of the .NET Framework 1.0):

// JScript File 
function displayAlert() {
    window.alert(DisplayFunctionsResources.Greeting);
    return false;
}
DisplayFunctionsResources = {
    "Greeting": "Bonjour Le Monde"
};

The File-Based Localization Model

The second of the two AJAX localization models is what I call file-based. That is, the JavaScript files are not embedded in an assembly. Instead they are returned unchanged to the client. Clearly, this model is suitable for project types that do not have an easily identifiable assembly, but there are other benefits as well. Let's work through an example.

Create an AJAX-Enabled Web Site (File | New | Web Site... | ASP.NET AJAX-Enabled Web Site). You can call it anything you like, as the name is not used for any purpose. Add the same button as you did in the first exercise:

<asp:Button ID="Button1" runat="server" OnClientClick="return displayAlert();" Text="Button"/>

Next, add a JScript file called DisplayFunctions.js consisting of the code shown here:

function displayAlert() { window.alert("Hello World"); return false; }

Notice that in this example the text is clearly hardwired and is not in any way localizable.

Now create a second JScript file and call it DisplayFunctions.fr.js:

function displayAlert() { window.alert("Bonjour Le Monde"); return false; }

As in the first example, add Culture and UICulture attributes to the page header and set the values to auto. Finally, set EnableScriptLocalization="true" to a ScriptManager and add a ScriptReference to the DisplayFunctions.js file:

<asp:ScriptManager ID="ScriptManager1" runat="server" 
  EnableScriptLocalization="true">
    <Scripts>
        <asp:ScriptReference Path="DisplayFunctions.js" 
          ResourceUICultures="fr"/>
    </Scripts>
</asp:ScriptManager>

The ScriptReference uses Path and ResourceUICultures attributes instead of the Name and Assembly attributes used in the assembly-based model. If you run the Web site now and switch between no language and French, you will see that the button results in the correctly localized message.

Inside the File-Based Model

The file-based model is clearly different from the assembly-based model. If you right-click the page in Internet Explorer and select view source, you will see the generated script tag here for no language:

<script src="DisplayFunctions.js" type="text/javascript"></script>

Here's the script tag for French:

<script src="DisplayFunctions.fr.js" type="text/javascript"></script>

So the first difference is that the file-based model has a separate and completely self-contained .js file for each culture. There are no .resx files containing resources that are subsequently converted to JavaScript classes. Each .js file contains both the logic and the resources. This is both good and bad: good because it allows you to customize the logic for each culture very easily, bad because if you don't want to customize the logic, you end up having to duplicate the logic for each culture simply to change the resources for each. Maintenance can get ugly.

Let's return to the ScriptReference you saw earlier:

<asp:ScriptReference Path="DisplayFunctions.js" ResourceUICultures="fr"/>

The Path attribute refers to the name of the JavaScript file (DisplayFunctions.js). The ResourceUICultures attribute lists the cultures for which a unique script exists, and it is required. You might argue that the ScriptResource HTTP handler could easily work this out from the file directory and that this attribute is therefore redundant. However, in order to boost performance, the static script files are served directly by IIS, so the ScriptResource handler never has the opportunity to evaluate the request. Because of this process, the list must be hardcoded.

Given what I have covered so far, the file-based model might appear somewhat lacking, but with a bit of reorganization it can be more useful. Recall from the assembly-based model that the JavaScript that was returned to the client by the ScriptResource HTTP handler is an amalgamation of the original JavaScript file plus a JavaScript class representing the resources from a .resx file. The same approach could be adopted for the file-based model. Instead of writing the same functions from earlier, write this

function displayAlert() { window.alert("Hello World"); return false; }

and this:

function displayAlert() { window.alert("Bonjour Le Monde"); return false; }

You could refactor this code so that the logic remains exactly the same for each culture and the resources are moved to the latter part of the JavaScript file. Try this

function displayAlert() {
    window.alert(DisplayFunctionsResources.Greeting);
}
DisplayFunctionsResources = {
    'Greeting': 'Hello World'
}

and this:

function displayAlert() {
    window.alert(DisplayFunctionsResources.Greeting);
}
DisplayFunctionsResources = {
    'Greeting': 'Bonjour Le Monde'
}

This certainly improves the maintenance story because, although you still have to apply the same change to each culture, it is significantly easier to do since the top part of the file is the same for every one.

Now consider a further revision. You could keep the logic in a separate common JavaScript file (such as DisplayFunctions.Common.js) and generate the localized versions from corresponding .resx files at build time using a custom build task following the same approach that the ScriptResource HTTP handler does for the assembly-based model. I have left this as an exercise for the reader.

Choosing a Localization Model

So now that you know both models, the obvious question is which one should you use? Figure 2 provides a comparison.

Figure 2 File-Based versus Assembly-Based Localization

Criterion File-Based Assembly-Based
ScriptReference Attributes Path, ResourceUICultures Name, Assembly
Scripts can use localized resources No Yes
Scripts can be localized Yes No
Use with Web Application Projects Yes Yes
Use with Web Site Projects Yes No
Use with Control Libraries Yes Yes

The big benefit of the file-based model is the fact that you can easily customize the client-side logic and it can be used with Web Site Projects. On the other hand, the assembly-based model lets you easily leverage localized resources, and the JavaScript files can be embedded in an assembly. This makes the model ideal for use with control libraries. The nature of your work will probably dictate which model is best for you. As I use Visual Studio 2005 SP1 and don't often need to customize logic for different cultures, the assembly-based model works best for me.

Localizing the ASP.NET AJAX Framework

The .NET Framework is itself available in localized versions—.NET Framework Language Packs. When installed, the .NET Framework can draw upon these language packs to expose localized resources such as error messages and dialogs. The ASP.NET AJAX framework itself draws upon resources such as error messages that must be available on the client. And while it is not possible for sources outside of Microsoft to create new .NET Framework language packs, anyone can create localized versions of the ASP.NET framework. At the present time, the ASP.NET AJAX framework is available only in English, but when it becomes part of Visual Studio 2008, localized versions of the framework will be available. However, the localization support will be limited to the subset of languages that are supported by the .NET Framework, so the chances of getting, say, a Welsh localization of the ASP.NET AJAX framework are quite small. Hence, we still need a method to localize the ASP.NET AJAX framework.

Fortunately, this method already exists and is built on the file-based localization model already discussed. The ASP.NET AJAX assemblies contain English resources. If you open System.Web.Extensions.dll using Reflector (see Figure 3), you can see the JavaScript resources that are used by default.

Figure 3 Using Reflector to View the System.Web.Extensions.dll's JavaScript Resources

Figure 3** Using Reflector to View the System.Web.Extensions.dll's JavaScript Resources **(Click the image for a larger view)

However, you can instruct the ScriptManager to use a different set of JavaScript files simply by adding a ScriptReference for the same JavaScript resource name, as you see here:

<asp:ScriptManager ID="ScriptManager1" runat="server" 
  ScriptMode="Debug" EnableScriptLocalization="true">
    <Scripts>
        <asp:ScriptReference Name="MicrosoftAjax.js" 
          Path="~/AjaxFramework/MicrosoftAjax.js" ResourceUICultures="fr" />
    </Scripts>
</asp:ScriptManager>

To see this in action, you need to create a new AJAX-Enabled Web Application Project, change the ScriptManager to the one shown directly above, add a folder called AjaxFramework, and copy the MicrosoftAjax.Debug.js file from the ASP.NET AJAX MicrosoftAjaxLibrary\System.Web.Extensions\1.061025.0 folder to the AjaxFramework folder. Now copy MicrosoftAjax.Debug.js to MicrosoftAjax.Debug.fr.js to create the French localization of the framework. Open MicrosoftAjax.Debug.fr.js, go to the bottom of the file where Sys.Res is located, find the argumentType field and change its value to "L\' objet ne peut pas être converti le type demandé." (This is the French translation of "Object cannot be converted to the required type.")

Now add a button to the form with an OnClientClick event that calls showError. Add the showError JavaScript function here to default.aspx:

function showError() { window.alert(Sys.Res.argumentType); return false; }

Add Culture and UICulture attributes to the page and assign them values of "auto". Run the application. When the browser's language is French, you'll see the French resources being displayed.

Creating a Release Version of the Framework

The problem is solved—but only as far as the debug version is concerned. If you set the ScriptManager's ScriptMode to Release, the application will fail at run time because it will be looking for the MicrosoftAjax.js or MicrosoftAjax.fr.js file, and these are clearly missing. The simplest solution is to copy the debug versions of these files and rename them to their release file names. This solves the initial problem, but obviously it means that the release and debug versions are identical and, more importantly, the release versions are not optimized and are bloated with comments and white space.

Fortunately, ASP.NET AJAX has a solution. The AJAX Control Toolkit includes a MSBuild task called JavaScriptCommentStripper to address this exact problem. This task takes a JavaScript file as an input and outputs the same file with the comments and white space stripped out. Figure 4 shows a simple MSBuild script that converts debug.js files into stripped versions of the same files. So in our example, the MicrosoftAjax.Debug.js and MicrosoftAjax.Debug.fr.js files would result in two additional files—MicrosoftAjax.Debug.js.Stripped and MicrosoftAjax.Debug.fr.js.Stripped. When you are happy with the result, rename these to MicrosoftAjax.js and MicrosoftAjax.fr.js, respectively, and you're all set.

Figure 4 JavaScriptCommentStripper MSBuild Task

<Project
    xmlns="https://schemas.microsoft.com/developer/msbuild/2003" 
      DefaultTargets="StripComments">
    <UsingTask TaskName="JavaScriptCommentStripperTask" 
      AssemblyFile="C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\AJAXControlToolkit\Binaries\JavaScriptCommentStripper.dll"/>
    <ItemGroup>
        <SourceFiles Include="*.Debug*.js"/>
    </ItemGroup>
    <Target Name="StripComments">
        <JavaScriptCommentStripperTask SourceFiles="@(SourceFiles)" 
          DestinationFiles="@(SourceFiles->'$(TargetDir)%(RecursiveDir) %(Filename)%(Extension).Stripped')"/>
    </Target>
</Project>

You can see the effect that this task has on the JavaScript by opening up the before and after JavaScript files. Figure 5 shows the top part of the original Microsoft.Debug.js file.

Figure 5 The Top of Microsoft.Debug.js

//-----------------------------------------------------------------------
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------
// MicrosoftAjax.js 
// Microsoft AJAX Framework. 
Function.__typeName = 'Function';
Function.__class = true;
Function.createCallback = function Function$createCallback(method, context) {
    /// <param name="method" type="Function"></param> 
    /// <param name="context" mayBeNull="true"></param> 
    /// <returns type="Function"></returns> 
    var e = Function._validateParams(arguments, [{
        name: "method",
        type: Function
    }, {
        name: "context",
        mayBeNull: true
    }]);

Here's the stripped code from the Microsoft.Debug.js.Stripped file. It is clearly shorter (and less readable). In this example, the JavaScriptCommentStripper task has reduced the file in size from 260,096 bytes to 182,454 bytes:

Function.__typeName = 'Function';
Function.__class = true;
Function.createCallback = function Function$createCallback(method, context) {
    var e = Function._validateParams(arguments, [{
        name: "method",
        type: Function
    }, {
        name: "context",
        mayBeNull: true
    }]);

Globalizing ASP.NET AJAX

To say that JavaScript is lacking in globalization support compared to the .NET Framework is something of an understatement. The .NET Framework has a vast ocean of globalization classes, and the support in JavaScript could fit into a puddle. And there's a bit of a dilemma surrounding adding more. A very large part of the System.Globalization namespace could be implemented in the ASP.NET AJAX framework, but should it be? Each class increases the download size, so the business case needs to be assessed. Consequently, even with globalization support in the ASP.NET AJAX framework, support is considerably more limited than in the .NET Framework. With that in mind, let's look at what we do have.

By default, globalization support in ASP.NET AJAX is disabled. To enable it, you must set the ScriptManager's EnableScriptGlobalization property to true. This simple act includes a __cultureInfo variable in the generated runtime page, which will look like this:

var __cultureInfo = '{"name":"en-GB",
  "numberFormat":{ "CurrencyDecimalDigits":2, "CurrencyDecimalSeparator":".","IsReadOnly":true, 
  "CurrencyGroupSizes":[3],"NumberGroupSizes":[3], "PercentGroupSizes":[3],"CurrencyGroupSeparator":", ",
  "CurrencySymbol":"£","NaNSymbol":"NaN", "CurrencyNegativePattern":1,"NumberNegativePattern":1, 
  "PercentPositivePattern":0,"PercentNegativePattern":0, "NegativeInfinitySymbol":"-Infinity","NegativeSign":"- ...

This variable is generated from the server's CultureInfo.CurrentCulture and respects the server's user settings. ScriptManager does not have an option to ignore the user's settings.

MicrosoftAjax.js contains a reference to this large variable:

Sys.CultureInfo.CurrentCulture = Sys.CultureInfo._parse(__cultureInfo); delete __cultureInfo;

The variable is read in, parsed, assigned to a Sys.CultureInfo.CurrentCulture variable, and then released.

A few lines up from this in MicrosoftAjax.js you can see the definition of the InvariantCulture:

Sys.CultureInfo.InvariantCulture = Sys.CultureInfo._parse('{"name":"", "numberFormat": 
  {"CurrencyDecimalDigits":2,"CurrencyDecimalSeparator":".", "IsReadOnly":true,
  "CurrencyGroupSizes":[3],"NumberGroupSizes":[3], "PercentGroupSizes":[3],"CurrencyGroupSeparator":",", 
  "CurrencySymbol":"\u00A4","NaNSymbol":"NaN", "CurrencyNegativePattern":0,"NumberNegativePattern":1,   "PercentPositivePattern":0,"PercentNegativePattern":0, "NegativeInfinitySymbol":"-Infinity","NegativeSign":"- ...

What is missing is a value for CurrentUICulture. You could either assume that it is the same as the CurrentCulture (a rather dubious assumption), or you could manufacture a solution that is similar to that of the CurrentCulture and inject a suitable variable into the generated HTML sent to the client.

So let's see what we can do with our globalization support. Create an AJAX-Enabled Web Application and set EnableScriptGlobalization to true in the ScriptManager. Add a Label and a Button and add an OnClientClick event to return a call to the showToday JavaScript function:

< script type = "text/javascript" > function showToday() {
    var date = new Date();
    $get('Label1').innerHTML = date.localeFormat("D");
    return false;
} < /script>

Add Culture and UICulture attributes to the page and assign them values of "auto". Run the application with the browser's language set to English or nothing, click on the button, and the date will display in the default format for the culture set in your Regional and Language Options dialog. Change the browser's language to "fr", refresh the page, click on the button, and the Label will show the date in French using the French date format (lundi 2 juillet 2007, for instance).

If you are familiar with the JavaScript Date class, you will be aware that it does not have the method called localeFormat that is used in the showToday function. This is one of the type extensions included in the ASP.NET AJAX framework. You can see the code for these in MicrosoftAjax.Debug.js. Here is the localeFormat method for the Date class:

Date.prototype.localeFormat = function Date$localeFormat(format) {
    /// <param name="format" type="String"></param> 
    /// <returns type="String"></returns>
    var e = Function._validateParams(arguments, [{
        name: "format",
        type: String
    }]);
    if (e) throw e;
    return this._toFormattedString(format, Sys.CultureInfo.CurrentCulture);
}

For a full list of these type extensions, see ajax.asp.net/docs/ClientReference/Global/JavascriptTypeExtensions/default.aspx.

So, through the Sys.CultureInfo class, the Sys.CultureInfo.InvariantCulture, and Sys.CultureInfo.CurrentCulture properties and type extensions, the ASP.NET AJAX framework offers a limited globalization framework.

La Fin

The ASP.NET AJAX framework brings localization and globalization support to JavaScript on a level not previously seen outside of custom implementations. The framework offers a choice of two localization models, and one of them is likely to meet your needs. The globalization support is rudimentary compared to the richness of the .NET Framework, but that doesn't mean you can't get creative and add your own type extensions and JavaScript classes to support specific needs. I would imagine that AJAX will continue to see upgrades, as all technologies do, so expect to see enhancements to this model in future releases of ASP.NET AJAX.

Guy Smith-Ferrier is an MVP, an INETA speaker, and the author of .NET Internationalization (Addison-Wesley, 2006). He is a Microsoft Certified Professional Developer, has spoken at many European and U.S. conferences, and has written more than 50 articles for numerous magazines. Guy runs The .NET Developer Network (dotnetdevnet.com) in England. Find him at guysmithferrier.com.