Software localization

Upgraded Resource Strings in ASP.NET MVC i18n

.NET resource strings gives the bare minimum for i18n. This tutorial builds on that with interpolation, HTML in messages, and plurals.
Software localization blog category featured image | Phrase

Globalization in ASP .NET MVC can be a bit hit and miss. Resource strings (stored in .resx files), for example, are excellent for automatically loading translations that correspond to our app's active culture. But resource strings lack many features found in many other i18n solutions: interpolation, HTML in messages, and culture-aware pluralization aren't built into resource strings. We have to take care of these all too common i18n cases ourselves.

Luckily, .NET has a good standard library that can help us flesh out our i18n message handling. And for plurals, the SmartFormat third-party library can work wonders for us.

πŸ—’ Note Β» Outside of .NET, we often refer to the process of getting an application ready for delivery to people in different parts of the world as internationalization, often abbreviated as i18n. Microsoft calls this globalization, so we'll be using the terms i18n and globalization interchangeably here.

This article builds on our tutorial,Β Getting Started With ASP.NET MVC i18n, where we built a demo app and covered, among other things, how to add and manage resource (.resx) files. We recommend that you check out that article first if you're starting with globalization in ASP.NET MVC.

πŸ”— Resource Β» You can get the source code of the app this article is based on from the app's GitHub repo.

Basic Resource Strings

As a recap, let's take a look at some basic resource strings. In our project we have two files:

  • Resources.resx - translations for our default culture (English, for example)
  • Resources.ar.resx - translations for Arabic

We could, of course, have as many Resources.{culture}.resx files as we want in our project, and each would house the translation for one culture that our app supports. Let's take a look at the resource files we have now.

A basic English string | PhraseA basic English string

A basic Arabic translation | PhraseA basic Arabic translation

βœ‹πŸ½ Heads Up Β» We need to make sure that Access Modifier option is set to Public for our resource files. Otherwise, our the files won't be accessible in our code.

In our views, we can use the resource strings as properties on the Resources class that .NET creates for us behind the scenes.

https://gist.github.com/ashour/8237fc04fe7847808dd77c8aeb1e95fc.js

Heaventure is our app's name and top-level namespace. Now, when our app's current culture is Arabic, we'll see the Arabic version of AppName in our views. When we're viewing our app in English, we'll see the English version of the string, of course.

The verbosity of typing out the fully qualified Heaventure.Resources.AppName can be tedious. Instead, we can add the Heaventure.Resources namespace to our project's Web.config file, which will allow us to type the more concise Resources.AppName instead.

https://gist.github.com/ashour/2e4b41e146da7c340f46ebab086df212.js

Now we can type Resources.AppNameβ€”much shorter.

So that's basically how we use .NET resources strings in our ASP.NET MVC projects. It's a good start, but it's a bit limited. What if we have dynamic values in our strings, for example?

Interpolation

Suppose we wanted translations for a string like "Hello, Adam", where "Adam" is the name of the currently logged-in user, i.e. a dynamic value. Resource strings don't support this kind of dynamic interpolation by themselves. However, we can use .NET's String.Format() method to add interpolation to our translation messages.

In our resource files, we can use indexed placeholders for our dynamic values, starting at zero.

Indexed English placeholders for our dynamic values, starting at zero | Phrase

We could have {1} {2} and more if we wanted

Indexed Arabic placeholders for our dynamic values, starting at zero | Phrase

Each culture's translation uses the same placeholder

In our views, or anywhere else in our code, we can now use String.Format() to inject the value for {0} dynamically.

https://gist.github.com/ashour/41aab4c59b221993840b97299620c549.js

String.Format() takes a format string as its first parameter, and the values to inject into placeholders afterwards.

πŸ—’ Note Β» We can have as many placeholders, and respective replacement parameters as we want. We just have to make sure to number them {0}, {1}, {2}, etc., and to align the order of the placeholders with the order of the parameters.

πŸ”— Resource Β» Check out the official .NET Getting started with the String.Format guide, and the complete String.Format documentation.

Using HTML in Resource Strings

Sometimes we need to have HTML inside our translation messages. For example, we might need to apply a CSS class to an inner part of a message.

CSS class in an inner part of a message | Phrase

A common case: inner styling in a message

If we were to output this in our views, the rendering engine would try to protect us by escaping the HTML in our message.

https://gist.github.com/ashour/0a76a16122f129135dba8f99cb2e51c6.js

Message with wrong output | PhraseNot the output we're looking for

There's an easy fix for this: we just have to use ASP.NET MVC's Html.Raw() method, which will output the HTML without escaping it.

https://gist.github.com/ashour/18919059663201f15be813123f3b4691.js

Fixed output | PhraseThat's more like it

βœ‹πŸ½ Heads Up Β» Using Html.Raw() can expose your site to XSS (Cross-site scripting) attacks. Make sure you trust any code you're outputting without escaping and sanitizing.

Localized Plurals with SmartFormat

One of the biggest pain points with .NET resource files is the lack of built-in plural support. Suppose we had a forum app where we wanted to display the number of replies for each post. We could have a translation message that looks like the following.

English reply count string | Phrase This won't work for us

We could use String.Format() to interpolate the count variable in this message. However, this would only work in English for the plural case. If a post had zero or multiple replies, our message would be grammatically incorrect. Moreover, some languages have complex plural rules. Arabic, for example has six plural forms. So how do we deal with culture-aware pluralization?

πŸ”— Resource Β» Check out the Unicode CLDR charts for the plural rules of different languages.

One solution is using Axuno's SmartFormat third-party library. SmartFormat is template library that has a variety of features for dealing with grammar dynamically. Of particular interest to us here is SmartFormat's excellent support for plural grammar. We'll see how to use the library to solve our pluralization issues in a minute. Let's install it first.

Installing SmartFormat from NuGet

We can add SmartFormat to our project using the NuGet package manager. In Visual Studio, we open the NuGet command console by going to Tools > NuGet Package Manager > Package Manager Console. In the console, we can type Install-Package SmartFormat.NET -Version 2.5.0 to install the library.

Using SmartFormat for Plurals

SmartFormat supports culture-aware plurals. We can make use of this through a special syntax in our strings.

English reply count with pluralization | PhraseSingular and plural English forms

βœ‹πŸ½ Heads Up Β» Make sure to leave no white space between the | and surrounding text. Otherwise SmartFormat won't be able to parse your plural forms.

We can use this format string with the Smart.Format() method, passing in a count parameter as well.

https://gist.github.com/ashour/a1e2dd702e641895f1ca2af35d01b17f.js

By default, Smart.Format() assumes English rules, and selects the correct plural form from our message depending on the count we pass it.

What's great about SmartFormat, however, is that we can use per-culture plural rules. For example, given Arabic's six plural rules, we can add the following to Resources.ar.resx.

Arabic reply count with pluralization | PhraseAll six Arabic plural forms accounted for

We just need to make sure that we indicate the culture we're targeting in our message to SmarFormat. We do this here via the (ar) specified in our message.

https://gist.github.com/ashour/2fbe79c979a836c5a8efde08ca8caa6f.js

As you can see, SmartFormat elegantly fills the pluralization gap that stock resource strings leave behind.

πŸ—’ Note Β» Smart.Format() can be used as a drop-in replacement for .NET's built-in String.Format(), which is nice in case we want to standardize formatting calls across our app.

πŸ”— Resource Β» You can get the source code of the app this article is based on from the app's GitHub repo.

Ciao

While .NET's resource strings are bare-bones, we can build on them using a standard library, and third-party solutions, to flesh out our ASP.NET MVC i18n repertoire. And for a fully-fledged, professional i18n experience, check out Phrase. An i18n solution built by developers for developers, Phrase features a CLI and API, over-the-air (OTA) translations for mobile apps, a slick web UI for translators, and GitHub, GitLab, and Bitbucket sync. Check out all of Phrase's products, and sign up for a free 14-day trial today.