Formatting Messages Handling plurality adequately
Published: March 1st, 2016, Updated: Some days ago
Once in a while I stumble upon code to format message texts in C#, either mine or from developers on Stack Overflow. It looks like this:
string messageTemplate; if (_itemCount == _persistedItemCount) messageTemplate = Resources.ProtocolMessageAllItemsAreFiled; else if (_persistedItemCount == 0) messageTemplate = Resources.ProtocolMessageNoItemsAreFiled; else messageTemplate = Resources.ProtocolMessageSomeItemsAreFiled; return String.Format(messageTemplate, _persistedItemCount, _itemCount);
Developers get quite — let's say — "creative" to format readable message texts for the user, especially when it comes to plurality. Source code like this is ok, but we can do better.
I cannot decide which code provided with the answers of the Stack Overflow page I dislike most, so I spare you the sight of it. (I should mention that some chose to solve the problem the way I did or quite similar.) Many of the offered solutions don't use resources, which will lead to not-localizable applications. Or the code handles plurality for exactly one language: English. French and other languages often request adjustments at more than one or two parts of the sentence. So every source code using just two localization resources as parts of a sentence putting a number or date in between might fail to be adequately localizable.
I don't like my solution presented above neither. If the messages get more complex the code to format them proliferates. All solutions I saw were not elegant, but I know from my past how an elegant solution looks like.
Recently I got so tired of this that I decided to implement my C# version of the Java class MessageFormat
and its mighty brother ChoiceFormat
. A lot of Java developers know MessageFormat
. Unfortunately not many know ChoiceFormat
, either because they don't use it directly or they don't use it at all. What do these two classes do? Have a look:
String messageText =
MessageFormat.format("There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.", numberOfFiles);
Of course, you would get the real message format from a resource file. MessageFormat
parses the format. If it discovers the embedded choice format pattern, it converts this part of the message with the help of ChoiceFormat
. ChoiceFormat
examines the value in the argument list (numberOfFiles
) and renders the appropriate text. That's it. Depending on the numberOfFiles messageText
will contain the following:
numberOfFiles | messageText |
---|---|
0 | There are no files. |
1 | There is one file. |
1234 | There are 1,234 files. |
The last example, of course, depends on the current locale.
My own MessageFormat
was not too complicated to implement. I only adjusted the format pattern for C#, so the example above reads as follows:
string messageText =
MessageFormat.Format("There {0:choice,0#are no files|1#is one file|1<are {{0}} files}.", numberOfFiles);
If you want to do it as well, beware of the double opening and closing curly braces ({{ and }}), which also have special meaning in String.Format
. Also consider that you have to pass over the result of your formatter to String.Format
. If you want to be as accurate as me, you also have to consider that a choice format pattern might contain another choice format pattern.