From 4b855316018dd4f41241acd1770619f6edef9269 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 18 Aug 2026 20:03:55 +0200 Subject: [PATCH] =?UTF-8?q?fix(api):=20les=20uids=20du=20catalogue=20sont?= =?UTF-8?q?=20en=20anglais,=20pas=20des=20slugs=20fran=C3=A7ais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reference-seed-data.ts reste rédigé en français (c'est juste le libellé d'autoring, jamais stocké/exposé), mais la clé stable (`Diet.key`/ `Category.key`/`Ingredient.key`) qu'on en dérive doit elle-même être un identifiant anglais, indépendant de la langue d'autoring — pas juste le même texte français passé à slugify(). - apps/api/src/db/catalog-en-keys.ts : dictionnaire écrit à la main (label français -> clé anglaise) pour les 5 régimes, 14 allergènes et 437 ingrédients ; getEnglishKey() lève une erreur explicite si un nouvel élément n'a pas encore d'entrée plutôt que de retomber sur un slug français silencieux. - scripts/validate-catalog-en-keys.ts : vérifie que chaque diet/allergène/ ingrédient de reference-seed-data.ts a une entrée, et que les clés anglaises résultantes sont uniques (437/437, 14/14, 5/5 — zéro manquant, zéro collision). - reference-seed-data.ts et scripts/generate-catalog-i18n.ts utilisent désormais getEnglishKey() au lieu de slugify(nom français). - Nouvelle migration (20260818193000_catalog_keys_to_english) qui remappe les lignes déjà seedées avec un slug français (par la migration précédente) vers leur clé anglaise définitive. - apps/web/src/locales/fr/translation.json régénéré : catalog.* est maintenant indexé par clé anglaise ("vegetarian", "eggs", "ground_beef"...), toujours avec le libellé français en valeur. - Tests/step-definitions mis à jour (getEnglishKey() au lieu de slugify()) ; 102 tests Mocha + 32 scénarios Cucumber passent contre la base migrée. Vérifié aussi en direct via GET /reference/diets et /reference/allergies. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 2 - .../step-definitions/profile.steps.ts | 8 +- .../features/step-definitions/recipe.steps.ts | 6 +- .../step-definitions/reference.steps.ts | 4 +- .../migration.sql | 465 +++++++++++ apps/api/scripts/generate-catalog-i18n.ts | 86 +- apps/api/scripts/validate-catalog-en-keys.ts | 43 + apps/api/src/db/catalog-en-keys.ts | 557 +++++++++++++ apps/api/src/db/reference-seed-data.ts | 32 +- apps/api/test/profile.test.ts | 22 +- apps/api/test/recipe.test.ts | 20 +- apps/api/test/reference.test.ts | 14 +- apps/web/src/locales/fr/translation.json | 764 +++++++++--------- 13 files changed, 1522 insertions(+), 501 deletions(-) create mode 100644 apps/api/prisma/migrations/20260818193000_catalog_keys_to_english/migration.sql create mode 100644 apps/api/scripts/validate-catalog-en-keys.ts create mode 100644 apps/api/src/db/catalog-en-keys.ts diff --git a/.gitignore b/.gitignore index b873d1e..bf8131d 100644 --- a/.gitignore +++ b/.gitignore @@ -150,5 +150,3 @@ tmp-mockups/ # IA .claude/ -# Scratch output of apps/api/scripts/generate-catalog-i18n.ts — regenerate on demand. -apps/api/scripts/backfill.sql diff --git a/apps/api/features/step-definitions/profile.steps.ts b/apps/api/features/step-definitions/profile.steps.ts index b5783e8..028c9b8 100644 --- a/apps/api/features/step-definitions/profile.steps.ts +++ b/apps/api/features/step-definitions/profile.steps.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { Then, When } from "@cucumber/cucumber"; import { prisma } from "../../src/db/prisma.js"; -import { slugify } from "../../src/utils/slugify.js"; +import { getEnglishKey } from "../../src/db/catalog-en-keys.js"; import type { CustomWorld } from "../support/world.js"; /** Splits a comma-separated list of names from a `.feature` string into trimmed, non-empty parts. */ @@ -22,7 +22,7 @@ function splitNames(names: string): string[] { async function allergyIdsFor(names: string[]): Promise { const allergies = await prisma.allergy.findMany({ include: { category: true } }); return names.map((name) => { - const key = slugify(name); + const key = getEnglishKey(name); const match = allergies.find((allergy) => allergy.category.key === key); if (!match) throw new Error(`No seeded allergen named "${name}"`); return match.id; @@ -30,14 +30,14 @@ async function allergyIdsFor(names: string[]): Promise { } When("I set my regime to {string}", async function (this: CustomWorld, dietName: string) { - const diet = await prisma.diet.findFirstOrThrow({ where: { key: slugify(dietName) } }); + const diet = await prisma.diet.findFirstOrThrow({ where: { key: getEnglishKey(dietName) } }); this.response = await this.agent.patch("/profile/diet").send({ dietId: diet.id }); }); Then( "my profile's regime should be {string}", async function (this: CustomWorld, dietName: string) { - const diet = await prisma.diet.findFirstOrThrow({ where: { key: slugify(dietName) } }); + const diet = await prisma.diet.findFirstOrThrow({ where: { key: getEnglishKey(dietName) } }); assert.equal(this.response.body.dietId, diet.id); }, ); diff --git a/apps/api/features/step-definitions/recipe.steps.ts b/apps/api/features/step-definitions/recipe.steps.ts index d61c078..d5b684f 100644 --- a/apps/api/features/step-definitions/recipe.steps.ts +++ b/apps/api/features/step-definitions/recipe.steps.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { Given, Then, When } from "@cucumber/cucumber"; import { prisma } from "../../src/db/prisma.js"; -import { slugify } from "../../src/utils/slugify.js"; +import { getEnglishKey } from "../../src/db/catalog-en-keys.js"; import { TEST_REFERENCE_DATE } from "../../test-support/reference-date.js"; import type { CustomWorld } from "../support/world.js"; @@ -12,7 +12,7 @@ import type { CustomWorld } from "../support/world.js"; * matching. */ async function findIngredientId(name: string): Promise { - const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { key: slugify(name) } }); + const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey(name) } }); return ingredient.id; } @@ -64,7 +64,7 @@ Then( ingredients: Array<{ ingredient: { key: string } }>; steps: Array<{ description: string }>; }; - const expectedKey = slugify(ingredientName); + const expectedKey = getEnglishKey(ingredientName); assert.ok(body.ingredients.some((line) => line.ingredient.key === expectedKey)); assert.ok(body.steps.some((s) => s.description === step)); }, diff --git a/apps/api/features/step-definitions/reference.steps.ts b/apps/api/features/step-definitions/reference.steps.ts index f5d9b58..722e945 100644 --- a/apps/api/features/step-definitions/reference.steps.ts +++ b/apps/api/features/step-definitions/reference.steps.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { Then } from "@cucumber/cucumber"; -import { slugify } from "../../src/utils/slugify.js"; +import { getEnglishKey } from "../../src/db/catalog-en-keys.js"; import type { CustomWorld } from "../support/world.js"; // The feature file still names an item by its French label, for @@ -11,7 +11,7 @@ Then( "the reference list response should include {string}", function (this: CustomWorld, name: string) { const keys = (this.response.body as Array<{ key: string }>).map((item) => item.key); - const expectedKey = slugify(name); + const expectedKey = getEnglishKey(name); assert.ok(keys.includes(expectedKey), `expected ${JSON.stringify(keys)} to include "${expectedKey}"`); }, ); diff --git a/apps/api/prisma/migrations/20260818193000_catalog_keys_to_english/migration.sql b/apps/api/prisma/migrations/20260818193000_catalog_keys_to_english/migration.sql new file mode 100644 index 0000000..7538101 --- /dev/null +++ b/apps/api/prisma/migrations/20260818193000_catalog_keys_to_english/migration.sql @@ -0,0 +1,465 @@ +-- Auto-generated once by scripts/gen-english-remap-sql.ts — do not re-run, +-- do not hand-edit. Remaps every Diet/Category(allergen)/Ingredient row's +-- "key" from the French slug the previous migration +-- (20260818_catalog_labels_to_keys) produced to the hand-assigned English +-- key in catalog-en-keys.ts. + +UPDATE "diet" SET "key" = 'omnivore' WHERE "key" = 'omnivore'; +UPDATE "diet" SET "key" = 'vegetarian' WHERE "key" = 'vegetarien'; +UPDATE "diet" SET "key" = 'vegan' WHERE "key" = 'vegan'; +UPDATE "diet" SET "key" = 'pescatarian' WHERE "key" = 'pescetarien'; +UPDATE "diet" SET "key" = 'gluten_free' WHERE "key" = 'sans_gluten'; + +UPDATE "category" SET "key" = 'gluten' WHERE "key" = 'gluten'; +UPDATE "category" SET "key" = 'crustaceans' WHERE "key" = 'crustaces'; +UPDATE "category" SET "key" = 'eggs' WHERE "key" = 'oeufs'; +UPDATE "category" SET "key" = 'fish' WHERE "key" = 'poissons'; +UPDATE "category" SET "key" = 'peanuts' WHERE "key" = 'arachides'; +UPDATE "category" SET "key" = 'soy' WHERE "key" = 'soja'; +UPDATE "category" SET "key" = 'milk' WHERE "key" = 'lait'; +UPDATE "category" SET "key" = 'tree_nuts' WHERE "key" = 'fruits_a_coque'; +UPDATE "category" SET "key" = 'celery' WHERE "key" = 'celeri'; +UPDATE "category" SET "key" = 'mustard' WHERE "key" = 'moutarde'; +UPDATE "category" SET "key" = 'sesame_seeds' WHERE "key" = 'graines_de_sesame'; +UPDATE "category" SET "key" = 'sulfites' WHERE "key" = 'sulfites'; +UPDATE "category" SET "key" = 'lupin' WHERE "key" = 'lupin'; +UPDATE "category" SET "key" = 'molluscs' WHERE "key" = 'mollusques'; + +UPDATE "ingredients" SET "key" = 'tomato' WHERE "key" = 'tomate'; +UPDATE "ingredients" SET "key" = 'onion' WHERE "key" = 'oignon'; +UPDATE "ingredients" SET "key" = 'shallot' WHERE "key" = 'echalote'; +UPDATE "ingredients" SET "key" = 'garlic' WHERE "key" = 'ail'; +UPDATE "ingredients" SET "key" = 'carrot' WHERE "key" = 'carotte'; +UPDATE "ingredients" SET "key" = 'zucchini' WHERE "key" = 'courgette'; +UPDATE "ingredients" SET "key" = 'cucumber' WHERE "key" = 'concombre'; +UPDATE "ingredients" SET "key" = 'gherkins' WHERE "key" = 'cornichons'; +UPDATE "ingredients" SET "key" = 'bell_pepper' WHERE "key" = 'poivron'; +UPDATE "ingredients" SET "key" = 'mushroom' WHERE "key" = 'champignon'; +UPDATE "ingredients" SET "key" = 'porcini' WHERE "key" = 'cepes'; +UPDATE "ingredients" SET "key" = 'eggplant' WHERE "key" = 'aubergine'; +UPDATE "ingredients" SET "key" = 'broccoli' WHERE "key" = 'brocoli'; +UPDATE "ingredients" SET "key" = 'cauliflower' WHERE "key" = 'chou_fleur'; +UPDATE "ingredients" SET "key" = 'white_cabbage' WHERE "key" = 'chou_blanc'; +UPDATE "ingredients" SET "key" = 'red_cabbage' WHERE "key" = 'chou_rouge'; +UPDATE "ingredients" SET "key" = 'brussels_sprouts' WHERE "key" = 'chou_de_bruxelles'; +UPDATE "ingredients" SET "key" = 'spinach' WHERE "key" = 'epinard'; +UPDATE "ingredients" SET "key" = 'swiss_chard' WHERE "key" = 'blette'; +UPDATE "ingredients" SET "key" = 'lettuce' WHERE "key" = 'salade'; +UPDATE "ingredients" SET "key" = 'arugula' WHERE "key" = 'roquette'; +UPDATE "ingredients" SET "key" = 'watercress' WHERE "key" = 'cresson'; +UPDATE "ingredients" SET "key" = 'leek' WHERE "key" = 'poireau'; +UPDATE "ingredients" SET "key" = 'celery' WHERE "key" = 'celeri'; +UPDATE "ingredients" SET "key" = 'radish' WHERE "key" = 'radis'; +UPDATE "ingredients" SET "key" = 'beetroot' WHERE "key" = 'betterave'; +UPDATE "ingredients" SET "key" = 'turnip' WHERE "key" = 'navet'; +UPDATE "ingredients" SET "key" = 'parsnip' WHERE "key" = 'panais'; +UPDATE "ingredients" SET "key" = 'green_bean' WHERE "key" = 'haricot_vert'; +UPDATE "ingredients" SET "key" = 'pea' WHERE "key" = 'petit_pois'; +UPDATE "ingredients" SET "key" = 'corn' WHERE "key" = 'mais'; +UPDATE "ingredients" SET "key" = 'artichoke' WHERE "key" = 'artichaut'; +UPDATE "ingredients" SET "key" = 'fennel' WHERE "key" = 'fenouil'; +UPDATE "ingredients" SET "key" = 'endive' WHERE "key" = 'endive'; +UPDATE "ingredients" SET "key" = 'pumpkin' WHERE "key" = 'potiron'; +UPDATE "ingredients" SET "key" = 'butternut_squash' WHERE "key" = 'butternut'; +UPDATE "ingredients" SET "key" = 'asparagus' WHERE "key" = 'asperge'; +UPDATE "ingredients" SET "key" = 'avocado' WHERE "key" = 'avocat'; +UPDATE "ingredients" SET "key" = 'potato' WHERE "key" = 'pomme_de_terre'; +UPDATE "ingredients" SET "key" = 'sweet_potato' WHERE "key" = 'patate_douce'; +UPDATE "ingredients" SET "key" = 'cherry_tomato' WHERE "key" = 'tomates_cerises'; +UPDATE "ingredients" SET "key" = 'bok_choy' WHERE "key" = 'pak_choi'; +UPDATE "ingredients" SET "key" = 'soybean_sprouts' WHERE "key" = 'germes_de_soja'; +UPDATE "ingredients" SET "key" = 'shiitake' WHERE "key" = 'shiitake'; +UPDATE "ingredients" SET "key" = 'daikon' WHERE "key" = 'daikon'; +UPDATE "ingredients" SET "key" = 'fresh_green_chili' WHERE "key" = 'piment_vert_frais'; +UPDATE "ingredients" SET "key" = 'lemon' WHERE "key" = 'citron'; +UPDATE "ingredients" SET "key" = 'lime' WHERE "key" = 'citron_vert'; +UPDATE "ingredients" SET "key" = 'apple' WHERE "key" = 'pomme'; +UPDATE "ingredients" SET "key" = 'pear' WHERE "key" = 'poire'; +UPDATE "ingredients" SET "key" = 'banana' WHERE "key" = 'banane'; +UPDATE "ingredients" SET "key" = 'orange' WHERE "key" = 'orange'; +UPDATE "ingredients" SET "key" = 'clementine' WHERE "key" = 'clementine'; +UPDATE "ingredients" SET "key" = 'grapefruit' WHERE "key" = 'pamplemousse'; +UPDATE "ingredients" SET "key" = 'strawberry' WHERE "key" = 'fraise'; +UPDATE "ingredients" SET "key" = 'raspberry' WHERE "key" = 'framboise'; +UPDATE "ingredients" SET "key" = 'blueberry' WHERE "key" = 'myrtille'; +UPDATE "ingredients" SET "key" = 'blackberry' WHERE "key" = 'mure'; +UPDATE "ingredients" SET "key" = 'cherry' WHERE "key" = 'cerise'; +UPDATE "ingredients" SET "key" = 'apricot' WHERE "key" = 'abricot'; +UPDATE "ingredients" SET "key" = 'peach' WHERE "key" = 'peche'; +UPDATE "ingredients" SET "key" = 'plum' WHERE "key" = 'prune'; +UPDATE "ingredients" SET "key" = 'grape' WHERE "key" = 'raisin'; +UPDATE "ingredients" SET "key" = 'melon' WHERE "key" = 'melon'; +UPDATE "ingredients" SET "key" = 'watermelon' WHERE "key" = 'pasteque'; +UPDATE "ingredients" SET "key" = 'pineapple' WHERE "key" = 'ananas'; +UPDATE "ingredients" SET "key" = 'mango' WHERE "key" = 'mangue'; +UPDATE "ingredients" SET "key" = 'kiwi' WHERE "key" = 'kiwi'; +UPDATE "ingredients" SET "key" = 'fig' WHERE "key" = 'figue'; +UPDATE "ingredients" SET "key" = 'date' WHERE "key" = 'datte'; +UPDATE "ingredients" SET "key" = 'lychee' WHERE "key" = 'litchi'; +UPDATE "ingredients" SET "key" = 'pomegranate' WHERE "key" = 'grenade'; +UPDATE "ingredients" SET "key" = 'rhubarb' WHERE "key" = 'rhubarbe'; +UPDATE "ingredients" SET "key" = 'quince' WHERE "key" = 'coing'; +UPDATE "ingredients" SET "key" = 'basil' WHERE "key" = 'basilic'; +UPDATE "ingredients" SET "key" = 'parsley' WHERE "key" = 'persil'; +UPDATE "ingredients" SET "key" = 'thyme' WHERE "key" = 'thym'; +UPDATE "ingredients" SET "key" = 'rosemary' WHERE "key" = 'romarin'; +UPDATE "ingredients" SET "key" = 'bay_leaf' WHERE "key" = 'laurier'; +UPDATE "ingredients" SET "key" = 'chives' WHERE "key" = 'ciboulette'; +UPDATE "ingredients" SET "key" = 'fresh_cilantro' WHERE "key" = 'coriandre_fraiche'; +UPDATE "ingredients" SET "key" = 'mint' WHERE "key" = 'menthe'; +UPDATE "ingredients" SET "key" = 'oregano' WHERE "key" = 'origan'; +UPDATE "ingredients" SET "key" = 'dill' WHERE "key" = 'aneth'; +UPDATE "ingredients" SET "key" = 'tarragon' WHERE "key" = 'estragon'; +UPDATE "ingredients" SET "key" = 'savory' WHERE "key" = 'sarriette'; +UPDATE "ingredients" SET "key" = 'marjoram' WHERE "key" = 'marjolaine'; +UPDATE "ingredients" SET "key" = 'sage' WHERE "key" = 'sauge'; +UPDATE "ingredients" SET "key" = 'chervil' WHERE "key" = 'cerfeuil'; +UPDATE "ingredients" SET "key" = 'ginger' WHERE "key" = 'gingembre'; +UPDATE "ingredients" SET "key" = 'lemongrass' WHERE "key" = 'citronnelle'; +UPDATE "ingredients" SET "key" = 'kaffir_lime' WHERE "key" = 'combava'; +UPDATE "ingredients" SET "key" = 'rabbit' WHERE "key" = 'lapin'; +UPDATE "ingredients" SET "key" = 'ground_beef' WHERE "key" = 'boeuf_hache'; +UPDATE "ingredients" SET "key" = 'beef_steak' WHERE "key" = 'steak_de_boeuf'; +UPDATE "ingredients" SET "key" = 'beef_roast' WHERE "key" = 'roti_de_boeuf'; +UPDATE "ingredients" SET "key" = 'veal_cutlet' WHERE "key" = 'escalope_de_veau'; +UPDATE "ingredients" SET "key" = 'pork_tenderloin' WHERE "key" = 'filet_mignon_de_porc'; +UPDATE "ingredients" SET "key" = 'pork_chop' WHERE "key" = 'cote_de_porc'; +UPDATE "ingredients" SET "key" = 'lamb' WHERE "key" = 'agneau'; +UPDATE "ingredients" SET "key" = 'leg_of_lamb' WHERE "key" = 'gigot_d_agneau'; +UPDATE "ingredients" SET "key" = 'bacon_lardons' WHERE "key" = 'lardons'; +UPDATE "ingredients" SET "key" = 'bacon' WHERE "key" = 'bacon'; +UPDATE "ingredients" SET "key" = 'ham' WHERE "key" = 'jambon_blanc'; +UPDATE "ingredients" SET "key" = 'cured_ham' WHERE "key" = 'jambon_cru'; +UPDATE "ingredients" SET "key" = 'sausage' WHERE "key" = 'saucisse'; +UPDATE "ingredients" SET "key" = 'chorizo' WHERE "key" = 'chorizo'; +UPDATE "ingredients" SET "key" = 'merguez' WHERE "key" = 'merguez'; +UPDATE "ingredients" SET "key" = 'prosciutto' WHERE "key" = 'prosciutto'; +UPDATE "ingredients" SET "key" = 'pancetta' WHERE "key" = 'pancetta'; +UPDATE "ingredients" SET "key" = 'mortadella' WHERE "key" = 'mortadelle'; +UPDATE "ingredients" SET "key" = 'salami' WHERE "key" = 'salami'; +UPDATE "ingredients" SET "key" = 'chicken' WHERE "key" = 'poulet'; +UPDATE "ingredients" SET "key" = 'turkey' WHERE "key" = 'dinde'; +UPDATE "ingredients" SET "key" = 'duck' WHERE "key" = 'canard'; +UPDATE "ingredients" SET "key" = 'duck_breast' WHERE "key" = 'magret_de_canard'; +UPDATE "ingredients" SET "key" = 'salmon' WHERE "key" = 'saumon'; +UPDATE "ingredients" SET "key" = 'tuna' WHERE "key" = 'thon'; +UPDATE "ingredients" SET "key" = 'cod' WHERE "key" = 'cabillaud'; +UPDATE "ingredients" SET "key" = 'trout' WHERE "key" = 'truite'; +UPDATE "ingredients" SET "key" = 'sardine' WHERE "key" = 'sardine'; +UPDATE "ingredients" SET "key" = 'anchovy' WHERE "key" = 'anchois'; +UPDATE "ingredients" SET "key" = 'whiting' WHERE "key" = 'merlan'; +UPDATE "ingredients" SET "key" = 'surimi' WHERE "key" = 'surimi'; +UPDATE "ingredients" SET "key" = 'sea_bass' WHERE "key" = 'bar_loup_de_mer'; +UPDATE "ingredients" SET "key" = 'sea_bream' WHERE "key" = 'dorade'; +UPDATE "ingredients" SET "key" = 'sole' WHERE "key" = 'sole'; +UPDATE "ingredients" SET "key" = 'turbot' WHERE "key" = 'turbot'; +UPDATE "ingredients" SET "key" = 'hake' WHERE "key" = 'merlu'; +UPDATE "ingredients" SET "key" = 'pollock' WHERE "key" = 'colin'; +UPDATE "ingredients" SET "key" = 'saithe' WHERE "key" = 'lieu_noir'; +UPDATE "ingredients" SET "key" = 'haddock' WHERE "key" = 'eglefin'; +UPDATE "ingredients" SET "key" = 'mackerel' WHERE "key" = 'maquereau'; +UPDATE "ingredients" SET "key" = 'herring' WHERE "key" = 'hareng'; +UPDATE "ingredients" SET "key" = 'red_mullet' WHERE "key" = 'rouget'; +UPDATE "ingredients" SET "key" = 'skate' WHERE "key" = 'raie'; +UPDATE "ingredients" SET "key" = 'monkfish' WHERE "key" = 'lotte'; +UPDATE "ingredients" SET "key" = 'halibut' WHERE "key" = 'fletan'; +UPDATE "ingredients" SET "key" = 'swordfish' WHERE "key" = 'espadon'; +UPDATE "ingredients" SET "key" = 'carp' WHERE "key" = 'carpe'; +UPDATE "ingredients" SET "key" = 'pike' WHERE "key" = 'brochet'; +UPDATE "ingredients" SET "key" = 'perch' WHERE "key" = 'perche'; +UPDATE "ingredients" SET "key" = 'tilapia' WHERE "key" = 'tilapia'; +UPDATE "ingredients" SET "key" = 'pangasius' WHERE "key" = 'panga'; +UPDATE "ingredients" SET "key" = 'smoked_salmon' WHERE "key" = 'saumon_fume'; +UPDATE "ingredients" SET "key" = 'dried_fish' WHERE "key" = 'poisson_seche'; +UPDATE "ingredients" SET "key" = 'shrimp' WHERE "key" = 'crevettes'; +UPDATE "ingredients" SET "key" = 'langoustine' WHERE "key" = 'langoustines'; +UPDATE "ingredients" SET "key" = 'lobster' WHERE "key" = 'homard'; +UPDATE "ingredients" SET "key" = 'crab' WHERE "key" = 'crabe'; +UPDATE "ingredients" SET "key" = 'spiny_lobster' WHERE "key" = 'langouste'; +UPDATE "ingredients" SET "key" = 'mussels' WHERE "key" = 'moules'; +UPDATE "ingredients" SET "key" = 'oysters' WHERE "key" = 'huitres'; +UPDATE "ingredients" SET "key" = 'scallops' WHERE "key" = 'saint_jacques'; +UPDATE "ingredients" SET "key" = 'squid' WHERE "key" = 'calamar'; +UPDATE "ingredients" SET "key" = 'octopus' WHERE "key" = 'poulpe'; +UPDATE "ingredients" SET "key" = 'clams' WHERE "key" = 'palourdes'; +UPDATE "ingredients" SET "key" = 'whelks' WHERE "key" = 'bulots'; +UPDATE "ingredients" SET "key" = 'semolina' WHERE "key" = 'semoule'; +UPDATE "ingredients" SET "key" = 'couscous' WHERE "key" = 'couscous'; +UPDATE "ingredients" SET "key" = 'bulgur' WHERE "key" = 'boulgour'; +UPDATE "ingredients" SET "key" = 'polenta' WHERE "key" = 'polenta'; +UPDATE "ingredients" SET "key" = 'quinoa' WHERE "key" = 'quinoa'; +UPDATE "ingredients" SET "key" = 'pasta' WHERE "key" = 'pates'; +UPDATE "ingredients" SET "key" = 'whole_wheat_pasta' WHERE "key" = 'pates_completes'; +UPDATE "ingredients" SET "key" = 'rice' WHERE "key" = 'riz'; +UPDATE "ingredients" SET "key" = 'basmati_rice' WHERE "key" = 'riz_basmati'; +UPDATE "ingredients" SET "key" = 'brown_rice' WHERE "key" = 'riz_complet'; +UPDATE "ingredients" SET "key" = 'oats' WHERE "key" = 'flocons_d_avoine'; +UPDATE "ingredients" SET "key" = 'spaghetti' WHERE "key" = 'spaghetti'; +UPDATE "ingredients" SET "key" = 'penne' WHERE "key" = 'penne'; +UPDATE "ingredients" SET "key" = 'tagliatelle' WHERE "key" = 'tagliatelles'; +UPDATE "ingredients" SET "key" = 'lasagna_sheets' WHERE "key" = 'lasagnes_feuilles'; +UPDATE "ingredients" SET "key" = 'gnocchi' WHERE "key" = 'gnocchi'; +UPDATE "ingredients" SET "key" = 'arborio_rice' WHERE "key" = 'riz_arborio'; +UPDATE "ingredients" SET "key" = 'rice_noodles' WHERE "key" = 'nouilles_de_riz'; +UPDATE "ingredients" SET "key" = 'udon_noodles' WHERE "key" = 'nouilles_udon'; +UPDATE "ingredients" SET "key" = 'soba_noodles' WHERE "key" = 'nouilles_soba'; +UPDATE "ingredients" SET "key" = 'chinese_noodles' WHERE "key" = 'nouilles_chinoises'; +UPDATE "ingredients" SET "key" = 'rice_vermicelli' WHERE "key" = 'vermicelles_de_riz'; +UPDATE "ingredients" SET "key" = 'soy_vermicelli' WHERE "key" = 'vermicelles_de_soja'; +UPDATE "ingredients" SET "key" = 'sticky_rice' WHERE "key" = 'riz_gluant'; +UPDATE "ingredients" SET "key" = 'sushi_rice' WHERE "key" = 'riz_a_sushi'; +UPDATE "ingredients" SET "key" = 'jasmine_rice' WHERE "key" = 'riz_jasmin'; +UPDATE "ingredients" SET "key" = 'green_lentils' WHERE "key" = 'lentilles_vertes'; +UPDATE "ingredients" SET "key" = 'red_lentils' WHERE "key" = 'lentilles_corail'; +UPDATE "ingredients" SET "key" = 'chickpeas' WHERE "key" = 'pois_chiches'; +UPDATE "ingredients" SET "key" = 'white_beans' WHERE "key" = 'haricots_blancs'; +UPDATE "ingredients" SET "key" = 'kidney_beans' WHERE "key" = 'haricots_rouges'; +UPDATE "ingredients" SET "key" = 'black_beans' WHERE "key" = 'haricots_noirs'; +UPDATE "ingredients" SET "key" = 'split_peas' WHERE "key" = 'pois_casses'; +UPDATE "ingredients" SET "key" = 'fava_beans' WHERE "key" = 'feves'; +UPDATE "ingredients" SET "key" = 'edamame' WHERE "key" = 'edamame'; +UPDATE "ingredients" SET "key" = 'pinto_beans' WHERE "key" = 'haricots_pinto'; +UPDATE "ingredients" SET "key" = 'peanuts_shelled' WHERE "key" = 'cacahuetes'; +UPDATE "ingredients" SET "key" = 'almonds' WHERE "key" = 'amandes'; +UPDATE "ingredients" SET "key" = 'walnuts' WHERE "key" = 'noix'; +UPDATE "ingredients" SET "key" = 'hazelnuts' WHERE "key" = 'noisettes'; +UPDATE "ingredients" SET "key" = 'cashews' WHERE "key" = 'noix_de_cajou'; +UPDATE "ingredients" SET "key" = 'pistachios' WHERE "key" = 'pistaches'; +UPDATE "ingredients" SET "key" = 'pecans' WHERE "key" = 'noix_de_pecan'; +UPDATE "ingredients" SET "key" = 'almond_powder' WHERE "key" = 'poudre_d_amande'; +UPDATE "ingredients" SET "key" = 'pine_nuts' WHERE "key" = 'pignons_de_pin'; +UPDATE "ingredients" SET "key" = 'sunflower_seeds' WHERE "key" = 'graines_de_tournesol'; +UPDATE "ingredients" SET "key" = 'pumpkin_seeds' WHERE "key" = 'graines_de_courge'; +UPDATE "ingredients" SET "key" = 'shredded_coconut' WHERE "key" = 'noix_de_coco_rapee'; +UPDATE "ingredients" SET "key" = 'raisins' WHERE "key" = 'raisins_secs'; +UPDATE "ingredients" SET "key" = 'prunes' WHERE "key" = 'pruneaux'; +UPDATE "ingredients" SET "key" = 'dried_apricots' WHERE "key" = 'abricots_secs'; +UPDATE "ingredients" SET "key" = 'sesame_seeds' WHERE "key" = 'graines_de_sesame'; +UPDATE "ingredients" SET "key" = 'black_mushrooms' WHERE "key" = 'champignons_noirs'; +UPDATE "ingredients" SET "key" = 'nori_seaweed' WHERE "key" = 'algue_nori'; +UPDATE "ingredients" SET "key" = 'wakame_seaweed' WHERE "key" = 'algue_wakame'; +UPDATE "ingredients" SET "key" = 'kombu_seaweed' WHERE "key" = 'algue_kombu'; +UPDATE "ingredients" SET "key" = 'bamboo_shoots' WHERE "key" = 'pousses_de_bambou'; +UPDATE "ingredients" SET "key" = 'water_chestnuts' WHERE "key" = 'chataignes_d_eau'; +UPDATE "ingredients" SET "key" = 'bread' WHERE "key" = 'pain'; +UPDATE "ingredients" SET "key" = 'sandwich_bread' WHERE "key" = 'pain_de_mie'; +UPDATE "ingredients" SET "key" = 'whole_wheat_bread' WHERE "key" = 'pain_complet'; +UPDATE "ingredients" SET "key" = 'baguette' WHERE "key" = 'baguette'; +UPDATE "ingredients" SET "key" = 'rye_bread' WHERE "key" = 'pain_de_seigle'; +UPDATE "ingredients" SET "key" = 'breadcrumbs' WHERE "key" = 'chapelure'; +UPDATE "ingredients" SET "key" = 'burger_bun' WHERE "key" = 'pain_a_burger'; +UPDATE "ingredients" SET "key" = 'brioche_bun' WHERE "key" = 'pain_brioche'; +UPDATE "ingredients" SET "key" = 'hot_dog_bun' WHERE "key" = 'pain_a_hot_dog'; +UPDATE "ingredients" SET "key" = 'pita_bread' WHERE "key" = 'pain_pita'; +UPDATE "ingredients" SET "key" = 'bagel' WHERE "key" = 'pain_bagel'; +UPDATE "ingredients" SET "key" = 'naan' WHERE "key" = 'naan'; +UPDATE "ingredients" SET "key" = 'wrap_bread' WHERE "key" = 'pain_wrap'; +UPDATE "ingredients" SET "key" = 'viennese_bread' WHERE "key" = 'pain_viennois'; +UPDATE "ingredients" SET "key" = 'country_bread' WHERE "key" = 'pain_de_campagne'; +UPDATE "ingredients" SET "key" = 'multigrain_bread' WHERE "key" = 'pain_aux_cereales'; +UPDATE "ingredients" SET "key" = 'bread_roll' WHERE "key" = 'petit_pain'; +UPDATE "ingredients" SET "key" = 'swedish_bread' WHERE "key" = 'pain_suedois'; +UPDATE "ingredients" SET "key" = 'gluten_free_bread' WHERE "key" = 'pain_sans_gluten'; +UPDATE "ingredients" SET "key" = 'rusk' WHERE "key" = 'biscotte'; +UPDATE "ingredients" SET "key" = 'croutons' WHERE "key" = 'croutons'; +UPDATE "ingredients" SET "key" = 'focaccia' WHERE "key" = 'focaccia'; +UPDATE "ingredients" SET "key" = 'ciabatta' WHERE "key" = 'ciabatta'; +UPDATE "ingredients" SET "key" = 'corn_tortilla' WHERE "key" = 'tortilla_de_mais'; +UPDATE "ingredients" SET "key" = 'wheat_tortilla' WHERE "key" = 'tortilla_de_ble'; +UPDATE "ingredients" SET "key" = 'puff_pastry' WHERE "key" = 'pate_feuilletee'; +UPDATE "ingredients" SET "key" = 'shortcrust_pastry' WHERE "key" = 'pate_brisee'; +UPDATE "ingredients" SET "key" = 'pizza_dough' WHERE "key" = 'pate_a_pizza'; +UPDATE "ingredients" SET "key" = 'sweet_shortcrust_pastry' WHERE "key" = 'pate_a_tarte_sablee'; +UPDATE "ingredients" SET "key" = 'milk' WHERE "key" = 'lait'; +UPDATE "ingredients" SET "key" = 'butter' WHERE "key" = 'beurre'; +UPDATE "ingredients" SET "key" = 'creme_fraiche' WHERE "key" = 'creme_fraiche'; +UPDATE "ingredients" SET "key" = 'liquid_cream' WHERE "key" = 'creme_liquide'; +UPDATE "ingredients" SET "key" = 'cheese' WHERE "key" = 'fromage'; +UPDATE "ingredients" SET "key" = 'emmental' WHERE "key" = 'emmental'; +UPDATE "ingredients" SET "key" = 'gruyere' WHERE "key" = 'gruyere'; +UPDATE "ingredients" SET "key" = 'parmesan' WHERE "key" = 'parmesan'; +UPDATE "ingredients" SET "key" = 'mozzarella' WHERE "key" = 'mozzarella'; +UPDATE "ingredients" SET "key" = 'goat_cheese' WHERE "key" = 'chevre_fromage'; +UPDATE "ingredients" SET "key" = 'feta' WHERE "key" = 'feta'; +UPDATE "ingredients" SET "key" = 'comte' WHERE "key" = 'comte'; +UPDATE "ingredients" SET "key" = 'fromage_blanc' WHERE "key" = 'fromage_blanc'; +UPDATE "ingredients" SET "key" = 'mascarpone' WHERE "key" = 'mascarpone'; +UPDATE "ingredients" SET "key" = 'yogurt' WHERE "key" = 'yaourt'; +UPDATE "ingredients" SET "key" = 'burrata' WHERE "key" = 'burrata'; +UPDATE "ingredients" SET "key" = 'ricotta' WHERE "key" = 'ricotta'; +UPDATE "ingredients" SET "key" = 'pecorino' WHERE "key" = 'pecorino'; +UPDATE "ingredients" SET "key" = 'gorgonzola' WHERE "key" = 'gorgonzola'; +UPDATE "ingredients" SET "key" = 'cheddar' WHERE "key" = 'cheddar'; +UPDATE "ingredients" SET "key" = 'egg' WHERE "key" = 'oeuf'; +UPDATE "ingredients" SET "key" = 'coconut_milk' WHERE "key" = 'lait_de_coco'; +UPDATE "ingredients" SET "key" = 'coconut_cream' WHERE "key" = 'creme_de_coco'; +UPDATE "ingredients" SET "key" = 'almond_milk' WHERE "key" = 'lait_d_amande'; +UPDATE "ingredients" SET "key" = 'oat_milk' WHERE "key" = 'lait_d_avoine'; +UPDATE "ingredients" SET "key" = 'tofu' WHERE "key" = 'tofu'; +UPDATE "ingredients" SET "key" = 'silken_tofu' WHERE "key" = 'tofu_soyeux'; +UPDATE "ingredients" SET "key" = 'herbes_de_provence' WHERE "key" = 'herbes_de_provence'; +UPDATE "ingredients" SET "key" = 'black_pepper' WHERE "key" = 'poivre_noir'; +UPDATE "ingredients" SET "key" = 'paprika' WHERE "key" = 'paprika'; +UPDATE "ingredients" SET "key" = 'espelette_pepper' WHERE "key" = 'piment_d_espelette'; +UPDATE "ingredients" SET "key" = 'cayenne_pepper' WHERE "key" = 'piment_de_cayenne'; +UPDATE "ingredients" SET "key" = 'cumin' WHERE "key" = 'cumin'; +UPDATE "ingredients" SET "key" = 'curry_powder' WHERE "key" = 'curry_poudre'; +UPDATE "ingredients" SET "key" = 'turmeric' WHERE "key" = 'curcuma'; +UPDATE "ingredients" SET "key" = 'cinnamon' WHERE "key" = 'cannelle'; +UPDATE "ingredients" SET "key" = 'nutmeg' WHERE "key" = 'muscade'; +UPDATE "ingredients" SET "key" = 'saffron' WHERE "key" = 'safran'; +UPDATE "ingredients" SET "key" = 'clove' WHERE "key" = 'clou_de_girofle'; +UPDATE "ingredients" SET "key" = 'vanilla_bean' WHERE "key" = 'vanille_gousse'; +UPDATE "ingredients" SET "key" = 'white_pepper' WHERE "key" = 'poivre_blanc'; +UPDATE "ingredients" SET "key" = 'pink_pepper' WHERE "key" = 'poivre_rose'; +UPDATE "ingredients" SET "key" = 'sichuan_pepper' WHERE "key" = 'poivre_du_sichuan'; +UPDATE "ingredients" SET "key" = 'smoked_paprika' WHERE "key" = 'paprika_fume'; +UPDATE "ingredients" SET "key" = 'bird_eye_chili' WHERE "key" = 'piment_oiseau'; +UPDATE "ingredients" SET "key" = 'juniper_berries' WHERE "key" = 'baies_de_genievre'; +UPDATE "ingredients" SET "key" = 'star_anise' WHERE "key" = 'anis_etoile_badiane'; +UPDATE "ingredients" SET "key" = 'green_anise' WHERE "key" = 'anis_vert'; +UPDATE "ingredients" SET "key" = 'fennel_seeds' WHERE "key" = 'graines_de_fenouil'; +UPDATE "ingredients" SET "key" = 'sumac' WHERE "key" = 'sumac'; +UPDATE "ingredients" SET "key" = 'nigella' WHERE "key" = 'nigelle'; +UPDATE "ingredients" SET "key" = 'allspice' WHERE "key" = 'quatre_epices'; +UPDATE "ingredients" SET "key" = 'colombo_powder' WHERE "key" = 'colombo_poudre'; +UPDATE "ingredients" SET "key" = 'baharat' WHERE "key" = 'baharat'; +UPDATE "ingredients" SET "key" = 'horseradish' WHERE "key" = 'raifort'; +UPDATE "ingredients" SET "key" = 'herb_salt' WHERE "key" = 'sel_aux_herbes'; +UPDATE "ingredients" SET "key" = 'celery_salt' WHERE "key" = 'sel_de_celeri'; +UPDATE "ingredients" SET "key" = 'fleur_de_sel' WHERE "key" = 'fleur_de_sel'; +UPDATE "ingredients" SET "key" = 'salt' WHERE "key" = 'sel'; +UPDATE "ingredients" SET "key" = 'five_spice' WHERE "key" = 'cinq_epices'; +UPDATE "ingredients" SET "key" = 'garam_masala' WHERE "key" = 'garam_masala'; +UPDATE "ingredients" SET "key" = 'coriander_seeds' WHERE "key" = 'graines_de_coriandre'; +UPDATE "ingredients" SET "key" = 'cardamom' WHERE "key" = 'cardamome'; +UPDATE "ingredients" SET "key" = 'fenugreek' WHERE "key" = 'fenugrec'; +UPDATE "ingredients" SET "key" = 'jalapeno' WHERE "key" = 'piment_jalapeno'; +UPDATE "ingredients" SET "key" = 'chipotle' WHERE "key" = 'piment_chipotle'; +UPDATE "ingredients" SET "key" = 'poblano_pepper' WHERE "key" = 'piment_poblano'; +UPDATE "ingredients" SET "key" = 'habanero' WHERE "key" = 'piment_habanero'; +UPDATE "ingredients" SET "key" = 'ras_el_hanout' WHERE "key" = 'ras_el_hanout'; +UPDATE "ingredients" SET "key" = 'zaatar' WHERE "key" = 'za_atar'; +UPDATE "ingredients" SET "key" = 'soy_sauce' WHERE "key" = 'sauce_soja'; +UPDATE "ingredients" SET "key" = 'mustard' WHERE "key" = 'moutarde'; +UPDATE "ingredients" SET "key" = 'mayonnaise' WHERE "key" = 'mayonnaise'; +UPDATE "ingredients" SET "key" = 'ketchup' WHERE "key" = 'ketchup'; +UPDATE "ingredients" SET "key" = 'tabasco' WHERE "key" = 'tabasco'; +UPDATE "ingredients" SET "key" = 'worcestershire_sauce' WHERE "key" = 'sauce_worcestershire'; +UPDATE "ingredients" SET "key" = 'fish_sauce' WHERE "key" = 'sauce_nuoc_mam'; +UPDATE "ingredients" SET "key" = 'wasabi' WHERE "key" = 'wasabi'; +UPDATE "ingredients" SET "key" = 'harissa' WHERE "key" = 'harissa'; +UPDATE "ingredients" SET "key" = 'curry_paste' WHERE "key" = 'pate_de_curry'; +UPDATE "ingredients" SET "key" = 'peanut_butter' WHERE "key" = 'beurre_de_cacahuete'; +UPDATE "ingredients" SET "key" = 'dijon_mustard' WHERE "key" = 'moutarde_de_dijon'; +UPDATE "ingredients" SET "key" = 'wholegrain_mustard' WHERE "key" = 'moutarde_a_l_ancienne'; +UPDATE "ingredients" SET "key" = 'barbecue_sauce' WHERE "key" = 'sauce_barbecue'; +UPDATE "ingredients" SET "key" = 'tartar_sauce' WHERE "key" = 'sauce_tartare'; +UPDATE "ingredients" SET "key" = 'cocktail_sauce' WHERE "key" = 'sauce_cocktail'; +UPDATE "ingredients" SET "key" = 'bearnaise_sauce' WHERE "key" = 'sauce_bearnaise'; +UPDATE "ingredients" SET "key" = 'hollandaise_sauce' WHERE "key" = 'sauce_hollandaise'; +UPDATE "ingredients" SET "key" = 'bechamel_sauce' WHERE "key" = 'sauce_bechamel'; +UPDATE "ingredients" SET "key" = 'teriyaki_sauce' WHERE "key" = 'sauce_teriyaki'; +UPDATE "ingredients" SET "key" = 'ponzu_sauce' WHERE "key" = 'sauce_ponzu'; +UPDATE "ingredients" SET "key" = 'chimichurri' WHERE "key" = 'chimichurri'; +UPDATE "ingredients" SET "key" = 'red_pesto' WHERE "key" = 'pesto_rouge_tomates_sechees'; +UPDATE "ingredients" SET "key" = 'pesto' WHERE "key" = 'pesto'; +UPDATE "ingredients" SET "key" = 'oyster_sauce' WHERE "key" = 'sauce_huitre'; +UPDATE "ingredients" SET "key" = 'hoisin_sauce' WHERE "key" = 'sauce_hoisin'; +UPDATE "ingredients" SET "key" = 'sriracha' WHERE "key" = 'sauce_sriracha'; +UPDATE "ingredients" SET "key" = 'sweet_chili_sauce' WHERE "key" = 'sauce_sweet_chili'; +UPDATE "ingredients" SET "key" = 'miso' WHERE "key" = 'miso'; +UPDATE "ingredients" SET "key" = 'shrimp_paste' WHERE "key" = 'pate_de_crevettes'; +UPDATE "ingredients" SET "key" = 'red_curry_paste' WHERE "key" = 'pate_de_curry_rouge_thai'; +UPDATE "ingredients" SET "key" = 'green_curry_paste' WHERE "key" = 'pate_de_curry_vert_thai'; +UPDATE "ingredients" SET "key" = 'tahini' WHERE "key" = 'tahini'; +UPDATE "ingredients" SET "key" = 'olive_oil' WHERE "key" = 'huile_d_olive'; +UPDATE "ingredients" SET "key" = 'sunflower_oil' WHERE "key" = 'huile_de_tournesol'; +UPDATE "ingredients" SET "key" = 'rapeseed_oil' WHERE "key" = 'huile_de_colza'; +UPDATE "ingredients" SET "key" = 'coconut_oil' WHERE "key" = 'huile_de_coco'; +UPDATE "ingredients" SET "key" = 'sesame_oil' WHERE "key" = 'huile_de_sesame'; +UPDATE "ingredients" SET "key" = 'cider_vinegar' WHERE "key" = 'vinaigre_de_cidre'; +UPDATE "ingredients" SET "key" = 'white_vinegar' WHERE "key" = 'vinaigre_blanc'; +UPDATE "ingredients" SET "key" = 'balsamic_vinegar' WHERE "key" = 'vinaigre_balsamique'; +UPDATE "ingredients" SET "key" = 'capers' WHERE "key" = 'capres'; +UPDATE "ingredients" SET "key" = 'olives' WHERE "key" = 'olives'; +UPDATE "ingredients" SET "key" = 'white_wine' WHERE "key" = 'vin_blanc_cuisine'; +UPDATE "ingredients" SET "key" = 'red_wine' WHERE "key" = 'vin_rouge_cuisine'; +UPDATE "ingredients" SET "key" = 'red_wine_vinegar' WHERE "key" = 'vinaigre_de_vin_rouge'; +UPDATE "ingredients" SET "key" = 'white_wine_vinegar' WHERE "key" = 'vinaigre_de_vin_blanc'; +UPDATE "ingredients" SET "key" = 'sherry_vinegar' WHERE "key" = 'vinaigre_de_xeres'; +UPDATE "ingredients" SET "key" = 'walnut_oil' WHERE "key" = 'huile_de_noix'; +UPDATE "ingredients" SET "key" = 'hazelnut_oil' WHERE "key" = 'huile_de_noisette'; +UPDATE "ingredients" SET "key" = 'peanut_oil' WHERE "key" = 'huile_d_arachide'; +UPDATE "ingredients" SET "key" = 'chili_oil' WHERE "key" = 'huile_pimentee'; +UPDATE "ingredients" SET "key" = 'rice_vinegar' WHERE "key" = 'vinaigre_de_riz'; +UPDATE "ingredients" SET "key" = 'mirin' WHERE "key" = 'mirin'; +UPDATE "ingredients" SET "key" = 'sake' WHERE "key" = 'sake_cuisine'; +UPDATE "ingredients" SET "key" = 'lemon_juice' WHERE "key" = 'jus_de_citron'; +UPDATE "ingredients" SET "key" = 'lime_juice' WHERE "key" = 'jus_de_citron_vert'; +UPDATE "ingredients" SET "key" = 'orange_juice' WHERE "key" = 'jus_d_orange'; +UPDATE "ingredients" SET "key" = 'apple_juice' WHERE "key" = 'jus_de_pomme'; +UPDATE "ingredients" SET "key" = 'grape_juice' WHERE "key" = 'jus_de_raisin'; +UPDATE "ingredients" SET "key" = 'tomato_juice' WHERE "key" = 'jus_de_tomate'; +UPDATE "ingredients" SET "key" = 'cranberry_juice' WHERE "key" = 'jus_de_cranberry'; +UPDATE "ingredients" SET "key" = 'coffee' WHERE "key" = 'cafe'; +UPDATE "ingredients" SET "key" = 'tea' WHERE "key" = 'the'; +UPDATE "ingredients" SET "key" = 'beer' WHERE "key" = 'biere_cuisine'; +UPDATE "ingredients" SET "key" = 'cider' WHERE "key" = 'cidre_cuisine'; +UPDATE "ingredients" SET "key" = 'champagne' WHERE "key" = 'champagne_vin_petillant_cuisine'; +UPDATE "ingredients" SET "key" = 'port_wine' WHERE "key" = 'porto_cuisine'; +UPDATE "ingredients" SET "key" = 'vin_jaune' WHERE "key" = 'vin_jaune_cuisine'; +UPDATE "ingredients" SET "key" = 'cognac' WHERE "key" = 'cognac'; +UPDATE "ingredients" SET "key" = 'rum' WHERE "key" = 'rhum'; +UPDATE "ingredients" SET "key" = 'whisky' WHERE "key" = 'whisky'; +UPDATE "ingredients" SET "key" = 'vodka' WHERE "key" = 'vodka'; +UPDATE "ingredients" SET "key" = 'wheat_flour' WHERE "key" = 'farine_de_ble'; +UPDATE "ingredients" SET "key" = 'whole_wheat_flour' WHERE "key" = 'farine_complete'; +UPDATE "ingredients" SET "key" = 'corn_flour' WHERE "key" = 'farine_de_mais'; +UPDATE "ingredients" SET "key" = 'buckwheat_flour' WHERE "key" = 'farine_de_sarrasin'; +UPDATE "ingredients" SET "key" = 'rice_flour' WHERE "key" = 'farine_de_riz'; +UPDATE "ingredients" SET "key" = 'vegetable_stock_cube' WHERE "key" = 'bouillon_cube_legumes'; +UPDATE "ingredients" SET "key" = 'chicken_stock_cube' WHERE "key" = 'bouillon_cube_volaille'; +UPDATE "ingredients" SET "key" = 'tomato_paste' WHERE "key" = 'concentre_de_tomate'; +UPDATE "ingredients" SET "key" = 'tomato_coulis' WHERE "key" = 'coulis_de_tomate'; +UPDATE "ingredients" SET "key" = 'canned_peeled_tomatoes' WHERE "key" = 'tomates_pelees_conserve'; +UPDATE "ingredients" SET "key" = 'sun_dried_tomatoes' WHERE "key" = 'tomates_sechees'; +UPDATE "ingredients" SET "key" = 'veal_stock' WHERE "key" = 'fond_de_veau'; +UPDATE "ingredients" SET "key" = 'chicken_stock' WHERE "key" = 'fond_de_volaille'; +UPDATE "ingredients" SET "key" = 'beef_stock_cube' WHERE "key" = 'bouillon_cube_boeuf'; +UPDATE "ingredients" SET "key" = 'fish_stock_cube' WHERE "key" = 'bouillon_cube_poisson'; +UPDATE "ingredients" SET "key" = 'vegetable_broth' WHERE "key" = 'bouillon_de_legumes'; +UPDATE "ingredients" SET "key" = 'chicken_broth' WHERE "key" = 'bouillon_de_volaille'; +UPDATE "ingredients" SET "key" = 'beef_broth' WHERE "key" = 'bouillon_de_boeuf'; +UPDATE "ingredients" SET "key" = 'court_bouillon' WHERE "key" = 'court_bouillon'; +UPDATE "ingredients" SET "key" = 'dashi' WHERE "key" = 'dashi_bouillon_japonais'; +UPDATE "ingredients" SET "key" = 'shellfish_bisque' WHERE "key" = 'bisque_de_crustaces'; +UPDATE "ingredients" SET "key" = 'tapioca_flour' WHERE "key" = 'farine_de_tapioca'; +UPDATE "ingredients" SET "key" = 'masa_harina' WHERE "key" = 'masa_harina'; +UPDATE "ingredients" SET "key" = 'water' WHERE "key" = 'eau'; +UPDATE "ingredients" SET "key" = 'sparkling_water' WHERE "key" = 'eau_gazeuse'; +UPDATE "ingredients" SET "key" = 'orange_blossom_water' WHERE "key" = 'eau_de_fleur_d_oranger'; +UPDATE "ingredients" SET "key" = 'rose_water' WHERE "key" = 'eau_de_rose'; +UPDATE "ingredients" SET "key" = 'fish_fumet' WHERE "key" = 'fumet_de_poisson'; +UPDATE "ingredients" SET "key" = 'bakers_yeast' WHERE "key" = 'levure_boulangere'; +UPDATE "ingredients" SET "key" = 'baking_powder' WHERE "key" = 'levure_chimique'; +UPDATE "ingredients" SET "key" = 'cornstarch' WHERE "key" = 'maizena'; +UPDATE "ingredients" SET "key" = 'lupin_flour' WHERE "key" = 'farine_de_lupin'; +UPDATE "ingredients" SET "key" = 'gelatin' WHERE "key" = 'gelatine'; +UPDATE "ingredients" SET "key" = 'baking_soda' WHERE "key" = 'bicarbonate_de_soude'; +UPDATE "ingredients" SET "key" = 'potato_starch' WHERE "key" = 'fecule_de_pomme_de_terre'; +UPDATE "ingredients" SET "key" = 'sugar' WHERE "key" = 'sucre'; +UPDATE "ingredients" SET "key" = 'honey' WHERE "key" = 'miel'; +UPDATE "ingredients" SET "key" = 'maple_syrup' WHERE "key" = 'sirop_d_erable'; +UPDATE "ingredients" SET "key" = 'brown_sugar' WHERE "key" = 'sucre_roux'; +UPDATE "ingredients" SET "key" = 'powdered_sugar' WHERE "key" = 'sucre_glace'; +UPDATE "ingredients" SET "key" = 'demerara_sugar' WHERE "key" = 'cassonade'; +UPDATE "ingredients" SET "key" = 'dark_chocolate' WHERE "key" = 'chocolat_noir'; +UPDATE "ingredients" SET "key" = 'milk_chocolate' WHERE "key" = 'chocolat_au_lait'; +UPDATE "ingredients" SET "key" = 'white_chocolate' WHERE "key" = 'chocolat_blanc'; +UPDATE "ingredients" SET "key" = 'chocolate_chips' WHERE "key" = 'pepites_de_chocolat'; +UPDATE "ingredients" SET "key" = 'cocoa_powder' WHERE "key" = 'cacao_en_poudre'; +UPDATE "ingredients" SET "key" = 'vanilla_extract' WHERE "key" = 'extrait_de_vanille'; +UPDATE "ingredients" SET "key" = 'palm_sugar' WHERE "key" = 'sucre_de_palme'; +UPDATE "ingredients" SET "key" = 'cane_syrup' WHERE "key" = 'sirop_de_sucre_de_canne'; + diff --git a/apps/api/scripts/generate-catalog-i18n.ts b/apps/api/scripts/generate-catalog-i18n.ts index fc58a05..6852241 100644 --- a/apps/api/scripts/generate-catalog-i18n.ts +++ b/apps/api/scripts/generate-catalog-i18n.ts @@ -1,99 +1,49 @@ /** * One-off generator, run by hand whenever the catalog's reference data * changes (a new ingredient/diet/allergen added to - * `db/reference-seed-data.ts`): derives every row's slug `key` from its - * French name (see `slugify.ts`), fails loudly on any collision, and - * regenerates `apps/web/src/locales/fr/translation.json`'s - * `catalog.{diets,allergens,ingredients}` sections (key -> French label), - * merged in without touching the rest of the file. + * `db/reference-seed-data.ts`, or an English key corrected in + * `catalog-en-keys.ts`): regenerates + * `apps/web/src/locales/fr/translation.json`'s + * `catalog.{diets,allergens,ingredients}` sections (English key -> French + * label), merged in without touching the rest of the file. * - * Also (re-)writes `backfill.sql` alongside itself — the `UPDATE ... SET - * key = ...` statements a migration adding a brand new item needs to carry - * forward, in case that ever happens again; the one for this refactor's own - * migration (`prisma/migrations/20260818190000_catalog_labels_to_keys/`) - * was generated once and copied in by hand, and `backfill.sql` itself is - * gitignored scratch output, not the source of truth. + * Doesn't touch the database — a brand new diet/allergen/ingredient is + * created fresh by `seedReferenceData`'s normal create path (see + * `reference-seed-data.ts`), no backfill needed. Renaming an *existing* + * item's English key in `catalog-en-keys.ts` does need a one-off migration + * (`UPDATE ... SET key = ...`, keyed by the *old* key value) written by + * hand for that occasion — see + * `prisma/migrations/20260818193000_catalog_keys_to_english/` for the shape + * one looks like. * * Never imported by the app itself — a dev-time tool, run via * `tsx scripts/generate-catalog-i18n.ts`. */ import { readFileSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; import { ALLERGENS, DIETS, INGREDIENT_GROUPS } from "../src/db/reference-seed-data.js"; -import { slugify } from "../src/utils/slugify.js"; const here = fileURLToPath(new URL(".", import.meta.url)); function toKeyLabelMap(labels: string[]): Record { const map: Record = {}; - const seenKeys = new Map(); for (const label of labels) { - const key = slugify(label); - const clashingLabel = seenKeys.get(key); - if (clashingLabel !== undefined && clashingLabel !== label) { - throw new Error(`Slug collision: "${clashingLabel}" and "${label}" both slugify to "${key}"`); - } - seenKeys.set(key, label); - map[key] = label; + map[getEnglishKey(label)] = label; } return map; } -const dietLabels = DIETS; -const allergenLabels = ALLERGENS.map((a) => a.name); -const ingredientLabels = INGREDIENT_GROUPS.flatMap((g) => g.items.map((i) => i.name)); - -const diets = toKeyLabelMap(dietLabels); -const allergens = toKeyLabelMap(allergenLabels); -const ingredients = toKeyLabelMap(ingredientLabels); +const diets = toKeyLabelMap(DIETS); +const allergens = toKeyLabelMap(ALLERGENS.map((a) => a.name)); +const ingredients = toKeyLabelMap(INGREDIENT_GROUPS.flatMap((g) => g.items.map((i) => i.name))); console.log( `diets: ${Object.keys(diets).length}, allergens: ${Object.keys(allergens).length}, ingredients: ${Object.keys(ingredients).length}`, ); -// --- Merge into the fr locale file ----------------------------------------- const localePath = here + "../../web/src/locales/fr/translation.json"; const locale = JSON.parse(readFileSync(localePath, "utf8")); locale.catalog = { diets, allergens, ingredients }; writeFileSync(localePath, JSON.stringify(locale, null, 2) + "\n"); console.log(`wrote ${localePath}`); - -// --- Emit the migration's backfill SQL -------------------------------------- -function escapeSql(value: string): string { - return value.replace(/'/g, "''"); -} - -// The migration this feeds renames each table's "name" column to "key" -// *before* running this backfill — so by the time these UPDATEs run, the -// "key" column still holds the old French label value (just under its new -// column name), which is exactly what the WHERE clause below matches on. -const dietUpdates = Object.entries(diets) - .map(([key, label]) => `UPDATE "diet" SET "key" = '${escapeSql(key)}' WHERE "key" = '${escapeSql(label)}';`) - .join("\n"); -const categoryUpdates = Object.entries(allergens) - .map( - ([key, label]) => `UPDATE "category" SET "key" = '${escapeSql(key)}' WHERE "key" = '${escapeSql(label)}';`, - ) - .join("\n"); -const ingredientUpdates = Object.entries(ingredients) - .map( - ([key, label]) => - `UPDATE "ingredients" SET "key" = '${escapeSql(key)}' WHERE "key" = '${escapeSql(label)}';`, - ) - .join("\n"); - -const sql = `-- Auto-generated by scripts/generate-catalog-i18n.ts — do not hand-edit. --- Backfills the "key" column (just renamed from "name" by this migration's --- preceding statement, so it still holds the old French label) to its slug --- value, for every row already seeded in a database this migration runs --- against. A fresh database has none of these rows yet (the seed script --- inserts by "key" from the start), so this is a no-op there. - -${dietUpdates} - -${categoryUpdates} - -${ingredientUpdates} -`; -writeFileSync(here + "backfill.sql", sql); -console.log(`wrote ${here}backfill.sql`); diff --git a/apps/api/scripts/validate-catalog-en-keys.ts b/apps/api/scripts/validate-catalog-en-keys.ts new file mode 100644 index 0000000..05bab54 --- /dev/null +++ b/apps/api/scripts/validate-catalog-en-keys.ts @@ -0,0 +1,43 @@ +/** + * One-off validation, run by hand: checks that `catalog-en-keys.ts` has an + * entry for every diet/allergen/ingredient currently in + * `reference-seed-data.ts`, and that the resulting English keys are unique + * within each table. Not part of the app or the seed itself — just a + * pre-flight check while building/editing the dictionary by hand. + */ +import { ALLERGENS, DIETS, INGREDIENT_GROUPS } from "../src/db/reference-seed-data.js"; +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; + +function check(label: string, names: string[]) { + const keys = new Map(); + const missing: string[] = []; + const duplicates: string[] = []; + for (const name of names) { + let key: string; + try { + key = getEnglishKey(name); + } catch { + missing.push(name); + continue; + } + const existing = keys.get(key); + if (existing !== undefined && existing !== name) { + duplicates.push(`"${existing}" and "${name}" both map to "${key}"`); + } + keys.set(key, name); + } + console.log(`${label}: ${names.length} names, ${keys.size} unique keys`); + if (missing.length > 0) { + console.log(` MISSING (${missing.length}):`, missing); + } + if (duplicates.length > 0) { + console.log(` DUPLICATES (${duplicates.length}):`, duplicates); + } +} + +check("Diets", DIETS); +check("Allergens", ALLERGENS.map((a) => a.name)); +check( + "Ingredients", + INGREDIENT_GROUPS.flatMap((g) => g.items.map((i) => i.name)), +); diff --git a/apps/api/src/db/catalog-en-keys.ts b/apps/api/src/db/catalog-en-keys.ts new file mode 100644 index 0000000..875548b --- /dev/null +++ b/apps/api/src/db/catalog-en-keys.ts @@ -0,0 +1,557 @@ +/** + * English key for every catalog reference label — `Diet`/`Category` + * (allergens)/`Ingredient` rows must carry a stable, storage-safe `key` + * that is itself English, independent of whatever language the *seed's* + * authoring label (`DIETS`/`ALLERGENS`/`INGREDIENT_GROUPS` in + * `reference-seed-data.ts`, currently French) happens to be in — a French + * `key` would tie the identifier to the one language it's meant to be + * decoupled from (see `utils/slugify.ts`'s doc comment and `apps/web`'s + * `locales/fr/translation.json` `catalog.*` namespace, which resolves the + * *display* label from this same key). + * + * Hand-assigned (not machine-translated) — an English label is chosen once + * and never changes, exactly like the key it produces (via {@link + * slugify}). Keyed by the exact French authoring label so + * `reference-seed-data.ts` and `scripts/generate-catalog-i18n.ts` can look + * a row's key up by the same string they already have in hand. + * + * `getEnglishKey` throws on a missing entry rather than falling back to + * slugifying the French label — a silently-French key defeats the point, + * so a newly-added diet/allergen/ingredient must get an entry here before + * it can seed. + */ +import { slugify } from "../utils/slugify.js"; + +const DIET_KEYS: Record = { + Omnivore: "omnivore", + Végétarien: "vegetarian", + Végan: "vegan", + Pescétarien: "pescatarian", + "Sans gluten": "gluten_free", +}; + +const ALLERGEN_KEYS: Record = { + Gluten: "gluten", + Crustacés: "crustaceans", + Œufs: "eggs", + Poissons: "fish", + Arachides: "peanuts", + Soja: "soy", + Lait: "milk", + "Fruits à coque": "tree_nuts", + Céleri: "celery", + Moutarde: "mustard", + "Graines de sésame": "sesame_seeds", + Sulfites: "sulfites", + Lupin: "lupin", + Mollusques: "molluscs", +}; + +// Grouped by (category, subcategory), mirroring `INGREDIENT_GROUPS` in +// reference-seed-data.ts, purely so a translator can find/check an entry +// against its source group — this is one flat lookup table at runtime. +const INGREDIENT_KEYS: Record = { + // --- Produits frais / Légumes --------------------------------------- + Tomate: "tomato", + Oignon: "onion", + Échalote: "shallot", + Ail: "garlic", + Carotte: "carrot", + Courgette: "zucchini", + Concombre: "cucumber", + Cornichons: "gherkins", + Poivron: "bell_pepper", + Champignon: "mushroom", + Cèpes: "porcini", + Aubergine: "eggplant", + Brocoli: "broccoli", + "Chou-fleur": "cauliflower", + "Chou blanc": "white_cabbage", + "Chou rouge": "red_cabbage", + "Chou de Bruxelles": "brussels_sprouts", + Épinard: "spinach", + Blette: "swiss_chard", + Salade: "lettuce", + Roquette: "arugula", + Cresson: "watercress", + Poireau: "leek", + Radis: "radish", + Betterave: "beetroot", + Navet: "turnip", + Panais: "parsnip", + "Haricot vert": "green_bean", + "Petit pois": "pea", + Maïs: "corn", + Artichaut: "artichoke", + Fenouil: "fennel", + Endive: "endive", + Potiron: "pumpkin", + Butternut: "butternut_squash", + Asperge: "asparagus", + Avocat: "avocado", + "Pomme de terre": "potato", + "Patate douce": "sweet_potato", + "Tomates cerises": "cherry_tomato", + "Pak-choï": "bok_choy", + "Germes de soja": "soybean_sprouts", + Shiitake: "shiitake", + Daikon: "daikon", + "Piment vert frais": "fresh_green_chili", + + // --- Produits frais / Fruits ----------------------------------------- + Citron: "lemon", + "Citron vert": "lime", + Pomme: "apple", + Poire: "pear", + Banane: "banana", + Orange: "orange", + Clémentine: "clementine", + Pamplemousse: "grapefruit", + Fraise: "strawberry", + Framboise: "raspberry", + Myrtille: "blueberry", + Mûre: "blackberry", + Cerise: "cherry", + Abricot: "apricot", + Pêche: "peach", + Prune: "plum", + Raisin: "grape", + Melon: "melon", + Pastèque: "watermelon", + Ananas: "pineapple", + Mangue: "mango", + Kiwi: "kiwi", + Figue: "fig", + Datte: "date", + Litchi: "lychee", + Grenade: "pomegranate", + Rhubarbe: "rhubarb", + Coing: "quince", + + // --- Produits frais / Herbes fraîches --------------------------------- + Basilic: "basil", + Persil: "parsley", + Thym: "thyme", + Romarin: "rosemary", + Laurier: "bay_leaf", + Ciboulette: "chives", + "Coriandre fraîche": "fresh_cilantro", + Menthe: "mint", + Origan: "oregano", + Aneth: "dill", + Estragon: "tarragon", + Sarriette: "savory", + Marjolaine: "marjoram", + Sauge: "sage", + Cerfeuil: "chervil", + Gingembre: "ginger", + Citronnelle: "lemongrass", + Combava: "kaffir_lime", + + // --- Boucherie & poissonnerie / Viandes ------------------------------- + Lapin: "rabbit", + "Bœuf haché": "ground_beef", + "Steak de bœuf": "beef_steak", + "Rôti de bœuf": "beef_roast", + "Escalope de veau": "veal_cutlet", + "Filet mignon de porc": "pork_tenderloin", + "Côte de porc": "pork_chop", + Agneau: "lamb", + "Gigot d'agneau": "leg_of_lamb", + Lardons: "bacon_lardons", + Bacon: "bacon", + "Jambon blanc": "ham", + "Jambon cru": "cured_ham", + Saucisse: "sausage", + Chorizo: "chorizo", + Merguez: "merguez", + Prosciutto: "prosciutto", + Pancetta: "pancetta", + Mortadelle: "mortadella", + Salami: "salami", + + // --- Boucherie & poissonnerie / Volailles ----------------------------- + Poulet: "chicken", + Dinde: "turkey", + Canard: "duck", + "Magret de canard": "duck_breast", + + // --- Boucherie & poissonnerie / Poissons ------------------------------- + Saumon: "salmon", + Thon: "tuna", + Cabillaud: "cod", + Truite: "trout", + Sardine: "sardine", + Anchois: "anchovy", + Merlan: "whiting", + Surimi: "surimi", + "Bar (loup de mer)": "sea_bass", + Dorade: "sea_bream", + Sole: "sole", + Turbot: "turbot", + Merlu: "hake", + Colin: "pollock", + "Lieu noir": "saithe", + Églefin: "haddock", + Maquereau: "mackerel", + Hareng: "herring", + Rouget: "red_mullet", + Raie: "skate", + Lotte: "monkfish", + Flétan: "halibut", + Espadon: "swordfish", + Carpe: "carp", + Brochet: "pike", + Perche: "perch", + Tilapia: "tilapia", + Panga: "pangasius", + "Saumon fumé": "smoked_salmon", + "Poisson séché": "dried_fish", + + // --- Boucherie & poissonnerie / Crustacés & fruits de mer ------------- + Crevettes: "shrimp", + Langoustines: "langoustine", + Homard: "lobster", + Crabe: "crab", + Langouste: "spiny_lobster", + Moules: "mussels", + Huîtres: "oysters", + "Saint-Jacques": "scallops", + Calamar: "squid", + Poulpe: "octopus", + Palourdes: "clams", + Bulots: "whelks", + + // --- Épicerie sèche / Féculents ---------------------------------------- + Semoule: "semolina", + Couscous: "couscous", + Boulgour: "bulgur", + Polenta: "polenta", + Quinoa: "quinoa", + Pâtes: "pasta", + "Pâtes complètes": "whole_wheat_pasta", + Riz: "rice", + "Riz basmati": "basmati_rice", + "Riz complet": "brown_rice", + "Flocons d'avoine": "oats", + Spaghetti: "spaghetti", + Penne: "penne", + Tagliatelles: "tagliatelle", + "Lasagnes (feuilles)": "lasagna_sheets", + Gnocchi: "gnocchi", + "Riz arborio": "arborio_rice", + "Nouilles de riz": "rice_noodles", + "Nouilles udon": "udon_noodles", + "Nouilles soba": "soba_noodles", + "Nouilles chinoises": "chinese_noodles", + "Vermicelles de riz": "rice_vermicelli", + "Vermicelles de soja": "soy_vermicelli", + "Riz gluant": "sticky_rice", + "Riz à sushi": "sushi_rice", + "Riz jasmin": "jasmine_rice", + + // --- Épicerie sèche / Légumineuses -------------------------------------- + "Lentilles vertes": "green_lentils", + "Lentilles corail": "red_lentils", + "Pois chiches": "chickpeas", + "Haricots blancs": "white_beans", + "Haricots rouges": "kidney_beans", + "Haricots noirs": "black_beans", + "Pois cassés": "split_peas", + Fèves: "fava_beans", + Edamame: "edamame", + "Haricots pinto": "pinto_beans", + + // --- Épicerie sèche / Graines & fruits secs ---------------------------- + Cacahuètes: "peanuts_shelled", + Amandes: "almonds", + Noix: "walnuts", + Noisettes: "hazelnuts", + "Noix de cajou": "cashews", + Pistaches: "pistachios", + "Noix de pécan": "pecans", + "Poudre d'amande": "almond_powder", + "Pignons de pin": "pine_nuts", + "Graines de tournesol": "sunflower_seeds", + "Graines de courge": "pumpkin_seeds", + "Noix de coco râpée": "shredded_coconut", + "Raisins secs": "raisins", + Pruneaux: "prunes", + "Abricots secs": "dried_apricots", + + // --- Épicerie sèche / Autres -------------------------------------------- + "Champignons noirs": "black_mushrooms", + "Algue nori": "nori_seaweed", + "Algue wakamé": "wakame_seaweed", + "Algue kombu": "kombu_seaweed", + "Pousses de bambou": "bamboo_shoots", + "Châtaignes d'eau": "water_chestnuts", + + // --- Boulangerie / Pains ------------------------------------------------- + Pain: "bread", + "Pain de mie": "sandwich_bread", + "Pain complet": "whole_wheat_bread", + Baguette: "baguette", + "Pain de seigle": "rye_bread", + Chapelure: "breadcrumbs", + "Pain à burger": "burger_bun", + "Pain brioché": "brioche_bun", + "Pain à hot-dog": "hot_dog_bun", + "Pain pita": "pita_bread", + "Pain bagel": "bagel", + Naan: "naan", + "Pain wrap": "wrap_bread", + "Pain viennois": "viennese_bread", + "Pain de campagne": "country_bread", + "Pain aux céréales": "multigrain_bread", + "Petit pain": "bread_roll", + "Pain suédois": "swedish_bread", + "Pain sans gluten": "gluten_free_bread", + Biscotte: "rusk", + Croûtons: "croutons", + Focaccia: "focaccia", + Ciabatta: "ciabatta", + "Tortilla de maïs": "corn_tortilla", + "Tortilla de blé": "wheat_tortilla", + + // --- Boulangerie / Pâtes à cuire ----------------------------------------- + "Pâte feuilletée": "puff_pastry", + "Pâte brisée": "shortcrust_pastry", + "Pâte à pizza": "pizza_dough", + "Pâte à tarte sablée": "sweet_shortcrust_pastry", + + // --- Crémerie & fromage / Produits laitiers ------------------------------ + Lait: "milk", + Beurre: "butter", + "Crème fraîche": "creme_fraiche", + "Crème liquide": "liquid_cream", + Fromage: "cheese", + Emmental: "emmental", + Gruyère: "gruyere", + Parmesan: "parmesan", + Mozzarella: "mozzarella", + "Chèvre (fromage)": "goat_cheese", + Feta: "feta", + Comté: "comte", + "Fromage blanc": "fromage_blanc", + Mascarpone: "mascarpone", + Yaourt: "yogurt", + Burrata: "burrata", + Ricotta: "ricotta", + Pecorino: "pecorino", + Gorgonzola: "gorgonzola", + Cheddar: "cheddar", + + // --- Crémerie & fromage / Œufs ------------------------------------------- + Œuf: "egg", + + // --- Crémerie & fromage / Alternatives ------------------------------------ + "Lait de coco": "coconut_milk", + "Crème de coco": "coconut_cream", + "Lait d'amande": "almond_milk", + "Lait d'avoine": "oat_milk", + Tofu: "tofu", + "Tofu soyeux": "silken_tofu", + + // --- Condiments & épices / Épices ----------------------------------------- + "Herbes de Provence": "herbes_de_provence", + "Poivre noir": "black_pepper", + Paprika: "paprika", + "Piment d'Espelette": "espelette_pepper", + "Piment de Cayenne": "cayenne_pepper", + Cumin: "cumin", + "Curry (poudre)": "curry_powder", + Curcuma: "turmeric", + Cannelle: "cinnamon", + Muscade: "nutmeg", + Safran: "saffron", + "Clou de girofle": "clove", + "Vanille (gousse)": "vanilla_bean", + "Poivre blanc": "white_pepper", + "Poivre rose": "pink_pepper", + "Poivre du Sichuan": "sichuan_pepper", + "Paprika fumé": "smoked_paprika", + "Piment oiseau": "bird_eye_chili", + "Baies de genièvre": "juniper_berries", + "Anis étoilé (badiane)": "star_anise", + "Anis vert": "green_anise", + "Graines de fenouil": "fennel_seeds", + Sumac: "sumac", + Nigelle: "nigella", + "Quatre épices": "allspice", + "Colombo (poudre)": "colombo_powder", + Baharat: "baharat", + Raifort: "horseradish", + "Sel aux herbes": "herb_salt", + "Sel de céleri": "celery_salt", + "Fleur de sel": "fleur_de_sel", + Sel: "salt", + "Cinq épices": "five_spice", + "Garam masala": "garam_masala", + "Graines de coriandre": "coriander_seeds", + Cardamome: "cardamom", + Fenugrec: "fenugreek", + "Piment jalapeño": "jalapeno", + "Piment chipotle": "chipotle", + "Piment poblano": "poblano_pepper", + "Piment habanero": "habanero", + "Ras el hanout": "ras_el_hanout", + "Za'atar": "zaatar", + + // --- Condiments & épices / Sauces ------------------------------------------- + "Sauce soja": "soy_sauce", + Moutarde: "mustard", + Mayonnaise: "mayonnaise", + Ketchup: "ketchup", + Tabasco: "tabasco", + "Sauce Worcestershire": "worcestershire_sauce", + "Sauce nuoc-mâm": "fish_sauce", + Wasabi: "wasabi", + Harissa: "harissa", + "Pâte de curry": "curry_paste", + "Beurre de cacahuète": "peanut_butter", + "Moutarde de Dijon": "dijon_mustard", + "Moutarde à l'ancienne": "wholegrain_mustard", + "Sauce barbecue": "barbecue_sauce", + "Sauce tartare": "tartar_sauce", + "Sauce cocktail": "cocktail_sauce", + "Sauce béarnaise": "bearnaise_sauce", + "Sauce hollandaise": "hollandaise_sauce", + "Sauce béchamel": "bechamel_sauce", + "Sauce teriyaki": "teriyaki_sauce", + "Sauce ponzu": "ponzu_sauce", + Chimichurri: "chimichurri", + "Pesto rouge (tomates séchées)": "red_pesto", + Pesto: "pesto", + "Sauce huître": "oyster_sauce", + "Sauce hoisin": "hoisin_sauce", + "Sauce sriracha": "sriracha", + "Sauce sweet chili": "sweet_chili_sauce", + Miso: "miso", + "Pâte de crevettes": "shrimp_paste", + "Pâte de curry rouge (thaï)": "red_curry_paste", + "Pâte de curry vert (thaï)": "green_curry_paste", + Tahini: "tahini", + + // --- Condiments & épices / Assaisonnements ----------------------------------- + "Huile d'olive": "olive_oil", + "Huile de tournesol": "sunflower_oil", + "Huile de colza": "rapeseed_oil", + "Huile de coco": "coconut_oil", + "Huile de sésame": "sesame_oil", + "Vinaigre de cidre": "cider_vinegar", + "Vinaigre blanc": "white_vinegar", + "Vinaigre balsamique": "balsamic_vinegar", + Câpres: "capers", + Olives: "olives", + "Vin blanc (cuisine)": "white_wine", + "Vin rouge (cuisine)": "red_wine", + "Vinaigre de vin rouge": "red_wine_vinegar", + "Vinaigre de vin blanc": "white_wine_vinegar", + "Vinaigre de xérès": "sherry_vinegar", + "Huile de noix": "walnut_oil", + "Huile de noisette": "hazelnut_oil", + "Huile d'arachide": "peanut_oil", + "Huile pimentée": "chili_oil", + "Vinaigre de riz": "rice_vinegar", + Mirin: "mirin", + "Saké (cuisine)": "sake", + "Jus de citron": "lemon_juice", + "Jus de citron vert": "lime_juice", + "Jus d'orange": "orange_juice", + "Jus de pomme": "apple_juice", + "Jus de raisin": "grape_juice", + "Jus de tomate": "tomato_juice", + "Jus de cranberry": "cranberry_juice", + Café: "coffee", + Thé: "tea", + "Bière (cuisine)": "beer", + "Cidre (cuisine)": "cider", + "Champagne / vin pétillant (cuisine)": "champagne", + "Porto (cuisine)": "port_wine", + "Vin jaune (cuisine)": "vin_jaune", + Cognac: "cognac", + Rhum: "rum", + Whisky: "whisky", + Vodka: "vodka", + + // --- Aides culinaires / Bases ------------------------------------------- + "Farine de blé": "wheat_flour", + "Farine complète": "whole_wheat_flour", + "Farine de maïs": "corn_flour", + "Farine de sarrasin": "buckwheat_flour", + "Farine de riz": "rice_flour", + "Bouillon cube légumes": "vegetable_stock_cube", + "Bouillon cube volaille": "chicken_stock_cube", + "Concentré de tomate": "tomato_paste", + "Coulis de tomate": "tomato_coulis", + "Tomates pelées (conserve)": "canned_peeled_tomatoes", + "Tomates séchées": "sun_dried_tomatoes", + "Fond de veau": "veal_stock", + "Fond de volaille": "chicken_stock", + "Bouillon cube bœuf": "beef_stock_cube", + "Bouillon cube poisson": "fish_stock_cube", + "Bouillon de légumes": "vegetable_broth", + "Bouillon de volaille": "chicken_broth", + "Bouillon de bœuf": "beef_broth", + "Court-bouillon": "court_bouillon", + "Dashi (bouillon japonais)": "dashi", + "Bisque de crustacés": "shellfish_bisque", + "Farine de tapioca": "tapioca_flour", + "Masa harina": "masa_harina", + Eau: "water", + "Eau gazeuse": "sparkling_water", + "Eau de fleur d'oranger": "orange_blossom_water", + "Eau de rose": "rose_water", + "Fumet de poisson": "fish_fumet", + + // --- Aides culinaires / Épaississants ------------------------------------- + "Levure boulangère": "bakers_yeast", + "Levure chimique": "baking_powder", + Maïzena: "cornstarch", + "Farine de lupin": "lupin_flour", + Gélatine: "gelatin", + "Bicarbonate de soude": "baking_soda", + "Fécule de pomme de terre": "potato_starch", + + // --- Aides culinaires / Sucres --------------------------------------------- + Sucre: "sugar", + Miel: "honey", + "Sirop d'érable": "maple_syrup", + "Sucre roux": "brown_sugar", + "Sucre glace": "powdered_sugar", + Cassonade: "demerara_sugar", + "Chocolat noir": "dark_chocolate", + "Chocolat au lait": "milk_chocolate", + "Chocolat blanc": "white_chocolate", + "Pépites de chocolat": "chocolate_chips", + "Cacao en poudre": "cocoa_powder", + "Extrait de vanille": "vanilla_extract", + "Sucre de palme": "palm_sugar", + "Sirop de sucre de canne": "cane_syrup", +}; + +const ALL_KEYS: Record = { + ...DIET_KEYS, + ...ALLERGEN_KEYS, + ...INGREDIENT_KEYS, +}; + +/** + * Resolves a seed-time French authoring label to its English `key` — + * throws if it's missing an entry above rather than silently falling back + * to a French slug, since a new diet/allergen/ingredient needs a + * deliberately-chosen English key before it can seed at all. + * `slugify` still runs over the result so a stray character/casing slip in + * the table above can't produce a key that doesn't match the + * `[a-z0-9_]`-only shape every other key has. + */ +export function getEnglishKey(frenchLabel: string): string { + const english = ALL_KEYS[frenchLabel]; + if (english === undefined) { + throw new Error( + `No English key registered for "${frenchLabel}" — add one to catalog-en-keys.ts`, + ); + } + return slugify(english); +} diff --git a/apps/api/src/db/reference-seed-data.ts b/apps/api/src/db/reference-seed-data.ts index 52e76fa..5c9dade 100644 --- a/apps/api/src/db/reference-seed-data.ts +++ b/apps/api/src/db/reference-seed-data.ts @@ -5,7 +5,7 @@ import type { IngredientSubcategory, PrismaClient, } from "@prisma/client"; -import { slugify } from "../utils/slugify.js"; +import { getEnglishKey } from "./catalog-en-keys.js"; // Short, optional-to-pick regime list — `UserProfile.dietId` stays // nullable, this is not meant to be exhaustive. Exported for @@ -890,14 +890,16 @@ const INGREDIENTS: Array< * * Every `name` below (`DIETS`, `ALLERGENS`, `INGREDIENT_GROUPS`) is an * *authoring* label, never written to the database or seen by a client — - * {@link slugify} derives each row's real, stable `key` from it once, up - * front. The database only ever stores that slug; the French label itself - * lives in `apps/web`'s `locales/fr/translation.json` (`catalog.*` + * {@link getEnglishKey} resolves each row's real, stable, English `key` + * from it (see `catalog-en-keys.ts` for why the key must be English even + * though this file's labels are French). The database only ever stores + * that key; the French label itself lives in `apps/web`'s + * `locales/fr/translation.json` (`catalog.*` * namespace, kept in sync by `scripts/generate-catalog-i18n.ts`). */ export async function seedReferenceData(prisma: PrismaClient): Promise { for (const name of DIETS) { - const key = slugify(name); + const key = getEnglishKey(name); await prisma.diet.upsert({ where: { key }, update: {}, create: { key } }); } @@ -908,7 +910,7 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { // must correct `kind` on an already-existing category if the // classification above ever changes, not just skip it. for (const { name, kind } of ALLERGENS) { - const key = slugify(name); + const key = getEnglishKey(name); const category = await prisma.category.upsert({ where: { key }, update: { kind }, @@ -929,18 +931,18 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { // test-suite case) that's zero updates, on a real re-deploy it's however // many rows were edited in code since the last deploy, never the full // list. - const ingredientKeys = INGREDIENTS.map((i) => slugify(i.name)); + const ingredientKeys = INGREDIENTS.map((i) => getEnglishKey(i.name)); const existingIngredients = await prisma.ingredient.findMany({ where: { key: { in: ingredientKeys } }, select: { id: true, key: true, icon: true, category: true, subcategory: true }, }); const existingByKey = new Map(existingIngredients.map((i) => [i.key, i])); - const missingIngredients = INGREDIENTS.filter((i) => !existingByKey.has(slugify(i.name))); + const missingIngredients = INGREDIENTS.filter((i) => !existingByKey.has(getEnglishKey(i.name))); if (missingIngredients.length > 0) { await prisma.ingredient.createMany({ data: missingIngredients.map(({ name, icon, category, subcategory }) => ({ - key: slugify(name), + key: getEnglishKey(name), icon, category, subcategory, @@ -949,7 +951,7 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { } const changed = INGREDIENTS.filter((i) => { - const existing = existingByKey.get(slugify(i.name)); + const existing = existingByKey.get(getEnglishKey(i.name)); return ( existing && (existing.icon !== i.icon || @@ -958,7 +960,7 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { ); }); for (const { name, icon, category, subcategory } of changed) { - await prisma.ingredient.update({ where: { key: slugify(name) }, data: { icon, category, subcategory } }); + await prisma.ingredient.update({ where: { key: getEnglishKey(name) }, data: { icon, category, subcategory } }); } // Re-resolve every ingredient's id (existing + just-created) and every @@ -977,10 +979,10 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { const links: Array<{ ingredientId: number; allergyId: number }> = []; for (const { name, allergenNames } of INGREDIENTS) { - const ingredientId = ingredientIdByKey.get(slugify(name)); + const ingredientId = ingredientIdByKey.get(getEnglishKey(name)); if (ingredientId === undefined) continue; for (const allergenName of allergenNames) { - const allergyId = allergyIdByCategoryKey.get(slugify(allergenName)); + const allergyId = allergyIdByCategoryKey.get(getEnglishKey(allergenName)); if (allergyId !== undefined) links.push({ ingredientId, allergyId }); } } @@ -996,10 +998,10 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { const dietLinks: Array<{ ingredientId: number; dietId: number }> = []; for (const { name, dietNames } of INGREDIENTS) { - const ingredientId = ingredientIdByKey.get(slugify(name)); + const ingredientId = ingredientIdByKey.get(getEnglishKey(name)); if (ingredientId === undefined) continue; for (const dietName of dietNames) { - const dietId = dietIdByKey.get(slugify(dietName)); + const dietId = dietIdByKey.get(getEnglishKey(dietName)); if (dietId !== undefined) dietLinks.push({ ingredientId, dietId }); } } diff --git a/apps/api/test/profile.test.ts b/apps/api/test/profile.test.ts index 4ea42b3..9a57cf0 100644 --- a/apps/api/test/profile.test.ts +++ b/apps/api/test/profile.test.ts @@ -4,7 +4,7 @@ import { expect } from "chai"; import request from "supertest"; import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; -import { slugify } from "../src/utils/slugify.js"; +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; import { resetDatabase } from "../test-support/reset-db.js"; function buildSignupPayload(): SignupInput { @@ -40,7 +40,7 @@ describe("Profile", () => { it("sets the profile's regime to a valid, seeded diet", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const diet = await prisma.diet.findFirstOrThrow({ where: { key: slugify("Végétarien") } }); + const diet = await prisma.diet.findFirstOrThrow({ where: { key: getEnglishKey("Végétarien") } }); const res = await agent.patch("/profile/diet").send({ dietId: diet.id }); @@ -51,7 +51,7 @@ describe("Profile", () => { it("clears the regime when dietId is null", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const diet = await prisma.diet.findFirstOrThrow({ where: { key: slugify("Végan") } }); + const diet = await prisma.diet.findFirstOrThrow({ where: { key: getEnglishKey("Végan") } }); await agent.patch("/profile/diet").send({ dietId: diet.id }); const res = await agent.patch("/profile/diet").send({ dietId: null }); @@ -84,8 +84,8 @@ describe("Profile", () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); const allergies = await prisma.allergy.findMany({ include: { category: true } }); - const peanuts = allergies.find((a) => a.category.key === slugify("Arachides")); - const gluten = allergies.find((a) => a.category.key === slugify("Gluten")); + const peanuts = allergies.find((a) => a.category.key === getEnglishKey("Arachides")); + const gluten = allergies.find((a) => a.category.key === getEnglishKey("Gluten")); if (!peanuts || !gluten) throw new Error("expected seeded allergens missing"); const initial = await agent.get("/profile/allergies"); @@ -105,8 +105,8 @@ describe("Profile", () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); const allergies = await prisma.allergy.findMany({ include: { category: true } }); - const peanuts = allergies.find((a) => a.category.key === slugify("Arachides")); - const gluten = allergies.find((a) => a.category.key === slugify("Gluten")); + const peanuts = allergies.find((a) => a.category.key === getEnglishKey("Arachides")); + const gluten = allergies.find((a) => a.category.key === getEnglishKey("Gluten")); if (!peanuts || !gluten) throw new Error("expected seeded allergens missing"); await agent.patch("/profile/allergies").send({ allergyIds: [peanuts.id] }); @@ -141,8 +141,8 @@ describe("Profile", () => { it("starts empty, then reflects a saved selection", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const tomate = await prisma.ingredient.findFirstOrThrow({ where: { key: slugify("Tomate") } }); - const oignon = await prisma.ingredient.findFirstOrThrow({ where: { key: slugify("Oignon") } }); + const tomate = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Tomate") } }); + const oignon = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Oignon") } }); const initial = await agent.get("/profile/disliked-ingredients"); expect(initial.body).to.deep.equal([]); @@ -160,8 +160,8 @@ describe("Profile", () => { it("replaces (not merges) the previous selection", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const tomate = await prisma.ingredient.findFirstOrThrow({ where: { key: slugify("Tomate") } }); - const oignon = await prisma.ingredient.findFirstOrThrow({ where: { key: slugify("Oignon") } }); + const tomate = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Tomate") } }); + const oignon = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Oignon") } }); await agent .patch("/profile/disliked-ingredients") diff --git a/apps/api/test/recipe.test.ts b/apps/api/test/recipe.test.ts index a00ef65..889e2c7 100644 --- a/apps/api/test/recipe.test.ts +++ b/apps/api/test/recipe.test.ts @@ -5,7 +5,7 @@ import { expect } from "chai"; import request from "supertest"; import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; -import { slugify } from "../src/utils/slugify.js"; +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** See `auth.test.ts` — generated rather than hardcoded, no test fixture looks like a real person's data. */ @@ -22,7 +22,7 @@ function buildSignupPayload(): SignupInput { /** Resolves a reference ingredient's id by its `reference-seed-data.ts` French name (slugified to match its `key`). */ async function ingredientId(name: string): Promise { - const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { key: slugify(name) } }); + const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey(name) } }); return ingredient.id; } @@ -164,7 +164,9 @@ describe("Recipes", () => { const { agent } = await signup(); const tomate = await ingredientId("Tomate"); const oeuf = await ingredientId("Œuf"); - const vegetarien = await prisma.diet.findFirstOrThrow({ where: { key: "vegetarien" } }); + const vegetarien = await prisma.diet.findFirstOrThrow({ + where: { key: getEnglishKey("Végétarien") }, + }); const res = await agent.post("/recipes").send({ name: "Omelette provençale", @@ -184,8 +186,12 @@ describe("Recipes", () => { res.body.steps.map((s: { description: string; order: number }) => s.order), ).to.deep.equal([0, 1]); // Allergens aggregated across ingredients — "Œuf" carries "Œufs". - expect(res.body.allergens.map((a: { key: string }) => a.key)).to.include("oeufs"); - expect(res.body.diets.map((d: { key: string }) => d.key)).to.deep.equal(["vegetarien"]); + expect(res.body.allergens.map((a: { key: string }) => a.key)).to.include( + getEnglishKey("Œufs"), + ); + expect(res.body.diets.map((d: { key: string }) => d.key)).to.deep.equal([ + getEnglishKey("Végétarien"), + ]); }); it("defaults to PERSONAL visibility, and stamps the author's current household", async () => { @@ -280,7 +286,7 @@ describe("Recipes", () => { expect(res.status).to.equal(200); expect(res.body.name).to.equal("Salade"); - expect(res.body.ingredients[0].ingredient.key).to.equal("tomate"); + expect(res.body.ingredients[0].ingredient.key).to.equal(getEnglishKey("Tomate")); expect(res.body.isFavorite).to.equal(false); }); @@ -365,7 +371,7 @@ describe("Recipes", () => { expect(res.body.name).to.equal("Salade composée"); expect(res.body.visibility).to.equal("PUBLIC"); expect(res.body.ingredients).to.have.length(1); - expect(res.body.ingredients[0].ingredient.key).to.equal("oignon"); + expect(res.body.ingredients[0].ingredient.key).to.equal(getEnglishKey("Oignon")); expect(res.body.steps).to.have.length(2); }); diff --git a/apps/api/test/reference.test.ts b/apps/api/test/reference.test.ts index 64a0a80..e7ed4e0 100644 --- a/apps/api/test/reference.test.ts +++ b/apps/api/test/reference.test.ts @@ -3,7 +3,7 @@ import request from "supertest"; import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; import { resetDatabase } from "../test-support/reset-db.js"; -import { slugify } from "../src/utils/slugify.js"; +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; describe("Reference data", () => { const app = createApp(); @@ -22,7 +22,7 @@ describe("Reference data", () => { expect(res.status).to.equal(200); expect(res.body).to.have.length(5); - expect(res.body.map((d: { key: string }) => d.key)).to.include(slugify("Végétarien")); + expect(res.body.map((d: { key: string }) => d.key)).to.include(getEnglishKey("Végétarien")); expect(res.body[0]).to.have.keys(["id", "key"]); }); }); @@ -33,7 +33,7 @@ describe("Reference data", () => { expect(res.status).to.equal(200); expect(res.body).to.have.length(14); - expect(res.body.map((a: { key: string }) => a.key)).to.include(slugify("Arachides")); + expect(res.body.map((a: { key: string }) => a.key)).to.include(getEnglishKey("Arachides")); expect(res.body[0]).to.have.keys(["id", "key", "kind"]); }); @@ -41,7 +41,7 @@ describe("Reference data", () => { const res = await request(app).get("/reference/allergies"); const byKey = (name: string) => - res.body.find((a: { key: string }) => a.key === slugify(name)); + res.body.find((a: { key: string }) => a.key === getEnglishKey(name)); expect(byKey("Gluten").kind).to.equal("INTOLERANCE"); expect(byKey("Sulfites").kind).to.equal("INTOLERANCE"); expect(byKey("Arachides").kind).to.equal("ALLERGY"); @@ -55,7 +55,7 @@ describe("Reference data", () => { expect(res.status).to.equal(200); expect(res.body.length).to.be.greaterThan(0); - expect(res.body.map((i: { key: string }) => i.key)).to.include(slugify("Tomate")); + expect(res.body.map((i: { key: string }) => i.key)).to.include(getEnglishKey("Tomate")); expect(res.body[0]).to.have.keys([ "id", "key", @@ -71,9 +71,9 @@ describe("Reference data", () => { const res = await request(app).get("/reference/ingredients"); const byKey = (name: string) => - res.body.find((i: { key: string }) => i.key === slugify(name)); + res.body.find((i: { key: string }) => i.key === getEnglishKey(name)); expect(byKey("Œuf").allergens.map((a: { key: string }) => a.key)).to.include( - slugify("Œufs"), + getEnglishKey("Œufs"), ); expect(byKey("Tomate").allergens).to.deep.equal([]); }); diff --git a/apps/web/src/locales/fr/translation.json b/apps/web/src/locales/fr/translation.json index 1a181b0..83edfca 100644 --- a/apps/web/src/locales/fr/translation.json +++ b/apps/web/src/locales/fr/translation.json @@ -301,465 +301,465 @@ "catalog": { "diets": { "omnivore": "Omnivore", - "vegetarien": "Végétarien", + "vegetarian": "Végétarien", "vegan": "Végan", - "pescetarien": "Pescétarien", - "sans_gluten": "Sans gluten" + "pescatarian": "Pescétarien", + "gluten_free": "Sans gluten" }, "allergens": { "gluten": "Gluten", - "crustaces": "Crustacés", - "oeufs": "Œufs", - "poissons": "Poissons", - "arachides": "Arachides", - "soja": "Soja", - "lait": "Lait", - "fruits_a_coque": "Fruits à coque", - "celeri": "Céleri", - "moutarde": "Moutarde", - "graines_de_sesame": "Graines de sésame", + "crustaceans": "Crustacés", + "eggs": "Œufs", + "fish": "Poissons", + "peanuts": "Arachides", + "soy": "Soja", + "milk": "Lait", + "tree_nuts": "Fruits à coque", + "celery": "Céleri", + "mustard": "Moutarde", + "sesame_seeds": "Graines de sésame", "sulfites": "Sulfites", "lupin": "Lupin", - "mollusques": "Mollusques" + "molluscs": "Mollusques" }, "ingredients": { - "tomate": "Tomate", - "oignon": "Oignon", - "echalote": "Échalote", - "ail": "Ail", - "carotte": "Carotte", - "courgette": "Courgette", - "concombre": "Concombre", - "cornichons": "Cornichons", - "poivron": "Poivron", - "champignon": "Champignon", - "cepes": "Cèpes", - "aubergine": "Aubergine", - "brocoli": "Brocoli", - "chou_fleur": "Chou-fleur", - "chou_blanc": "Chou blanc", - "chou_rouge": "Chou rouge", - "chou_de_bruxelles": "Chou de Bruxelles", - "epinard": "Épinard", - "blette": "Blette", - "salade": "Salade", - "roquette": "Roquette", - "cresson": "Cresson", - "poireau": "Poireau", - "celeri": "Céleri", - "radis": "Radis", - "betterave": "Betterave", - "navet": "Navet", - "panais": "Panais", - "haricot_vert": "Haricot vert", - "petit_pois": "Petit pois", - "mais": "Maïs", - "artichaut": "Artichaut", - "fenouil": "Fenouil", + "tomato": "Tomate", + "onion": "Oignon", + "shallot": "Échalote", + "garlic": "Ail", + "carrot": "Carotte", + "zucchini": "Courgette", + "cucumber": "Concombre", + "gherkins": "Cornichons", + "bell_pepper": "Poivron", + "mushroom": "Champignon", + "porcini": "Cèpes", + "eggplant": "Aubergine", + "broccoli": "Brocoli", + "cauliflower": "Chou-fleur", + "white_cabbage": "Chou blanc", + "red_cabbage": "Chou rouge", + "brussels_sprouts": "Chou de Bruxelles", + "spinach": "Épinard", + "swiss_chard": "Blette", + "lettuce": "Salade", + "arugula": "Roquette", + "watercress": "Cresson", + "leek": "Poireau", + "celery": "Céleri", + "radish": "Radis", + "beetroot": "Betterave", + "turnip": "Navet", + "parsnip": "Panais", + "green_bean": "Haricot vert", + "pea": "Petit pois", + "corn": "Maïs", + "artichoke": "Artichaut", + "fennel": "Fenouil", "endive": "Endive", - "potiron": "Potiron", - "butternut": "Butternut", - "asperge": "Asperge", - "avocat": "Avocat", - "pomme_de_terre": "Pomme de terre", - "patate_douce": "Patate douce", - "tomates_cerises": "Tomates cerises", - "pak_choi": "Pak-choï", - "germes_de_soja": "Germes de soja", + "pumpkin": "Potiron", + "butternut_squash": "Butternut", + "asparagus": "Asperge", + "avocado": "Avocat", + "potato": "Pomme de terre", + "sweet_potato": "Patate douce", + "cherry_tomato": "Tomates cerises", + "bok_choy": "Pak-choï", + "soybean_sprouts": "Germes de soja", "shiitake": "Shiitake", "daikon": "Daikon", - "piment_vert_frais": "Piment vert frais", - "citron": "Citron", - "citron_vert": "Citron vert", - "pomme": "Pomme", - "poire": "Poire", - "banane": "Banane", + "fresh_green_chili": "Piment vert frais", + "lemon": "Citron", + "lime": "Citron vert", + "apple": "Pomme", + "pear": "Poire", + "banana": "Banane", "orange": "Orange", "clementine": "Clémentine", - "pamplemousse": "Pamplemousse", - "fraise": "Fraise", - "framboise": "Framboise", - "myrtille": "Myrtille", - "mure": "Mûre", - "cerise": "Cerise", - "abricot": "Abricot", - "peche": "Pêche", - "prune": "Prune", - "raisin": "Raisin", + "grapefruit": "Pamplemousse", + "strawberry": "Fraise", + "raspberry": "Framboise", + "blueberry": "Myrtille", + "blackberry": "Mûre", + "cherry": "Cerise", + "apricot": "Abricot", + "peach": "Pêche", + "plum": "Prune", + "grape": "Raisin", "melon": "Melon", - "pasteque": "Pastèque", - "ananas": "Ananas", - "mangue": "Mangue", + "watermelon": "Pastèque", + "pineapple": "Ananas", + "mango": "Mangue", "kiwi": "Kiwi", - "figue": "Figue", - "datte": "Datte", - "litchi": "Litchi", - "grenade": "Grenade", - "rhubarbe": "Rhubarbe", - "coing": "Coing", - "basilic": "Basilic", - "persil": "Persil", - "thym": "Thym", - "romarin": "Romarin", - "laurier": "Laurier", - "ciboulette": "Ciboulette", - "coriandre_fraiche": "Coriandre fraîche", - "menthe": "Menthe", - "origan": "Origan", - "aneth": "Aneth", - "estragon": "Estragon", - "sarriette": "Sarriette", - "marjolaine": "Marjolaine", - "sauge": "Sauge", - "cerfeuil": "Cerfeuil", - "gingembre": "Gingembre", - "citronnelle": "Citronnelle", - "combava": "Combava", - "lapin": "Lapin", - "boeuf_hache": "Bœuf haché", - "steak_de_boeuf": "Steak de bœuf", - "roti_de_boeuf": "Rôti de bœuf", - "escalope_de_veau": "Escalope de veau", - "filet_mignon_de_porc": "Filet mignon de porc", - "cote_de_porc": "Côte de porc", - "agneau": "Agneau", - "gigot_d_agneau": "Gigot d'agneau", - "lardons": "Lardons", + "fig": "Figue", + "date": "Datte", + "lychee": "Litchi", + "pomegranate": "Grenade", + "rhubarb": "Rhubarbe", + "quince": "Coing", + "basil": "Basilic", + "parsley": "Persil", + "thyme": "Thym", + "rosemary": "Romarin", + "bay_leaf": "Laurier", + "chives": "Ciboulette", + "fresh_cilantro": "Coriandre fraîche", + "mint": "Menthe", + "oregano": "Origan", + "dill": "Aneth", + "tarragon": "Estragon", + "savory": "Sarriette", + "marjoram": "Marjolaine", + "sage": "Sauge", + "chervil": "Cerfeuil", + "ginger": "Gingembre", + "lemongrass": "Citronnelle", + "kaffir_lime": "Combava", + "rabbit": "Lapin", + "ground_beef": "Bœuf haché", + "beef_steak": "Steak de bœuf", + "beef_roast": "Rôti de bœuf", + "veal_cutlet": "Escalope de veau", + "pork_tenderloin": "Filet mignon de porc", + "pork_chop": "Côte de porc", + "lamb": "Agneau", + "leg_of_lamb": "Gigot d'agneau", + "bacon_lardons": "Lardons", "bacon": "Bacon", - "jambon_blanc": "Jambon blanc", - "jambon_cru": "Jambon cru", - "saucisse": "Saucisse", + "ham": "Jambon blanc", + "cured_ham": "Jambon cru", + "sausage": "Saucisse", "chorizo": "Chorizo", "merguez": "Merguez", "prosciutto": "Prosciutto", "pancetta": "Pancetta", - "mortadelle": "Mortadelle", + "mortadella": "Mortadelle", "salami": "Salami", - "poulet": "Poulet", - "dinde": "Dinde", - "canard": "Canard", - "magret_de_canard": "Magret de canard", - "saumon": "Saumon", - "thon": "Thon", - "cabillaud": "Cabillaud", - "truite": "Truite", + "chicken": "Poulet", + "turkey": "Dinde", + "duck": "Canard", + "duck_breast": "Magret de canard", + "salmon": "Saumon", + "tuna": "Thon", + "cod": "Cabillaud", + "trout": "Truite", "sardine": "Sardine", - "anchois": "Anchois", - "merlan": "Merlan", + "anchovy": "Anchois", + "whiting": "Merlan", "surimi": "Surimi", - "bar_loup_de_mer": "Bar (loup de mer)", - "dorade": "Dorade", + "sea_bass": "Bar (loup de mer)", + "sea_bream": "Dorade", "sole": "Sole", "turbot": "Turbot", - "merlu": "Merlu", - "colin": "Colin", - "lieu_noir": "Lieu noir", - "eglefin": "Églefin", - "maquereau": "Maquereau", - "hareng": "Hareng", - "rouget": "Rouget", - "raie": "Raie", - "lotte": "Lotte", - "fletan": "Flétan", - "espadon": "Espadon", - "carpe": "Carpe", - "brochet": "Brochet", - "perche": "Perche", + "hake": "Merlu", + "pollock": "Colin", + "saithe": "Lieu noir", + "haddock": "Églefin", + "mackerel": "Maquereau", + "herring": "Hareng", + "red_mullet": "Rouget", + "skate": "Raie", + "monkfish": "Lotte", + "halibut": "Flétan", + "swordfish": "Espadon", + "carp": "Carpe", + "pike": "Brochet", + "perch": "Perche", "tilapia": "Tilapia", - "panga": "Panga", - "saumon_fume": "Saumon fumé", - "poisson_seche": "Poisson séché", - "crevettes": "Crevettes", - "langoustines": "Langoustines", - "homard": "Homard", - "crabe": "Crabe", - "langouste": "Langouste", - "moules": "Moules", - "huitres": "Huîtres", - "saint_jacques": "Saint-Jacques", - "calamar": "Calamar", - "poulpe": "Poulpe", - "palourdes": "Palourdes", - "bulots": "Bulots", - "semoule": "Semoule", + "pangasius": "Panga", + "smoked_salmon": "Saumon fumé", + "dried_fish": "Poisson séché", + "shrimp": "Crevettes", + "langoustine": "Langoustines", + "lobster": "Homard", + "crab": "Crabe", + "spiny_lobster": "Langouste", + "mussels": "Moules", + "oysters": "Huîtres", + "scallops": "Saint-Jacques", + "squid": "Calamar", + "octopus": "Poulpe", + "clams": "Palourdes", + "whelks": "Bulots", + "semolina": "Semoule", "couscous": "Couscous", - "boulgour": "Boulgour", + "bulgur": "Boulgour", "polenta": "Polenta", "quinoa": "Quinoa", - "pates": "Pâtes", - "pates_completes": "Pâtes complètes", - "riz": "Riz", - "riz_basmati": "Riz basmati", - "riz_complet": "Riz complet", - "flocons_d_avoine": "Flocons d'avoine", + "pasta": "Pâtes", + "whole_wheat_pasta": "Pâtes complètes", + "rice": "Riz", + "basmati_rice": "Riz basmati", + "brown_rice": "Riz complet", + "oats": "Flocons d'avoine", "spaghetti": "Spaghetti", "penne": "Penne", - "tagliatelles": "Tagliatelles", - "lasagnes_feuilles": "Lasagnes (feuilles)", + "tagliatelle": "Tagliatelles", + "lasagna_sheets": "Lasagnes (feuilles)", "gnocchi": "Gnocchi", - "riz_arborio": "Riz arborio", - "nouilles_de_riz": "Nouilles de riz", - "nouilles_udon": "Nouilles udon", - "nouilles_soba": "Nouilles soba", - "nouilles_chinoises": "Nouilles chinoises", - "vermicelles_de_riz": "Vermicelles de riz", - "vermicelles_de_soja": "Vermicelles de soja", - "riz_gluant": "Riz gluant", - "riz_a_sushi": "Riz à sushi", - "riz_jasmin": "Riz jasmin", - "lentilles_vertes": "Lentilles vertes", - "lentilles_corail": "Lentilles corail", - "pois_chiches": "Pois chiches", - "haricots_blancs": "Haricots blancs", - "haricots_rouges": "Haricots rouges", - "haricots_noirs": "Haricots noirs", - "pois_casses": "Pois cassés", - "feves": "Fèves", + "arborio_rice": "Riz arborio", + "rice_noodles": "Nouilles de riz", + "udon_noodles": "Nouilles udon", + "soba_noodles": "Nouilles soba", + "chinese_noodles": "Nouilles chinoises", + "rice_vermicelli": "Vermicelles de riz", + "soy_vermicelli": "Vermicelles de soja", + "sticky_rice": "Riz gluant", + "sushi_rice": "Riz à sushi", + "jasmine_rice": "Riz jasmin", + "green_lentils": "Lentilles vertes", + "red_lentils": "Lentilles corail", + "chickpeas": "Pois chiches", + "white_beans": "Haricots blancs", + "kidney_beans": "Haricots rouges", + "black_beans": "Haricots noirs", + "split_peas": "Pois cassés", + "fava_beans": "Fèves", "edamame": "Edamame", - "haricots_pinto": "Haricots pinto", - "cacahuetes": "Cacahuètes", - "amandes": "Amandes", - "noix": "Noix", - "noisettes": "Noisettes", - "noix_de_cajou": "Noix de cajou", - "pistaches": "Pistaches", - "noix_de_pecan": "Noix de pécan", - "poudre_d_amande": "Poudre d'amande", - "pignons_de_pin": "Pignons de pin", - "graines_de_tournesol": "Graines de tournesol", - "graines_de_courge": "Graines de courge", - "noix_de_coco_rapee": "Noix de coco râpée", - "raisins_secs": "Raisins secs", - "pruneaux": "Pruneaux", - "abricots_secs": "Abricots secs", - "graines_de_sesame": "Graines de sésame", - "champignons_noirs": "Champignons noirs", - "algue_nori": "Algue nori", - "algue_wakame": "Algue wakamé", - "algue_kombu": "Algue kombu", - "pousses_de_bambou": "Pousses de bambou", - "chataignes_d_eau": "Châtaignes d'eau", - "pain": "Pain", - "pain_de_mie": "Pain de mie", - "pain_complet": "Pain complet", + "pinto_beans": "Haricots pinto", + "peanuts_shelled": "Cacahuètes", + "almonds": "Amandes", + "walnuts": "Noix", + "hazelnuts": "Noisettes", + "cashews": "Noix de cajou", + "pistachios": "Pistaches", + "pecans": "Noix de pécan", + "almond_powder": "Poudre d'amande", + "pine_nuts": "Pignons de pin", + "sunflower_seeds": "Graines de tournesol", + "pumpkin_seeds": "Graines de courge", + "shredded_coconut": "Noix de coco râpée", + "raisins": "Raisins secs", + "prunes": "Pruneaux", + "dried_apricots": "Abricots secs", + "sesame_seeds": "Graines de sésame", + "black_mushrooms": "Champignons noirs", + "nori_seaweed": "Algue nori", + "wakame_seaweed": "Algue wakamé", + "kombu_seaweed": "Algue kombu", + "bamboo_shoots": "Pousses de bambou", + "water_chestnuts": "Châtaignes d'eau", + "bread": "Pain", + "sandwich_bread": "Pain de mie", + "whole_wheat_bread": "Pain complet", "baguette": "Baguette", - "pain_de_seigle": "Pain de seigle", - "chapelure": "Chapelure", - "pain_a_burger": "Pain à burger", - "pain_brioche": "Pain brioché", - "pain_a_hot_dog": "Pain à hot-dog", - "pain_pita": "Pain pita", - "pain_bagel": "Pain bagel", + "rye_bread": "Pain de seigle", + "breadcrumbs": "Chapelure", + "burger_bun": "Pain à burger", + "brioche_bun": "Pain brioché", + "hot_dog_bun": "Pain à hot-dog", + "pita_bread": "Pain pita", + "bagel": "Pain bagel", "naan": "Naan", - "pain_wrap": "Pain wrap", - "pain_viennois": "Pain viennois", - "pain_de_campagne": "Pain de campagne", - "pain_aux_cereales": "Pain aux céréales", - "petit_pain": "Petit pain", - "pain_suedois": "Pain suédois", - "pain_sans_gluten": "Pain sans gluten", - "biscotte": "Biscotte", + "wrap_bread": "Pain wrap", + "viennese_bread": "Pain viennois", + "country_bread": "Pain de campagne", + "multigrain_bread": "Pain aux céréales", + "bread_roll": "Petit pain", + "swedish_bread": "Pain suédois", + "gluten_free_bread": "Pain sans gluten", + "rusk": "Biscotte", "croutons": "Croûtons", "focaccia": "Focaccia", "ciabatta": "Ciabatta", - "tortilla_de_mais": "Tortilla de maïs", - "tortilla_de_ble": "Tortilla de blé", - "pate_feuilletee": "Pâte feuilletée", - "pate_brisee": "Pâte brisée", - "pate_a_pizza": "Pâte à pizza", - "pate_a_tarte_sablee": "Pâte à tarte sablée", - "lait": "Lait", - "beurre": "Beurre", + "corn_tortilla": "Tortilla de maïs", + "wheat_tortilla": "Tortilla de blé", + "puff_pastry": "Pâte feuilletée", + "shortcrust_pastry": "Pâte brisée", + "pizza_dough": "Pâte à pizza", + "sweet_shortcrust_pastry": "Pâte à tarte sablée", + "milk": "Lait", + "butter": "Beurre", "creme_fraiche": "Crème fraîche", - "creme_liquide": "Crème liquide", - "fromage": "Fromage", + "liquid_cream": "Crème liquide", + "cheese": "Fromage", "emmental": "Emmental", "gruyere": "Gruyère", "parmesan": "Parmesan", "mozzarella": "Mozzarella", - "chevre_fromage": "Chèvre (fromage)", + "goat_cheese": "Chèvre (fromage)", "feta": "Feta", "comte": "Comté", "fromage_blanc": "Fromage blanc", "mascarpone": "Mascarpone", - "yaourt": "Yaourt", + "yogurt": "Yaourt", "burrata": "Burrata", "ricotta": "Ricotta", "pecorino": "Pecorino", "gorgonzola": "Gorgonzola", "cheddar": "Cheddar", - "oeuf": "Œuf", - "lait_de_coco": "Lait de coco", - "creme_de_coco": "Crème de coco", - "lait_d_amande": "Lait d'amande", - "lait_d_avoine": "Lait d'avoine", + "egg": "Œuf", + "coconut_milk": "Lait de coco", + "coconut_cream": "Crème de coco", + "almond_milk": "Lait d'amande", + "oat_milk": "Lait d'avoine", "tofu": "Tofu", - "tofu_soyeux": "Tofu soyeux", + "silken_tofu": "Tofu soyeux", "herbes_de_provence": "Herbes de Provence", - "poivre_noir": "Poivre noir", + "black_pepper": "Poivre noir", "paprika": "Paprika", - "piment_d_espelette": "Piment d'Espelette", - "piment_de_cayenne": "Piment de Cayenne", + "espelette_pepper": "Piment d'Espelette", + "cayenne_pepper": "Piment de Cayenne", "cumin": "Cumin", - "curry_poudre": "Curry (poudre)", - "curcuma": "Curcuma", - "cannelle": "Cannelle", - "muscade": "Muscade", - "safran": "Safran", - "clou_de_girofle": "Clou de girofle", - "vanille_gousse": "Vanille (gousse)", - "poivre_blanc": "Poivre blanc", - "poivre_rose": "Poivre rose", - "poivre_du_sichuan": "Poivre du Sichuan", - "paprika_fume": "Paprika fumé", - "piment_oiseau": "Piment oiseau", - "baies_de_genievre": "Baies de genièvre", - "anis_etoile_badiane": "Anis étoilé (badiane)", - "anis_vert": "Anis vert", - "graines_de_fenouil": "Graines de fenouil", + "curry_powder": "Curry (poudre)", + "turmeric": "Curcuma", + "cinnamon": "Cannelle", + "nutmeg": "Muscade", + "saffron": "Safran", + "clove": "Clou de girofle", + "vanilla_bean": "Vanille (gousse)", + "white_pepper": "Poivre blanc", + "pink_pepper": "Poivre rose", + "sichuan_pepper": "Poivre du Sichuan", + "smoked_paprika": "Paprika fumé", + "bird_eye_chili": "Piment oiseau", + "juniper_berries": "Baies de genièvre", + "star_anise": "Anis étoilé (badiane)", + "green_anise": "Anis vert", + "fennel_seeds": "Graines de fenouil", "sumac": "Sumac", - "nigelle": "Nigelle", - "quatre_epices": "Quatre épices", - "colombo_poudre": "Colombo (poudre)", + "nigella": "Nigelle", + "allspice": "Quatre épices", + "colombo_powder": "Colombo (poudre)", "baharat": "Baharat", - "raifort": "Raifort", - "sel_aux_herbes": "Sel aux herbes", - "sel_de_celeri": "Sel de céleri", + "horseradish": "Raifort", + "herb_salt": "Sel aux herbes", + "celery_salt": "Sel de céleri", "fleur_de_sel": "Fleur de sel", - "sel": "Sel", - "cinq_epices": "Cinq épices", + "salt": "Sel", + "five_spice": "Cinq épices", "garam_masala": "Garam masala", - "graines_de_coriandre": "Graines de coriandre", - "cardamome": "Cardamome", - "fenugrec": "Fenugrec", - "piment_jalapeno": "Piment jalapeño", - "piment_chipotle": "Piment chipotle", - "piment_poblano": "Piment poblano", - "piment_habanero": "Piment habanero", + "coriander_seeds": "Graines de coriandre", + "cardamom": "Cardamome", + "fenugreek": "Fenugrec", + "jalapeno": "Piment jalapeño", + "chipotle": "Piment chipotle", + "poblano_pepper": "Piment poblano", + "habanero": "Piment habanero", "ras_el_hanout": "Ras el hanout", - "za_atar": "Za'atar", - "sauce_soja": "Sauce soja", - "moutarde": "Moutarde", + "zaatar": "Za'atar", + "soy_sauce": "Sauce soja", + "mustard": "Moutarde", "mayonnaise": "Mayonnaise", "ketchup": "Ketchup", "tabasco": "Tabasco", - "sauce_worcestershire": "Sauce Worcestershire", - "sauce_nuoc_mam": "Sauce nuoc-mâm", + "worcestershire_sauce": "Sauce Worcestershire", + "fish_sauce": "Sauce nuoc-mâm", "wasabi": "Wasabi", "harissa": "Harissa", - "pate_de_curry": "Pâte de curry", - "beurre_de_cacahuete": "Beurre de cacahuète", - "moutarde_de_dijon": "Moutarde de Dijon", - "moutarde_a_l_ancienne": "Moutarde à l'ancienne", - "sauce_barbecue": "Sauce barbecue", - "sauce_tartare": "Sauce tartare", - "sauce_cocktail": "Sauce cocktail", - "sauce_bearnaise": "Sauce béarnaise", - "sauce_hollandaise": "Sauce hollandaise", - "sauce_bechamel": "Sauce béchamel", - "sauce_teriyaki": "Sauce teriyaki", - "sauce_ponzu": "Sauce ponzu", + "curry_paste": "Pâte de curry", + "peanut_butter": "Beurre de cacahuète", + "dijon_mustard": "Moutarde de Dijon", + "wholegrain_mustard": "Moutarde à l'ancienne", + "barbecue_sauce": "Sauce barbecue", + "tartar_sauce": "Sauce tartare", + "cocktail_sauce": "Sauce cocktail", + "bearnaise_sauce": "Sauce béarnaise", + "hollandaise_sauce": "Sauce hollandaise", + "bechamel_sauce": "Sauce béchamel", + "teriyaki_sauce": "Sauce teriyaki", + "ponzu_sauce": "Sauce ponzu", "chimichurri": "Chimichurri", - "pesto_rouge_tomates_sechees": "Pesto rouge (tomates séchées)", + "red_pesto": "Pesto rouge (tomates séchées)", "pesto": "Pesto", - "sauce_huitre": "Sauce huître", - "sauce_hoisin": "Sauce hoisin", - "sauce_sriracha": "Sauce sriracha", - "sauce_sweet_chili": "Sauce sweet chili", + "oyster_sauce": "Sauce huître", + "hoisin_sauce": "Sauce hoisin", + "sriracha": "Sauce sriracha", + "sweet_chili_sauce": "Sauce sweet chili", "miso": "Miso", - "pate_de_crevettes": "Pâte de crevettes", - "pate_de_curry_rouge_thai": "Pâte de curry rouge (thaï)", - "pate_de_curry_vert_thai": "Pâte de curry vert (thaï)", + "shrimp_paste": "Pâte de crevettes", + "red_curry_paste": "Pâte de curry rouge (thaï)", + "green_curry_paste": "Pâte de curry vert (thaï)", "tahini": "Tahini", - "huile_d_olive": "Huile d'olive", - "huile_de_tournesol": "Huile de tournesol", - "huile_de_colza": "Huile de colza", - "huile_de_coco": "Huile de coco", - "huile_de_sesame": "Huile de sésame", - "vinaigre_de_cidre": "Vinaigre de cidre", - "vinaigre_blanc": "Vinaigre blanc", - "vinaigre_balsamique": "Vinaigre balsamique", - "capres": "Câpres", + "olive_oil": "Huile d'olive", + "sunflower_oil": "Huile de tournesol", + "rapeseed_oil": "Huile de colza", + "coconut_oil": "Huile de coco", + "sesame_oil": "Huile de sésame", + "cider_vinegar": "Vinaigre de cidre", + "white_vinegar": "Vinaigre blanc", + "balsamic_vinegar": "Vinaigre balsamique", + "capers": "Câpres", "olives": "Olives", - "vin_blanc_cuisine": "Vin blanc (cuisine)", - "vin_rouge_cuisine": "Vin rouge (cuisine)", - "vinaigre_de_vin_rouge": "Vinaigre de vin rouge", - "vinaigre_de_vin_blanc": "Vinaigre de vin blanc", - "vinaigre_de_xeres": "Vinaigre de xérès", - "huile_de_noix": "Huile de noix", - "huile_de_noisette": "Huile de noisette", - "huile_d_arachide": "Huile d'arachide", - "huile_pimentee": "Huile pimentée", - "vinaigre_de_riz": "Vinaigre de riz", + "white_wine": "Vin blanc (cuisine)", + "red_wine": "Vin rouge (cuisine)", + "red_wine_vinegar": "Vinaigre de vin rouge", + "white_wine_vinegar": "Vinaigre de vin blanc", + "sherry_vinegar": "Vinaigre de xérès", + "walnut_oil": "Huile de noix", + "hazelnut_oil": "Huile de noisette", + "peanut_oil": "Huile d'arachide", + "chili_oil": "Huile pimentée", + "rice_vinegar": "Vinaigre de riz", "mirin": "Mirin", - "sake_cuisine": "Saké (cuisine)", - "jus_de_citron": "Jus de citron", - "jus_de_citron_vert": "Jus de citron vert", - "jus_d_orange": "Jus d'orange", - "jus_de_pomme": "Jus de pomme", - "jus_de_raisin": "Jus de raisin", - "jus_de_tomate": "Jus de tomate", - "jus_de_cranberry": "Jus de cranberry", - "cafe": "Café", - "the": "Thé", - "biere_cuisine": "Bière (cuisine)", - "cidre_cuisine": "Cidre (cuisine)", - "champagne_vin_petillant_cuisine": "Champagne / vin pétillant (cuisine)", - "porto_cuisine": "Porto (cuisine)", - "vin_jaune_cuisine": "Vin jaune (cuisine)", + "sake": "Saké (cuisine)", + "lemon_juice": "Jus de citron", + "lime_juice": "Jus de citron vert", + "orange_juice": "Jus d'orange", + "apple_juice": "Jus de pomme", + "grape_juice": "Jus de raisin", + "tomato_juice": "Jus de tomate", + "cranberry_juice": "Jus de cranberry", + "coffee": "Café", + "tea": "Thé", + "beer": "Bière (cuisine)", + "cider": "Cidre (cuisine)", + "champagne": "Champagne / vin pétillant (cuisine)", + "port_wine": "Porto (cuisine)", + "vin_jaune": "Vin jaune (cuisine)", "cognac": "Cognac", - "rhum": "Rhum", + "rum": "Rhum", "whisky": "Whisky", "vodka": "Vodka", - "farine_de_ble": "Farine de blé", - "farine_complete": "Farine complète", - "farine_de_mais": "Farine de maïs", - "farine_de_sarrasin": "Farine de sarrasin", - "farine_de_riz": "Farine de riz", - "bouillon_cube_legumes": "Bouillon cube légumes", - "bouillon_cube_volaille": "Bouillon cube volaille", - "concentre_de_tomate": "Concentré de tomate", - "coulis_de_tomate": "Coulis de tomate", - "tomates_pelees_conserve": "Tomates pelées (conserve)", - "tomates_sechees": "Tomates séchées", - "fond_de_veau": "Fond de veau", - "fond_de_volaille": "Fond de volaille", - "bouillon_cube_boeuf": "Bouillon cube bœuf", - "bouillon_cube_poisson": "Bouillon cube poisson", - "bouillon_de_legumes": "Bouillon de légumes", - "bouillon_de_volaille": "Bouillon de volaille", - "bouillon_de_boeuf": "Bouillon de bœuf", + "wheat_flour": "Farine de blé", + "whole_wheat_flour": "Farine complète", + "corn_flour": "Farine de maïs", + "buckwheat_flour": "Farine de sarrasin", + "rice_flour": "Farine de riz", + "vegetable_stock_cube": "Bouillon cube légumes", + "chicken_stock_cube": "Bouillon cube volaille", + "tomato_paste": "Concentré de tomate", + "tomato_coulis": "Coulis de tomate", + "canned_peeled_tomatoes": "Tomates pelées (conserve)", + "sun_dried_tomatoes": "Tomates séchées", + "veal_stock": "Fond de veau", + "chicken_stock": "Fond de volaille", + "beef_stock_cube": "Bouillon cube bœuf", + "fish_stock_cube": "Bouillon cube poisson", + "vegetable_broth": "Bouillon de légumes", + "chicken_broth": "Bouillon de volaille", + "beef_broth": "Bouillon de bœuf", "court_bouillon": "Court-bouillon", - "dashi_bouillon_japonais": "Dashi (bouillon japonais)", - "bisque_de_crustaces": "Bisque de crustacés", - "farine_de_tapioca": "Farine de tapioca", + "dashi": "Dashi (bouillon japonais)", + "shellfish_bisque": "Bisque de crustacés", + "tapioca_flour": "Farine de tapioca", "masa_harina": "Masa harina", - "eau": "Eau", - "eau_gazeuse": "Eau gazeuse", - "eau_de_fleur_d_oranger": "Eau de fleur d'oranger", - "eau_de_rose": "Eau de rose", - "fumet_de_poisson": "Fumet de poisson", - "levure_boulangere": "Levure boulangère", - "levure_chimique": "Levure chimique", - "maizena": "Maïzena", - "farine_de_lupin": "Farine de lupin", - "gelatine": "Gélatine", - "bicarbonate_de_soude": "Bicarbonate de soude", - "fecule_de_pomme_de_terre": "Fécule de pomme de terre", - "sucre": "Sucre", - "miel": "Miel", - "sirop_d_erable": "Sirop d'érable", - "sucre_roux": "Sucre roux", - "sucre_glace": "Sucre glace", - "cassonade": "Cassonade", - "chocolat_noir": "Chocolat noir", - "chocolat_au_lait": "Chocolat au lait", - "chocolat_blanc": "Chocolat blanc", - "pepites_de_chocolat": "Pépites de chocolat", - "cacao_en_poudre": "Cacao en poudre", - "extrait_de_vanille": "Extrait de vanille", - "sucre_de_palme": "Sucre de palme", - "sirop_de_sucre_de_canne": "Sirop de sucre de canne" + "water": "Eau", + "sparkling_water": "Eau gazeuse", + "orange_blossom_water": "Eau de fleur d'oranger", + "rose_water": "Eau de rose", + "fish_fumet": "Fumet de poisson", + "bakers_yeast": "Levure boulangère", + "baking_powder": "Levure chimique", + "cornstarch": "Maïzena", + "lupin_flour": "Farine de lupin", + "gelatin": "Gélatine", + "baking_soda": "Bicarbonate de soude", + "potato_starch": "Fécule de pomme de terre", + "sugar": "Sucre", + "honey": "Miel", + "maple_syrup": "Sirop d'érable", + "brown_sugar": "Sucre roux", + "powdered_sugar": "Sucre glace", + "demerara_sugar": "Cassonade", + "dark_chocolate": "Chocolat noir", + "milk_chocolate": "Chocolat au lait", + "white_chocolate": "Chocolat blanc", + "chocolate_chips": "Pépites de chocolat", + "cocoa_powder": "Cacao en poudre", + "vanilla_extract": "Extrait de vanille", + "palm_sugar": "Sucre de palme", + "cane_syrup": "Sirop de sucre de canne" } } }