platform/packages/support/lib/src/pluralizer.dart

292 lines
7.7 KiB
Dart
Raw Normal View History

/// A class that provides word pluralization functionality.
///
/// This class handles pluralization of English words, including regular rules,
/// irregular cases, and uncountable words.
class Pluralizer {
/// Regular expression for matching word inflection.
static final RegExp _regex = RegExp(r'^(.*?)(s|ss|sh|ch|x|z|o|is|us|um|a)$');
/// Regular expression for matching sibilant sounds.
static final RegExp _sibilantRegex = RegExp(r'(s|ss|sh|ch|x|z)$');
/// Regular expression for matching words ending in 'y'.
static final RegExp _yRegex = RegExp(r'^(.*?)([^aeiou])y$');
/// Words that are uncountable and don't have plural forms.
static final Set<String> _uncountable = {
'equipment',
'information',
'rice',
'money',
'species',
'series',
'fish',
'sheep',
'deer',
'aircraft',
'offspring',
'news',
};
/// Irregular word forms that don't follow standard rules.
static final Map<String, String> _irregular = {
'person': 'people',
'man': 'men',
'child': 'children',
'sex': 'sexes',
'move': 'moves',
'foot': 'feet',
'tooth': 'teeth',
'goose': 'geese',
'criterion': 'criteria',
'radius': 'radii',
'phenomenon': 'phenomena',
'index': 'indices',
'vertex': 'vertices',
'matrix': 'matrices',
'quiz': 'quizzes',
'analysis': 'analyses',
'thesis': 'theses',
'datum': 'data',
'bacterium': 'bacteria',
'syllabus': 'syllabi',
'focus': 'foci',
'fungus': 'fungi',
'cactus': 'cacti',
'hypothesis': 'hypotheses',
'crisis': 'crises',
'basis': 'bases',
'diagnosis': 'diagnoses',
'ellipsis': 'ellipses',
'oasis': 'oases',
'parenthesis': 'parentheses',
'synopsis': 'synopses',
'thesis': 'theses',
};
/// Custom pluralization rules.
static final Map<String, String> _rules = {};
/// Add a custom pluralization rule.
static void addRule(String singular, String plural) {
_rules[singular.toLowerCase()] = plural.toLowerCase();
}
/// Add an irregular word form.
static void addIrregular(String singular, String plural) {
_irregular[singular.toLowerCase()] = plural.toLowerCase();
}
/// Add an uncountable word.
static void addUncountable(String word) {
_uncountable.add(word.toLowerCase());
}
/// Get the plural form of a word.
static String plural(String word, [int count = 2]) {
if (count == 1) {
return word;
}
final lower = word.toLowerCase();
// Check uncountable
if (_uncountable.contains(lower)) {
return word;
}
// Check custom rules
if (_rules.containsKey(lower)) {
return _matchCase(_rules[lower]!, word);
}
// Check irregular forms
if (_irregular.containsKey(lower)) {
return _matchCase(_irregular[lower]!, word);
}
// Check for words ending in 'y'
final yMatch = _yRegex.firstMatch(lower);
if (yMatch != null) {
return _matchCase('${yMatch.group(1)}${yMatch.group(2)}ies', word);
}
// Apply regular rules
final match = _regex.firstMatch(lower);
if (match != null) {
final base = match.group(1)!;
final suffix = match.group(2)!;
String plural;
switch (suffix) {
case 'is':
plural = '${base}es';
break;
case 'us':
if (lower.endsWith('bus')) {
plural = '${base}${suffix}es';
} else {
plural = '${base}i';
}
break;
case 'um':
case 'a':
plural = '${base}a';
break;
case 'o':
plural = '${base}oes';
break;
case 'ss':
case 'sh':
case 'ch':
case 'x':
case 'z':
plural = '${word}es';
break;
case 's':
plural = '${base}ses';
break;
default:
plural = '${word}s';
}
return _matchCase(plural, word);
}
// Default to adding 's'
return _matchCase('${word}s', word);
}
/// Get the singular form of a word.
static String singular(String word) {
final lower = word.toLowerCase();
// Check uncountable
if (_uncountable.contains(lower)) {
return word;
}
// Check custom rules
for (final entry in _rules.entries) {
if (entry.value == lower) {
return _matchCase(entry.key, word);
}
}
// Check irregular forms
for (final entry in _irregular.entries) {
if (entry.value == lower) {
return _matchCase(entry.key, word);
}
}
// Apply regular rules
if (lower.endsWith('ies') && !lower.endsWith('series')) {
return _matchCase('${word.substring(0, word.length - 3)}y', word);
}
if (lower.endsWith('es')) {
if (lower.endsWith('ses') && !lower.endsWith('bases')) {
return _matchCase(word.substring(0, word.length - 2), word);
}
if (lower.endsWith('oes')) {
return _matchCase(word.substring(0, word.length - 2), word);
}
if (lower.endsWith('uses')) {
return _matchCase(word.substring(0, word.length - 2), word);
}
if (_sibilantRegex.hasMatch(lower.substring(0, lower.length - 2))) {
return _matchCase(word.substring(0, word.length - 2), word);
}
return _matchCase(word.substring(0, word.length - 2), word);
}
if (lower.endsWith('a')) {
if (lower.endsWith('phenomena')) {
return _matchCase('phenomenon', word);
}
if (lower.endsWith('criteria')) {
return _matchCase('criterion', word);
}
if (lower.endsWith('bacteria')) {
return _matchCase('bacterium', word);
}
return _matchCase('${word.substring(0, word.length - 1)}um', word);
}
if (lower.endsWith('i')) {
if (lower.endsWith('radii')) {
return _matchCase('radius', word);
}
if (lower.endsWith('fungi')) {
return _matchCase('fungus', word);
}
if (lower.endsWith('cacti')) {
return _matchCase('cactus', word);
}
if (lower.endsWith('syllabi')) {
return _matchCase('syllabus', word);
}
return _matchCase('${word.substring(0, word.length - 1)}us', word);
}
if (lower.endsWith('s')) {
return _matchCase(word.substring(0, word.length - 1), word);
}
return word;
}
/// Check if a word is plural.
static bool isPlural(String word) {
final lower = word.toLowerCase();
if (_uncountable.contains(lower)) return false;
// Check irregular plurals
for (final entry in _irregular.entries) {
if (entry.value == lower) return true;
}
// Check custom rules
for (final entry in _rules.entries) {
if (entry.value == lower) return true;
}
// Check common plural endings
if (lower.endsWith('s') && !lower.endsWith('ss')) return true;
if (lower.endsWith('es')) return true;
if (lower.endsWith('ies')) return true;
if (lower.endsWith('i')) return true;
if (lower.endsWith('a') && !lower.endsWith('ia')) return true;
return false;
}
/// Check if a word is singular.
static bool isSingular(String word) {
final lower = word.toLowerCase();
if (_uncountable.contains(lower)) return true;
// Check irregular singulars
if (_irregular.containsKey(lower)) return true;
// Check custom rules
if (_rules.containsKey(lower)) return true;
// If it's plural, it's not singular
if (isPlural(word)) return false;
return true;
}
/// Match the case of the target word.
static String _matchCase(String word, String target) {
if (target.toUpperCase() == target) {
return word.toUpperCase();
}
if (target[0].toUpperCase() == target[0]) {
return '${word[0].toUpperCase()}${word.substring(1).toLowerCase()}';
}
return word.toLowerCase();
}
}