72 lines
2.5 KiB
JavaScript
72 lines
2.5 KiB
JavaScript
/**
|
|
* 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 };
|