Click here to Skip to main content
15,886,055 members
Articles / General Programming / String
Tip/Trick

Proper, Translatable Pluralization in .NET with MessageFormat

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
12 Apr 2015CPOL2 min read 17.4K   13   2
Writing properly formatted, grammatically correct, translatable UI messages

Introduction

Formatting user-interface messages is hard. This quick tip attempts to ease that pain by resorting to a minimal, cross-platform library for better maintainability.

Scenario

We want to display a message in our UI that indicates how many unread messages a user has.

Example: There are no unread messages for Jeff.

Example: There is one unread message for Bob.

Example: There are 4 unread messages for Joe.

The requirement is proper pluralization, as well as translations in English and Danish.

Background

Let's take a look at a naive implementation for the scenario above.

C#
// English version
public string GetUnreadMessagesString_English(int unreadMessagesCount, string user) {
  var message = "There ";
  if (unreadMessagesCount == 0) {
    message += "are no unread messages"
  } else if (unreadMessagesCount == 1) {
    message += "is one unread message";
  } else {
    message += string.Format("are {0} unread messages", unreadMessagesCount);
  }

  message += string.Format("for {0}", user);
  return message;
}

// Danish version
public string GetUnreadMessagesString_Danish(int unreadMessagesCount, string user) {
  var message = "Der er";
  if (unreadMessagesCount == 0) {
    message += "ingen ulaeste beskedder"
  } else if (unreadMessagesCount == 1) {
    message += "kun 1 ulaest besked";
  } else {
    message += string.Format("{0} ulaeste beskedder", unreadMessagesCount);
  }

  message += string.Format("til {0}", user);
  return message;
}

// Helper
public string GetUnreadMessagesString(int unreadMessagesCount, string user) {
  // Pseudo code for getting the current language.
  string currentLanguage = LanguageManager.GetCurrentLanguage();
  if (currentLanguage == "en")
    return GetUnreadMessagesString_English(unreadMessagesCount, user);
  else if (currentLanguage == "da")
    return GetUnreadMessagesString_Danish(unreadMessagesCount, user);
  throw new Exception("Not supported!")
}

// Usage
label1.Text = GetUnreadMessagesString(2, "Jeff");

This code is:

  • Hard to maintain
  • Hard to read and understand
  • Hard to get translated externally

We can do better.

A Better Implementation, Using MessageFormat!

C, Java, PHP and JavaScript among others have this neat utility called MessageFormat.
.NET did not have one, so I wrote one myself, and it is very performant.

It is open-sourced on GitHub at https://github.com/jeffijoe/messageformat.net.

Installing MessageFormat Into Your Project

MessageFormat is available via the NuGet package manager. All you have to do, is run...

Install-Package MessageFormat

...from the Package Manager Console. Alternatively, you can use the Manage NuGet Packages UI for this.

Note: It works with Xamarin as well.

Using MessageFormat to Implement Our Solution

MessageFormat will parse strings and properly format them based on the input given. It does not generate code. This means we can put our actual UI strings in external files, and I strongly advise you to do this.

So, for brevity, these are our translation files:

UnreadMessages.en.txt

There {unreadMessagesCount, plural,
      zero {are no unread messages}
      one {is one unread message}
      other {are # unread messages}} for {user}.

UnreadMessages.da.txt

Der er {unreadMessagesCount, plural,
      zero {ingen ulaeste beskedder}
      one {kun 1 ulaest besked}
      other {# ulaeste beskedder}} til {user}.

Notice the format it was written in. I won't go over the specifics, as they can be found in the readme in the GitHub repository.

unreadMessagesCount is the variable, plural is the formatter being used, zero, one, and other are the parameters passed to the formatter.

Simply put, it does exactly what the previous, hard-to-maintain code did - except here there's no code, just strings.

  • zero: if the variable equals 0
  • one: if the variable equals 1
  • other: none of the above. The # will be replaced by the variable's value.

White space in the format is ignored. Personally, I think it makes it more readable.

This is how you would use it:

C#
// Load string from an external, maintainable file. Pseudo code!!
var language = LanguageManager.GetCurrentLanguage();
var str = File.ReadAllText("UnreadMessages."+language+".txt");
// Format it using MessageFormatter. The second parameter is an object 
// or a Dictionary<string, object> containing
// the parameters.
label1.Text = MessageFormatter.Format(str, new {
  unreadMessagesCount = 2,
  user = "Jeff"
});

Points of Interest

We have now achieved:

  • externalized strings, that can be changed without recompiling the code
  • no more custom formatting code

Additionally, there is a select formatter, that works pretty much like a switch statement.

This is what it looks like without MessageFormat:

C#
string message = string.Empty;
switch(gender) {
  case "male":
    message += "He likes"
    break;
  case "female":
    message += "She likes"
    break;
  default:
    message += "They like"
}

message += " this stuff";

And with MessageFormat:

C#
var str = "{gender, select, male {He likes} female {She likes} other {They like}} this stuff.";
var message = MessageFormatter.Format(str, new {
  gender = "male"
});

You can read all about it in the project's README.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Denmark Denmark
Jeff Hansen is a software developer from Denmark, specializing mostly in web development, both front-end and back-end. He started programming at the age of 16.

Jeff's programming languages of choice are C# and JavaScript.

Comments and Discussions

 
QuestionOther plural forms Pin
pchinery13-Apr-15 2:12
pchinery13-Apr-15 2:12 
AnswerRe: Other plural forms Pin
Jeffijoe13-Apr-15 6:08
professionalJeffijoe13-Apr-15 6:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.