Dealing with singular plural phrasing

code 0 comments suggest edit

This is an age old problem and one that’s probably been solved countless times before, but I’m going to write about it anyways.

Say you’re writing code like this:

<p>You have the following themes:</p>
<ul>
@foreach(var theme in Model) {
  <li>@theme.Id</li>
}
</ul>

The natural inclination for the lazy developer is to leave enough alone and stop there. It’s good enough, right? Right?

Sure, when the value of Model.Count is zero or larger than one. But when it is exactly one, the phrase is incorrect English as it should be singular “You have the following theme”.

I must fight my natural inclination here! On the NuGet team, we have a rallying cry intended to inspire us to strive for better, “Apple Polish!”. We tend to blurt it out at random in meetings. I’m thinking of purchasing each member of the team a WWSJD bracelet (What Would Steve Jobs Do?).

To handle this case, I wrote a simple extension method:

public static string CardinalityLabel(this int count, string singular,
    string plural)
{
    return count == 1 ? singular : plural;
}
    

Notice that I didn’t try automatic pluralization. That’s just a pain.

With this method, I can change the markup to say:

<p>You have the following 
  @Model.Count.CardinalityLabel("theme", "themes"):</p>

I’m still just not sure about the name of that method though. What should it be?

Do you have such a method? Or are you fine with the awkward phrasing once in a while?

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

40 responses

  1. Avatar for Shannon Deminick
    Shannon Deminick August 16th, 2011

    In Umbraco v5, we've got a custom Localization framework that takes care of this and much more in the form of languages, most of the details are here:
    kuhnel.wordpress.com/...

  2. Avatar for Ian Mercer
    Ian Mercer August 16th, 2011

    Take a look at Inflector in Castle Active Record.

  3. Avatar for Ken Egozi
    Ken Egozi August 16th, 2011

    I second Ian Mercer.
    The Inflector class is awesome, and I've been using it for ages, carrying it around for various projects - thank you Castle Project for choosing Apache License !

  4. Avatar for Ryan
    Ryan August 16th, 2011

    The label doesn't change with every cardinality, so perhaps it's just better to use PluralizedLabel, or Pluralizer, or PluralityLabel?

  5. Avatar for Pop Catalin
    Pop Catalin August 16th, 2011

    A problem that ain't worth solving, complicates localization (wasted effort) and code (wasted effort), for no real benefit.
    From Jeff Atwood's twitter:
    "Dear Next Person Who Opens a Pluralization 'Bug', I will personally come to your house and bludgeon you to death with a giant S"
    - 3:33 AM Feb 1st, 2009 via web

  6. Avatar for Fredrik Kalseth
    Fredrik Kalseth August 16th, 2011

    What about: "you have the following theme(s):". Problem solved! ;-)

  7. Avatar for Boris Yankov
    Boris Yankov August 16th, 2011

    You should have the second parameter as optional.
    If no second parameter is passed, try the automatic pluralization.

  8. Avatar for Shawn Miller
    Shawn Miller August 16th, 2011

    I wrote it this way:

    public static string ToPlural(this string @this, int count = 0)
    {
    return count == 1 ? @this : System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(new System.Globalization.CultureInfo("en-US")).Pluralize(@this);
    }

    Which allows me to do things like:

    "goose".ToPlural();
    String.Format("{0} {1}", commentCount, "comment".ToPlural(commentCount));

  9. Avatar for Yuri Khan
    Yuri Khan August 16th, 2011

    You will run into trouble with this ”singular for exactly 1, plural for all other numbers” approach as soon as you try to localize into any language that has more than two cardinalities, or where the rules for cardinality selection are different.
    Consider Russian. Here we have forms for singular, dual and plural.
    Singular is used for all numerals that end in ”один” (“one”), that is, for all n that n % 10 == 1 && n % 100 != 11 (because *11 ends in “одиннадцать” (“eleven”) which needs plural).
    Similarly, dual is used for numerals ending in “два”, “три” and “четыре” (2, 3 and 4; n % 10 in {2, 3, 4} && n % 100 not in {12, 13, 14}, again because teen-numerals require plural).
    Although, if the extension method is not defined in the model but somewhere near the localization, it can be redefined appropriately for each language.

  10. Avatar for Karel Vandenhove
    Karel Vandenhove August 16th, 2011

    You should add a switch for count == 0 => "no themes"

  11. Avatar for Ken Egozi
    Ken Egozi August 16th, 2011

    @Yuri's point is spot on - one more reason for keeping rendering concerns in the presentation layer

  12. Avatar for Remy
    Remy August 16th, 2011

    We actually store the whole sentence in the resx files.E.g. A sentence for zero, 1 and more results. If you mingle individual words like in your example it gets difficult to translate afterwards. And yes, don't forget the 0 case as Karel pointed out. Maybe we need something where you can add as many different plural phrasing as you want, so it works for all languages.

  13. Avatar for Neil Barnwell
    Neil Barnwell August 16th, 2011

    I've done the exact same thing, but for the sake of I18n, I tend to use string.Format() with the usual placeholders:

    public static string Pluralise(object value, string singularPattern, string pluralPattern)
    {
    int number;
    if (int.TryParse(value.ToString(), out number))
    {
    if (number == 1)
    return string.Format(singularPattern, value);
    return string.Format(pluralPattern, value);
    }
    throw new ArgumentException("Value must be numeric", "value");
    }

  14. Avatar for Marcin Floryan
    Marcin Floryan August 16th, 2011

    Perhaps consider approach long used by the gettext project, mostly the ngettext method as described here: www.gnu.org/.../gettext.html#Plural-forms
    If you want to pluralise and localise you open a world of very interesting rules in various languages.

  15. Avatar for John Ludlow
    John Ludlow August 16th, 2011

    Whether you need to fix this depends on a whole raft of factors and trying to apply it to all software is just a recipe for failure. Consider the following scenarios:
    1) A customer-facing website, available in a limited number of languages, for a company that wants to showcase its professionalism.
    2) A command-line utility written in my spare time for use by power users, administrators and developers.
    3) An open-source web-based project, early in its development, with a limited development team and available in as many languages as possible.
    4) A troubleshooting message intended to appear in a log somewhere.
    In only one of those examples can I really say that fixing this kind of thing with a pluralisation method is really worth it - can you guess which one it is?
    (Yeah, using the "theme(s)" fix might be acceptable in the others).

  16. Avatar for Andy Norman
    Andy Norman August 16th, 2011

    I just looked at the Inflector class. It doesn't correctly pluralise virus to viruses opting instead for viri, which doesn't bode well.

  17. Avatar for Bertrand Le Roy
    Bertrand Le Roy August 16th, 2011

    What they said. This just won't work with localization. As you suspected, this is a solved problem: gettext is the standard solution.

  18. Avatar for Neil A
    Neil A August 16th, 2011

    What about something more re-usable; two generic extension methods like this?

    T1 ValueChoice<T0,T1>(Func<T> predicate, T1 valueIfTrue, T1 valueIfFalse)

    and

    T1 ActionChoice<T0,T1>(Func<T> predicate, T1 actionIfTrue, T1 actionIfFalse)

    So then you could do...

    Model.ValueChoice(m=>m.Count==1,"theme","themes")

  19. Avatar for Neil A
    Neil A August 16th, 2011

    D'oh - spotted some dumb errors striaght after posting, but hopefully you got the idea! This might work out better?...
    T1 ValueChoice<T0,T1>(T0 this target, Func<T0,bool> predicate, T1 valueIfTrue, T1 valueIfFalse)

  20. Avatar for Tim Iles
    Tim Iles August 16th, 2011

    Further to Andy's comment, Inflector also goes for "octopi". It also singularizes "loves" to "lofe" (but has explicit fix for the more common exception "moves"). Looks like this code would be fine for internal usage, e.g. convention-based pluralised table-name mapping from singular entity-name, but I wouldn't use it generically on a public facing front-end.

  21. Avatar for RayH
    RayH August 16th, 2011

    # Install-Package Pluralizer
    Allows very nice conventions such as (I use an extension method "Pluralize" to shorten even more):
    var str = "There {is} {_} {person}.";
    var single = Pluralizer.Instance.Pluralize(str, 1);
    Assert.AreEqual("There is 1 person.", single);
    var plural = Pluralizer.Instance.Pluralize(str, 67);
    Assert.AreEqual("There are 67 people.", plural);
    Following these simple rules:
    1) start by writing the sentence in its singular form.
    2) wrap any words that you want “pluralized” in curly braces
    3) nouns are handled automatically by the built-in PluralizationService class in Entity Framework for .NET 4
    4) only the most simple verbs and pronouns are automatically handled. You’ll probably have to handle those by yourself, separated by the pipe character.
    5) the number itself is displayed using the special underscore placeholder (i.e. write this to get the number itself printed: {_})
    6) manually define the singular and plural words by separating them with a pipe character (e.g. {singular|plural}).

  22. Avatar for Justin
    Justin August 16th, 2011

    I have to second Boris Yankov's point. Just use theme(s) and move on with things that matter. Just my opinion. Haven't had a complaint about this from a client yet in nearly 10 years.

  23. Avatar for Tim Murphy
    Tim Murphy August 16th, 2011

    My method name is SingleOrPlural.

  24. Avatar for kamranayub
    kamranayub August 16th, 2011

    I recently created a Plural Razor helper to do this for me. Mine does an extra step and will treat your inputs as format strings so if you want, you can embed the number. Sometimes I do, sometimes I don't (i.e. wrap the number in a tag).

    @helper Plural(int count, string singular, string plural) {
    @String.Format(count == 1 ? singular : plural, count)
    }

    Works very nicely! I like Neil's function in that you can pass any kind of numeric (decimal, double, etc.) but I haven't run into that yet. Keeping it simple!
    For localization, since I haven't dealt with that yet, I like Yuri's suggestion of having separate implementations. So you can do:

    Culture.Plural(count, singular, plural)

    Assuming you've implemented an architecture that will support that. That should work if you have a defined set of locales you support. Or like others have suggested, perhaps an existing library takes care of that.

  25. Avatar for Eric Malamisura
    Eric Malamisura August 16th, 2011

    I think you are right on here, this is quality. Anyone who says this is a waste of time and effort has no freaking clue what polish is or what quality is for that matter. We have reached an evolution in software design that begs for a high degree of polish, its no longer acceptable for our software to "do" what its suppose to do to get the job done. Consumers, and users alike assume it will "do", and want polish/shine. Their needs and wants have shifted, for the better IMO!

  26. Avatar for Sam
    Sam August 16th, 2011

    @pop catalin: 'no real benefit' - this is why most developers shouldn't be making UX decisions. I'm fairly sure you believe this is true, but if you're building a product where customer experience is important, generating grammatically incorrect output is not a good look (or, in my view, correct code).
    Yes, localization is hard. Accept the fact you're going to have to invest a fair bit of additional development if you want the reap the rewards of a larger market. Don't use it as an excuse for mediocrity.

  27. Avatar for MisterJames
    MisterJames August 16th, 2011

    +1 for RayH. Pluralizer also has an Mvc package with helpers called Pluralizer.Mvc.
    I deal with Français and English and this library suits me fine.

  28. Avatar for Quentin
    Quentin August 16th, 2011

    A while ago I ran into a promising string formatting library for .Net, SmartFormat.
    https://github.com/scottrippey/SmartFormat/wiki
    (originally www.codeproject.com/.../Custom_Formatting.aspx)
    It has functionality built in for pluralizing, or rather conditionally formatting based on a count.
    It also tests as one of the fastest string formatting libraries available for .Net.

  29. Avatar for Pop Catalin
    Pop Catalin August 16th, 2011

    @Sam,
    Obviously UX decisions are rather subjective and depend heavily on the type of the project. Having pluralization may or may not add any benefit at all.
    What I'm arguing is the value obtained relative to (overall) effort, and in my opinion this effort can be used elsewhere with more added value.
    Having worked on some heavily localized projects, I can say that ignoring this issue can bring more benefit (even more polish in other areas) than trying to solve it.


  30. Avatar for adamralph
    adamralph August 16th, 2011

    Whilst I have a deep love of polishing my UI's (and code), my vote leans towards the 'not worth the effort' side on this one. As various comments above have noted, this gets tricky when it comes to localization and it seems to be a can of worms that's just not worth opening.

    You have the following theme(s):

    sits just fine with me and I don't consider it 'mediocre' or 'unpolished' ;-)

  31. Avatar for adamralph
    adamralph August 16th, 2011

    (this comment can be deleted)

  32. Avatar for Jarrett
    Jarrett August 16th, 2011

    Please "borrow" the name from RoR core: pluralize is a great method name, and I wouldn't have to learn a new method name.

  33. Avatar for Nicholas Carey
    Nicholas Carey August 16th, 2011

    As noted earlier, different languages have quite a variance in their representation of grammatical:
    + Singular/Plural. The base form is singular; the plural form is inflected.
    + Singulative/Plural. Inverse of above: the base form is plural; the singulative form is inflected. To complicate thing, in Russian and Arabic, the singulative form always has feminine gender regardless of the base word's gender.
    + Singular/Dual/Plural. Some languages differentiate between singular (1), dual(2) and collective (>2, many).
    + Singular/Dual/Trial/Plural. And, just to spice it up a bit, some languages toss in a trial (3) form for pronouns (but not nouns), to boot.
    It gets further complicated because in some languages, the inflected form will change not only based on the count, but on gender and case as well.

  34. Avatar for Dmytrii Nagirniak
    Dmytrii Nagirniak August 17th, 2011

    I like how Rails does it with pluralize method that optinally allow you to pass plural and do its magic otherwise.
    So ideally I would prefer usage similar to:

    Model.Count.PluralizeFor("theme", "themes");
    Model.Count.PluralizeFor("theme");

    With the method definition of:

    public static string PluralizeFor(this int number, string singular, string plural = null) {
    if (number == 1)
    return singular;
    return plural ?? Inflector.Pluralize(singular);
    }
    To me this seem pretty clear approach.

  35. Avatar for Josh Carroll
    Josh Carroll August 17th, 2011

    I found this c# port of the Ruby pluralize method a while back, and have been using it fairly extensively ever since then.
    Super light weight and easy to turn into an extension method.
    It only works with English, but my guess is the percentage of people who actually code for internationalization in their apps is small.
    Old School Nuget: Ctrl + C, Ctrl + V

  36. Avatar for Mike
    Mike August 21st, 2011

    Sean M. Burke and Jordan Lachler once wrote a humorous piece on the subject: http://bit.ly/oQglzN

  37. Avatar for James Barrow
    James Barrow August 22nd, 2011

    Haven't read all the comments. Did see a similar thing in CodeIgniter, which has an Inflector helper [ codeigniter.com/.../inflector_helper.html ]. Inflection seems to be the right term, and also seems quite complicated [ http://en.wikipedia.org/wiki/Inflection ].

  38. Avatar for Scott Hanselmnan
    Scott Hanselmnan August 23rd, 2011

    What about the Pluralizer built into EF that we already ship with .NET4? www.hanselman.com/...

  39. Avatar for Michael Fokken
    Michael Fokken September 8th, 2011

    I totally agree. I hate going to sites or using programs that say "1 days". How long would it take to program what you did? 10 seconds? I think it's really interesting the major websites don't take stuff like this into consideration.
    -
    http://www.michaelfokken.com/

  40. Avatar for Anonymous
    Anonymous September 11th, 2011

    If you used proper Internet English, problem would be none:

    All your theme:


    <ul>
    @foreach(var theme in Model) {
    <li>@theme.Id</li>
    }
    </ul>