Reputation: 9692
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/
Upvotes: 88
Views: 124849
Reputation: 6233
Just use Intl.PluralRules.
If you care about English only you can use this code:
/**
* As English only has two forms for cardinal numbers,
* the select() method returns only two tags:
* "one" for the singular case, and "other" for all other cardinal numbers.
*/
export const createEnglishPluralizer = (wordForms: {
one: string;
other: string;
}) => {
const enCardinalRules = new Intl.PluralRules('en-US');
return (number: number) => {
const rule = enCardinalRules.select(number) as 'one' | 'other';
return wordForms[rule];
};
};
Test
import { createEnglishPluralizer } from './createEnglishPluralizer';
describe('createEnglishPluralizer', () => {
it('pluralizes', () => {
const pluralizeNodes = createEnglishPluralizer({
one: 'node',
other: 'nodes',
});
expect(pluralizeNodes(0)).toBe('nodes');
expect(pluralizeNodes(1)).toBe('node');
expect(pluralizeNodes(2)).toBe('nodes');
// ...
expect(pluralizeNodes(9)).toBe('nodes');
expect(pluralizeNodes(10)).toBe('nodes');
expect(pluralizeNodes(11)).toBe('nodes');
expect(pluralizeNodes(12)).toBe('nodes');
expect(pluralizeNodes(13)).toBe('nodes');
// ...
expect(pluralizeNodes(19)).toBe('nodes');
expect(pluralizeNodes(20)).toBe('nodes');
expect(pluralizeNodes(21)).toBe('nodes');
expect(pluralizeNodes(22)).toBe('nodes');
expect(pluralizeNodes(23)).toBe('nodes');
// ...
expect(pluralizeNodes(100)).toBe('nodes');
expect(pluralizeNodes(101)).toBe('nodes');
});
});
for other languages, you'll need different pluaralizers that use different tags. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules
Upvotes: 0
Reputation: 130730
Intl.PluralRules
:Intl.PluralRules
is better than a simple count > 1
check because it handles complex pluralization rules for different languages. English pluralization rules are very easy but it isn't the case for other languages.
const getPlural = (locale = navigator.language) => (count, singular, plural) => {
const rules = new Intl.PluralRules(locale);
const pluralCategory = rules.select(count);
if (pluralCategory === "one") return singular;
if (!plural) {
// fallback for English locales. You can add other languages' fallbacks (see German below)
if (locale.toLowerCase().startsWith("en")) return `${singular}s`;
// example for an automatic German fallback
if (locale.toLowerCase().startsWith("de")) {
// Common German plural endings
if (singular.endsWith("e")) return `${singular}n`;
if (singular.endsWith("er") ||
singular.endsWith("el") ||
singular.endsWith("en")) return singular;
return `${singular}e`;
}
}
return plural || singular;
}
// English examples
const pluralize = getPlural("en-US"); // when initialized with "English"
console.log(`1 ${pluralize(1, 'item')}`) // "1 item"
console.log(`2 ${pluralize(2, 'item')}`) // "2 items"
console.log(`3 ${pluralize(2, 'child', 'children')}`) // "3 children"
// German examples
console.log(`1 ${getPlural("DE")(1, 'buch')}`) // "1 book"
console.log(`2 ${getPlural("DE")(2, 'buch')}`) // "2 items"
// Russian
console.log(`2 ${getPlural("RU")(2, 'книга', 'книги')}`) // (books)
Upvotes: 1
Reputation: 5353
// 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}`;
Upvotes: 0
Reputation: 9
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
Upvotes: 0
Reputation: 29759
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' : ''}`
Upvotes: -1
Reputation: 347
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
Upvotes: -2
Reputation: 31
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
Upvotes: 0
Reputation: 18556
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
Upvotes: 113
Reputation: 643
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.
Upvotes: 5
Reputation: 487
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.
Upvotes: 2
Reputation: 3868
I use this simple inline statement
const number = 2;
const string = `${number} trutle${number === 1 ? "" : "s"}`; //this one
console.log(string)
Upvotes: 7
Reputation: 917
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
}
Upvotes: 26
Reputation: 181
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
Upvotes: 0
Reputation: 85
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
Upvotes: 0
Reputation: 9825
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))
Upvotes: 7
Reputation: 47621
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
Upvotes: 87
Reputation: 28661
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.
Upvotes: 2
Reputation: 2334
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
Upvotes: 8
Reputation: 9692
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
Upvotes: 47