Locations of visitors to this page

    Blog List       Minimize  
,NET:ASP:MVP
.NET
.NET 3.5
.NET:ACL
.NET:AppDomains
.NET:ASP
.NET:ASP ServerControls
.NET:ASP:Commerce
.NET:ASP:Config
.NET:ASP:JSON
.NET:ASP:Layout
.NET:ASP:Media/Flash
.NET:ASP:Mobile
.NET:ASP:Monitoring
.NET:ASP:MVC
.NET:ASP:Navigation
.NET:ASP:Stress Testing
.NET:ASP:Validation
.NET:ASP:WebParts
.NET:C#-Trig
.NET:CAB
.NET:CAS
.NET:Certification
.NET:CF
.NET:Collections
.NET:Configuration
.NET:Cryptography
.NET:Db
.NET:Delegates
.NET:Deployment
.NET:Diagnostics
.NET:Documentation
.NET:Encoding
.NET:Environment
.NET:Extension Methods
.NET:Globalization
.NET:I/O Streams
.NET:Interop
.NET:IO:Mail
.NET:IsolatedStorage
.NET:LicenseManager
.NET:LINQ
.NET:Metrics/Quality
.NET:Mono
.NET:MSOffice
.NET:Optimization
.NET:Patterns/Practices
.NET:Phone7
.NET:Reflection
.NET:Remoting
.NET:Reverse Engineering
.NET:Serialization
.NET:Silverlight
.NET:Silverlight UserGroup
.NET:Silverlight:Phone7
.NET:Threading
.NET:WCF
.NET:Windows Services
.NET:WinForms
.NET:WPF
.NET:Xml
Admin
Admin:Creating Software
Admin:CruiseControl
Admin:Estimating
Admin:Installers/Packaging
Admin:Methodologies
Admin:PM
Admin:SourceControl
Admin:UnitTesting
Admin:VisualStudio
Arch:Gen
Arch:Patterns
Arch:UML
Blogging
DB:Sqlite
DB:SqlServer
DB:SqlServer CE
DB:VistaDB
Graphs
IT
IT:DNN
IT:DOS
IT:IIS
IT:MailServers
IT:MS Office
IT:OS (XP/Vista/7)
Misc
Misc:Hardware
Misc:Humour
mISV:Accounting
mISV:Marketing
OS:Vista
Personal
Personal:Children
Personal:Faith
Personal:Family
Personal:History
Personal:Politics
Places:New Zealand
Places:Paris
Presentations
Tech:CSS
Tech:Regex
Tech:SQL
Tech:Web:HTML
Tech:XML/XSL
Web:HTML5

             
    Sprouting Synapses       Minimize  

             
Summary:

The Client's Point of View.

The old phrase "The client is King!" sums up succinctly the need for globalization: you may have written the website in the US, but a French client would much prefer to visit your website in a language he can understand. 
This is where Globalization and Localization comes in.

Globalization (format) versus Localization (resources)

The two words are used so often together that they turn into a blurry phrase rather than being distinct subjects...let's try to sort that out first.

Although they are very tightly related, and partially overlap, the two concepts are distinct.

  • Globalization as the process of designing and developing software to work in various cultures:
    • formatting most (not all) output according to the client's UICulture, while formatting specific outputs (usually currency) in another Culture.
    • designing layouts for more than just yo

The Client's Point of View.

The old phrase "The client is King!" sums up succinctly the need for globalization: you may have written the website in the US, but a French client would much prefer to visit your website in a language he can understand. 
This is where Globalization and Localization comes in.

Globalization (format) versus Localization (resources)

The two words are used so often together that they turn into a blurry phrase rather than being distinct subjects...let's try to sort that out first.

Although they are very tightly related, and partially overlap, the two concepts are distinct.

  • Globalization as the process of designing and developing software to work in various cultures:
    • formatting most (not all) output according to the client's UICulture, while formatting specific outputs (usually currency) in another Culture.
    • designing layouts for more than just your native tongue
      • taking into account additional screen label space when translating strings. (A rule of thumb is to add at least 25% or more space than is required in English).
      • Allowing for layout to be flipped for cultures who read from right to left.
    • Using international encodings (UTF-8 rather than ASCII, etc).
  • Localization as the process of translating resources to work in different regions:
    • Ensuring that no text (UI labels or Error messages) or resource (image) is hard coded into the code, but instead moved to Resource files that can be easily be translated.
  • The difference between the two is that Globalization is the process of identifying localized resources to adopt a multiple culture support, while Localization is actual process of translating resource to a specific culture.

What Globalization and Localization is not is:

Q: What is Globalization/Localization in Microsoft Framework perspective for building multi-cultural applications?
A: If you declare a variable locally, it's localization. If you declare it globally, its globalization.

Or (less funny but just as wrong):

Q: What is Globalization/Localization in Microsoft Framework perspective for building multi-cultural applications?
A: Localization is using a resource in a Local Resource File. Globalization is using a resource in a Global Resource File.

 

CultureInfo and RegionInfo - A Recap:
We've already investigated Culture and Region in depth here, but a quick summary of the subject would be that a Culture is something which transcends borders, countries, regions, and can span the globe, whereas a Region is a subset of culture, and generally means a specific country, or sometimes even smaller area. 
A simple example of this concept of culture-region is the French culture (fr), which is spoken in many countries/regions: as it is spoken not only in France(fr-FR), but also in (fr-LU), Monaco (fr-MC), the Belgian region of Wallonie (fr-BL), the Canadian province of Québec (fr-CA), the Swiss districts of Vaud (fr-CH)....and many more (src: http://french.about.com/library/bl-whatisfrench.htm)
Note that we always denote it in a 'Culture-Region' format (eg: fr-FR, or fr-CH), and if no region is specified, we just use the 'culture' code, or fall back to using a predetermined region as a stand-in (en-US, or fr-FR).

In .NET the running thread is set to a specific Culture/Region.


The Client's Culture

A Client's browser's culture is by default left to be the same culture as the OS within which it is running, a setting that was set when the OS was first installed, or modified later via the Control Panel:

image

In some cases, with admin rights, the browser can be configured to override the OS's default setting to a list of preferred culture/locales:

image image

How the Client Transmits the Culture Preference to the Server: The Accept-Language Header

A client browser transmits this culture preference (no matter how it was set) to the server as the Accept-Language header of a HTTP Request:

Accept-Language: da, en-gb;q=0.8, en;q=0.7

which is the basis of how a server can then determine what resources to send back to the Client -- adjusted to the locale/language the client browser requested.

Unfortunately, due to lack of knowledge about these options or simply because the permissions were locked down by a system administrator, it is very possible that a visitor to your website is using a browser configured to a language he is not comfortable with (eg: a french traveler in an Italian webcafe). This is why, several top end web sites offer a means for users to set their preferred language for the website only. 

The CurrentCulture and CurrentUICulture of a Request Thread

The string value of the sent Accept-Language Header can be used by ASP.NET to set two properties of each Request's thread:

  • The Page.Culture property (just a wrapper around the System.Threading.Thread.CurrentThread.CurrentCulture)
    which can be used to determines formatting of date, number, and currency formatting.
  • The Page.UICulture property (just a wrapper around the System.Threading.Thread.CurrentThread.CurrentUICulture)
    which determines which string resources are loaded for the page.

Which one should you set to match the client browser's culture?

There is some discussion here. The very naming of the properties hints that UICulture should be set to the client's culture, and culture should maybe be reserved for the server's culture, but this is a red-herring really.

The answer is: 'both'.

Consider the following summary text that you want to save as a localizable resource:

Transaction Date: {0:d} - {1:F2} lbs of {2} (@{3:C2}/lb) = {4:C2}

For the system to correctly fetch the french version of the above resource string, you will have to set UICulture to fr-FR. But if you leave the Culture to the seller's culture (en-US), you would get:

Date de Transaction: 10/20/2008 - 0.25 livre de Beef (@$10.50/livre) = $2.63

which is correct for the selling currency, but all wrong for the date and float...
Whereas if you set Culture to fr-FR as well, you would get:

Date de Transaction: 20/10/2008 - 0,25 livre de Beef (@10,50 €/livre) = 2,63 €
.

which fixes the date, float...but now makes the currency wrong.

No...the only way to do it right is to make both Culture and UICulture match the client's culture, and isolate strings that contain currency transformations, formatting them by a third culture -- the transaction culture -- which is neutral to either the seller or client:

wcSummaryTotal.Text =
    string.Format(_SellersCulture,
    "=<b>Total: {0:C2}</b><br/>",
    finalPrice
    );

Note:
See 3.4 of http://www.w3.org/2003/Talks/0324WWL/paper.html for confirmation of this approach.

Note:
Not everyone will agree with me that Culture should be set to be the same as UICulture (see: http://msdn.microsoft.com/fr-fr/magazine/cc163609(en-us).aspx)

Note that if you are not dealing with currency on your website (but who isn't?!?) then you don't have to worry about using a server side culture parameter.

 

 

Setting the Page/Request's CurrentCulture and CurrentUICulture:

Notice that I stated that the Thread's Culture and UICulture can be set from the Accept-Header, and not is set by the Accept-Header
This is because this behavior is optional (not every web designer wants the page to switch culture according to the culture of the requesting client).

The framework first looks to the Page directive for the values to be used for the Culture and UICulture attributes:

<%@ Page Language="C#" Culture="en-US" UICulture="en-US"%>

If not set there, it falls back to looking at the web.config's globalization section (not the pages section as one might expect), which is how one would set the culture and uiculture  for all pages in the website:

<configuration>
  <system.web>
    ...
    <globalization uiculture="fr" culture="fr-FR"/>
    ...
  </system.web>
</configuration>

If you want the values to be set to the browser's Accept-Language header, you can use the auto value instead, either in the page directive or globalization element:

<configuration>
  <system.web>
    <globalization uiculture="auto" culture="auto"/>
  </system.web>
</configuration>

or an even use the enableClientBasedCulture attribute, which sets the globalization element's culture and uiculture to auto for you:

<configuration>
  <system.web>
    <globalization enableClientCulture="auto"/>
  </system.web>
</configuration>

Note:
Both the auto and enableClientBasedCulture work off of the HTTP's Accept-Language header sent from the browser.

Why so many ways to Rome? The .NET Framework in this case didn't want to lock programmers into any pattern and left it all up to you.

And finally, you have the option of making the page be more dynamic and set it to a user set culture within the Page.InitializeCulture method:

protected override void InitializeCulture() {
    string x = Request.Form["wcCulture"];
    if (!string.IsNullOrEmpty(x)) {
        Thread.CurrentThread.CurrentCulture = new CultureInfo(x);
    }

    x = Request.Form["wcUICulture"];
    if (!string.IsNullOrEmpty(x)) {
        Thread.CurrentThread.CurrentUICulture = new CultureInfo(x);
    }
    base.InitializeCulture();
}

 


Localizing your Application with Global Resources (resources shared across Pages)

As pointed out above, WebApps

Create a Global Resource Folder:

The first step is to ensure that your WebApp has a App_GlobalResource folder in its root directory.
Since not all websites use global resources, its not generated by default for you, so you have to request that it is built:

image

Create a Global Resource File within the App_GlobalResources folder:

Note that when you created the App_GlobalResource Folder, that it didn't create any *.resx resource files in it: its up to you to determine how many you need, and to create them (a website's Global Resource Folder (App_GlobalResource) can have one or more Global resource files in it).
To add a global *.resx file, right click on the App_GlobalResource folder, and add a new item (in this case, a *.resx file):

image

Add Strings and Images to your Global Resource File:

Once you have a new *.resx file, its time to add named Resource Strings and Images.
In our case, we just need a FirstName and LastName string.

  1. Select the newly created GlobalResource.resx file in the App_GlobalResource folder
  2. Change to the String View of the Resource File
  3. Add two string resources (in our case FirstName, and LastName):

image

Create alternate Locale Global Resource Files:

To create resource files for other locals,

  1. Copy the *.resx file to the same directory,
  2. Rename it, adding the culture-local tag just before the *.resx extension
  3. Edit the file to create alternate local strings and images:

image

 

Refer to the Global Resource from the Design View of the Page:
Now that the resource strings are defined in at least one locale, we can refer back to them from the web page:

  1. Right click on a webcontrol (in our case the Product Label), in order to bring up its Property Grid,
  2. select the Expressions property
  3. Bring up the Expressions wizard
  4. Select to bind to the Text property of the Label
  5. Verify we are talking about binding to a Resource
  6. Enter the name of the Resource File (without the *.resx extension) as the classname
  7. Enter the name of the Resource we are binding to  (eg: 'Product'):

image

The above steps will modify the wclProduct webcontrol's markup in the page's Source View, in order to add a binding using the Resources syntax:

<asp:Label runat="server" ID="wclProduct" 
    Text="<%$ Resources:MyGlobalResource, Product %>" 
     />

 

Referring to Global Resources by Code:
The above examples demonstrated referring to Global Resources using an explicit declarative binding syntax.

Sometimes you need to refer to Global Resources in the code behind file as well.
This is not only possible, but extremely easy, as the *.resx file is automatically recompiled into a useable resource class every time it is modified and saved. Once recompiled the keys in the *.resx file are accessible as any class property would be:

string myLabelText =
    Resources.MyGlobalResource.Weight;

Here is screen grab demonstrating that all properties in the global resource file are easily accessible:

image

 


Localizing your Application with Local Resources (specific to a single Page)

By default a new Page does not have an associated *.resx page, so its up to you to create it first:

  1. Set the page to Design Mode (the next step is grayed out in Code View)
  2. Click the Tools/Generate Local Resource menu button

image

This will create a sub folder called (App_LocalResources) in the Page's directory (not automatically the Application's Root Directory as the App_GlobalResources directory is), and within it, a *.resx file:

image 

In addition to making the App_LocalResources folder, and generating a new *.resx file within it, the IDE will modify the Page as well:

  1. Adds a meta:resourcekey="PageResource1" to the <%@ Page %> declaration
  2. Adds a meta:resourcekey="wclFirstNameResource1" to all the controls (notice how the resourceKey is automatically copied from the already present ID).

image 

Adding Resources (strings) to the Local Resource File:

At this point, you've probably noticed the completely different syntax used to refer to Global Resources versus Local Resources
This is an important point to notice.

To refer to Global Resources, via code, we simply refer to the properties of an auto generated Resource class:
  (wclWeight.Text = Resources.MyGlobalResource.Weight;),
and declaratively, we point the property's attribute towards the global resource file + resource key:
  (Text="<%$ Resources:MyGlobalResource, FirstName %>").

Whereas here, for the Local resource, there is no mention of any property at all (ie "Text" is not specified) and the whole control has just one meta:resourcekey attribute (meta:resourcekey="wclFirstNameResource1").

We saw above that to refer to Global Resources by code, we referred to the properties of an auto compiled strongly typed class:

Tip:
We'll come back to this to look at this more closely in a second.

If we look at the actual Local Resource file, we notice that the keys are different than a Global Resource file's keys, as well.

Notice how here in the Local Resource file we see the label's key being "wclProductResource1.Text", and -- surprisingly, since we haven't even talked about it yet -- we also see a "wclProductResource1.Tooltip" key:

image

What's going on is quite magical really.

It's wiring it all up for you! All you had to do was:

  • ensure that there was a resource in the resource file that fit the expected ControlID.PropertyName syntax, and
  • your control has a meta:resourcekey attribute set on it.

Creating Localized Local Resources Files

Up to this point, technically, you've successfully globalized the application's UI resources -- but you haven't actually localized them.
To do that, you actually have to make copies of your *.resx resource files for other locals than the default local -- which is extremely easy once you've globalized the application. All you have to do is:

  • Copy the Local resource file to the same directory,
  • Rename it, inserting a culture (and optionally a region) code just prior to the *.resx part of the filename:
  • Change the values in the *.resx to match the culture.

 image

Run the program again, changing the request's UICulture, and the localized strings will change:

image 

Q: What about the <%@ Page meta:resourceKey %>?

It has nothing to do with the resources of the controls on the page -- its for the page itself. In other words, you can set Page.Title from a local resource if there is a resource labelled "Page1Resource.Title".

Q: What about Resource1 on the resource keys?

It's just fluff that can be removed -- as long as you remember to

  • remove it from all the *.resx files
  • and remove it from declarative code as well
  • and you rebuild the website to force a full recompilation, of satellite assemblies (*.resx) as well.

FAQ:

Q:What happens if a control points to a local resource that doesn't exist (ie, the meta:resourcekey points to a non existent resource key)?

If a meta:resourcekey is used that is not listed the associated *.resx file:
<td>
  <asp:Label runat="server" ID="wclProduct" 
    meta:resourcekey="wclProductResource1_NONEXISTENT" />
 </td>

...the IDE permits it during design time, and compiles, and the runtime doesn't throw an exception.
All that will happen is that string will be a blank space on the screen:

image

Q:What happens if a control points to a global resource that doesn't exist (ie, the <@$ Resources: points either a non existent ResourceFile or resource key)?

The IDE allows it through...sortof (it just can't find the resource, but it doesn't raise a stink):

image

But Runtime doesn't like it one bit:

image

Q: What happens if I wire up both a local and Remote resource?

Fair question...inquisitive minds want to know...

First of all, Windows tries to stop you from doing this: notice the warning text if raises if you try to attach a Global resource to a web control that already has a meta:resourcekey reference to a local resource:

image

But even if you force it (by editing the page in Code View):

<asp:TextBox runat="server" ID="wcLastName" 
    meta:resourcekey="wcLastNameResource1"
    Text="<%$ Resources:MyGlobalResource, LastName %>" 
    />

you'll get the following error:

image

 

Q: What are all the different ways of accessing Resources?

To refer to global resources in code, we refer to the the Properties of an auto generated strongly typed class:

wclWeight.Text = Resources.MyGlobalResource.Weight; 

and by declarative explicit binding, we used the Resources binding keyword + the resource file name + the resource key:

<asp:Label runat="server" id="wclWeight" 
   (Text="<%$ Resources:MyGlobalResource, Weight %>")/>

Note: there is no global resource declarative implicit binding similar to the local resource implicit binding as demonstrated below.

For Local Resources, in code, we use the control's GetLocalResource method:

wclWeight.Text = this.GetLocalResourceObject("wclWeight.Text") as string;

If the page's resources were automatically built for you, you can use the meta:resourcekey syntax, which is called implicit declarative binding:

<asp:Label runat="server" ID="wclWeight" meta:resourcekey="wclWeight" />

There is one more type we haven't discussed, explicit declarative binding, which closely resembles how it is done for global resources:

<asp:Label runat="server" id="wclWeight" Text="<%$ Resources: Weight %>"/>

Notice how the only difference is that we removed the classname argument.

Q: What about images?
Not much I can add about images: it really is practically the same process as for strings, really...just have to change the resource file's editing tab to images:

image


Best Practices

Resource Files

Over the years, I've found that breaking resources into several files makes a bit of sense. I use the following list as a starting point:

  • Local Resources:
    • Personally, I try my best to use Global resources as much as possible so that translations are not missed in the rush to release -- but invariably Global resources are not ready/finalized enough to rely on them by the time I need them.
  • Navigation.resx
    • Examples being: Go, Forward, Back, Next, Stop, Pause, etc.
    • These types of strings are usually the same for most programs so once defined, and translated, end up being just a copy, paste job between existing applications and new ones.
    • Although, in practice, this often ends up often merged together with:
  • Menu.resx
    • Examples being: &File,&Open, &Edit, &Cut, &Copy, &Tools, &Help
    • Most of these strings are usually the same for most programs so once defined, and translated, and so start off being just a copy, paste job between existing applications and new ones.
  • Messages.resx: for shared messages (and optionally error messages, in small programs).
    • Examples being: "The file '{0}' has been downloaded.", "Total: {0:C}", etc.
    • Mostly custom per program.
  • ErrorMessages.resx: for shared error messages.
    • Examples being: "Could not open the following file: '{0}'."
    • Some error messages are quite common.
  • Glossary.resx:
    • Examples being: "Note", "Contact", "Email", etc.
    • Definitely specific to an application.
  • Configuration.resx
    • FileNames, etc. not translated. Only used by developers.
    • This can be argued that if not translated, why use a resource file for it (to keep a consistent approach, and modifiable prior to release).

Caching

Caching is a necessity -- but in a multicultural website, make sure you cache based on culture.

 


Links:

BookMark: Trackback

What now?!?

You've got to the end of the post...now what?

Well...a Comment would be nice... It doesn't have to be long...Will just a take a sec...

Thanks!

And (in a perfect world) if I was able to save you some time on your project:

0 comment(s) so far...


Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment   Cancel 
Copyright 2007 by Sky Sigal