123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /* global define */
- (function (root, pluralize) {
- /* istanbul ignore else */
- if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
- // Node.
- module.exports = pluralize();
- } else if (typeof define === 'function' && define.amd) {
- // AMD, registers as an anonymous module.
- define(function () {
- return pluralize();
- });
- } else {
- // Browser global.
- root.pluralize = pluralize();
- }
- })(this, function () {
- // Rule storage - pluralize and singularize need to be run sequentially,
- // while other rules can be optimized using an object for instant lookups.
- var pluralRules = [];
- var singularRules = [];
- var uncountables = {};
- var irregularPlurals = {};
- var irregularSingles = {};
- /**
- * Sanitize a pluralization rule to a usable regular expression.
- *
- * @param {(RegExp|string)} rule
- * @return {RegExp}
- */
- function sanitizeRule (rule) {
- if (typeof rule === 'string') {
- return new RegExp('^' + rule + '$', 'i');
- }
- return rule;
- }
- /**
- * Pass in a word token to produce a function that can replicate the case on
- * another word.
- *
- * @param {string} word
- * @param {string} token
- * @return {Function}
- */
- function restoreCase (word, token) {
- // Tokens are an exact match.
- if (word === token) return token;
- // Lower cased words. E.g. "hello".
- if (word === word.toLowerCase()) return token.toLowerCase();
- // Upper cased words. E.g. "WHISKY".
- if (word === word.toUpperCase()) return token.toUpperCase();
- // Title cased words. E.g. "Title".
- if (word[0] === word[0].toUpperCase()) {
- return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();
- }
- // Lower cased words. E.g. "test".
- return token.toLowerCase();
- }
- /**
- * Interpolate a regexp string.
- *
- * @param {string} str
- * @param {Array} args
- * @return {string}
- */
- function interpolate (str, args) {
- return str.replace(/\$(\d{1,2})/g, function (match, index) {
- return args[index] || '';
- });
- }
- /**
- * Replace a word using a rule.
- *
- * @param {string} word
- * @param {Array} rule
- * @return {string}
- */
- function replace (word, rule) {
- return word.replace(rule[0], function (match, index) {
- var result = interpolate(rule[1], arguments);
- if (match === '') {
- return restoreCase(word[index - 1], result);
- }
- return restoreCase(match, result);
- });
- }
- /**
- * Sanitize a word by passing in the word and sanitization rules.
- *
- * @param {string} token
- * @param {string} word
- * @param {Array} rules
- * @return {string}
- */
- function sanitizeWord (token, word, rules) {
- // Empty string or doesn't need fixing.
- if (!token.length || uncountables.hasOwnProperty(token)) {
- return word;
- }
- var len = rules.length;
- // Iterate over the sanitization rules and use the first one to match.
- while (len--) {
- var rule = rules[len];
- if (rule[0].test(word)) return replace(word, rule);
- }
- return word;
- }
- /**
- * Replace a word with the updated word.
- *
- * @param {Object} replaceMap
- * @param {Object} keepMap
- * @param {Array} rules
- * @return {Function}
- */
- function replaceWord (replaceMap, keepMap, rules) {
- return function (word) {
- // Get the correct token and case restoration functions.
- var token = word.toLowerCase();
- // Check against the keep object map.
- if (keepMap.hasOwnProperty(token)) {
- return restoreCase(word, token);
- }
- // Check against the replacement map for a direct word replacement.
- if (replaceMap.hasOwnProperty(token)) {
- return restoreCase(word, replaceMap[token]);
- }
- // Run all the rules against the word.
- return sanitizeWord(token, word, rules);
- };
- }
- /**
- * Check if a word is part of the map.
- */
- function checkWord (replaceMap, keepMap, rules, bool) {
- return function (word) {
- var token = word.toLowerCase();
- if (keepMap.hasOwnProperty(token)) return true;
- if (replaceMap.hasOwnProperty(token)) return false;
- return sanitizeWord(token, token, rules) === token;
- };
- }
- /**
- * Pluralize or singularize a word based on the passed in count.
- *
- * @param {string} word The word to pluralize
- * @param {number} count How many of the word exist
- * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks)
- * @return {string}
- */
- function pluralize (word, count, inclusive) {
- var pluralized = count === 1
- ? pluralize.singular(word) : pluralize.plural(word);
- return (inclusive ? count + ' ' : '') + pluralized;
- }
- /**
- * Pluralize a word.
- *
- * @type {Function}
- */
- pluralize.plural = replaceWord(
- irregularSingles, irregularPlurals, pluralRules
- );
- /**
- * Check if a word is plural.
- *
- * @type {Function}
- */
- pluralize.isPlural = checkWord(
- irregularSingles, irregularPlurals, pluralRules
- );
- /**
- * Singularize a word.
- *
- * @type {Function}
- */
- pluralize.singular = replaceWord(
- irregularPlurals, irregularSingles, singularRules
- );
- /**
- * Check if a word is singular.
- *
- * @type {Function}
- */
- pluralize.isSingular = checkWord(
- irregularPlurals, irregularSingles, singularRules
- );
- /**
- * Add a pluralization rule to the collection.
- *
- * @param {(string|RegExp)} rule
- * @param {string} replacement
- */
- pluralize.addPluralRule = function (rule, replacement) {
- pluralRules.push([sanitizeRule(rule), replacement]);
- };
- /**
- * Add a singularization rule to the collection.
- *
- * @param {(string|RegExp)} rule
- * @param {string} replacement
- */
- pluralize.addSingularRule = function (rule, replacement) {
- singularRules.push([sanitizeRule(rule), replacement]);
- };
- /**
- * Add an uncountable word rule.
- *
- * @param {(string|RegExp)} word
- */
- pluralize.addUncountableRule = function (word) {
- if (typeof word === 'string') {
- uncountables[word.toLowerCase()] = true;
- return;
- }
- // Set singular and plural references for the word.
- pluralize.addPluralRule(word, '$0');
- pluralize.addSingularRule(word, '$0');
- };
- /**
- * Add an irregular word definition.
- *
- * @param {string} single
- * @param {string} plural
- */
- pluralize.addIrregularRule = function (single, plural) {
- plural = plural.toLowerCase();
- single = single.toLowerCase();
- irregularSingles[single] = plural;
- irregularPlurals[plural] = single;
- };
- /**
- * Irregular rules.
- */
- [
- // Pronouns.
- ['I', 'we'],
- ['me', 'us'],
- ['he', 'they'],
- ['she', 'they'],
- ['them', 'them'],
- ['myself', 'ourselves'],
- ['yourself', 'yourselves'],
- ['itself', 'themselves'],
- ['herself', 'themselves'],
- ['himself', 'themselves'],
- ['themself', 'themselves'],
- ['is', 'are'],
- ['was', 'were'],
- ['has', 'have'],
- ['this', 'these'],
- ['that', 'those'],
- // Words ending in with a consonant and `o`.
- ['echo', 'echoes'],
- ['dingo', 'dingoes'],
- ['volcano', 'volcanoes'],
- ['tornado', 'tornadoes'],
- ['torpedo', 'torpedoes'],
- // Ends with `us`.
- ['genus', 'genera'],
- ['viscus', 'viscera'],
- // Ends with `ma`.
- ['stigma', 'stigmata'],
- ['stoma', 'stomata'],
- ['dogma', 'dogmata'],
- ['lemma', 'lemmata'],
- ['schema', 'schemata'],
- ['anathema', 'anathemata'],
- // Other irregular rules.
- ['ox', 'oxen'],
- ['axe', 'axes'],
- ['die', 'dice'],
- ['yes', 'yeses'],
- ['foot', 'feet'],
- ['eave', 'eaves'],
- ['goose', 'geese'],
- ['tooth', 'teeth'],
- ['quiz', 'quizzes'],
- ['human', 'humans'],
- ['proof', 'proofs'],
- ['carve', 'carves'],
- ['valve', 'valves'],
- ['looey', 'looies'],
- ['thief', 'thieves'],
- ['groove', 'grooves'],
- ['pickaxe', 'pickaxes'],
- ['passerby', 'passersby']
- ].forEach(function (rule) {
- return pluralize.addIrregularRule(rule[0], rule[1]);
- });
- /**
- * Pluralization rules.
- */
- [
- [/s?$/i, 's'],
- [/[^\u0000-\u007F]$/i, '$0'],
- [/([^aeiou]ese)$/i, '$1'],
- [/(ax|test)is$/i, '$1es'],
- [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'],
- [/(e[mn]u)s?$/i, '$1s'],
- [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'],
- [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],
- [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],
- [/(seraph|cherub)(?:im)?$/i, '$1im'],
- [/(her|at|gr)o$/i, '$1oes'],
- [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],
- [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],
- [/sis$/i, 'ses'],
- [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],
- [/([^aeiouy]|qu)y$/i, '$1ies'],
- [/([^ch][ieo][ln])ey$/i, '$1ies'],
- [/(x|ch|ss|sh|zz)$/i, '$1es'],
- [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],
- [/\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'],
- [/(pe)(?:rson|ople)$/i, '$1ople'],
- [/(child)(?:ren)?$/i, '$1ren'],
- [/eaux$/i, '$0'],
- [/m[ae]n$/i, 'men'],
- ['thou', 'you']
- ].forEach(function (rule) {
- return pluralize.addPluralRule(rule[0], rule[1]);
- });
- /**
- * Singularization rules.
- */
- [
- [/s$/i, ''],
- [/(ss)$/i, '$1'],
- [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'],
- [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],
- [/ies$/i, 'y'],
- [/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, '$1ie'],
- [/\b(mon|smil)ies$/i, '$1ey'],
- [/\b((?:tit)?m|l)ice$/i, '$1ouse'],
- [/(seraph|cherub)im$/i, '$1'],
- [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'],
- [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'],
- [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'],
- [/(test)(?:is|es)$/i, '$1is'],
- [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],
- [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],
- [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],
- [/(alumn|alg|vertebr)ae$/i, '$1a'],
- [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],
- [/(matr|append)ices$/i, '$1ix'],
- [/(pe)(rson|ople)$/i, '$1rson'],
- [/(child)ren$/i, '$1'],
- [/(eau)x?$/i, '$1'],
- [/men$/i, 'man']
- ].forEach(function (rule) {
- return pluralize.addSingularRule(rule[0], rule[1]);
- });
- /**
- * Uncountable rules.
- */
- [
- // Singular words with no plurals.
- 'adulthood',
- 'advice',
- 'agenda',
- 'aid',
- 'aircraft',
- 'alcohol',
- 'ammo',
- 'analytics',
- 'anime',
- 'athletics',
- 'audio',
- 'bison',
- 'blood',
- 'bream',
- 'buffalo',
- 'butter',
- 'carp',
- 'cash',
- 'chassis',
- 'chess',
- 'clothing',
- 'cod',
- 'commerce',
- 'cooperation',
- 'corps',
- 'debris',
- 'diabetes',
- 'digestion',
- 'elk',
- 'energy',
- 'equipment',
- 'excretion',
- 'expertise',
- 'firmware',
- 'flounder',
- 'fun',
- 'gallows',
- 'garbage',
- 'graffiti',
- 'hardware',
- 'headquarters',
- 'health',
- 'herpes',
- 'highjinks',
- 'homework',
- 'housework',
- 'information',
- 'jeans',
- 'justice',
- 'kudos',
- 'labour',
- 'literature',
- 'machinery',
- 'mackerel',
- 'mail',
- 'media',
- 'mews',
- 'moose',
- 'music',
- 'mud',
- 'manga',
- 'news',
- 'only',
- 'personnel',
- 'pike',
- 'plankton',
- 'pliers',
- 'police',
- 'pollution',
- 'premises',
- 'rain',
- 'research',
- 'rice',
- 'salmon',
- 'scissors',
- 'series',
- 'sewage',
- 'shambles',
- 'shrimp',
- 'software',
- 'species',
- 'staff',
- 'swine',
- 'tennis',
- 'traffic',
- 'transportation',
- 'trout',
- 'tuna',
- 'wealth',
- 'welfare',
- 'whiting',
- 'wildebeest',
- 'wildlife',
- 'you',
- /pok[eé]mon$/i,
- // Regexes.
- /[^aeiou]ese$/i, // "chinese", "japanese"
- /deer$/i, // "deer", "reindeer"
- /fish$/i, // "fish", "blowfish", "angelfish"
- /measles$/i,
- /o[iu]s$/i, // "carnivorous"
- /pox$/i, // "chickpox", "smallpox"
- /sheep$/i
- ].forEach(pluralize.addUncountableRule);
- return pluralize;
- });
|