| /* 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; |
| |
| // Upper cased words. E.g. "HELLO". |
| 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 |
| * @param {number} count |
| * @param {boolean} inclusive |
| * @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'], |
| ['whiskey', 'whiskies'] |
| ].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|tlas|gas|ris)$/i, '$1es'], |
| [/(e[mn]u)s?$/i, '$1s'], |
| [/([^l]ias|[aeiou]las|[emjzr]as|[iu]am)$/i, '$1'], |
| [/(alumn|syllab|octop|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'], |
| [/(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'], |
| [/(m|l)ice$/i, '$1ouse'], |
| [/(seraph|cherub)im$/i, '$1'], |
| [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|tlas|gas|(?:her|at|gr)o|ris)(?:es)?$/i, '$1'], |
| [/(analy|ba|diagno|parenthe|progno|synop|the|empha|cri)(?:sis|ses)$/i, '$1sis'], |
| [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'], |
| [/(test)(?:is|es)$/i, '$1is'], |
| [/(alumn|syllab|octop|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', |
| 'alcohol', |
| 'ammo', |
| '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', |
| 'flounder', |
| 'fun', |
| 'gallows', |
| 'garbage', |
| 'graffiti', |
| 'headquarters', |
| 'health', |
| 'herpes', |
| 'highjinks', |
| 'homework', |
| 'housework', |
| 'information', |
| 'jeans', |
| 'justice', |
| 'kudos', |
| 'labour', |
| 'literature', |
| 'machinery', |
| 'mackerel', |
| 'mail', |
| 'media', |
| 'mews', |
| 'moose', |
| 'music', |
| 'manga', |
| 'news', |
| 'pike', |
| 'plankton', |
| 'pliers', |
| 'pollution', |
| 'premises', |
| 'rain', |
| 'research', |
| 'rice', |
| 'salmon', |
| 'scissors', |
| 'series', |
| 'sewage', |
| 'shambles', |
| 'shrimp', |
| 'species', |
| 'staff', |
| 'swine', |
| 'tennis', |
| 'traffic', |
| 'transporation', |
| 'trout', |
| 'tuna', |
| 'wealth', |
| 'welfare', |
| 'whiting', |
| 'wildebeest', |
| 'wildlife', |
| 'you', |
| // 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; |
| }); |