nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.
153 lines (152 loc) • 5.32 kB
JavaScript
import { isNonEmptyString, isString, isUndefined } from '../guards/primitives.js';
import { normalizeNumber } from '../number/utilities.js';
import { irregularRules, pluralRules, singularRules, uncountables } from './rules.js';
export class Pluralizer {
#pluralRules = [];
#singularRules = [];
#uncountables = new Set();
#irregularSingles = {};
#irregularPlurals = {};
constructor() {
this.#loadRules();
}
#loadRules() {
irregularRules.forEach(([single, plural]) => {
this.addIrregular(single, plural);
});
pluralRules.forEach(([rule, replacement]) => {
this.addPluralRule(rule, replacement);
});
singularRules.forEach(([rule, replacement]) => {
this.addSingularRule(rule, replacement);
});
uncountables.forEach((word) => {
this.addUncountable(word);
});
}
#restoreCase(original, transformed) {
original = original?.trim();
if (original === transformed)
return transformed;
if (original === original.toLowerCase()) {
return transformed.toLowerCase();
}
if (original === original.toUpperCase()) {
return transformed.toUpperCase();
}
if (original[0] === original[0].toUpperCase() &&
original.slice(1) === original.slice(1).toLowerCase()) {
return transformed.charAt(0).toUpperCase() + transformed.slice(1).toLowerCase();
}
let result = '';
for (let i = 0; i < transformed.length; i++) {
const origChar = original[i];
if (origChar &&
origChar === origChar.toUpperCase() &&
origChar !== origChar.toLowerCase()) {
result += transformed[i].toUpperCase();
}
else {
result += transformed[i].toLowerCase();
}
}
return result;
}
#applyRules(word, rules) {
if (!isNonEmptyString(word))
return '';
if (this.#isUncountable(word)) {
return word;
}
for (let i = rules.length - 1; i >= 0; i--) {
const [rule, replacement] = rules[i];
if (rule.test(word)) {
return word.replace(rule, replacement);
}
}
return word;
}
#isUncountable(word) {
for (const entry of this.#uncountables) {
if (isString(entry)) {
if (entry.toLowerCase() === word)
return true;
}
else {
if (entry?.test(word))
return true;
}
}
return false;
}
addPluralRule(rule, replacement) {
this.#pluralRules.push([rule, replacement]);
}
addSingularRule(rule, replacement) {
this.#singularRules.push([rule, replacement]);
}
addUncountable(word) {
this.#uncountables.add(isString(word) ? word?.toLowerCase() : word);
}
addIrregular(single, plural) {
const singleLower = single?.toLowerCase();
const pluralLower = plural?.toLowerCase();
this.#irregularSingles[singleLower] = pluralLower;
this.#irregularPlurals[pluralLower] = singleLower;
}
pluralize(word, options = {}) {
const count = normalizeNumber(options?.count);
if (!isUndefined(count)) {
const pluralized = count === 1 ? this.toSingular(word) : this.toPlural(word);
return options?.inclusive ? `${count} ${pluralized}` : pluralized;
}
return this.toPlural(word);
}
toPlural(word) {
if (!isNonEmptyString(word))
return '';
const lower = word?.trim()?.toLowerCase();
if (this.#isUncountable(lower))
return word;
if (this.#irregularSingles[lower]) {
return this.#restoreCase(word, this.#irregularSingles[lower]);
}
return this.#restoreCase(word, this.#applyRules(lower, this.#pluralRules));
}
toSingular(word) {
if (!isNonEmptyString(word))
return '';
const lower = word?.trim()?.toLowerCase();
if (this.#isUncountable(lower))
return word;
if (this.#irregularPlurals[lower]) {
return this.#restoreCase(word, this.#irregularPlurals[lower]);
}
return this.#restoreCase(word, this.#applyRules(lower, this.#singularRules));
}
isPlural(word) {
if (!isNonEmptyString(word))
return false;
const lower = word?.trim()?.toLowerCase();
if (this.#isUncountable(lower))
return true;
if (this.#irregularPlurals[lower])
return true;
if (this.#irregularSingles[lower])
return false;
return this.toSingular(lower) !== lower;
}
isSingular(word) {
if (!isNonEmptyString(word))
return false;
const lower = word?.trim()?.toLowerCase();
if (this.#isUncountable(lower))
return true;
if (this.#irregularSingles[lower])
return true;
if (this.#irregularPlurals[lower])
return false;
return this.toSingular(lower) === lower;
}
}
export const pluralizer = new Pluralizer();