UNPKG

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
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();