You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2016/09/28 17:28:08 UTC
[15/17] ambari git commit: AMBARI-18478. Ambari UI - Service Actions
menu for pluralized value has grammatical error (onechiporenko)
AMBARI-18478. Ambari UI - Service Actions menu for pluralized value has grammatical error (onechiporenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e44b8805
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e44b8805
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e44b8805
Branch: refs/heads/branch-feature-AMBARI-18456
Commit: e44b880514109011fadb9c274b3b8163f13390d8
Parents: 39858cc
Author: Oleg Nechiporenko <on...@apache.org>
Authored: Wed Sep 28 11:41:20 2016 +0300
Committer: Oleg Nechiporenko <on...@apache.org>
Committed: Wed Sep 28 15:29:33 2016 +0300
----------------------------------------------------------------------
ambari-web/app/assets/licenses/NOTICE.txt | 3 +
ambari-web/app/messages.js | 13 +-
ambari-web/app/utils/string_utils.js | 7 +-
.../app/views/common/rolling_restart_view.js | 19 +-
ambari-web/app/views/main/service/item.js | 3 +-
.../service/widgets/create/expression_view.js | 2 +-
ambari-web/brunch-config.js | 3 +-
.../resourceManager/wizard_controller_test.js | 1 -
ambari-web/test/models/cluster_test.js | 12 +-
.../objects/service_config_property_test.js | 31 +-
.../configs/theme/sub_section_tab_test.js | 2 +-
.../test/views/main/host/log_metrics_test.js | 1 -
ambari-web/test/views/main/host_test.js | 4 +-
ambari-web/vendor/scripts/pluralize.js | 461 +++++++++++++++++++
14 files changed, 506 insertions(+), 56 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/app/assets/licenses/NOTICE.txt
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/licenses/NOTICE.txt b/ambari-web/app/assets/licenses/NOTICE.txt
index c750a37..75a13ea 100644
--- a/ambari-web/app/assets/licenses/NOTICE.txt
+++ b/ambari-web/app/assets/licenses/NOTICE.txt
@@ -60,3 +60,6 @@ Copyright (C) 2015 Leaf Corcoran (leafot [at] gmail [*dot*] com)
This product includes bootstrap-contextmenu v.0.3.3 (https://github.com/sydcanem/bootstrap-contextmenu - MIT License)
Copyright (C) 2015 James Santos
+
+This product includes pluralize v.3.0.0 (https://github.com/blakeembrey/pluralize - MIT License)
+Copyright (C) 2016 Blake Embrey
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 2c819e5..1c53839 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -2913,14 +2913,13 @@ Em.I18n.translations = {
'tableView.filters.filteredAlertInstancesInfo': '{0} of {1} instances showing',
'tableView.filters.filteredLogsInfo': '{0} of {1} logs showing',
- 'rollingrestart.dialog.title': 'Restart {0}s',
+ 'rollingrestart.dialog.title': 'Restart {0}',
'rollingrestart.dialog.primary': 'Trigger Rolling Restart',
'rollingrestart.notsupported.hostComponent': 'Rolling restart not supported for {0} components',
- 'rollingrestart.dialog.msg.restart': 'This will restart a specified number of {0}s at a time.',
- 'rollingrestart.dialog.msg.noRestartHosts': 'There are no {0}s to do rolling restarts',
+ 'rollingrestart.dialog.msg.restart': 'This will restart a specified number of {0} at a time.',
+ 'rollingrestart.dialog.msg.noRestartHosts': 'There are no {0} to do rolling restarts',
'rollingrestart.dialog.msg.maintainance': 'Note: {0} {1} in Maintenance Mode will not be restarted',
- 'rollingrestart.dialog.msg.maintainance.plural': 'Note: {0} {1}s in Maintenance Mode will not be restarted',
- 'rollingrestart.dialog.msg.componentsAtATime': '{0}s at a time',
+ 'rollingrestart.dialog.msg.componentsAtATime': '{0} at a time',
'rollingrestart.dialog.msg.timegap.prefix': 'Wait ',
'rollingrestart.dialog.msg.timegap.suffix': 'seconds between batches ',
'rollingrestart.dialog.msg.toleration.prefix': 'Tolerate up to ',
@@ -2930,7 +2929,7 @@ Em.I18n.translations = {
'rollingrestart.dialog.err.invalid.toleratesize': 'Invalid failure toleration count: {0}',
'rollingrestart.dialog.warn.datanode.batch.size': 'Restarting more than one DataNode at a time is not recommended. Doing so can lead to data unavailability and/or possible loss of data being actively written to HDFS.',
'rollingrestart.dialog.msg.serviceNotInMM':'Note: This will trigger alerts. To suppress alerts, turn on Maintenance Mode for {0} prior to triggering a rolling restart',
- 'rollingrestart.dialog.msg.staleConfigsOnly': 'Only restart {0}s with stale configs',
+ 'rollingrestart.dialog.msg.staleConfigsOnly': 'Only restart {0} with stale configs',
'rollingrestart.rest.context': 'Rolling Restart of {0}s - batch {1} of {2}',
'rollingrestart.context.allOnSelectedHosts':'Restart all components on the selected hosts',
'rollingrestart.context.allForSelectedService':'Restart all components for {0}',
@@ -2962,7 +2961,7 @@ Em.I18n.translations = {
'widget.create.wizard.step2.addExpression': 'Add Expression',
'widget.create.wizard.step2.addDataset': 'Add data set',
'widget.create.wizard.step2.body.gauge.overflow.warning':'Overflowed! Gauge can only display number between 0 and 1.',
- 'widget.create.wizard.step2.allComponents': 'All {0}s',
+ 'widget.create.wizard.step2.allComponents': 'All {0}',
'widget.create.wizard.step2.activeComponents': 'Active {0}',
'widget.create.wizard.step2.noMetricFound': 'No metric found',
'widget.create.wizard.step3.widgetName': 'Name',
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/app/utils/string_utils.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/string_utils.js b/ambari-web/app/utils/string_utils.js
index 3754ba1..f4e3674 100644
--- a/ambari-web/app/utils/string_utils.js
+++ b/ambari-web/app/utils/string_utils.js
@@ -201,11 +201,8 @@ module.exports = {
* @method pluralize
*/
pluralize: function(count, singular, plural) {
- plural = plural || singular + 's';
- if (count > 1) {
- return plural;
- }
- return singular;
+ var _plural = plural || pluralize(singular);
+ return count > 1 ? _plural : singular;
},
/**
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/app/views/common/rolling_restart_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/rolling_restart_view.js b/ambari-web/app/views/common/rolling_restart_view.js
index 0d849a2..64b8610 100644
--- a/ambari-web/app/views/common/rolling_restart_view.js
+++ b/ambari-web/app/views/common/rolling_restart_view.js
@@ -119,7 +119,7 @@ App.RollingRestartView = Em.View.extend({
* List of errors is saved to <code>errors</code>
*/
validate : function() {
- var displayName = this.get('hostComponentDisplayName');
+ var displayName = pluralize(this.get('hostComponentDisplayName'));
var componentName = this.get('hostComponentName');
var totalCount = this.get('restartHostComponents.length');
var bs = this.get('batchSize');
@@ -207,7 +207,9 @@ App.RollingRestartView = Em.View.extend({
/**
* @type {String}
*/
- restartMessage: Em.computed.i18nFormat('rollingrestart.dialog.msg.restart', 'hostComponentDisplayName'),
+ restartMessage : function() {
+ return Em.I18n.t('rollingrestart.dialog.msg.restart').format(pluralize(this.get('hostComponentDisplayName')));
+ }.property('hostComponentDisplayName'),
/**
* @type {String}
@@ -216,10 +218,7 @@ App.RollingRestartView = Em.View.extend({
var count = this.get('componentsWithMaintenanceHost.length');
if (count > 0) {
var name = this.get('hostComponentDisplayName');
- if (count > 1) {
- return Em.I18n.t('rollingrestart.dialog.msg.maintainance.plural').format(count, name)
- }
- return Em.I18n.t('rollingrestart.dialog.msg.maintainance').format(count, name)
+ return Em.I18n.t('rollingrestart.dialog.msg.maintainance').format(count, pluralize(name));
}
return null;
}.property('componentsWithMaintenanceHost', 'hostComponentDisplayName'),
@@ -227,11 +226,15 @@ App.RollingRestartView = Em.View.extend({
/**
* @type {String}
*/
- batchSizeMessage: Em.computed.i18nFormat('rollingrestart.dialog.msg.componentsAtATime', 'hostComponentDisplayName'),
+ batchSizeMessage : function() {
+ return Em.I18n.t('rollingrestart.dialog.msg.componentsAtATime').format(pluralize(this.get('hostComponentDisplayName')));
+ }.property('hostComponentDisplayName'),
/**
* @type {String}
*/
- staleConfigsOnlyMessage: Em.computed.i18nFormat('rollingrestart.dialog.msg.staleConfigsOnly', 'hostComponentDisplayName')
+ staleConfigsOnlyMessage : function() {
+ return Em.I18n.t('rollingrestart.dialog.msg.staleConfigsOnly').format(pluralize(this.get('hostComponentDisplayName')));
+ }.property('hostComponentDisplayName')
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/app/views/main/service/item.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/item.js b/ambari-web/app/views/main/service/item.js
index a007e17..fc9c4f3 100644
--- a/ambari-web/app/views/main/service/item.js
+++ b/ambari-web/app/views/main/service/item.js
@@ -146,9 +146,10 @@ App.MainServiceItemView = Em.View.extend({
allSlaves.concat(allMasters).filter(function (_component) {
return App.get('components.rollinRestartAllowed').contains(_component);
}).forEach(function(_component) {
+ var _componentNamePluralized = pluralize(App.format.role(_component, false));
options.push(self.createOption(actionMap.ROLLING_RESTART, {
context: _component,
- label: actionMap.ROLLING_RESTART.label.format(App.format.role(_component, false))
+ label: actionMap.ROLLING_RESTART.label.format(_componentNamePluralized)
}));
});
allMasters.filter(function(master) {
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/app/views/main/service/widgets/create/expression_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/widgets/create/expression_view.js b/ambari-web/app/views/main/service/widgets/create/expression_view.js
index a12bf99..7afe287 100644
--- a/ambari-web/app/views/main/service/widgets/create/expression_view.js
+++ b/ambari-web/app/views/main/service/widgets/create/expression_view.js
@@ -358,7 +358,7 @@ App.AddMetricExpressionView = Em.View.extend({
return Em.I18n.t('widget.create.wizard.step2.activeComponents').format(stackComponent.get('displayName'));
}
}
- return Em.I18n.t('widget.create.wizard.step2.allComponents').format(stackComponent.get('displayName'));
+ return Em.I18n.t('widget.create.wizard.step2.allComponents').format(pluralize(stackComponent.get('displayName')));
}.property('componentName', 'level'),
count: servicesMap[serviceName].components[componentId].count,
metrics: servicesMap[serviceName].components[componentId].metrics.uniq().sort(),
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/brunch-config.js
----------------------------------------------------------------------
diff --git a/ambari-web/brunch-config.js b/ambari-web/brunch-config.js
index 64ac946..d71f8da 100644
--- a/ambari-web/brunch-config.js
+++ b/ambari-web/brunch-config.js
@@ -74,7 +74,8 @@ module.exports.config = {
'vendor/scripts/spin.js',
'vendor/scripts/jquery.flexibleArea.js',
'vendor/scripts/FileSaver.js',
- 'vendor/scripts/Blob.js'
+ 'vendor/scripts/Blob.js',
+ 'vendor/scripts/pluralize.js'
]
}
},
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/test/controllers/main/admin/highAvailability/resourceManager/wizard_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/resourceManager/wizard_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/resourceManager/wizard_controller_test.js
index 5a991d3..19fbea6 100644
--- a/ambari-web/test/controllers/main/admin/highAvailability/resourceManager/wizard_controller_test.js
+++ b/ambari-web/test/controllers/main/admin/highAvailability/resourceManager/wizard_controller_test.js
@@ -18,7 +18,6 @@
var App = require('app');
require('controllers/main/admin/highAvailability/resourceManager/wizard_controller');
-var testHelpers = require('test/helpers');
describe('App.RMHighAvailabilityWizardController', function () {
var controller;
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/test/models/cluster_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/cluster_test.js b/ambari-web/test/models/cluster_test.js
index a6bafba..604e50a 100644
--- a/ambari-web/test/models/cluster_test.js
+++ b/ambari-web/test/models/cluster_test.js
@@ -29,7 +29,7 @@ describe('App.Cluster', function () {
describe('#isKerberosEnabled', function () {
- var cases = [
+ [
{
securityType: 'KERBEROS',
isKerberosEnabled: true,
@@ -40,9 +40,7 @@ describe('App.Cluster', function () {
isKerberosEnabled: false,
title: 'Kerberos disabled'
}
- ];
-
- cases.forEach(function (item) {
+ ].forEach(function (item) {
it(item.title, function () {
cluster.set('securityType', item.securityType);
@@ -53,7 +51,7 @@ describe('App.Cluster', function () {
describe('#isCredentialStorePersistent', function () {
- var cases = [
+ [
{
propertyValue: 'false',
isCredentialStorePersistent: false,
@@ -69,9 +67,7 @@ describe('App.Cluster', function () {
isCredentialStorePersistent: true,
title: 'persistent credential store'
}
- ];
-
- cases.forEach(function (item) {
+ ].forEach(function (item) {
it(item.title, function () {
cluster.set('credentialStoreProperties', {
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/test/models/configs/objects/service_config_property_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/configs/objects/service_config_property_test.js b/ambari-web/test/models/configs/objects/service_config_property_test.js
index 49613a44..ef0bd61 100644
--- a/ambari-web/test/models/configs/objects/service_config_property_test.js
+++ b/ambari-web/test/models/configs/objects/service_config_property_test.js
@@ -189,9 +189,7 @@ describe('App.ServiceConfigProperty', function () {
App.TestAliases.testAsComputedAnd(getProperty(), 'hideFinalIcon', ['!isFinal', 'isNotEditable']);
describe('#placeholder', function () {
- it('should equal foo', function() {
- serviceConfigProperty.set('isEditable', true);
- var testCases = [
+ [
{
placeholderText: 'foo',
savedValue: ''
@@ -204,26 +202,19 @@ describe('App.ServiceConfigProperty', function () {
placeholderText: 'foo',
savedValue: 'bar'
}
- ];
- testCases.forEach(function (item) {
- serviceConfigProperty.set('placeholderText', item.placeholderText);
- serviceConfigProperty.set('savedValue', item.savedValue);
- expect(serviceConfigProperty.get('placeholder')).to.equal('foo');
- });
+ ].forEach(function (item) {
+ it('should equal foo, placeholder = ' + JSON.stringify(item.placeholderText), function() {
+ serviceConfigProperty.set('isEditable', true);
+ serviceConfigProperty.set('placeholderText', item.placeholderText);
+ serviceConfigProperty.set('savedValue', item.savedValue);
+ expect(serviceConfigProperty.get('placeholder')).to.equal('foo');
+ });
});
it('should equal null', function() {
serviceConfigProperty.set('isEditable', false);
- var testCases = [
- {
- placeholderText: 'foo',
- savedValue: 'bar'
- }
- ];
- testCases.forEach(function (item) {
- serviceConfigProperty.set('placeholderText', item.placeholderText);
- serviceConfigProperty.set('savedValue', item.savedValue);
- expect(serviceConfigProperty.get('placeholder')).to.equal(null);
- });
+ serviceConfigProperty.set('placeholderText', 'foo');
+ serviceConfigProperty.set('savedValue', 'bar');
+ expect(serviceConfigProperty.get('placeholder')).to.equal(null);
});
});
describe('#isPropertyOverridable', function () {
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/test/models/configs/theme/sub_section_tab_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/configs/theme/sub_section_tab_test.js b/ambari-web/test/models/configs/theme/sub_section_tab_test.js
index 6044432..0c3b98c 100644
--- a/ambari-web/test/models/configs/theme/sub_section_tab_test.js
+++ b/ambari-web/test/models/configs/theme/sub_section_tab_test.js
@@ -155,7 +155,7 @@ describe('App.SubSectionTab', function () {
it('should include visible properties with errors', function () {
subSectionTab.set('configs', configs);
- expect(subSectionTab.get('errorsCount')).to.eql(8);
+ expect(subSectionTab.get('errorsCount')).to.be.equal(8);
});
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/test/views/main/host/log_metrics_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/log_metrics_test.js b/ambari-web/test/views/main/host/log_metrics_test.js
index a0a3c6c..52f4e55 100644
--- a/ambari-web/test/views/main/host/log_metrics_test.js
+++ b/ambari-web/test/views/main/host/log_metrics_test.js
@@ -17,7 +17,6 @@
*/
var App = require('app');
-var fileUtils = require('utils/file_utils');
describe('App.MainHostLogMetrics', function() {
var view;
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/test/views/main/host_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host_test.js b/ambari-web/test/views/main/host_test.js
index 0b789f6..79cc65c 100644
--- a/ambari-web/test/views/main/host_test.js
+++ b/ambari-web/test/views/main/host_test.js
@@ -773,7 +773,7 @@ describe('App.MainHostView', function () {
describe("#restartRequiredComponentsMessage", function () {
it("5 components require restart", function() {
- var content = 'c1, c2, c3, c4, c5' + ' ' + Em.I18n.t('common.components').toLowerCase();
+ var content = 'c1, c2, c3, c4, c5 ' + Em.I18n.t('common.components').toLowerCase();
hostView.set('content.componentsWithStaleConfigsCount', 5);
hostView.set('content.componentsWithStaleConfigs', [
{displayName: 'c1'},
@@ -789,7 +789,7 @@ describe('App.MainHostView', function () {
});
it("1 component require restart", function() {
- var content = 'c1' + ' ' + Em.I18n.t('common.component').toLowerCase();
+ var content = 'c1 ' + Em.I18n.t('common.component').toLowerCase();
hostView.set('content.componentsWithStaleConfigsCount', 1);
hostView.set('content.componentsWithStaleConfigs', [
{displayName: 'c1'}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e44b8805/ambari-web/vendor/scripts/pluralize.js
----------------------------------------------------------------------
diff --git a/ambari-web/vendor/scripts/pluralize.js b/ambari-web/vendor/scripts/pluralize.js
new file mode 100644
index 0000000..7246db1
--- /dev/null
+++ b/ambari-web/vendor/scripts/pluralize.js
@@ -0,0 +1,461 @@
+/* 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 = {};
+
+ /**
+ * Title case a string.
+ *
+ * @param {string} str
+ * @return {string}
+ */
+ function toTitleCase (str) {
+ return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
+ }
+
+ /**
+ * 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 toTitleCase(token);
+ }
+
+ // 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] || '';
+ });
+ }
+
+ /**
+ * Sanitize a word by passing in the word and sanitization rules.
+ *
+ * @param {string} token
+ * @param {string} word
+ * @param {Array} collection
+ * @return {string}
+ */
+ function sanitizeWord (token, word, collection) {
+ // Empty string or doesn't need fixing.
+ if (!token.length || uncountables.hasOwnProperty(token)) {
+ return word;
+ }
+
+ var len = collection.length;
+
+ // Iterate over the sanitization rules and use the first one to match.
+ while (len--) {
+ var rule = collection[len];
+
+ // If the rule passes, return the replacement.
+ if (rule[0].test(word)) {
+ return word.replace(rule[0], function (match, index, word) {
+ var result = interpolate(rule[1], arguments);
+
+ if (match === '') {
+ return restoreCase(word[index - 1], result);
+ }
+
+ return restoreCase(match, result);
+ });
+ }
+ }
+
+ 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);
+ };
+ }
+
+ /**
+ * 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
+ );
+
+ /**
+ * Singularize a word.
+ *
+ * @type {Function}
+ */
+ pluralize.singular = replaceWord(
+ 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'],
+ [/([^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'],
+ [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(?:sis|ses)$/i, '$1sis'],
+ [/(^analy)(?:sis|ses)$/i, '$1sis'],
+ [/(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'],
+ [/(e[mn]u)s?$/i, '$1'],
+ [/(movie|twelve)s$/i, '$1'],
+ [/(cris|test|diagnos)(?: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.
+ 'advice',
+ 'adulthood',
+ 'agenda',
+ 'aid',
+ 'alcohol',
+ 'ammo',
+ 'athletics',
+ 'bison',
+ 'blood',
+ 'bream',
+ 'buffalo',
+ 'butter',
+ 'carp',
+ 'cash',
+ 'chassis',
+ 'chess',
+ 'clothing',
+ 'commerce',
+ 'cod',
+ 'cooperation',
+ 'corps',
+ 'digestion',
+ 'debris',
+ 'diabetes',
+ 'energy',
+ 'equipment',
+ 'elk',
+ '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',
+ 'news',
+ 'pike',
+ 'plankton',
+ 'pliers',
+ 'pollution',
+ 'premises',
+ 'rain',
+ 'research',
+ 'rice',
+ 'salmon',
+ 'scissors',
+ 'series',
+ 'sewage',
+ 'shambles',
+ 'shrimp',
+ 'species',
+ 'staff',
+ 'swine',
+ 'trout',
+ 'traffic',
+ 'transporation',
+ 'tuna',
+ 'wealth',
+ 'welfare',
+ 'whiting',
+ 'wildebeest',
+ 'wildlife',
+ 'you',
+ // Regexes.
+ /pox$/i, // "chickpox", "smallpox"
+ /ois$/i,
+ /deer$/i, // "deer", "reindeer"
+ /fish$/i, // "fish", "blowfish", "angelfish"
+ /sheep$/i,
+ /measles$/i,
+ /[^aeiou]ese$/i // "chinese", "japanese"
+ ].forEach(pluralize.addUncountableRule);
+
+ return pluralize;
+});
\ No newline at end of file