JavaScript pluralize an english string

115.4k views Asked by At

In PHP, I use Kuwamoto's class to pluralize nouns in my strings. I didn't find something as good as this script in javascript except for some plugins. So, it would be great to have a javascript function based on Kuwamoto's class.

http://kuwamoto.org/2007/12/17/improved-pluralizing-in-php-actionscript-and-ror/

17

There are 17 answers

13
pmrotule On

So, I answer my own question by sharing my translation in javascript of Kuwamoto's PHP class.

String.prototype.plural = function(revert){

    var plural = {
        '(quiz)$'               : "$1zes",
        '^(ox)$'                : "$1en",
        '([m|l])ouse$'          : "$1ice",
        '(matr|vert|ind)ix|ex$' : "$1ices",
        '(x|ch|ss|sh)$'         : "$1es",
        '([^aeiouy]|qu)y$'      : "$1ies",
        '(hive)$'               : "$1s",
        '(?:([^f])fe|([lr])f)$' : "$1$2ves",
        '(shea|lea|loa|thie)f$' : "$1ves",
        'sis$'                  : "ses",
        '([ti])um$'             : "$1a",
        '(tomat|potat|ech|her|vet)o$': "$1oes",
        '(bu)s$'                : "$1ses",
        '(alias)$'              : "$1es",
        '(octop)us$'            : "$1i",
        '(ax|test)is$'          : "$1es",
        '(us)$'                 : "$1es",
        '([^s]+)$'              : "$1s"
    };

    var singular = {
        '(quiz)zes$'             : "$1",
        '(matr)ices$'            : "$1ix",
        '(vert|ind)ices$'        : "$1ex",
        '^(ox)en$'               : "$1",
        '(alias)es$'             : "$1",
        '(octop|vir)i$'          : "$1us",
        '(cris|ax|test)es$'      : "$1is",
        '(shoe)s$'               : "$1",
        '(o)es$'                 : "$1",
        '(bus)es$'               : "$1",
        '([m|l])ice$'            : "$1ouse",
        '(x|ch|ss|sh)es$'        : "$1",
        '(m)ovies$'              : "$1ovie",
        '(s)eries$'              : "$1eries",
        '([^aeiouy]|qu)ies$'     : "$1y",
        '([lr])ves$'             : "$1f",
        '(tive)s$'               : "$1",
        '(hive)s$'               : "$1",
        '(li|wi|kni)ves$'        : "$1fe",
        '(shea|loa|lea|thie)ves$': "$1f",
        '(^analy)ses$'           : "$1sis",
        '((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$': "$1$2sis",        
        '([ti])a$'               : "$1um",
        '(n)ews$'                : "$1ews",
        '(h|bl)ouses$'           : "$1ouse",
        '(corpse)s$'             : "$1",
        '(us)es$'                : "$1",
        's$'                     : ""
    };

    var irregular = {
        'move'   : 'moves',
        'foot'   : 'feet',
        'goose'  : 'geese',
        'sex'    : 'sexes',
        'child'  : 'children',
        'man'    : 'men',
        'tooth'  : 'teeth',
        'person' : 'people'
    };

    var uncountable = [
        'sheep', 
        'fish',
        'deer',
        'moose',
        'series',
        'species',
        'money',
        'rice',
        'information',
        'equipment'
    ];

    // save some time in the case that singular and plural are the same
    if(uncountable.indexOf(this.toLowerCase()) >= 0)
      return this;

    // check for irregular forms
    for(word in irregular){

      if(revert){
              var pattern = new RegExp(irregular[word]+'$', 'i');
              var replace = word;
      } else{ var pattern = new RegExp(word+'$', 'i');
              var replace = irregular[word];
      }
      if(pattern.test(this))
        return this.replace(pattern, replace);
    }

    if(revert) var array = singular;
         else  var array = plural;

    // check for matches using regular expressions
    for(reg in array){

      var pattern = new RegExp(reg, 'i');

      if(pattern.test(this))
        return this.replace(pattern, array[reg]);
    }

    return this;
}

Easy to use:

alert("page".plural()); // return plural form => pages
alert("mouse".plural()); // return plural form => mice
alert("women".plural(true)); // return singular form => woman

DEMO

0
Tudor Morar On

I use this simple inline statement

const number = 2;
const string = `${number} trutle${number === 1 ? "" : "s"}`; //this one
console.log(string)

2
Nirvana On

Use -ies or -s (depending on the second-to-last letter) if the word ends in a y, use -es if the word ends in a ‑s, -ss, -sh, -ch, -x, or -z, use a lookup table if the world is an irregular plural, and use -s otherwise.

var pluralize = (function () {
    const vowels = "aeiou";

    const irregulars = { "addendum": "addenda", "aircraft": "aircraft", "alumna": "alumnae", "alumnus": "alumni", "analysis": "analyses", "antenna": "antennae", "antithesis": "antitheses", "apex": "apices", "appendix": "appendices", "axis": "axes", "bacillus": "bacilli", "bacterium": "bacteria", "basis": "bases", "beau": "beaux", "bison": "bison", "bureau": "bureaux", "cactus": "cacti", "château": "châteaux", "child": "children", "codex": "codices", "concerto": "concerti", "corpus": "corpora", "crisis": "crises", "criterion": "criteria", "curriculum": "curricula", "datum": "data", "deer": "deer", "diagnosis": "diagnoses", "die": "dice", "dwarf": "dwarves", "ellipsis": "ellipses", "erratum": "errata", "faux pas": "faux pas", "fez": "fezzes", "fish": "fish", "focus": "foci", "foot": "feet", "formula": "formulae", "fungus": "fungi", "genus": "genera", "goose": "geese", "graffito": "graffiti", "grouse": "grouse", "half": "halves", "hoof": "hooves", "hypothesis": "hypotheses", "index": "indices", "larva": "larvae", "libretto": "libretti", "loaf": "loaves", "locus": "loci", "louse": "lice", "man": "men", "matrix": "matrices", "medium": "media", "memorandum": "memoranda", "minutia": "minutiae", "moose": "moose", "mouse": "mice", "nebula": "nebulae", "nucleus": "nuclei", "oasis": "oases", "offspring": "offspring", "opus": "opera", "ovum": "ova", "ox": "oxen", "parenthesis": "parentheses", "phenomenon": "phenomena", "phylum": "phyla", "quiz": "quizzes", "radius": "radii", "referendum": "referenda", "salmon": "salmon", "scarf": "scarves", "self": "selves", "series": "series", "sheep": "sheep", "shrimp": "shrimp", "species": "species", "stimulus": "stimuli", "stratum": "strata", "swine": "swine", "syllabus": "syllabi", "symposium": "symposia", "synopsis": "synopses", "tableau": "tableaux", "thesis": "theses", "thief": "thieves", "tooth": "teeth", "trout": "trout", "tuna": "tuna", "vertebra": "vertebrae", "vertex": "vertices", "vita": "vitae", "vortex": "vortices", "wharf": "wharves", "wife": "wives", "wolf": "wolves", "woman": "women", "guy": "guys", "buy": "buys", "person": "people" };

    function pluralize(word) {
        word = word.toLowerCase();


        if (irregulars[word]) {
            return irregulars[word];
        }

        if (word.length >= 2 && vowels.includes(word[word.length - 2])) {
            return word + "s";
        }

        if (word.endsWith("s") || word.endsWith("sh") || word.endsWith("ch") || word.endsWith("x") || word.endsWith("z")) {
            return word + "es";
        }

        if (word.endsWith("y")) {
            return word.slice(0, -1) + "ies";
        }


        return word + "s";
    }

    return pluralize;
})();

////////////////////////////////////////
console.log(pluralize("dog"));
console.log(pluralize("cat"));
console.log(pluralize("fox"));
console.log(pluralize("dwarf"));
console.log(pluralize("guy"));
console.log(pluralize("play"));

Obviously, this can't support all English edge-cases, but it has the most common ones.

0
Chintan Bhatt On

Using @sarink's answer, I made a function to create a string using key value pairs data and pluralizing the keys. Here's the snippet:

// Function to create a string from given key value pairs and pluralize keys
const stringPluralize = function(data){
    var suffix = 's';
    var str = '';
    $.each(data, function(key, val){
        if(str != ''){
            str += val>0 ? ` and ${val} ${key}${val !== 1 ? suffix : ''}` : '';
        }
        else{
            str = val>0 ? `${val} ${key}${val !== 1 ? suffix : ''}` : '';
        }
    });
    return str;
}
var leftDays = '1';
var leftHours = '12';
var str = stringPluralize({day:leftDays, hour:leftHours});
console.log(str) // Gives 1 day and 12 hours
2
Must Impress On

I'm a bit late to this party but this works nicely with very minimal code:

const pluralize = (count, single, plural) => `${count} ${count !== 1 ? plural || single+'s' : single}`;

Provide either a single word:

pluralize(1, 'house');
// Output: 1 house

pluralize(2, 'house');
// Output: 2 houses

or, 2 strings for words with different endings:

pluralize(1, 'company', 'companies');
// Output: 1 company

pluralize(2, 'company', 'companies');
// Output: 2 companies

pluralize(100000, "fish", "fish");
// Output: 100000 fish

pluralize(2, "piece of furniture", "pieces of furniture");
// Output: 2 pieces of furniture
0
Pepe El Toro On

I am late to the party too, but this will work for English words or Spanish:

String.prototype.pluralize = function(count, plural = 's') {
  return count === 1 ? this : plural.length > 2 ? plural : this + plural
};

This way you can use any string like so:

"Apple".pluralize(1) // Apple
"Apple".pluralize(2) // Apples
"Company".pluralize(1, "Companies") // Company
"Company".pluralize(5, "Companies") // Companies
"Tooth".pluralize(1, "Teeth") // Tooth
"Tooth".pluralize(2, "Teeth") // Teeth 

Some Spanish:

"arbol".pluralize(1,"es")  // arbol
"arbol".pluralize(2,"es")  // arboles
"manzana".pluralize(1) // manzana
"manzana".pluralize(2) // manzanas
0
gasolin On

The new intl API spec from ECMA will provide the plural rules function, https://github.com/tc39/proposal-intl-plural-rules

Here's the polyfill that can be used today https://github.com/eemeli/IntlPluralRules

1
barncat On
function pluralize( /* n, [ n2, n3, ... ] str */ ) {
    var n = Array.prototype.slice.call( arguments ) ;
    var str = n.pop(), iMax = n.length - 1, i = -1, j ;
    str = str.replace( /\$\$|\$(\d+)/g,
        function( m, p1 ) { return m == '$$' ? '$' : n[+p1-1] }
    ) ;
    return str.replace( /[(](.*?)([+-])(\d*)(?:,([^,)]*))?(?:,([^)]*))?[)]/g,
        function( match, one, sign, abs, not1, zero ) {
            // if abs, use indicated element in the array of numbers
            // instead of using the next element in sequence
            abs ? ( j = +abs - 1 ) : ( i < iMax && i++, j = i ) ;
            if ( zero != undefined && n[j] == 0 ) return zero ;
            return ( n[j] != 1 ) == ( sign == '+' ) ? ( not1 || 's' ) : one ;
        }
    ) ;  
}

console.log( pluralize( 1, 'the cat(+) live(-) outside' ) ) ;
// the cat lives outside
console.log( pluralize( 2, 'the child(+,ren) (is+,are) inside' ) ) ;
// the children are inside
console.log( pluralize( 0, '$1 dog(+), ($1+,$1,no) dog(+), ($1+,$1,no) dog(+,,)' ) ) ;
// 0 dogs, no dogs, no dog
console.log( pluralize( 100, 1, '$1 penn(y+,ies) make(-1) $$$2' ) ) ;
// 100 pennies make $1
console.log( pluralize( 1, 0.01, '$1 penn(y+,ies) make(-1) $$$2' ) ) ;
// 1 penny makes $0.01
0
Oded Breiner On

In case the singular/plural word is not dynamic and you just want to add an "s" if there's more than one:

`${x} minute${x - 1 ? 's' : ''}`
0
Tushar Garg On

In my company we were initially using the most voted answer on this thread https://stackoverflow.com/a/39835908/14204063.

Kudos to him for coming up with this algorithm.

Although there is a common missed case for this.

Consider plurals for words like company -> companies, category -> categories cannot be handled with that algorithm.

Thus I came up with this algorithm, please have a look at this.

const maybePluralize = ({ count, noun, suffix = "s", pluralNoun }) => {
  if (count <= 1) {
    return `${count} ${noun}`;
  }

  const pluralForm = pluralNoun ? `${pluralNoun}` : `${noun}${suffix}`;
  return `${count} ${pluralForm}`;
};

Example:

word: category

maybepluralize({count: 1, noun: category}) -> category

maybepluralize({count: 10, noun: category, pluralNoun: "categories"}) -> categories

0
srghma On
// 1 - apple
// 2 - apples
const pluralize_noun = (count, noun, suffix = 's') =>
  `${noun}${count !== 1 ? suffix : ''}`;

// 1 - (apple) gives
// 2 - (apples) give
// `This package includes ${package_.person_amount} ${pluralize_noun(package_.person_amount, "ticket")} that ${pluralize_verb(package_.person_amount, "give")} access to ${package_.person_amount} ${pluralize_noun(package_.person_amount, "seat")}.`
const pluralize_verb = (count, verb, suffix = 's') =>
  `${verb}${count !== 1 ? '' : suffix}`;
2
kmypwn On

To provide a simple and readable option (ES6):

export function pluralizeAndStringify(value, word, suffix = 's'){
   if (value == 1){
    return value + ' ' + word;
   }
   else {
    return value + ' ' + word + suffix;
   }
}

If you gave something like pluralizeAndStringify(5, 'dog') you'd get "5 dogs" as your output.

6
Kabir Sarin On

Simple version (ES6):

const pluralize = (count, noun, suffix = 's') =>
  `${count} ${noun}${count !== 1 ? suffix : ''}`;

Typescript:

const pluralize = (count: number, noun: string, suffix = 's') =>
  `${count} ${noun}${count !== 1 ? suffix : ''}`;

Usage:

pluralize(0, 'turtle'); // 0 turtles
pluralize(1, 'turtle'); // 1 turtle
pluralize(2, 'turtle'); // 2 turtles
pluralize(3, 'fox', 'es'); // 3 foxes

This obviously doesn't support all english edge-cases, but it's suitable for most purposes

2
Joshua Pinter On

Use Pluralize

There's a great little library called Pluralize that's packaged in npm and bower.

This is what it looks like to use:

import Pluralize from 'pluralize';

Pluralize( 'Towel', 42 );       // "Towels"

Pluralize( 'Towel', 42, true ); // "42 Towels"

And you can get it here:

https://github.com/blakeembrey/pluralize

0
Slava Fomin II On

I’ve created a very simple library that can be used for words pluralization in JavaScript. It transparently uses CLDR database for multiple locales, so it supports almost any language you would like to use. It’s API is very minimalistic and integration is extremely simple. It’s called Numerous.

I’ve also written a small introduction article to it: «How to pluralize any word in different languages using JavaScript?».

Feel free to use it in your project. I will also be glad for your feedback on it.

1
Sergio Tapia On

Taken from my blog: https://sergiotapia.me/pluralizing-strings-in-javascript-es6-b5d4d651d403


You can use the pluralize library for this.

NPM:
npm install pluralize --save

Yarn:
yarn add pluralize

Wherever you want to use the lib, you can require it easily.

var pluralize = require('pluralize')

I like to add it to the window object so I can just invoke pluralize() wherever I need it. Within my application.js root file:

window.pluralize = require('pluralize')

Then you can just use it anywhere, React components, or just plain Javascript:

<span className="pull-left">
  {`${item.score} ${pluralize('point', item.score)}`}
</span>

console.log(pluralize('point', item.score))
4
Erik Campobadal On

Based on @pmrotule answer with some typescript magic and some additions to the uncountable array. I add here the plural and singular functions.

The plural version:

/**
 * Returns the plural of an English word.
 *
 * @export
 * @param {string} word
 * @param {number} [amount]
 * @returns {string}
 */
export function plural(word: string, amount?: number): string {
    if (amount !== undefined && amount === 1) {
        return word
    }
    const plural: { [key: string]: string } = {
        '(quiz)$'               : "$1zes",
        '^(ox)$'                : "$1en",
        '([m|l])ouse$'          : "$1ice",
        '(matr|vert|ind)ix|ex$' : "$1ices",
        '(x|ch|ss|sh)$'         : "$1es",
        '([^aeiouy]|qu)y$'      : "$1ies",
        '(hive)$'               : "$1s",
        '(?:([^f])fe|([lr])f)$' : "$1$2ves",
        '(shea|lea|loa|thie)f$' : "$1ves",
        'sis$'                  : "ses",
        '([ti])um$'             : "$1a",
        '(tomat|potat|ech|her|vet)o$': "$1oes",
        '(bu)s$'                : "$1ses",
        '(alias)$'              : "$1es",
        '(octop)us$'            : "$1i",
        '(ax|test)is$'          : "$1es",
        '(us)$'                 : "$1es",
        '([^s]+)$'              : "$1s"
    }
    const irregular: { [key: string]: string } = {
        'move'   : 'moves',
        'foot'   : 'feet',
        'goose'  : 'geese',
        'sex'    : 'sexes',
        'child'  : 'children',
        'man'    : 'men',
        'tooth'  : 'teeth',
        'person' : 'people'
    }
    const uncountable: string[] = [
        'sheep',
        'fish',
        'deer',
        'moose',
        'series',
        'species',
        'money',
        'rice',
        'information',
        'equipment',
        'bison',
        'cod',
        'offspring',
        'pike',
        'salmon',
        'shrimp',
        'swine',
        'trout',
        'aircraft',
        'hovercraft',
        'spacecraft',
        'sugar',
        'tuna',
        'you',
        'wood'
    ]
    // save some time in the case that singular and plural are the same
    if (uncountable.indexOf(word.toLowerCase()) >= 0) {
        return word
    }
    // check for irregular forms
    for (const w in irregular) {
        const pattern = new RegExp(`${w}$`, 'i')
        const replace = irregular[w]
        if (pattern.test(word)) {
            return word.replace(pattern, replace)
        }
    }
    // check for matches using regular expressions
    for (const reg in plural) {
        const pattern = new RegExp(reg, 'i')
        if (pattern.test(word)) {
            return word.replace(pattern, plural[reg])
        }
    }
    return word
}

And the singular version:

/**
 * Returns the singular of an English word.
 *
 * @export
 * @param {string} word
 * @param {number} [amount]
 * @returns {string}
 */
export function singular(word: string, amount?: number): string {
    if (amount !== undefined && amount !== 1) {
        return word
    }
    const singular: { [key: string]: string } = {
        '(quiz)zes$'             : "$1",
        '(matr)ices$'            : "$1ix",
        '(vert|ind)ices$'        : "$1ex",
        '^(ox)en$'               : "$1",
        '(alias)es$'             : "$1",
        '(octop|vir)i$'          : "$1us",
        '(cris|ax|test)es$'      : "$1is",
        '(shoe)s$'               : "$1",
        '(o)es$'                 : "$1",
        '(bus)es$'               : "$1",
        '([m|l])ice$'            : "$1ouse",
        '(x|ch|ss|sh)es$'        : "$1",
        '(m)ovies$'              : "$1ovie",
        '(s)eries$'              : "$1eries",
        '([^aeiouy]|qu)ies$'     : "$1y",
        '([lr])ves$'             : "$1f",
        '(tive)s$'               : "$1",
        '(hive)s$'               : "$1",
        '(li|wi|kni)ves$'        : "$1fe",
        '(shea|loa|lea|thie)ves$': "$1f",
        '(^analy)ses$'           : "$1sis",
        '((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$': "$1$2sis",
        '([ti])a$'               : "$1um",
        '(n)ews$'                : "$1ews",
        '(h|bl)ouses$'           : "$1ouse",
        '(corpse)s$'             : "$1",
        '(us)es$'                : "$1",
        's$'                     : ""
    }
    const irregular: { [key: string]: string } = {
        'move'   : 'moves',
        'foot'   : 'feet',
        'goose'  : 'geese',
        'sex'    : 'sexes',
        'child'  : 'children',
        'man'    : 'men',
        'tooth'  : 'teeth',
        'person' : 'people'
    }
    const uncountable: string[] = [
        'sheep',
        'fish',
        'deer',
        'moose',
        'series',
        'species',
        'money',
        'rice',
        'information',
        'equipment',
        'bison',
        'cod',
        'offspring',
        'pike',
        'salmon',
        'shrimp',
        'swine',
        'trout',
        'aircraft',
        'hovercraft',
        'spacecraft',
        'sugar',
        'tuna',
        'you',
        'wood'
    ]
    // save some time in the case that singular and plural are the same
    if (uncountable.indexOf(word.toLowerCase()) >= 0) {
        return word
    }
    // check for irregular forms
    for (const w in irregular) {
        const pattern = new RegExp(`${irregular[w]}$`, 'i')
        const replace = w
        if (pattern.test(word)) {
            return word.replace(pattern, replace)
        }
    }
    // check for matches using regular expressions
    for (const reg in singular) {
        const pattern = new RegExp(reg, 'i')
        if (pattern.test(word)) {
            return word.replace(pattern, singular[reg])
        }
    }
    return word
}