feat: expand ingredient-alias seed list to ~137 entries, ignore accents in name matching (v0.86.0)
Ingredient-alias matching (pantry, can-cook, auto-deduct, shopping-list generation) now covers common bilingual EN/FR ingredients across all 8 grocery categories, up from ~10 staples. Also: matching now ignores accents as well as case (NFD-normalize + strip combining marks) in both the alias resolver (lib/ingredient-match.ts) and the older name-fallback matcher (pantry-shopping-match.ts) — "café"/"Café"/"cafe" and "Épinard"/"epinard" all recognize as the same ingredient without needing every accent variant manually listed as an alias. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together.
|
||||
|
||||
## 0.86.0 — 2026-07-24 21:30
|
||||
|
||||
### Added
|
||||
- Ingredient-alias matching now covers ~137 common bilingual EN/FR ingredients across all grocery categories (was ~10) — produce, dairy/eggs, meat/seafood, bakery, frozen, pantry staples, spices/condiments, and beverages.
|
||||
|
||||
### Fixed
|
||||
- Ingredient name matching (pantry, can-cook, auto-deduct, shopping-list generation) now ignores accents as well as case — "café"/"Café"/"cafe" and "Épinard"/"epinard" all recognized as the same ingredient.
|
||||
|
||||
## 0.85.0 — 2026-07-24 21:00
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ Status legend: **Exists** (fully working) · **Partial** (works but with a real
|
||||
| Grocery delivery integration | **Partial / stub** | Generic export payload works (integrator-shaped — no visible in-app UI consumer besides the Instacart adapter); Instacart adapter is an explicit stub (requires a partnership agreement Epicure doesn't have — returns 501 if unconfigured, throws if "configured") | `apps/web/lib/grocery-providers/instacart.ts` |
|
||||
| Other delivery/price integrations (DoorDash, Kroger, Walmart, live pricing) | **Missing** | Confirmed absent by repo-wide search | — |
|
||||
| Pantry manual CRUD | Exists (extended 2026-07-24, twice) | Full edit dialog added (previously add+delete only, despite the API already supporting `PUT`) — name/quantity/unit/expiry plus **notes** (free text) and **category** (same `GROCERY_CATEGORIES` slugs shopping lists use; edit dialog's category dropdown initially displayed the raw slug/`__other__` instead of the translated label — fixed by passing a value→label render function to `SelectValue`, same pattern already used in `shopping-list-view.tsx`'s sort dropdown). List always renders all 9 category sections (8 `GROCERY_CATEGORIES` + Other) as collapsible groups, even empty ones — not just categories that currently have items — and items can be dragged between sections (dnd-kit `useDraggable`/`useDroppable`, cross-category drop only; no within-category manual reorder since pantry items have no `sortOrder` column to persist one). An "Auto-categorize" action (same `guessAisle` heuristic as the shopping list) fills in categories for uncategorized items in one click. Includes a bulk case-insensitive name+unit merge endpoint (used by the scan-confirm flow). | `apps/web/app/api/v1/pantry/**`, `apps/web/components/meal-plan/pantry-manager.tsx`, `apps/web/components/pantry/pantry-item-dialog.tsx` |
|
||||
| Ingredient alias matching (new 2026-07-24) | Exists | The `ingredients` table (canonical name + `aliases[]`) existed but was never populated or queried anywhere. Seeded with ~10 bilingual EN/FR staples (salt, sugar, pepper, flour, butter, milk, egg, onion, garlic, olive oil — `packages/db/src/seed.ts`) and wired into every place that compares ingredient names by raw text: pantry add/edit (sets `ingredientId` when a name/alias matches), the can-cook / "use it up soon" scorer, auto-deduct-on-cook, and shopping-list pantry-awareness on generation. Resolution happens at compare-time from free text (`resolveIngredientKey`), not from a stored FK on both sides — recipe ingredients still don't carry `ingredientId`. | `apps/web/lib/ingredient-match.ts`, `packages/db/src/seed.ts` |
|
||||
| Ingredient alias matching (new 2026-07-24, expanded same day) | Exists | The `ingredients` table (canonical name + `aliases[]`) existed but was never populated or queried anywhere. Seeded with ~137 bilingual EN/FR entries across all 8 grocery categories (produce, dairy/eggs, meat/seafood, bakery, frozen, pantry, spices/condiments, beverages — `packages/db/src/seed.ts`) and wired into every place that compares ingredient names by raw text: pantry add/edit (sets `ingredientId` when a name/alias matches), the can-cook / "use it up soon" scorer, auto-deduct-on-cook, and shopping-list pantry-awareness on generation. Comparison is case- **and accent-insensitive** (`café`/`Café`/`cafe` all equal — NFD-normalize + strip combining marks) in both the alias resolver and the older plain-name fallback matcher (`pantry-shopping-match.ts`'s `normalizeName`, used for the reliable-ingredientId-or-name-fallback path). Resolution happens at compare-time from free text (`resolveIngredientKey`), not from a stored FK on both sides — recipe ingredients still don't carry `ingredientId`. Growing the list: Admin → Ingredients (UI) or editing `packages/db/src/seed.ts` + `pnpm db:seed` (idempotent). | `apps/web/lib/ingredient-match.ts`, `packages/db/src/seed.ts` |
|
||||
| Admin: Ingredients CRUD (new 2026-07-24) | Exists | Admin-only page to manage canonical ingredients + aliases without a code change/redeploy — previously the seed file was the only place to add them. Create/edit/delete; name is unique (409 on collision); deleting an ingredient just unlinks any pantry item/recipe ingredient that pointed at it (`ingredientId` is `onDelete: "set null"` on both), nothing else is deleted. | `apps/web/app/admin/ingredients/page.tsx`, `apps/web/app/api/v1/admin/ingredients/**`, `apps/web/components/admin/ingredients-manager.tsx` |
|
||||
| Merge duplicate pantry items (new 2026-07-24, relaxed same day) | Exists | One-shot cleanup for pre-existing pantry rows that are the same ingredient under different names (added before alias matching existed) — groups by resolved ingredient key **alone** (unit is deliberately not part of the grouping key, so two rows merge even with mismatched, missing, or differently-unit'd quantities); sums quantities only when every row in a group has a parseable quantity **and** the same unit, otherwise keeps the first known (quantity, unit) pair rather than guessing. Concatenates notes, keeps the soonest expiry. Manual trigger (a button in the pantry toolbar), not automatic — manual single-item add still always inserts a new row rather than silently merging, since two batches of the same ingredient can have different expiry dates worth tracking separately. | `apps/web/app/api/v1/pantry/merge-duplicates/route.ts` |
|
||||
| Auto-deduct pantry on cook | Exists (closed 2026-07-24) | Both UI callers that previously hardcoded `deductFromPantry: false` (`batch-cook-dishes.tsx`, `meal-planner.tsx`) now pass `true`. The new general "Mark cooked" feature (see below) additionally exposes it as a per-cook checkbox, default checked, rather than a silent always-on. Matching now goes through the ingredient-alias resolver, not a raw name string compare. | `apps/web/app/api/v1/recipes/[id]/cooked/route.ts`, `apps/web/components/recipe/batch-cook-dishes.tsx`, `apps/web/components/meal-plan/meal-planner.tsx` |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mirrors CHANGELOG.md at the repo root — update both together.
|
||||
export const APP_VERSION = "0.85.0";
|
||||
export const APP_VERSION = "0.86.0";
|
||||
|
||||
export type ChangelogEntry = {
|
||||
version: string;
|
||||
@@ -11,6 +11,16 @@ export type ChangelogEntry = {
|
||||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
version: "0.86.0",
|
||||
date: "2026-07-24 21:30",
|
||||
added: [
|
||||
"Ingredient-alias matching now covers ~137 common bilingual EN/FR ingredients across all grocery categories (was ~10) — produce, dairy/eggs, meat/seafood, bakery, frozen, pantry staples, spices/condiments, and beverages.",
|
||||
],
|
||||
fixed: [
|
||||
"Ingredient name matching (pantry, can-cook, auto-deduct, shopping-list generation) now ignores accents as well as case — \"café\"/\"Café\"/\"cafe\" and \"Épinard\"/\"epinard\" all recognized as the same ingredient.",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.85.0",
|
||||
date: "2026-07-24 21:00",
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { db, ingredients, sql } from "@epicure/db";
|
||||
import { db, ingredients } from "@epicure/db";
|
||||
|
||||
export type IngredientAliasIndex = Map<string, string>;
|
||||
|
||||
// Case- and accent-insensitive so "café"/"cafe" and "Épinard"/"epinard"
|
||||
// compare equal — NFD splits an accented letter into base letter +
|
||||
// combining mark, then the combining marks (U+0300-U+036F) are stripped.
|
||||
function normalize(name: string): string {
|
||||
return name.trim().toLowerCase();
|
||||
return name.trim().toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads every canonical ingredient's name + aliases into a flat
|
||||
* lowercased-string -> canonical-ingredient-id map, once per request. Used
|
||||
* normalized-string -> canonical-ingredient-id map, once per request. Used
|
||||
* to recognize that "sel", "sel fin", and "table salt" are all the same
|
||||
* ingredient, without requiring every recipe/pantry row to carry a stored
|
||||
* ingredientId (they don't — this resolves purely from the free-text name
|
||||
@@ -27,25 +30,24 @@ export async function loadIngredientAliasIndex(): Promise<IngredientAliasIndex>
|
||||
}
|
||||
|
||||
/** Canonical ingredient id if `rawName` matches a known name/alias exactly
|
||||
* (case/whitespace-insensitive); otherwise the normalized rawName itself,
|
||||
* so unmatched items still compare equal to other unmatched items with the
|
||||
* exact same text (today's behavior, unchanged for anything not seeded). */
|
||||
* (case/accent/whitespace-insensitive); otherwise the normalized rawName
|
||||
* itself, so unmatched items still compare equal to other unmatched items
|
||||
* with the same text (today's behavior, unchanged for anything not seeded). */
|
||||
export function resolveIngredientKey(rawName: string, index: IngredientAliasIndex): string {
|
||||
const normalized = normalize(rawName);
|
||||
return index.get(normalized) ?? normalized;
|
||||
}
|
||||
|
||||
/** Single-name lookup (pantry add/edit) — a direct query rather than
|
||||
* loading the whole table, since this runs once per add/rename rather than
|
||||
* in a loop. Returns null when there's no canonical match, meaning the item
|
||||
* stays a plain freeform pantry entry. */
|
||||
/** Single-name lookup (pantry add/edit). Loads the same small table as
|
||||
* loadIngredientAliasIndex and compares in JS rather than in SQL — accent
|
||||
* stripping via NFD has no simple SQL equivalent without the `unaccent`
|
||||
* extension, which isn't guaranteed to be installed. The ingredients table
|
||||
* is small (tens to low hundreds of rows), so this is cheap. Returns null
|
||||
* when there's no canonical match, meaning the item stays a plain freeform
|
||||
* pantry entry. */
|
||||
export async function findIngredientIdByName(rawName: string): Promise<string | null> {
|
||||
const normalized = normalize(rawName);
|
||||
if (!normalized) return null;
|
||||
const [match] = await db
|
||||
.select({ id: ingredients.id })
|
||||
.from(ingredients)
|
||||
.where(sql`lower(${ingredients.name}) = ${normalized} or exists (select 1 from unnest(${ingredients.aliases}) a where lower(a) = ${normalized})`)
|
||||
.limit(1);
|
||||
return match?.id ?? null;
|
||||
const index = await loadIngredientAliasIndex();
|
||||
return index.get(normalized) ?? null;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ export function scoreRecipesAgainstPantry<T>(
|
||||
pantry: { rawName: string; expiresAt: Date | null }[],
|
||||
aliasIndex?: IngredientAliasIndex
|
||||
) {
|
||||
// With no alias index, this resolves to a plain lowercase compare —
|
||||
// same behavior as before aliases existed.
|
||||
const keyOf = (name: string) => (aliasIndex ? resolveIngredientKey(name, aliasIndex) : name.trim().toLowerCase());
|
||||
// With no alias index, this resolves to a plain case/accent-insensitive
|
||||
// compare — same behavior as before aliases existed, just accent-aware.
|
||||
const keyOf = (name: string) =>
|
||||
aliasIndex ? resolveIngredientKey(name, aliasIndex) : name.trim().toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "");
|
||||
|
||||
const pantryKeys = new Set(pantry.map((p) => keyOf(p.rawName)));
|
||||
const expiringSoonKeys = new Set(
|
||||
|
||||
@@ -37,7 +37,10 @@ export type PantryAdjustedItem = {
|
||||
};
|
||||
|
||||
export function normalizeName(name: string): string {
|
||||
const trimmed = name.toLowerCase().trim().replace(/\s+/g, " ");
|
||||
// Accent-insensitive too ("café"/"cafe") — NFD splits an accented letter
|
||||
// into base letter + combining mark, then the marks (U+0300-U+036F) are
|
||||
// stripped.
|
||||
const trimmed = name.toLowerCase().trim().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/\s+/g, " ");
|
||||
// Simple plural trim — strip a single trailing "s" for words longer than 3 chars.
|
||||
return trimmed.length > 3 && trimmed.endsWith("s") ? trimmed.slice(0, -1) : trimmed;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@epicure/web",
|
||||
"version": "0.85.0",
|
||||
"version": "0.86.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "epicure",
|
||||
"version": "0.85.0",
|
||||
"version": "0.86.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter web dev",
|
||||
|
||||
+158
-15
@@ -1,22 +1,165 @@
|
||||
import { db } from "./client";
|
||||
import { tierDefinitions, ingredients } from "./schema";
|
||||
|
||||
// Starter set of canonical ingredients + common EN/FR synonyms, so pantry
|
||||
// items and recipe ingredients written differently ("sel", "sel fin", "sel
|
||||
// de table", "table salt") can still be recognized as the same thing (see
|
||||
// lib/ingredient-match.ts). Deliberately small — grows over time as gaps
|
||||
// are found, not meant to be exhaustive on day one.
|
||||
// Canonical ingredients + common EN/FR synonyms, so pantry items and recipe
|
||||
// ingredients written differently ("sel", "sel fin", "sel de table", "table
|
||||
// salt") can still be recognized as the same thing (see
|
||||
// lib/ingredient-match.ts). Broad everyday-cooking coverage, not exhaustive —
|
||||
// grows over time via Admin -> Ingredients or by editing this list (safe to
|
||||
// add more and re-run; onConflictDoNothing on name).
|
||||
const STAPLE_INGREDIENTS: { name: string; aliases: string[]; category: string }[] = [
|
||||
{ name: "salt", aliases: ["sel", "sel fin", "sel de table", "table salt", "fine salt", "sea salt", "sel de mer"], category: "spicesCondiments" },
|
||||
{ name: "sugar", aliases: ["sucre", "sucre blanc", "white sugar", "granulated sugar", "sucre en poudre"], category: "pantry" },
|
||||
{ name: "black pepper", aliases: ["pepper", "poivre", "poivre noir", "ground pepper", "poivre moulu"], category: "spicesCondiments" },
|
||||
{ name: "flour", aliases: ["farine", "all-purpose flour", "farine de blé", "plain flour", "wheat flour"], category: "pantry" },
|
||||
{ name: "butter", aliases: ["beurre", "unsalted butter", "beurre doux", "salted butter", "beurre demi-sel"], category: "dairyEggs" },
|
||||
{ name: "milk", aliases: ["lait", "whole milk", "lait entier", "lait demi-écrémé"], category: "dairyEggs" },
|
||||
{ name: "egg", aliases: ["eggs", "œuf", "oeuf", "œufs", "oeufs"], category: "dairyEggs" },
|
||||
{ name: "onion", aliases: ["oignon", "oignons", "yellow onion", "onions"], category: "produce" },
|
||||
{ name: "garlic", aliases: ["ail", "garlic clove", "gousse d'ail", "garlic cloves"], category: "produce" },
|
||||
{ name: "olive oil", aliases: ["huile d'olive", "extra virgin olive oil", "huile d'olive vierge extra"], category: "pantry" },
|
||||
// Produce
|
||||
{ name: "onion", aliases: ["onions", "yellow onion", "oignon", "oignons"], category: "produce" },
|
||||
{ name: "red onion", aliases: ["red onions", "oignon rouge", "oignons rouges"], category: "produce" },
|
||||
{ name: "shallot", aliases: ["shallots", "échalote", "échalotes", "echalote", "echalotes"], category: "produce" },
|
||||
{ name: "scallion", aliases: ["scallions", "green onion", "green onions", "spring onion", "oignon vert", "oignons verts", "ciboule"], category: "produce" },
|
||||
{ name: "garlic", aliases: ["garlic clove", "garlic cloves", "ail", "gousse d'ail", "gousses d'ail"], category: "produce" },
|
||||
{ name: "ginger", aliases: ["fresh ginger", "gingembre"], category: "produce" },
|
||||
{ name: "carrot", aliases: ["carrots", "carotte", "carottes"], category: "produce" },
|
||||
{ name: "celery", aliases: ["celery stalk", "celery stalks", "céleri", "celeri", "branche de céleri"], category: "produce" },
|
||||
{ name: "potato", aliases: ["potatoes", "pomme de terre", "pommes de terre"], category: "produce" },
|
||||
{ name: "sweet potato", aliases: ["sweet potatoes", "patate douce", "patates douces"], category: "produce" },
|
||||
{ name: "tomato", aliases: ["tomatoes", "tomate", "tomates"], category: "produce" },
|
||||
{ name: "cherry tomato", aliases: ["cherry tomatoes", "tomate cerise", "tomates cerises"], category: "produce" },
|
||||
{ name: "cucumber", aliases: ["cucumbers", "concombre", "concombres"], category: "produce" },
|
||||
{ name: "zucchini", aliases: ["zucchinis", "courgette", "courgettes"], category: "produce" },
|
||||
{ name: "bell pepper", aliases: ["bell peppers", "poivron", "poivrons", "poivron rouge", "poivron vert"], category: "produce" },
|
||||
{ name: "chili pepper", aliases: ["chili", "chile", "chilli", "piment", "piments"], category: "produce" },
|
||||
{ name: "broccoli", aliases: ["brocoli"], category: "produce" },
|
||||
{ name: "cauliflower", aliases: ["chou-fleur", "chou fleur"], category: "produce" },
|
||||
{ name: "spinach", aliases: ["épinard", "épinards", "epinard", "epinards"], category: "produce" },
|
||||
{ name: "lettuce", aliases: ["laitue"], category: "produce" },
|
||||
{ name: "kale", aliases: ["chou kale"], category: "produce" },
|
||||
{ name: "cabbage", aliases: ["chou"], category: "produce" },
|
||||
{ name: "mushroom", aliases: ["mushrooms", "champignon", "champignons"], category: "produce" },
|
||||
{ name: "avocado", aliases: ["avocados", "avocat", "avocats"], category: "produce" },
|
||||
{ name: "lemon", aliases: ["lemons", "citron", "citrons"], category: "produce" },
|
||||
{ name: "lime", aliases: ["limes", "citron vert", "citrons verts"], category: "produce" },
|
||||
{ name: "orange", aliases: ["oranges", "orange fruit"], category: "produce" },
|
||||
{ name: "apple", aliases: ["apples", "pomme", "pommes"], category: "produce" },
|
||||
{ name: "banana", aliases: ["bananas", "banane", "bananes"], category: "produce" },
|
||||
{ name: "leek", aliases: ["leeks", "poireau", "poireaux"], category: "produce" },
|
||||
{ name: "fresh basil", aliases: ["basil", "basilic"], category: "produce" },
|
||||
{ name: "fresh parsley", aliases: ["parsley", "persil"], category: "produce" },
|
||||
{ name: "fresh cilantro", aliases: ["cilantro", "coriander leaves", "coriandre fraîche", "coriandre"], category: "produce" },
|
||||
{ name: "fresh mint", aliases: ["mint", "menthe"], category: "produce" },
|
||||
{ name: "fresh thyme", aliases: ["thyme", "thym"], category: "produce" },
|
||||
{ name: "fresh rosemary", aliases: ["rosemary", "romarin"], category: "produce" },
|
||||
|
||||
// Dairy & eggs
|
||||
{ name: "butter", aliases: ["unsalted butter", "salted butter", "beurre", "beurre doux", "beurre demi-sel"], category: "dairyEggs" },
|
||||
{ name: "milk", aliases: ["whole milk", "lait", "lait entier", "lait demi-écrémé"], category: "dairyEggs" },
|
||||
{ name: "egg", aliases: ["eggs", "œuf", "œufs", "oeuf", "oeufs"], category: "dairyEggs" },
|
||||
{ name: "heavy cream", aliases: ["whipping cream", "crème fraîche épaisse", "crème entière", "creme fraiche"], category: "dairyEggs" },
|
||||
{ name: "sour cream", aliases: ["crème aigre"], category: "dairyEggs" },
|
||||
{ name: "plain yogurt", aliases: ["yogurt", "yoghurt", "yaourt", "yaourt nature"], category: "dairyEggs" },
|
||||
{ name: "cheddar cheese", aliases: ["cheddar", "cheddar cheese grated"], category: "dairyEggs" },
|
||||
{ name: "parmesan cheese", aliases: ["parmesan", "parmigiano"], category: "dairyEggs" },
|
||||
{ name: "mozzarella cheese", aliases: ["mozzarella"], category: "dairyEggs" },
|
||||
{ name: "feta cheese", aliases: ["feta"], category: "dairyEggs" },
|
||||
{ name: "goat cheese", aliases: ["fromage de chèvre", "chevre"], category: "dairyEggs" },
|
||||
{ name: "cream cheese", aliases: ["fromage à tartiner", "philadelphia"], category: "dairyEggs" },
|
||||
{ name: "ricotta cheese", aliases: ["ricotta"], category: "dairyEggs" },
|
||||
|
||||
// Meat & seafood
|
||||
{ name: "chicken breast", aliases: ["chicken breasts", "blanc de poulet", "escalope de poulet"], category: "meatSeafood" },
|
||||
{ name: "chicken thigh", aliases: ["chicken thighs", "cuisse de poulet", "cuisses de poulet"], category: "meatSeafood" },
|
||||
{ name: "whole chicken", aliases: ["poulet entier", "poulet"], category: "meatSeafood" },
|
||||
{ name: "ground beef", aliases: ["minced beef", "bœuf haché", "boeuf hache", "viande hachée"], category: "meatSeafood" },
|
||||
{ name: "beef steak", aliases: ["steak", "steak de bœuf", "bifteck"], category: "meatSeafood" },
|
||||
{ name: "ground pork", aliases: ["minced pork", "porc haché"], category: "meatSeafood" },
|
||||
{ name: "pork chop", aliases: ["pork chops", "côte de porc", "côtes de porc"], category: "meatSeafood" },
|
||||
{ name: "bacon", aliases: ["lardons", "lardon"], category: "meatSeafood" },
|
||||
{ name: "sausage", aliases: ["sausages", "saucisse", "saucisses"], category: "meatSeafood" },
|
||||
{ name: "ham", aliases: ["jambon"], category: "meatSeafood" },
|
||||
{ name: "salmon", aliases: ["saumon"], category: "meatSeafood" },
|
||||
{ name: "tuna", aliases: ["thon"], category: "meatSeafood" },
|
||||
{ name: "shrimp", aliases: ["prawns", "crevette", "crevettes"], category: "meatSeafood" },
|
||||
{ name: "cod", aliases: ["morue", "cabillaud"], category: "meatSeafood" },
|
||||
{ name: "mussels", aliases: ["moule", "moules"], category: "meatSeafood" },
|
||||
{ name: "tofu", aliases: ["tofu firme"], category: "meatSeafood" },
|
||||
|
||||
// Bakery
|
||||
{ name: "bread", aliases: ["pain"], category: "bakery" },
|
||||
{ name: "baguette", aliases: ["french baguette"], category: "bakery" },
|
||||
{ name: "tortilla", aliases: ["tortillas", "wrap", "wraps"], category: "bakery" },
|
||||
{ name: "pita bread", aliases: ["pita", "pain pita"], category: "bakery" },
|
||||
{ name: "breadcrumbs", aliases: ["bread crumbs", "chapelure", "panko"], category: "bakery" },
|
||||
{ name: "puff pastry", aliases: ["pâte feuilletée", "pate feuilletee"], category: "bakery" },
|
||||
{ name: "pie crust", aliases: ["pâte brisée", "pate brisee"], category: "bakery" },
|
||||
|
||||
// Frozen
|
||||
{ name: "frozen peas", aliases: ["peas", "petits pois", "petits pois surgelés"], category: "frozen" },
|
||||
{ name: "frozen corn", aliases: ["corn", "maïs", "mais", "maïs surgelé"], category: "frozen" },
|
||||
{ name: "ice cream", aliases: ["glace"], category: "frozen" },
|
||||
|
||||
// Pantry
|
||||
{ name: "sugar", aliases: ["white sugar", "granulated sugar", "sucre", "sucre blanc", "sucre en poudre"], category: "pantry" },
|
||||
{ name: "brown sugar", aliases: ["sucre roux", "sucre brun", "cassonade"], category: "pantry" },
|
||||
{ name: "flour", aliases: ["all-purpose flour", "plain flour", "wheat flour", "farine", "farine de blé"], category: "pantry" },
|
||||
{ name: "olive oil", aliases: ["extra virgin olive oil", "huile d'olive", "huile d'olive vierge extra"], category: "pantry" },
|
||||
{ name: "vegetable oil", aliases: ["cooking oil", "sunflower oil", "huile végétale", "huile de tournesol"], category: "pantry" },
|
||||
{ name: "rice", aliases: ["white rice", "riz", "riz blanc"], category: "pantry" },
|
||||
{ name: "pasta", aliases: ["spaghetti", "penne", "pâtes", "pates"], category: "pantry" },
|
||||
{ name: "couscous", aliases: ["couscous grains"], category: "pantry" },
|
||||
{ name: "quinoa", aliases: ["quinoa grains"], category: "pantry" },
|
||||
{ name: "lentils", aliases: ["lentil", "lentilles", "lentille"], category: "pantry" },
|
||||
{ name: "chickpeas", aliases: ["chickpea", "garbanzo beans", "pois chiches", "pois chiche"], category: "pantry" },
|
||||
{ name: "black beans", aliases: ["haricots noirs"], category: "pantry" },
|
||||
{ name: "kidney beans", aliases: ["red beans", "haricots rouges"], category: "pantry" },
|
||||
{ name: "canned tomatoes", aliases: ["diced tomatoes", "tomates en conserve", "tomates concassées"], category: "pantry" },
|
||||
{ name: "tomato paste", aliases: ["concentré de tomate", "concentre de tomate"], category: "pantry" },
|
||||
{ name: "tomato sauce", aliases: ["sauce tomate", "coulis de tomate"], category: "pantry" },
|
||||
{ name: "vegetable stock", aliases: ["vegetable broth", "bouillon de légumes", "bouillon legumes"], category: "pantry" },
|
||||
{ name: "chicken stock", aliases: ["chicken broth", "bouillon de poulet"], category: "pantry" },
|
||||
{ name: "beef stock", aliases: ["beef broth", "bouillon de bœuf", "bouillon de boeuf"], category: "pantry" },
|
||||
{ name: "baking powder", aliases: ["levure chimique"], category: "pantry" },
|
||||
{ name: "baking soda", aliases: ["bicarbonate of soda", "bicarbonate de soude", "bicarbonate"], category: "pantry" },
|
||||
{ name: "yeast", aliases: ["active dry yeast", "levure de boulanger", "levure boulangère"], category: "pantry" },
|
||||
{ name: "vanilla extract", aliases: ["vanilla", "extrait de vanille", "vanille"], category: "pantry" },
|
||||
{ name: "honey", aliases: ["miel"], category: "pantry" },
|
||||
{ name: "maple syrup", aliases: ["sirop d'érable", "sirop derable"], category: "pantry" },
|
||||
{ name: "peanut butter", aliases: ["beurre de cacahuète", "beurre de cacahuete"], category: "pantry" },
|
||||
{ name: "coconut milk", aliases: ["lait de coco"], category: "pantry" },
|
||||
{ name: "chocolate chips", aliases: ["pépites de chocolat", "pepites de chocolat"], category: "pantry" },
|
||||
{ name: "dark chocolate", aliases: ["chocolat noir"], category: "pantry" },
|
||||
{ name: "cornstarch", aliases: ["corn starch", "cornflour", "maïzena", "maizena", "fécule de maïs"], category: "pantry" },
|
||||
{ name: "soy sauce", aliases: ["sauce soja", "sauce soya"], category: "pantry" },
|
||||
{ name: "vinegar", aliases: ["white vinegar", "vinaigre", "vinaigre blanc"], category: "pantry" },
|
||||
{ name: "balsamic vinegar", aliases: ["vinaigre balsamique"], category: "pantry" },
|
||||
{ name: "almonds", aliases: ["almond", "amande", "amandes"], category: "pantry" },
|
||||
{ name: "walnuts", aliases: ["walnut", "noix"], category: "pantry" },
|
||||
{ name: "raisins", aliases: ["raisin", "raisins secs"], category: "pantry" },
|
||||
|
||||
// Spices & condiments
|
||||
{ name: "salt", aliases: ["table salt", "fine salt", "sea salt", "sel", "sel fin", "sel de table", "sel de mer"], category: "spicesCondiments" },
|
||||
{ name: "black pepper", aliases: ["pepper", "ground pepper", "poivre", "poivre noir", "poivre moulu"], category: "spicesCondiments" },
|
||||
{ name: "paprika", aliases: ["paprika doux"], category: "spicesCondiments" },
|
||||
{ name: "cumin", aliases: ["ground cumin", "cumin moulu", "cumin poudre"], category: "spicesCondiments" },
|
||||
{ name: "cinnamon", aliases: ["ground cinnamon", "cannelle", "cannelle moulue"], category: "spicesCondiments" },
|
||||
{ name: "nutmeg", aliases: ["muscade", "noix de muscade"], category: "spicesCondiments" },
|
||||
{ name: "chili powder", aliases: ["piment en poudre", "poudre de piment"], category: "spicesCondiments" },
|
||||
{ name: "cayenne pepper", aliases: ["cayenne", "poivre de cayenne"], category: "spicesCondiments" },
|
||||
{ name: "turmeric", aliases: ["curcuma"], category: "spicesCondiments" },
|
||||
{ name: "curry powder", aliases: ["curry", "poudre de curry"], category: "spicesCondiments" },
|
||||
{ name: "bay leaf", aliases: ["bay leaves", "laurier", "feuille de laurier"], category: "spicesCondiments" },
|
||||
{ name: "oregano", aliases: ["origan"], category: "spicesCondiments" },
|
||||
{ name: "dried thyme", aliases: ["thym séché", "thym sec"], category: "spicesCondiments" },
|
||||
{ name: "dried basil", aliases: ["basilic séché"], category: "spicesCondiments" },
|
||||
{ name: "mustard", aliases: ["dijon mustard", "moutarde", "moutarde de dijon"], category: "spicesCondiments" },
|
||||
{ name: "ketchup", aliases: ["tomato ketchup", "ketchup tomate"], category: "spicesCondiments" },
|
||||
{ name: "mayonnaise", aliases: ["mayo", "mayonnaise sauce"], category: "spicesCondiments" },
|
||||
{ name: "hot sauce", aliases: ["sauce piquante"], category: "spicesCondiments" },
|
||||
{ name: "garlic powder", aliases: ["ail en poudre", "poudre d'ail"], category: "spicesCondiments" },
|
||||
{ name: "onion powder", aliases: ["oignon en poudre", "poudre d'oignon"], category: "spicesCondiments" },
|
||||
|
||||
// Beverages
|
||||
{ name: "coffee", aliases: ["ground coffee", "café", "cafe"], category: "beverages" },
|
||||
{ name: "tea", aliases: ["thé", "the"], category: "beverages" },
|
||||
{ name: "orange juice", aliases: ["jus d'orange", "jus dorange"], category: "beverages" },
|
||||
{ name: "red wine", aliases: ["vin rouge"], category: "beverages" },
|
||||
{ name: "white wine", aliases: ["vin blanc"], category: "beverages" },
|
||||
{ name: "sparkling water", aliases: ["eau gazeuse", "eau pétillante"], category: "beverages" },
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
|
||||
Reference in New Issue
Block a user