Internationalization Strategies in ASP.Net

Internationalization can be a tough area to do properly, in a scalable and manageable way. Most languages have their own system for handling different languages and cultures: Ruby uses its i10n library and YML files, PHP uses GNU gettext and PO files, while ASP.Net uses XML files presented by the IDE in Resource files.

I will explain some useful tips we learned while implementing Internationalization in one of my latest projects.

Understanding Globalization and Localization

Internationalization can be roughly broken down into two sections: Globalization and Localization, and understanding the difference between these is important.

When we talk about internationalization, people mainly think about multiple languages. For example, we want to offer our project in English and Chinese. What we’re really saying is we want to localize our project with two different languages. Essentially replacing the text depending on the user’s preference.

Now say that project is selling a product… are we going to change the currency symbols to match the language? Are all your prices going to be converted to Yuan for your Chinese speaking users? In some cases, yes; in a lot of cases, no, we still want to display currency and date formats in a single culture. This is the difference between Localization, language, and Globalization, culture.

Take for instance the Apple Store.  The Apple Store is Globalized for several different cultures.  When you go to the Chinese store, it will be selling its products to people in China, using the Chinese language and culture (zh-CN) is the right thing to do.  Now say they offered a version of its USA store in Mexican-Spanish.  Apple would likely not display currency in Pesos, it is still the USA store, they just want to make it more friendly to native Spanish speakers.  In this case we would use the US culture (en-US) and Mexican-Spanish language (es-MX): the text would be displayed in Spanish, but the currency symbols would continue to be dollars.

ASP.Net allows us to specify these settings separately:

Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");  
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture("es-MX");  

What’s so important about the Culture

In my experience, cultural settings are most important in two areas: currency and date format.

Currency can have different currency symbols ($, €, ¥), different thousands separators, different decimal separators.  Dates can be displayed in different formats (m/d/y, d/m/y).

Since these can differ from our localization settings, we need a way to dynamically show them based on the culture (not the language).  ASP.Net has the handy .ToString() method which can take standard identifiers like “C” or “D”.  ASP.Net will automatically output the values based on your current culture, but when you want to customize the output, check out the following libraries:

System.Globalization.CultureInfo.CurrentCulture.NumberFormat  
System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat  

They will have everything you need, from the current cultures date format strings to the currency symbol.  Below are a couple of Extensions I commonly use to display data.  As you can see they take the format from the culture and the language from the ui culture.

public static string ToLongDateNoWeekDayString(this DateTime date, bool abbrieviate)  
{
    string dateFormat = System.Text.RegularExpressions.Regex.Replace(System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern, @",?\s*dddd?,?", "");

    if (abbrieviate)
        return date.ToString(System.Text.RegularExpressions.Regex.Replace(dateFormat, @"MMMM", "MMM"), System.Globalization.CultureInfo.CurrentUICulture);
    else
        return date.ToString(dateFormat, System.Globalization.CultureInfo.CurrentUICulture);
}

public static string ToLongDateTimeNoWeekDayString(this DateTime date, bool removeSeconds)  
{
    string pattern = (removeSeconds) ? @"(,?\s*dddd?,?)|(:ss)" : @",?\s*dddd?,?";
    return date.ToString(System.Text.RegularExpressions.Regex.Replace(System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FullDateTimePattern, pattern, ""), System.Globalization.CultureInfo.CurrentUICulture);
}

public static string ToCulturalCurrencyWithoutSign(this decimal amount)  
{
    System.Globalization.NumberFormatInfo ni = (System.Globalization.NumberFormatInfo)System.Globalization.CultureInfo.CurrentCulture.NumberFormat.Clone();
    ni.CurrencySymbol = "";
    return amount.ToString("C", ni).Trim();
}

Regional Dialects

With the culture, we always have to specify a region. For example, our site’s culture cannot be English (en), it has to be USA English (en-us), Canadian English (en-ca), UK English (en-gb), etc. Because all of these have slightly different settings, there is no “English” culture.

This is not true with language. We can create generalized resource files as well as specify regional or dialect specific files. ASP.Net will automatically select the best resource file to use. For example, we can create resource files for general english and override it with regional specific one. All we have to do is name the resource files properly:

App_LocalResources  
- Example.aspx.en.resx
- Example.aspx.en-CA.resx
- Example.aspx.es.resx
- Example.aspx.es-DO.resx
- Example.aspx.es-MX.resx

Above we have 5 different resource files:

  • en : General English.
  • en-CA : Canadian English (we have more u’s!)
  • es : Spanish
  • es-DO : Dominican Republic Spanish
  • es-MX : Mexican Spanish

ASP.Net will select the most specific file based on your UI Culture.

Localization Files and Variables

Another important feature of localization is the use of variables in our localization strings. Languages can be very complex and it is important to remember you cannot just break up a string around a variable.

For a very simple example, “You just added War and Peace to your Shopping Cart.”, is a common type of string you’ll need. The variable War and Peace can be replaced with any item you are adding to your shopping cart. You may be tempted to localize the string as:

  1. You just added
  2. to your Shopping Cart.

And output it around a variable on your page. Since in different languages, the structure of the sentence can vary greatly, it is highly recommended to use String.Format and numbered variables. This allows the localized versions of this sentence all the flexibility they need. You would want to localize the string like: You just added {0} to your Shopping Cart.

And display it with:

String.Format(GetLocalResourceObject("example").ToString(), item.Name)  

The same idea goes for logical paragraphs or basic formatting.  Instead of breaking a paragraph up into multiple separate sentences, always try to keep logically grouped text together as much as possible, and I personally have no problems allowing basic HTML formatting in localized strings.  This allows for smooth translations, as the more the text is broken up, the choppier the translations will be.

Plurals

The use of plurals is an important and difficult syntax to master.  In English, and some other languages, we only worry about 2 cases, maybe 3:

  • You have multiple items.
  • You have a single item.
  • You have zero items.  (usually the same as multiple, but in some cases it can be awkward)

This can be managed by storing the 2-3 different strings in our resource files and extending GetLocalResourceObject to include an identifier for which string to use based on the count.

In many languages, it can be more complex then that.  In Polish for example, the grammar for 1, 2-4, and 0 or 4+ is different, and changes again after 20.  GNU’s gettext has a pretty good solution to this dynamic type of pluralization, but ASP.Net does not, you will have to come up with your own solution if that need arises in your project.