/** * Noun pluralization for count measurements in recipes. * * Each entry has a singular and plural form. Entries are sorted * longest-first so that "garlic clove" matches before "clove", * "bell pepper" before "pepper", etc. */ const PLURALS = [ { singular: 'portobello mushroom', plural: 'portobello mushrooms' }, { singular: 'chile pepper', plural: 'chile peppers' }, { singular: 'garlic clove', plural: 'garlic cloves' }, { singular: 'bell pepper', plural: 'bell peppers' }, { singular: 'egg white', plural: 'egg whites' }, { singular: 'egg yolk', plural: 'egg yolks' }, { singular: 'egg yoke', plural: 'egg yokes' }, { singular: 'tortilla', plural: 'tortillas' }, { singular: 'mushroom', plural: 'mushrooms' }, { singular: 'biscuit', plural: 'biscuits' }, { singular: 'potato', plural: 'potatoes' }, { singular: 'tomato', plural: 'tomatoes' }, { singular: 'pepper', plural: 'peppers' }, { singular: 'hoagie', plural: 'hoagies' }, { singular: 'banana', plural: 'bananas' }, { singular: 'carrot', plural: 'carrots' }, { singular: 'onion', plural: 'onions' }, { singular: 'lemon', plural: 'lemons' }, { singular: 'apple', plural: 'apples' }, { singular: 'clove', plural: 'cloves' }, { singular: 'lime', plural: 'limes' }, { singular: 'egg', plural: 'eggs' }, ]; /** * Check if text ends with a known singular or plural noun. * Case-insensitive, word-boundary-aware (the noun must appear at a * word boundary in the text). * * @param {string} textBefore - text preceding the count measurement * @returns {{ singular: string, plural: string, matchStart: number, matchLength: number } | null} */ function matchPlural(textBefore) { if (!textBefore) return null; const trimmed = textBefore.trimEnd(); if (!trimmed) return null; const lower = trimmed.toLowerCase(); for (const entry of PLURALS) { // Try both singular and plural forms for (const form of [entry.singular, entry.plural]) { if (lower.length < form.length) continue; const tail = lower.slice(-form.length); if (tail !== form) continue; // Check word boundary: either start of string or preceded by non-word char const beforeIdx = lower.length - form.length; if (beforeIdx > 0 && /\w/.test(lower[beforeIdx - 1])) continue; return { singular: entry.singular, plural: entry.plural, matchStart: trimmed.length - form.length, matchLength: form.length, }; } } return null; } module.exports = { PLURALS, matchPlural };