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.
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;
}
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;
}
public string GetUnreadMessagesString(int unreadMessagesCount, string user) {
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!")
}
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 string
s and properly format them based on the input given. It does not generate code. This means we can put our actual UI string
s 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 string
s.
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:
var language = LanguageManager.GetCurrentLanguage();
var str = File.ReadAllText("UnreadMessages."+language+".txt");
label1.Text = MessageFormatter.Format(str, new {
unreadMessagesCount = 2,
user = "Jeff"
});
Points of Interest
We have now achieved:
- externalized
string
s, 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
:
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
:
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.