chore(web): session de polish global — version, checkbox, danger zone, icônes (#20)
* chore(web): session de polish global — version, checkbox, danger zone, icônes
- Affiche le numéro de version (package.json, injecté via Vite) en bas de
la sidebar, masqué en mode collapse et en mobile.
- Factorise les checkbox/radio dupliqués (AllergySelect, DietTagSelect,
IngredientPicker, UserPreferencesPage) en composants partagés
CheckboxOption/RadioOption (components/ui/), et inverse le layout pour
que la case soit à gauche du label.
- Teinte la "zone de danger" de suppression de compte en rouge (fond +
bordure), pas seulement le bouton.
- Migre les icônes de navigation générale vers lucide-react (nav-icons.tsx
devient un fichier de ré-export) ; les pictogrammes d'ingrédients métier
restent en SVG custom (pas d'équivalents fins côté lucide).
Vérifié : pnpm build, pnpm lint, pnpm --filter web e2e (43/43), et
vérification visuelle manuelle (sidebar desktop/collapsed/mobile, light/dark).
* feat(web): icônes d'ingrédients depuis foodiconpack.com + page de crédits
- Remplace 19 des 22 pictogrammes génériques d'ingrédients par des icônes
curées du pack gratuit "Common ingredient icons"/"Common Utensils" de
foodiconpack.com (CC BY 4.0) : carotte, pomme, basilic, bœuf, poulet,
saumon, crevette, riz, pois chiches, amandes, lait, cheddar, œufs,
cannelle, miel, huile d'olive, bière, marmite, sucre.
- BREAD/DOUGH/SPROUT restent en SVG custom : pas d'équivalent net dans le
pack (packs "ingrédients"/"ustensiles"/"plats"/"boissons" vérifiés).
Architecture inchangée : `icon` reste un enum de 22 valeurs partagées en
base (pas de migration, pas de mapping par ingrédient — cf. le
commentaire du fichier sur l'historique emoji→enum générique).
- Nouveau wrapper FilledIcon (fill="currentColor", viewBox 2048) à côté du
wrapper Icon existant (stroke) — les deux stylent au même endroit via
CSS, donc le mélange des 19+3 icônes reste visuellement homogène.
- Ajoute /parametres/credits (CreditsPage) créditant foodiconpack.com et
liant la licence CC BY 4.0, requis par la licence des icônes utilisées ;
nouvelle entrée de nav "Crédits" (icône lucide Info).
Vérifié : pnpm build, pnpm lint, pnpm --filter web e2e (43/43), et
vérification visuelle (grille des 22 icônes dans le picker, page crédits).
* feat(web,api): zone dangereuse rouge, préférences élargies, onglet favoris par défaut, e2e recettes, catalogue en uid+i18n
- Zone dangereuse (compte) : le bouton "Supprimer mon compte" est rouge.
- Pages préférences/paramétrage : contenu centré et élargi (32rem -> 56rem)
au lieu de coller à gauche sur un écran large.
- Page recettes : l'onglet "Favoris" est sélectionné par défaut.
- Ajout de apps/web/cypress/e2e/recipes.cy.ts (onglets, recherche, sélection
master-detail, favori, suppression, lien nouvelle recette).
- Catalogue de référence (ingrédients/régimes/allergènes) : la colonne
`name` (le libellé français, utilisé comme clé unique) devient `key`, un
slug stable et opaque au sens produit (ex. "vegetarien", "boeuf_hache").
Le libellé lui-même déménage entièrement côté client, dans
apps/web/src/locales/fr/translation.json sous le namespace `catalog.*`,
résolu via `t(\`catalog.ingredients.${key}\`)` etc. — même schéma que
IngredientCategory/IngredientSubcategory. Migration Prisma
(rename + backfill des ~456 lignes déjà seedées), seed/service/tests API
et composants web mis à jour en conséquence.
- apps/api/src/utils/slugify.ts + scripts/generate-catalog-i18n.ts
(regénère le fichier de traduction depuis reference-seed-data.ts).
- 102 tests Mocha + 32 scénarios Cucumber passent contre la base migrée.
Note : cypress run plante dans cet environnement (le processus GPU
Chromium/Electron crash même headless, indépendamment des flags) — les
recipes.cy.ts n'ont pas pu être exécutés ici ; vérifiés par lecture du code
source des composants visés et par un passage manuel dans le navigateur de
prévisualisation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(api): les uids du catalogue sont en anglais, pas des slugs français
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 <noreply@anthropic.com>
* fix(web): crypto.randomUUID plante hors contexte sécurisé, empêchant d'associer un ingrédient
Écran noir + "TypeError: crypto.randomUUID is not a function" au clic sur
une carte d'ingrédient dans le formulaire de recette. crypto.randomUUID()
n'est défini que dans un "contexte sécurisé" (https, ou littéralement le
host "localhost") — il est absent sur une IP locale (test sur un vrai
appareil), dans une WebView Capacitor (l'enrobage mobile prévu pour cette
app), ou en http sur un vrai domaine. RecipeFormPage/StepListEditor s'en
servaient pour générer l'identité React (`key`) de chaque ligne
d'ingrédient/étape en brouillon.
- apps/web/src/lib/client-key.ts : remplace par un générateur qui ne
touche jamais `crypto` — un compteur + Math.random suffit, cette valeur
n'a besoin d'être unique que le temps de la session de rendu, jamais
envoyée au serveur.
- apps/web/cypress/e2e/recipe-form.cy.ts : couvre l'association d'un
ingrédient (recherche, sélection, exclusion du picker une fois
sélectionné, retrait), la création et l'édition d'une recette, et un
test de non-régression dédié qui supprime crypto.randomUUID avant le
chargement de la page (comme le ferait un vrai contexte non sécurisé)
pour vérifier que l'ajout de plusieurs ingrédients/étapes ne plante
plus.
Vérifié en direct dans le navigateur de prévisualisation en supprimant
crypto.randomUUID à la main (reproduit le crash), puis en confirmant que
l'ajout d'ingrédient fonctionne à nouveau après le correctif. cypress run
ne peut toujours pas s'exécuter dans cet environnement (voir le commit
précédent) — non exécutés avec Cypress lui-même, mais vérifiés par
lecture des sélecteurs réels et rejoués à la main dans le navigateur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(ci): corrige les specs Cypress cassées par le refactor uid+i18n, applique biome
- onboarding.cy.ts / preferences.cy.ts / recipes.cy.ts mockaient encore
GET /reference/diets|allergies avec l'ancienne forme {id, name}. Depuis
les deux derniers commits l'API renvoie {id, key} (uid anglais) et le
composant résout le libellé via i18n (t(`catalog.diets.${key}`)) — avec
key manquant, ça affichait littéralement "catalog.diets.undefined" au
lieu de "Végétarien"/"Omnivore"/etc., faisant échouer cy.select()/
cy.contains() dans ces 3 specs. Corrigé pour mocker {key: "vegetarian"},
{key: "peanuts"}, etc.
- recipes.cy.ts : le test "shows a not-found message" utilisait le
mauvais code d'erreur (4041 au lieu de ErrorCode.RECIPE_NOT_FOUND =
4045), donc RecipeDetailPanel tombait dans son état d'erreur générique
au lieu du message "Cette recette n'existe pas." — bug dans mon propre
test, sans rapport avec le refactor.
- pnpm lint (biome) : les fichiers touchés par le refactor précédent
avaient quelques soucis de formatage/tri d'imports (des sed multi-
fichiers, pas d'édition via l'outil habituel) — corrigés par
`biome check --write`.
Vérifié : ces 3 specs + recipe-form.cy.ts passent maintenant dans le job
CI GitHub Actions (Linux, Cypress s'y exécute réellement — contrairement
à cet environnement Windows sandboxé, voir les commits précédents) ; 102
tests Mocha + 32 scénarios Cucumber toujours au vert en local.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c38097f522
commit
c34eaa89d0
53 changed files with 3221 additions and 454 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -149,4 +149,4 @@ Projet batch cooking.pdf
|
|||
tmp-mockups/
|
||||
|
||||
# IA
|
||||
.claude/
|
||||
.claude/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { Then, When } from "@cucumber/cucumber";
|
||||
import { getEnglishKey } from "../../src/db/catalog-en-keys.js";
|
||||
import { prisma } from "../../src/db/prisma.js";
|
||||
import type { CustomWorld } from "../support/world.js";
|
||||
|
||||
|
|
@ -11,25 +12,32 @@ function splitNames(names: string): string[] {
|
|||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/** Resolves allergen names (Category.name) to their Allergy id — see reference.service.ts for why the name lives on Category, not Allergy. */
|
||||
/**
|
||||
* Resolves allergen names (as written in a `.feature` file, e.g.
|
||||
* "Arachides") to their Allergy id — scenarios still name allergens by their
|
||||
* French label for readability, so this slugifies before matching against
|
||||
* `Category.key` (see reference.service.ts for why the key lives on
|
||||
* Category, not Allergy).
|
||||
*/
|
||||
async function allergyIdsFor(names: string[]): Promise<number[]> {
|
||||
const allergies = await prisma.allergy.findMany({ include: { category: true } });
|
||||
return names.map((name) => {
|
||||
const match = allergies.find((allergy) => allergy.category.name === 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;
|
||||
});
|
||||
}
|
||||
|
||||
When("I set my regime to {string}", async function (this: CustomWorld, dietName: string) {
|
||||
const diet = await prisma.diet.findFirstOrThrow({ where: { name: 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: { name: dietName } });
|
||||
const diet = await prisma.diet.findFirstOrThrow({ where: { key: getEnglishKey(dietName) } });
|
||||
assert.equal(this.response.body.dietId, diet.id);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { Given, Then, When } from "@cucumber/cucumber";
|
||||
import { getEnglishKey } from "../../src/db/catalog-en-keys.js";
|
||||
import { prisma } from "../../src/db/prisma.js";
|
||||
import { TEST_REFERENCE_DATE } from "../../test-support/reference-date.js";
|
||||
import type { CustomWorld } from "../support/world.js";
|
||||
|
||||
/** Resolves a reference ingredient by its seeded name — every scenario below names an ingredient by its `reference-seed-data.ts` name, never a raw id. */
|
||||
/**
|
||||
* Resolves a reference ingredient by its seeded French name — every
|
||||
* scenario below names an ingredient by its `reference-seed-data.ts` name,
|
||||
* never a raw id or its slug `key` directly, so this slugifies before
|
||||
* matching.
|
||||
*/
|
||||
async function findIngredientId(name: string): Promise<number> {
|
||||
const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { name } });
|
||||
const ingredient = await prisma.ingredient.findFirstOrThrow({
|
||||
where: { key: getEnglishKey(name) },
|
||||
});
|
||||
return ingredient.id;
|
||||
}
|
||||
|
||||
|
|
@ -55,10 +63,11 @@ Then(
|
|||
"the created recipe should have ingredient {string} and step {string}",
|
||||
function (this: CustomWorld, ingredientName: string, step: string) {
|
||||
const body = this.response.body as {
|
||||
ingredients: Array<{ ingredient: { name: string } }>;
|
||||
ingredients: Array<{ ingredient: { key: string } }>;
|
||||
steps: Array<{ description: string }>;
|
||||
};
|
||||
assert.ok(body.ingredients.some((line) => line.ingredient.name === ingredientName));
|
||||
const expectedKey = getEnglishKey(ingredientName);
|
||||
assert.ok(body.ingredients.some((line) => line.ingredient.key === expectedKey));
|
||||
assert.ok(body.steps.some((s) => s.description === step));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { Then } from "@cucumber/cucumber";
|
||||
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
|
||||
// readability — the response itself carries only the slug `key` (see
|
||||
// reference.service.ts), so this slugifies the expected label before
|
||||
// comparing.
|
||||
Then(
|
||||
"the reference list response should include {string}",
|
||||
function (this: CustomWorld, name: string) {
|
||||
const names = (this.response.body as Array<{ name: string }>).map((item) => item.name);
|
||||
assert.ok(names.includes(name), `expected ${JSON.stringify(names)} to include "${name}"`);
|
||||
const keys = (this.response.body as Array<{ key: string }>).map((item) => item.key);
|
||||
const expectedKey = getEnglishKey(name);
|
||||
assert.ok(
|
||||
keys.includes(expectedKey),
|
||||
`expected ${JSON.stringify(keys)} to include "${expectedKey}"`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,480 @@
|
|||
-- Renames the reference-data label column to a stable slug key on Diet,
|
||||
-- Category (allergens), and Ingredient — see `utils/slugify.ts` and
|
||||
-- `scripts/generate-catalog-i18n.ts`. The display label these columns used
|
||||
-- to hold moves to `apps/web`'s `locales/fr/translation.json` (`catalog.*`
|
||||
-- namespace) instead.
|
||||
|
||||
ALTER TABLE "diet" RENAME COLUMN "name" TO "key";
|
||||
ALTER INDEX "diet_name_key" RENAME TO "diet_key_key";
|
||||
|
||||
ALTER TABLE "category" RENAME COLUMN "name" TO "key";
|
||||
ALTER INDEX "category_name_key" RENAME TO "category_key_key";
|
||||
|
||||
ALTER TABLE "ingredients" RENAME COLUMN "name" TO "key";
|
||||
ALTER INDEX "ingredients_name_key" RENAME TO "ingredients_key_key";
|
||||
|
||||
-- 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.
|
||||
|
||||
UPDATE "diet" SET "key" = 'omnivore' WHERE "key" = 'Omnivore';
|
||||
UPDATE "diet" SET "key" = 'vegetarien' WHERE "key" = 'Végétarien';
|
||||
UPDATE "diet" SET "key" = 'vegan' WHERE "key" = 'Végan';
|
||||
UPDATE "diet" SET "key" = 'pescetarien' WHERE "key" = 'Pescétarien';
|
||||
UPDATE "diet" SET "key" = 'sans_gluten' WHERE "key" = 'Sans gluten';
|
||||
|
||||
UPDATE "category" SET "key" = 'gluten' WHERE "key" = 'Gluten';
|
||||
UPDATE "category" SET "key" = 'crustaces' WHERE "key" = 'Crustacés';
|
||||
UPDATE "category" SET "key" = 'oeufs' WHERE "key" = 'Œufs';
|
||||
UPDATE "category" SET "key" = 'poissons' WHERE "key" = 'Poissons';
|
||||
UPDATE "category" SET "key" = 'arachides' WHERE "key" = 'Arachides';
|
||||
UPDATE "category" SET "key" = 'soja' WHERE "key" = 'Soja';
|
||||
UPDATE "category" SET "key" = 'lait' WHERE "key" = 'Lait';
|
||||
UPDATE "category" SET "key" = 'fruits_a_coque' WHERE "key" = 'Fruits à coque';
|
||||
UPDATE "category" SET "key" = 'celeri' WHERE "key" = 'Céleri';
|
||||
UPDATE "category" SET "key" = 'moutarde' WHERE "key" = 'Moutarde';
|
||||
UPDATE "category" SET "key" = 'graines_de_sesame' WHERE "key" = 'Graines de sésame';
|
||||
UPDATE "category" SET "key" = 'sulfites' WHERE "key" = 'Sulfites';
|
||||
UPDATE "category" SET "key" = 'lupin' WHERE "key" = 'Lupin';
|
||||
UPDATE "category" SET "key" = 'mollusques' WHERE "key" = 'Mollusques';
|
||||
|
||||
UPDATE "ingredients" SET "key" = 'tomate' WHERE "key" = 'Tomate';
|
||||
UPDATE "ingredients" SET "key" = 'oignon' WHERE "key" = 'Oignon';
|
||||
UPDATE "ingredients" SET "key" = 'echalote' WHERE "key" = 'Échalote';
|
||||
UPDATE "ingredients" SET "key" = 'ail' WHERE "key" = 'Ail';
|
||||
UPDATE "ingredients" SET "key" = 'carotte' WHERE "key" = 'Carotte';
|
||||
UPDATE "ingredients" SET "key" = 'courgette' WHERE "key" = 'Courgette';
|
||||
UPDATE "ingredients" SET "key" = 'concombre' WHERE "key" = 'Concombre';
|
||||
UPDATE "ingredients" SET "key" = 'cornichons' WHERE "key" = 'Cornichons';
|
||||
UPDATE "ingredients" SET "key" = 'poivron' WHERE "key" = 'Poivron';
|
||||
UPDATE "ingredients" SET "key" = 'champignon' WHERE "key" = 'Champignon';
|
||||
UPDATE "ingredients" SET "key" = 'cepes' WHERE "key" = 'Cèpes';
|
||||
UPDATE "ingredients" SET "key" = 'aubergine' WHERE "key" = 'Aubergine';
|
||||
UPDATE "ingredients" SET "key" = 'brocoli' WHERE "key" = 'Brocoli';
|
||||
UPDATE "ingredients" SET "key" = 'chou_fleur' WHERE "key" = 'Chou-fleur';
|
||||
UPDATE "ingredients" SET "key" = 'chou_blanc' WHERE "key" = 'Chou blanc';
|
||||
UPDATE "ingredients" SET "key" = 'chou_rouge' WHERE "key" = 'Chou rouge';
|
||||
UPDATE "ingredients" SET "key" = 'chou_de_bruxelles' WHERE "key" = 'Chou de Bruxelles';
|
||||
UPDATE "ingredients" SET "key" = 'epinard' WHERE "key" = 'Épinard';
|
||||
UPDATE "ingredients" SET "key" = 'blette' WHERE "key" = 'Blette';
|
||||
UPDATE "ingredients" SET "key" = 'salade' WHERE "key" = 'Salade';
|
||||
UPDATE "ingredients" SET "key" = 'roquette' WHERE "key" = 'Roquette';
|
||||
UPDATE "ingredients" SET "key" = 'cresson' WHERE "key" = 'Cresson';
|
||||
UPDATE "ingredients" SET "key" = 'poireau' WHERE "key" = 'Poireau';
|
||||
UPDATE "ingredients" SET "key" = 'celeri' WHERE "key" = 'Céleri';
|
||||
UPDATE "ingredients" SET "key" = 'radis' WHERE "key" = 'Radis';
|
||||
UPDATE "ingredients" SET "key" = 'betterave' WHERE "key" = 'Betterave';
|
||||
UPDATE "ingredients" SET "key" = 'navet' WHERE "key" = 'Navet';
|
||||
UPDATE "ingredients" SET "key" = 'panais' WHERE "key" = 'Panais';
|
||||
UPDATE "ingredients" SET "key" = 'haricot_vert' WHERE "key" = 'Haricot vert';
|
||||
UPDATE "ingredients" SET "key" = 'petit_pois' WHERE "key" = 'Petit pois';
|
||||
UPDATE "ingredients" SET "key" = 'mais' WHERE "key" = 'Maïs';
|
||||
UPDATE "ingredients" SET "key" = 'artichaut' WHERE "key" = 'Artichaut';
|
||||
UPDATE "ingredients" SET "key" = 'fenouil' WHERE "key" = 'Fenouil';
|
||||
UPDATE "ingredients" SET "key" = 'endive' WHERE "key" = 'Endive';
|
||||
UPDATE "ingredients" SET "key" = 'potiron' WHERE "key" = 'Potiron';
|
||||
UPDATE "ingredients" SET "key" = 'butternut' WHERE "key" = 'Butternut';
|
||||
UPDATE "ingredients" SET "key" = 'asperge' WHERE "key" = 'Asperge';
|
||||
UPDATE "ingredients" SET "key" = 'avocat' WHERE "key" = 'Avocat';
|
||||
UPDATE "ingredients" SET "key" = 'pomme_de_terre' WHERE "key" = 'Pomme de terre';
|
||||
UPDATE "ingredients" SET "key" = 'patate_douce' WHERE "key" = 'Patate douce';
|
||||
UPDATE "ingredients" SET "key" = 'tomates_cerises' WHERE "key" = 'Tomates cerises';
|
||||
UPDATE "ingredients" SET "key" = 'pak_choi' WHERE "key" = 'Pak-choï';
|
||||
UPDATE "ingredients" SET "key" = 'germes_de_soja' 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" = 'piment_vert_frais' WHERE "key" = 'Piment vert frais';
|
||||
UPDATE "ingredients" SET "key" = 'citron' WHERE "key" = 'Citron';
|
||||
UPDATE "ingredients" SET "key" = 'citron_vert' WHERE "key" = 'Citron vert';
|
||||
UPDATE "ingredients" SET "key" = 'pomme' WHERE "key" = 'Pomme';
|
||||
UPDATE "ingredients" SET "key" = 'poire' WHERE "key" = 'Poire';
|
||||
UPDATE "ingredients" SET "key" = 'banane' WHERE "key" = 'Banane';
|
||||
UPDATE "ingredients" SET "key" = 'orange' WHERE "key" = 'Orange';
|
||||
UPDATE "ingredients" SET "key" = 'clementine' WHERE "key" = 'Clémentine';
|
||||
UPDATE "ingredients" SET "key" = 'pamplemousse' WHERE "key" = 'Pamplemousse';
|
||||
UPDATE "ingredients" SET "key" = 'fraise' WHERE "key" = 'Fraise';
|
||||
UPDATE "ingredients" SET "key" = 'framboise' WHERE "key" = 'Framboise';
|
||||
UPDATE "ingredients" SET "key" = 'myrtille' WHERE "key" = 'Myrtille';
|
||||
UPDATE "ingredients" SET "key" = 'mure' WHERE "key" = 'Mûre';
|
||||
UPDATE "ingredients" SET "key" = 'cerise' WHERE "key" = 'Cerise';
|
||||
UPDATE "ingredients" SET "key" = 'abricot' WHERE "key" = 'Abricot';
|
||||
UPDATE "ingredients" SET "key" = 'peche' WHERE "key" = 'Pêche';
|
||||
UPDATE "ingredients" SET "key" = 'prune' WHERE "key" = 'Prune';
|
||||
UPDATE "ingredients" SET "key" = 'raisin' WHERE "key" = 'Raisin';
|
||||
UPDATE "ingredients" SET "key" = 'melon' WHERE "key" = 'Melon';
|
||||
UPDATE "ingredients" SET "key" = 'pasteque' WHERE "key" = 'Pastèque';
|
||||
UPDATE "ingredients" SET "key" = 'ananas' WHERE "key" = 'Ananas';
|
||||
UPDATE "ingredients" SET "key" = 'mangue' WHERE "key" = 'Mangue';
|
||||
UPDATE "ingredients" SET "key" = 'kiwi' WHERE "key" = 'Kiwi';
|
||||
UPDATE "ingredients" SET "key" = 'figue' WHERE "key" = 'Figue';
|
||||
UPDATE "ingredients" SET "key" = 'datte' WHERE "key" = 'Datte';
|
||||
UPDATE "ingredients" SET "key" = 'litchi' WHERE "key" = 'Litchi';
|
||||
UPDATE "ingredients" SET "key" = 'grenade' WHERE "key" = 'Grenade';
|
||||
UPDATE "ingredients" SET "key" = 'rhubarbe' WHERE "key" = 'Rhubarbe';
|
||||
UPDATE "ingredients" SET "key" = 'coing' WHERE "key" = 'Coing';
|
||||
UPDATE "ingredients" SET "key" = 'basilic' WHERE "key" = 'Basilic';
|
||||
UPDATE "ingredients" SET "key" = 'persil' WHERE "key" = 'Persil';
|
||||
UPDATE "ingredients" SET "key" = 'thym' WHERE "key" = 'Thym';
|
||||
UPDATE "ingredients" SET "key" = 'romarin' WHERE "key" = 'Romarin';
|
||||
UPDATE "ingredients" SET "key" = 'laurier' WHERE "key" = 'Laurier';
|
||||
UPDATE "ingredients" SET "key" = 'ciboulette' WHERE "key" = 'Ciboulette';
|
||||
UPDATE "ingredients" SET "key" = 'coriandre_fraiche' WHERE "key" = 'Coriandre fraîche';
|
||||
UPDATE "ingredients" SET "key" = 'menthe' WHERE "key" = 'Menthe';
|
||||
UPDATE "ingredients" SET "key" = 'origan' WHERE "key" = 'Origan';
|
||||
UPDATE "ingredients" SET "key" = 'aneth' WHERE "key" = 'Aneth';
|
||||
UPDATE "ingredients" SET "key" = 'estragon' WHERE "key" = 'Estragon';
|
||||
UPDATE "ingredients" SET "key" = 'sarriette' WHERE "key" = 'Sarriette';
|
||||
UPDATE "ingredients" SET "key" = 'marjolaine' WHERE "key" = 'Marjolaine';
|
||||
UPDATE "ingredients" SET "key" = 'sauge' WHERE "key" = 'Sauge';
|
||||
UPDATE "ingredients" SET "key" = 'cerfeuil' WHERE "key" = 'Cerfeuil';
|
||||
UPDATE "ingredients" SET "key" = 'gingembre' WHERE "key" = 'Gingembre';
|
||||
UPDATE "ingredients" SET "key" = 'citronnelle' WHERE "key" = 'Citronnelle';
|
||||
UPDATE "ingredients" SET "key" = 'combava' WHERE "key" = 'Combava';
|
||||
UPDATE "ingredients" SET "key" = 'lapin' WHERE "key" = 'Lapin';
|
||||
UPDATE "ingredients" SET "key" = 'boeuf_hache' WHERE "key" = 'Bœuf haché';
|
||||
UPDATE "ingredients" SET "key" = 'steak_de_boeuf' WHERE "key" = 'Steak de bœuf';
|
||||
UPDATE "ingredients" SET "key" = 'roti_de_boeuf' WHERE "key" = 'Rôti de bœuf';
|
||||
UPDATE "ingredients" SET "key" = 'escalope_de_veau' WHERE "key" = 'Escalope de veau';
|
||||
UPDATE "ingredients" SET "key" = 'filet_mignon_de_porc' WHERE "key" = 'Filet mignon de porc';
|
||||
UPDATE "ingredients" SET "key" = 'cote_de_porc' WHERE "key" = 'Côte de porc';
|
||||
UPDATE "ingredients" SET "key" = 'agneau' WHERE "key" = 'Agneau';
|
||||
UPDATE "ingredients" SET "key" = 'gigot_d_agneau' WHERE "key" = 'Gigot d''agneau';
|
||||
UPDATE "ingredients" SET "key" = 'lardons' WHERE "key" = 'Lardons';
|
||||
UPDATE "ingredients" SET "key" = 'bacon' WHERE "key" = 'Bacon';
|
||||
UPDATE "ingredients" SET "key" = 'jambon_blanc' WHERE "key" = 'Jambon blanc';
|
||||
UPDATE "ingredients" SET "key" = 'jambon_cru' WHERE "key" = 'Jambon cru';
|
||||
UPDATE "ingredients" SET "key" = 'saucisse' 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" = 'mortadelle' WHERE "key" = 'Mortadelle';
|
||||
UPDATE "ingredients" SET "key" = 'salami' WHERE "key" = 'Salami';
|
||||
UPDATE "ingredients" SET "key" = 'poulet' WHERE "key" = 'Poulet';
|
||||
UPDATE "ingredients" SET "key" = 'dinde' WHERE "key" = 'Dinde';
|
||||
UPDATE "ingredients" SET "key" = 'canard' WHERE "key" = 'Canard';
|
||||
UPDATE "ingredients" SET "key" = 'magret_de_canard' WHERE "key" = 'Magret de canard';
|
||||
UPDATE "ingredients" SET "key" = 'saumon' WHERE "key" = 'Saumon';
|
||||
UPDATE "ingredients" SET "key" = 'thon' WHERE "key" = 'Thon';
|
||||
UPDATE "ingredients" SET "key" = 'cabillaud' WHERE "key" = 'Cabillaud';
|
||||
UPDATE "ingredients" SET "key" = 'truite' WHERE "key" = 'Truite';
|
||||
UPDATE "ingredients" SET "key" = 'sardine' WHERE "key" = 'Sardine';
|
||||
UPDATE "ingredients" SET "key" = 'anchois' WHERE "key" = 'Anchois';
|
||||
UPDATE "ingredients" SET "key" = 'merlan' WHERE "key" = 'Merlan';
|
||||
UPDATE "ingredients" SET "key" = 'surimi' WHERE "key" = 'Surimi';
|
||||
UPDATE "ingredients" SET "key" = 'bar_loup_de_mer' WHERE "key" = 'Bar (loup de mer)';
|
||||
UPDATE "ingredients" SET "key" = 'dorade' WHERE "key" = 'Dorade';
|
||||
UPDATE "ingredients" SET "key" = 'sole' WHERE "key" = 'Sole';
|
||||
UPDATE "ingredients" SET "key" = 'turbot' WHERE "key" = 'Turbot';
|
||||
UPDATE "ingredients" SET "key" = 'merlu' WHERE "key" = 'Merlu';
|
||||
UPDATE "ingredients" SET "key" = 'colin' WHERE "key" = 'Colin';
|
||||
UPDATE "ingredients" SET "key" = 'lieu_noir' WHERE "key" = 'Lieu noir';
|
||||
UPDATE "ingredients" SET "key" = 'eglefin' WHERE "key" = 'Églefin';
|
||||
UPDATE "ingredients" SET "key" = 'maquereau' WHERE "key" = 'Maquereau';
|
||||
UPDATE "ingredients" SET "key" = 'hareng' WHERE "key" = 'Hareng';
|
||||
UPDATE "ingredients" SET "key" = 'rouget' WHERE "key" = 'Rouget';
|
||||
UPDATE "ingredients" SET "key" = 'raie' WHERE "key" = 'Raie';
|
||||
UPDATE "ingredients" SET "key" = 'lotte' WHERE "key" = 'Lotte';
|
||||
UPDATE "ingredients" SET "key" = 'fletan' WHERE "key" = 'Flétan';
|
||||
UPDATE "ingredients" SET "key" = 'espadon' WHERE "key" = 'Espadon';
|
||||
UPDATE "ingredients" SET "key" = 'carpe' WHERE "key" = 'Carpe';
|
||||
UPDATE "ingredients" SET "key" = 'brochet' WHERE "key" = 'Brochet';
|
||||
UPDATE "ingredients" SET "key" = 'perche' WHERE "key" = 'Perche';
|
||||
UPDATE "ingredients" SET "key" = 'tilapia' WHERE "key" = 'Tilapia';
|
||||
UPDATE "ingredients" SET "key" = 'panga' WHERE "key" = 'Panga';
|
||||
UPDATE "ingredients" SET "key" = 'saumon_fume' WHERE "key" = 'Saumon fumé';
|
||||
UPDATE "ingredients" SET "key" = 'poisson_seche' WHERE "key" = 'Poisson séché';
|
||||
UPDATE "ingredients" SET "key" = 'crevettes' WHERE "key" = 'Crevettes';
|
||||
UPDATE "ingredients" SET "key" = 'langoustines' WHERE "key" = 'Langoustines';
|
||||
UPDATE "ingredients" SET "key" = 'homard' WHERE "key" = 'Homard';
|
||||
UPDATE "ingredients" SET "key" = 'crabe' WHERE "key" = 'Crabe';
|
||||
UPDATE "ingredients" SET "key" = 'langouste' WHERE "key" = 'Langouste';
|
||||
UPDATE "ingredients" SET "key" = 'moules' WHERE "key" = 'Moules';
|
||||
UPDATE "ingredients" SET "key" = 'huitres' WHERE "key" = 'Huîtres';
|
||||
UPDATE "ingredients" SET "key" = 'saint_jacques' WHERE "key" = 'Saint-Jacques';
|
||||
UPDATE "ingredients" SET "key" = 'calamar' WHERE "key" = 'Calamar';
|
||||
UPDATE "ingredients" SET "key" = 'poulpe' WHERE "key" = 'Poulpe';
|
||||
UPDATE "ingredients" SET "key" = 'palourdes' WHERE "key" = 'Palourdes';
|
||||
UPDATE "ingredients" SET "key" = 'bulots' WHERE "key" = 'Bulots';
|
||||
UPDATE "ingredients" SET "key" = 'semoule' WHERE "key" = 'Semoule';
|
||||
UPDATE "ingredients" SET "key" = 'couscous' WHERE "key" = 'Couscous';
|
||||
UPDATE "ingredients" SET "key" = 'boulgour' WHERE "key" = 'Boulgour';
|
||||
UPDATE "ingredients" SET "key" = 'polenta' WHERE "key" = 'Polenta';
|
||||
UPDATE "ingredients" SET "key" = 'quinoa' WHERE "key" = 'Quinoa';
|
||||
UPDATE "ingredients" SET "key" = 'pates' WHERE "key" = 'Pâtes';
|
||||
UPDATE "ingredients" SET "key" = 'pates_completes' WHERE "key" = 'Pâtes complètes';
|
||||
UPDATE "ingredients" SET "key" = 'riz' WHERE "key" = 'Riz';
|
||||
UPDATE "ingredients" SET "key" = 'riz_basmati' WHERE "key" = 'Riz basmati';
|
||||
UPDATE "ingredients" SET "key" = 'riz_complet' WHERE "key" = 'Riz complet';
|
||||
UPDATE "ingredients" SET "key" = 'flocons_d_avoine' 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" = 'tagliatelles' WHERE "key" = 'Tagliatelles';
|
||||
UPDATE "ingredients" SET "key" = 'lasagnes_feuilles' WHERE "key" = 'Lasagnes (feuilles)';
|
||||
UPDATE "ingredients" SET "key" = 'gnocchi' WHERE "key" = 'Gnocchi';
|
||||
UPDATE "ingredients" SET "key" = 'riz_arborio' WHERE "key" = 'Riz arborio';
|
||||
UPDATE "ingredients" SET "key" = 'nouilles_de_riz' WHERE "key" = 'Nouilles de riz';
|
||||
UPDATE "ingredients" SET "key" = 'nouilles_udon' WHERE "key" = 'Nouilles udon';
|
||||
UPDATE "ingredients" SET "key" = 'nouilles_soba' WHERE "key" = 'Nouilles soba';
|
||||
UPDATE "ingredients" SET "key" = 'nouilles_chinoises' WHERE "key" = 'Nouilles chinoises';
|
||||
UPDATE "ingredients" SET "key" = 'vermicelles_de_riz' WHERE "key" = 'Vermicelles de riz';
|
||||
UPDATE "ingredients" SET "key" = 'vermicelles_de_soja' WHERE "key" = 'Vermicelles de soja';
|
||||
UPDATE "ingredients" SET "key" = 'riz_gluant' WHERE "key" = 'Riz gluant';
|
||||
UPDATE "ingredients" SET "key" = 'riz_a_sushi' WHERE "key" = 'Riz à sushi';
|
||||
UPDATE "ingredients" SET "key" = 'riz_jasmin' WHERE "key" = 'Riz jasmin';
|
||||
UPDATE "ingredients" SET "key" = 'lentilles_vertes' WHERE "key" = 'Lentilles vertes';
|
||||
UPDATE "ingredients" SET "key" = 'lentilles_corail' WHERE "key" = 'Lentilles corail';
|
||||
UPDATE "ingredients" SET "key" = 'pois_chiches' WHERE "key" = 'Pois chiches';
|
||||
UPDATE "ingredients" SET "key" = 'haricots_blancs' WHERE "key" = 'Haricots blancs';
|
||||
UPDATE "ingredients" SET "key" = 'haricots_rouges' WHERE "key" = 'Haricots rouges';
|
||||
UPDATE "ingredients" SET "key" = 'haricots_noirs' WHERE "key" = 'Haricots noirs';
|
||||
UPDATE "ingredients" SET "key" = 'pois_casses' WHERE "key" = 'Pois cassés';
|
||||
UPDATE "ingredients" SET "key" = 'feves' WHERE "key" = 'Fèves';
|
||||
UPDATE "ingredients" SET "key" = 'edamame' WHERE "key" = 'Edamame';
|
||||
UPDATE "ingredients" SET "key" = 'haricots_pinto' WHERE "key" = 'Haricots pinto';
|
||||
UPDATE "ingredients" SET "key" = 'cacahuetes' WHERE "key" = 'Cacahuètes';
|
||||
UPDATE "ingredients" SET "key" = 'amandes' WHERE "key" = 'Amandes';
|
||||
UPDATE "ingredients" SET "key" = 'noix' WHERE "key" = 'Noix';
|
||||
UPDATE "ingredients" SET "key" = 'noisettes' WHERE "key" = 'Noisettes';
|
||||
UPDATE "ingredients" SET "key" = 'noix_de_cajou' WHERE "key" = 'Noix de cajou';
|
||||
UPDATE "ingredients" SET "key" = 'pistaches' WHERE "key" = 'Pistaches';
|
||||
UPDATE "ingredients" SET "key" = 'noix_de_pecan' WHERE "key" = 'Noix de pécan';
|
||||
UPDATE "ingredients" SET "key" = 'poudre_d_amande' WHERE "key" = 'Poudre d''amande';
|
||||
UPDATE "ingredients" SET "key" = 'pignons_de_pin' WHERE "key" = 'Pignons de pin';
|
||||
UPDATE "ingredients" SET "key" = 'graines_de_tournesol' WHERE "key" = 'Graines de tournesol';
|
||||
UPDATE "ingredients" SET "key" = 'graines_de_courge' WHERE "key" = 'Graines de courge';
|
||||
UPDATE "ingredients" SET "key" = 'noix_de_coco_rapee' WHERE "key" = 'Noix de coco râpée';
|
||||
UPDATE "ingredients" SET "key" = 'raisins_secs' WHERE "key" = 'Raisins secs';
|
||||
UPDATE "ingredients" SET "key" = 'pruneaux' WHERE "key" = 'Pruneaux';
|
||||
UPDATE "ingredients" SET "key" = 'abricots_secs' WHERE "key" = 'Abricots secs';
|
||||
UPDATE "ingredients" SET "key" = 'graines_de_sesame' WHERE "key" = 'Graines de sésame';
|
||||
UPDATE "ingredients" SET "key" = 'champignons_noirs' WHERE "key" = 'Champignons noirs';
|
||||
UPDATE "ingredients" SET "key" = 'algue_nori' WHERE "key" = 'Algue nori';
|
||||
UPDATE "ingredients" SET "key" = 'algue_wakame' WHERE "key" = 'Algue wakamé';
|
||||
UPDATE "ingredients" SET "key" = 'algue_kombu' WHERE "key" = 'Algue kombu';
|
||||
UPDATE "ingredients" SET "key" = 'pousses_de_bambou' WHERE "key" = 'Pousses de bambou';
|
||||
UPDATE "ingredients" SET "key" = 'chataignes_d_eau' WHERE "key" = 'Châtaignes d''eau';
|
||||
UPDATE "ingredients" SET "key" = 'pain' WHERE "key" = 'Pain';
|
||||
UPDATE "ingredients" SET "key" = 'pain_de_mie' WHERE "key" = 'Pain de mie';
|
||||
UPDATE "ingredients" SET "key" = 'pain_complet' WHERE "key" = 'Pain complet';
|
||||
UPDATE "ingredients" SET "key" = 'baguette' WHERE "key" = 'Baguette';
|
||||
UPDATE "ingredients" SET "key" = 'pain_de_seigle' WHERE "key" = 'Pain de seigle';
|
||||
UPDATE "ingredients" SET "key" = 'chapelure' WHERE "key" = 'Chapelure';
|
||||
UPDATE "ingredients" SET "key" = 'pain_a_burger' WHERE "key" = 'Pain à burger';
|
||||
UPDATE "ingredients" SET "key" = 'pain_brioche' WHERE "key" = 'Pain brioché';
|
||||
UPDATE "ingredients" SET "key" = 'pain_a_hot_dog' WHERE "key" = 'Pain à hot-dog';
|
||||
UPDATE "ingredients" SET "key" = 'pain_pita' WHERE "key" = 'Pain pita';
|
||||
UPDATE "ingredients" SET "key" = 'pain_bagel' WHERE "key" = 'Pain bagel';
|
||||
UPDATE "ingredients" SET "key" = 'naan' WHERE "key" = 'Naan';
|
||||
UPDATE "ingredients" SET "key" = 'pain_wrap' WHERE "key" = 'Pain wrap';
|
||||
UPDATE "ingredients" SET "key" = 'pain_viennois' WHERE "key" = 'Pain viennois';
|
||||
UPDATE "ingredients" SET "key" = 'pain_de_campagne' WHERE "key" = 'Pain de campagne';
|
||||
UPDATE "ingredients" SET "key" = 'pain_aux_cereales' WHERE "key" = 'Pain aux céréales';
|
||||
UPDATE "ingredients" SET "key" = 'petit_pain' WHERE "key" = 'Petit pain';
|
||||
UPDATE "ingredients" SET "key" = 'pain_suedois' WHERE "key" = 'Pain suédois';
|
||||
UPDATE "ingredients" SET "key" = 'pain_sans_gluten' WHERE "key" = 'Pain sans gluten';
|
||||
UPDATE "ingredients" SET "key" = 'biscotte' WHERE "key" = 'Biscotte';
|
||||
UPDATE "ingredients" SET "key" = 'croutons' WHERE "key" = 'Croûtons';
|
||||
UPDATE "ingredients" SET "key" = 'focaccia' WHERE "key" = 'Focaccia';
|
||||
UPDATE "ingredients" SET "key" = 'ciabatta' WHERE "key" = 'Ciabatta';
|
||||
UPDATE "ingredients" SET "key" = 'tortilla_de_mais' WHERE "key" = 'Tortilla de maïs';
|
||||
UPDATE "ingredients" SET "key" = 'tortilla_de_ble' WHERE "key" = 'Tortilla de blé';
|
||||
UPDATE "ingredients" SET "key" = 'pate_feuilletee' WHERE "key" = 'Pâte feuilletée';
|
||||
UPDATE "ingredients" SET "key" = 'pate_brisee' WHERE "key" = 'Pâte brisée';
|
||||
UPDATE "ingredients" SET "key" = 'pate_a_pizza' WHERE "key" = 'Pâte à pizza';
|
||||
UPDATE "ingredients" SET "key" = 'pate_a_tarte_sablee' WHERE "key" = 'Pâte à tarte sablée';
|
||||
UPDATE "ingredients" SET "key" = 'lait' WHERE "key" = 'Lait';
|
||||
UPDATE "ingredients" SET "key" = 'beurre' WHERE "key" = 'Beurre';
|
||||
UPDATE "ingredients" SET "key" = 'creme_fraiche' WHERE "key" = 'Crème fraîche';
|
||||
UPDATE "ingredients" SET "key" = 'creme_liquide' WHERE "key" = 'Crème liquide';
|
||||
UPDATE "ingredients" SET "key" = 'fromage' WHERE "key" = 'Fromage';
|
||||
UPDATE "ingredients" SET "key" = 'emmental' WHERE "key" = 'Emmental';
|
||||
UPDATE "ingredients" SET "key" = 'gruyere' WHERE "key" = 'Gruyère';
|
||||
UPDATE "ingredients" SET "key" = 'parmesan' WHERE "key" = 'Parmesan';
|
||||
UPDATE "ingredients" SET "key" = 'mozzarella' WHERE "key" = 'Mozzarella';
|
||||
UPDATE "ingredients" SET "key" = 'chevre_fromage' WHERE "key" = 'Chèvre (fromage)';
|
||||
UPDATE "ingredients" SET "key" = 'feta' WHERE "key" = 'Feta';
|
||||
UPDATE "ingredients" SET "key" = 'comte' WHERE "key" = 'Comté';
|
||||
UPDATE "ingredients" SET "key" = 'fromage_blanc' WHERE "key" = 'Fromage blanc';
|
||||
UPDATE "ingredients" SET "key" = 'mascarpone' WHERE "key" = 'Mascarpone';
|
||||
UPDATE "ingredients" SET "key" = 'yaourt' 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" = 'oeuf' WHERE "key" = 'Œuf';
|
||||
UPDATE "ingredients" SET "key" = 'lait_de_coco' WHERE "key" = 'Lait de coco';
|
||||
UPDATE "ingredients" SET "key" = 'creme_de_coco' WHERE "key" = 'Crème de coco';
|
||||
UPDATE "ingredients" SET "key" = 'lait_d_amande' WHERE "key" = 'Lait d''amande';
|
||||
UPDATE "ingredients" SET "key" = 'lait_d_avoine' WHERE "key" = 'Lait d''avoine';
|
||||
UPDATE "ingredients" SET "key" = 'tofu' WHERE "key" = 'Tofu';
|
||||
UPDATE "ingredients" SET "key" = 'tofu_soyeux' WHERE "key" = 'Tofu soyeux';
|
||||
UPDATE "ingredients" SET "key" = 'herbes_de_provence' WHERE "key" = 'Herbes de Provence';
|
||||
UPDATE "ingredients" SET "key" = 'poivre_noir' WHERE "key" = 'Poivre noir';
|
||||
UPDATE "ingredients" SET "key" = 'paprika' WHERE "key" = 'Paprika';
|
||||
UPDATE "ingredients" SET "key" = 'piment_d_espelette' WHERE "key" = 'Piment d''Espelette';
|
||||
UPDATE "ingredients" SET "key" = 'piment_de_cayenne' WHERE "key" = 'Piment de Cayenne';
|
||||
UPDATE "ingredients" SET "key" = 'cumin' WHERE "key" = 'Cumin';
|
||||
UPDATE "ingredients" SET "key" = 'curry_poudre' WHERE "key" = 'Curry (poudre)';
|
||||
UPDATE "ingredients" SET "key" = 'curcuma' WHERE "key" = 'Curcuma';
|
||||
UPDATE "ingredients" SET "key" = 'cannelle' WHERE "key" = 'Cannelle';
|
||||
UPDATE "ingredients" SET "key" = 'muscade' WHERE "key" = 'Muscade';
|
||||
UPDATE "ingredients" SET "key" = 'safran' WHERE "key" = 'Safran';
|
||||
UPDATE "ingredients" SET "key" = 'clou_de_girofle' WHERE "key" = 'Clou de girofle';
|
||||
UPDATE "ingredients" SET "key" = 'vanille_gousse' WHERE "key" = 'Vanille (gousse)';
|
||||
UPDATE "ingredients" SET "key" = 'poivre_blanc' WHERE "key" = 'Poivre blanc';
|
||||
UPDATE "ingredients" SET "key" = 'poivre_rose' WHERE "key" = 'Poivre rose';
|
||||
UPDATE "ingredients" SET "key" = 'poivre_du_sichuan' WHERE "key" = 'Poivre du Sichuan';
|
||||
UPDATE "ingredients" SET "key" = 'paprika_fume' WHERE "key" = 'Paprika fumé';
|
||||
UPDATE "ingredients" SET "key" = 'piment_oiseau' WHERE "key" = 'Piment oiseau';
|
||||
UPDATE "ingredients" SET "key" = 'baies_de_genievre' WHERE "key" = 'Baies de genièvre';
|
||||
UPDATE "ingredients" SET "key" = 'anis_etoile_badiane' WHERE "key" = 'Anis étoilé (badiane)';
|
||||
UPDATE "ingredients" SET "key" = 'anis_vert' WHERE "key" = 'Anis vert';
|
||||
UPDATE "ingredients" SET "key" = 'graines_de_fenouil' WHERE "key" = 'Graines de fenouil';
|
||||
UPDATE "ingredients" SET "key" = 'sumac' WHERE "key" = 'Sumac';
|
||||
UPDATE "ingredients" SET "key" = 'nigelle' WHERE "key" = 'Nigelle';
|
||||
UPDATE "ingredients" SET "key" = 'quatre_epices' WHERE "key" = 'Quatre épices';
|
||||
UPDATE "ingredients" SET "key" = 'colombo_poudre' WHERE "key" = 'Colombo (poudre)';
|
||||
UPDATE "ingredients" SET "key" = 'baharat' WHERE "key" = 'Baharat';
|
||||
UPDATE "ingredients" SET "key" = 'raifort' WHERE "key" = 'Raifort';
|
||||
UPDATE "ingredients" SET "key" = 'sel_aux_herbes' WHERE "key" = 'Sel aux herbes';
|
||||
UPDATE "ingredients" SET "key" = 'sel_de_celeri' WHERE "key" = 'Sel de céleri';
|
||||
UPDATE "ingredients" SET "key" = 'fleur_de_sel' WHERE "key" = 'Fleur de sel';
|
||||
UPDATE "ingredients" SET "key" = 'sel' WHERE "key" = 'Sel';
|
||||
UPDATE "ingredients" SET "key" = 'cinq_epices' WHERE "key" = 'Cinq épices';
|
||||
UPDATE "ingredients" SET "key" = 'garam_masala' WHERE "key" = 'Garam masala';
|
||||
UPDATE "ingredients" SET "key" = 'graines_de_coriandre' WHERE "key" = 'Graines de coriandre';
|
||||
UPDATE "ingredients" SET "key" = 'cardamome' WHERE "key" = 'Cardamome';
|
||||
UPDATE "ingredients" SET "key" = 'fenugrec' WHERE "key" = 'Fenugrec';
|
||||
UPDATE "ingredients" SET "key" = 'piment_jalapeno' WHERE "key" = 'Piment jalapeño';
|
||||
UPDATE "ingredients" SET "key" = 'piment_chipotle' WHERE "key" = 'Piment chipotle';
|
||||
UPDATE "ingredients" SET "key" = 'piment_poblano' WHERE "key" = 'Piment poblano';
|
||||
UPDATE "ingredients" SET "key" = 'piment_habanero' WHERE "key" = 'Piment habanero';
|
||||
UPDATE "ingredients" SET "key" = 'ras_el_hanout' WHERE "key" = 'Ras el hanout';
|
||||
UPDATE "ingredients" SET "key" = 'za_atar' WHERE "key" = 'Za''atar';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_soja' WHERE "key" = 'Sauce soja';
|
||||
UPDATE "ingredients" SET "key" = 'moutarde' 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" = 'sauce_worcestershire' WHERE "key" = 'Sauce Worcestershire';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_nuoc_mam' WHERE "key" = 'Sauce nuoc-mâm';
|
||||
UPDATE "ingredients" SET "key" = 'wasabi' WHERE "key" = 'Wasabi';
|
||||
UPDATE "ingredients" SET "key" = 'harissa' WHERE "key" = 'Harissa';
|
||||
UPDATE "ingredients" SET "key" = 'pate_de_curry' WHERE "key" = 'Pâte de curry';
|
||||
UPDATE "ingredients" SET "key" = 'beurre_de_cacahuete' WHERE "key" = 'Beurre de cacahuète';
|
||||
UPDATE "ingredients" SET "key" = 'moutarde_de_dijon' WHERE "key" = 'Moutarde de Dijon';
|
||||
UPDATE "ingredients" SET "key" = 'moutarde_a_l_ancienne' WHERE "key" = 'Moutarde à l''ancienne';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_barbecue' WHERE "key" = 'Sauce barbecue';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_tartare' WHERE "key" = 'Sauce tartare';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_cocktail' WHERE "key" = 'Sauce cocktail';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_bearnaise' WHERE "key" = 'Sauce béarnaise';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_hollandaise' WHERE "key" = 'Sauce hollandaise';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_bechamel' WHERE "key" = 'Sauce béchamel';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_teriyaki' WHERE "key" = 'Sauce teriyaki';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_ponzu' WHERE "key" = 'Sauce ponzu';
|
||||
UPDATE "ingredients" SET "key" = 'chimichurri' WHERE "key" = 'Chimichurri';
|
||||
UPDATE "ingredients" SET "key" = 'pesto_rouge_tomates_sechees' WHERE "key" = 'Pesto rouge (tomates séchées)';
|
||||
UPDATE "ingredients" SET "key" = 'pesto' WHERE "key" = 'Pesto';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_huitre' WHERE "key" = 'Sauce huître';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_hoisin' WHERE "key" = 'Sauce hoisin';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_sriracha' WHERE "key" = 'Sauce sriracha';
|
||||
UPDATE "ingredients" SET "key" = 'sauce_sweet_chili' WHERE "key" = 'Sauce sweet chili';
|
||||
UPDATE "ingredients" SET "key" = 'miso' WHERE "key" = 'Miso';
|
||||
UPDATE "ingredients" SET "key" = 'pate_de_crevettes' WHERE "key" = 'Pâte de crevettes';
|
||||
UPDATE "ingredients" SET "key" = 'pate_de_curry_rouge_thai' WHERE "key" = 'Pâte de curry rouge (thaï)';
|
||||
UPDATE "ingredients" SET "key" = 'pate_de_curry_vert_thai' WHERE "key" = 'Pâte de curry vert (thaï)';
|
||||
UPDATE "ingredients" SET "key" = 'tahini' WHERE "key" = 'Tahini';
|
||||
UPDATE "ingredients" SET "key" = 'huile_d_olive' WHERE "key" = 'Huile d''olive';
|
||||
UPDATE "ingredients" SET "key" = 'huile_de_tournesol' WHERE "key" = 'Huile de tournesol';
|
||||
UPDATE "ingredients" SET "key" = 'huile_de_colza' WHERE "key" = 'Huile de colza';
|
||||
UPDATE "ingredients" SET "key" = 'huile_de_coco' WHERE "key" = 'Huile de coco';
|
||||
UPDATE "ingredients" SET "key" = 'huile_de_sesame' WHERE "key" = 'Huile de sésame';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_de_cidre' WHERE "key" = 'Vinaigre de cidre';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_blanc' WHERE "key" = 'Vinaigre blanc';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_balsamique' WHERE "key" = 'Vinaigre balsamique';
|
||||
UPDATE "ingredients" SET "key" = 'capres' WHERE "key" = 'Câpres';
|
||||
UPDATE "ingredients" SET "key" = 'olives' WHERE "key" = 'Olives';
|
||||
UPDATE "ingredients" SET "key" = 'vin_blanc_cuisine' WHERE "key" = 'Vin blanc (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'vin_rouge_cuisine' WHERE "key" = 'Vin rouge (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_de_vin_rouge' WHERE "key" = 'Vinaigre de vin rouge';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_de_vin_blanc' WHERE "key" = 'Vinaigre de vin blanc';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_de_xeres' WHERE "key" = 'Vinaigre de xérès';
|
||||
UPDATE "ingredients" SET "key" = 'huile_de_noix' WHERE "key" = 'Huile de noix';
|
||||
UPDATE "ingredients" SET "key" = 'huile_de_noisette' WHERE "key" = 'Huile de noisette';
|
||||
UPDATE "ingredients" SET "key" = 'huile_d_arachide' WHERE "key" = 'Huile d''arachide';
|
||||
UPDATE "ingredients" SET "key" = 'huile_pimentee' WHERE "key" = 'Huile pimentée';
|
||||
UPDATE "ingredients" SET "key" = 'vinaigre_de_riz' WHERE "key" = 'Vinaigre de riz';
|
||||
UPDATE "ingredients" SET "key" = 'mirin' WHERE "key" = 'Mirin';
|
||||
UPDATE "ingredients" SET "key" = 'sake_cuisine' WHERE "key" = 'Saké (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'jus_de_citron' WHERE "key" = 'Jus de citron';
|
||||
UPDATE "ingredients" SET "key" = 'jus_de_citron_vert' WHERE "key" = 'Jus de citron vert';
|
||||
UPDATE "ingredients" SET "key" = 'jus_d_orange' WHERE "key" = 'Jus d''orange';
|
||||
UPDATE "ingredients" SET "key" = 'jus_de_pomme' WHERE "key" = 'Jus de pomme';
|
||||
UPDATE "ingredients" SET "key" = 'jus_de_raisin' WHERE "key" = 'Jus de raisin';
|
||||
UPDATE "ingredients" SET "key" = 'jus_de_tomate' WHERE "key" = 'Jus de tomate';
|
||||
UPDATE "ingredients" SET "key" = 'jus_de_cranberry' WHERE "key" = 'Jus de cranberry';
|
||||
UPDATE "ingredients" SET "key" = 'cafe' WHERE "key" = 'Café';
|
||||
UPDATE "ingredients" SET "key" = 'the' WHERE "key" = 'Thé';
|
||||
UPDATE "ingredients" SET "key" = 'biere_cuisine' WHERE "key" = 'Bière (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'cidre_cuisine' WHERE "key" = 'Cidre (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'champagne_vin_petillant_cuisine' WHERE "key" = 'Champagne / vin pétillant (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'porto_cuisine' WHERE "key" = 'Porto (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'vin_jaune_cuisine' WHERE "key" = 'Vin jaune (cuisine)';
|
||||
UPDATE "ingredients" SET "key" = 'cognac' WHERE "key" = 'Cognac';
|
||||
UPDATE "ingredients" SET "key" = 'rhum' WHERE "key" = 'Rhum';
|
||||
UPDATE "ingredients" SET "key" = 'whisky' WHERE "key" = 'Whisky';
|
||||
UPDATE "ingredients" SET "key" = 'vodka' WHERE "key" = 'Vodka';
|
||||
UPDATE "ingredients" SET "key" = 'farine_de_ble' WHERE "key" = 'Farine de blé';
|
||||
UPDATE "ingredients" SET "key" = 'farine_complete' WHERE "key" = 'Farine complète';
|
||||
UPDATE "ingredients" SET "key" = 'farine_de_mais' WHERE "key" = 'Farine de maïs';
|
||||
UPDATE "ingredients" SET "key" = 'farine_de_sarrasin' WHERE "key" = 'Farine de sarrasin';
|
||||
UPDATE "ingredients" SET "key" = 'farine_de_riz' WHERE "key" = 'Farine de riz';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_cube_legumes' WHERE "key" = 'Bouillon cube légumes';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_cube_volaille' WHERE "key" = 'Bouillon cube volaille';
|
||||
UPDATE "ingredients" SET "key" = 'concentre_de_tomate' WHERE "key" = 'Concentré de tomate';
|
||||
UPDATE "ingredients" SET "key" = 'coulis_de_tomate' WHERE "key" = 'Coulis de tomate';
|
||||
UPDATE "ingredients" SET "key" = 'tomates_pelees_conserve' WHERE "key" = 'Tomates pelées (conserve)';
|
||||
UPDATE "ingredients" SET "key" = 'tomates_sechees' WHERE "key" = 'Tomates séchées';
|
||||
UPDATE "ingredients" SET "key" = 'fond_de_veau' WHERE "key" = 'Fond de veau';
|
||||
UPDATE "ingredients" SET "key" = 'fond_de_volaille' WHERE "key" = 'Fond de volaille';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_cube_boeuf' WHERE "key" = 'Bouillon cube bœuf';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_cube_poisson' WHERE "key" = 'Bouillon cube poisson';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_de_legumes' WHERE "key" = 'Bouillon de légumes';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_de_volaille' WHERE "key" = 'Bouillon de volaille';
|
||||
UPDATE "ingredients" SET "key" = 'bouillon_de_boeuf' WHERE "key" = 'Bouillon de bœuf';
|
||||
UPDATE "ingredients" SET "key" = 'court_bouillon' WHERE "key" = 'Court-bouillon';
|
||||
UPDATE "ingredients" SET "key" = 'dashi_bouillon_japonais' WHERE "key" = 'Dashi (bouillon japonais)';
|
||||
UPDATE "ingredients" SET "key" = 'bisque_de_crustaces' WHERE "key" = 'Bisque de crustacés';
|
||||
UPDATE "ingredients" SET "key" = 'farine_de_tapioca' WHERE "key" = 'Farine de tapioca';
|
||||
UPDATE "ingredients" SET "key" = 'masa_harina' WHERE "key" = 'Masa harina';
|
||||
UPDATE "ingredients" SET "key" = 'eau' WHERE "key" = 'Eau';
|
||||
UPDATE "ingredients" SET "key" = 'eau_gazeuse' WHERE "key" = 'Eau gazeuse';
|
||||
UPDATE "ingredients" SET "key" = 'eau_de_fleur_d_oranger' WHERE "key" = 'Eau de fleur d''oranger';
|
||||
UPDATE "ingredients" SET "key" = 'eau_de_rose' WHERE "key" = 'Eau de rose';
|
||||
UPDATE "ingredients" SET "key" = 'fumet_de_poisson' WHERE "key" = 'Fumet de poisson';
|
||||
UPDATE "ingredients" SET "key" = 'levure_boulangere' WHERE "key" = 'Levure boulangère';
|
||||
UPDATE "ingredients" SET "key" = 'levure_chimique' WHERE "key" = 'Levure chimique';
|
||||
UPDATE "ingredients" SET "key" = 'maizena' WHERE "key" = 'Maïzena';
|
||||
UPDATE "ingredients" SET "key" = 'farine_de_lupin' WHERE "key" = 'Farine de lupin';
|
||||
UPDATE "ingredients" SET "key" = 'gelatine' WHERE "key" = 'Gélatine';
|
||||
UPDATE "ingredients" SET "key" = 'bicarbonate_de_soude' WHERE "key" = 'Bicarbonate de soude';
|
||||
UPDATE "ingredients" SET "key" = 'fecule_de_pomme_de_terre' WHERE "key" = 'Fécule de pomme de terre';
|
||||
UPDATE "ingredients" SET "key" = 'sucre' WHERE "key" = 'Sucre';
|
||||
UPDATE "ingredients" SET "key" = 'miel' WHERE "key" = 'Miel';
|
||||
UPDATE "ingredients" SET "key" = 'sirop_d_erable' WHERE "key" = 'Sirop d''érable';
|
||||
UPDATE "ingredients" SET "key" = 'sucre_roux' WHERE "key" = 'Sucre roux';
|
||||
UPDATE "ingredients" SET "key" = 'sucre_glace' WHERE "key" = 'Sucre glace';
|
||||
UPDATE "ingredients" SET "key" = 'cassonade' WHERE "key" = 'Cassonade';
|
||||
UPDATE "ingredients" SET "key" = 'chocolat_noir' WHERE "key" = 'Chocolat noir';
|
||||
UPDATE "ingredients" SET "key" = 'chocolat_au_lait' WHERE "key" = 'Chocolat au lait';
|
||||
UPDATE "ingredients" SET "key" = 'chocolat_blanc' WHERE "key" = 'Chocolat blanc';
|
||||
UPDATE "ingredients" SET "key" = 'pepites_de_chocolat' WHERE "key" = 'Pépites de chocolat';
|
||||
UPDATE "ingredients" SET "key" = 'cacao_en_poudre' WHERE "key" = 'Cacao en poudre';
|
||||
UPDATE "ingredients" SET "key" = 'extrait_de_vanille' WHERE "key" = 'Extrait de vanille';
|
||||
UPDATE "ingredients" SET "key" = 'sucre_de_palme' WHERE "key" = 'Sucre de palme';
|
||||
UPDATE "ingredients" SET "key" = 'sirop_de_sucre_de_canne' WHERE "key" = 'Sirop de sucre de canne';
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
@ -34,13 +34,17 @@ model House {
|
|||
@@map("house")
|
||||
}
|
||||
|
||||
/// `name` is `@unique` — not in the original spec doc, added so the seed
|
||||
/// script (prisma/seed.ts) can `upsert` by name and stay idempotent/safe to
|
||||
/// `key` is `@unique` — not in the original spec doc, added so the seed
|
||||
/// script (prisma/seed.ts) can `upsert` by key and stay idempotent/safe to
|
||||
/// re-run, and so two reference rows can never silently duplicate the same
|
||||
/// regime.
|
||||
/// regime. A stable slug (e.g. `"vegetarien"`), not the display label —
|
||||
/// the label itself lives in `apps/web`'s `locales/fr/translation.json`
|
||||
/// under `catalog.diets.<key>` (see `reference-seed-data.ts`'s `DIETS` and
|
||||
/// `utils/slugify.ts`), so it can be edited/translated without ever
|
||||
/// touching this column or the rows that reference it by id.
|
||||
model Diet {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
id Int @id @default(autoincrement())
|
||||
key String @unique
|
||||
|
||||
users UserProfile[]
|
||||
recipes RecipeDiet[]
|
||||
|
|
@ -59,11 +63,13 @@ enum AllergenKind {
|
|||
}
|
||||
|
||||
/// Enumeration-style table, meant to grow over time (e.g. allergy nuances).
|
||||
/// `name` is `@unique` for the same reason as `Diet.name` above. `kind` is
|
||||
/// also not in the original spec doc — see {@link AllergenKind}.
|
||||
/// `key` is `@unique` for the same reason as `Diet.key` above — a stable
|
||||
/// slug (`catalog.allergens.<key>` in `apps/web`'s locale file), not the
|
||||
/// display label. `kind` is also not in the original spec doc — see
|
||||
/// {@link AllergenKind}.
|
||||
model Category {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
key String @unique
|
||||
kind AllergenKind @default(ALLERGY)
|
||||
|
||||
allergies Allergy[]
|
||||
|
|
@ -288,9 +294,11 @@ model RecipeDiet {
|
|||
@@map("recipe_diet")
|
||||
}
|
||||
|
||||
/// `name` is `@unique` — not in the original spec doc, added so the seed
|
||||
/// script (reference-seed-data.ts) can `upsert` by name and stay
|
||||
/// idempotent/safe to re-run, same reason as `Diet.name`/`Category.name`.
|
||||
/// `key` is `@unique` — not in the original spec doc, added so the seed
|
||||
/// script (reference-seed-data.ts) can `upsert` by key and stay
|
||||
/// idempotent/safe to re-run, same reason as `Diet.key`/`Category.key`. A
|
||||
/// stable slug (`catalog.ingredients.<key>` in `apps/web`'s locale file),
|
||||
/// not the display label.
|
||||
/// Ingredients are reference data (like Diet/Allergy): seeded, never
|
||||
/// created/edited/deleted through the API.
|
||||
/// Not in the original spec doc — supermarket-aisle grouping ("rayons") so
|
||||
|
|
@ -424,7 +432,7 @@ enum IngredientIcon {
|
|||
|
||||
model Ingredient {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
key String @unique
|
||||
icon IngredientIcon @default(JAR)
|
||||
category IngredientCategory @default(EPICERIE_SECHE)
|
||||
subcategory IngredientSubcategory @default(AUTRES)
|
||||
|
|
|
|||
49
apps/api/scripts/generate-catalog-i18n.ts
Normal file
49
apps/api/scripts/generate-catalog-i18n.ts
Normal file
|
|
@ -0,0 +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`, 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.
|
||||
*
|
||||
* 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";
|
||||
|
||||
const here = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
function toKeyLabelMap(labels: string[]): Record<string, string> {
|
||||
const map: Record<string, string> = {};
|
||||
for (const label of labels) {
|
||||
map[getEnglishKey(label)] = label;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
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}`,
|
||||
);
|
||||
|
||||
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}`);
|
||||
46
apps/api/scripts/validate-catalog-en-keys.ts
Normal file
46
apps/api/scripts/validate-catalog-en-keys.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { getEnglishKey } from "../src/db/catalog-en-keys.js";
|
||||
/**
|
||||
* 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";
|
||||
|
||||
function check(label: string, names: string[]) {
|
||||
const keys = new Map<string, string>();
|
||||
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)),
|
||||
);
|
||||
557
apps/api/src/db/catalog-en-keys.ts
Normal file
557
apps/api/src/db/catalog-en-keys.ts
Normal file
|
|
@ -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<string, string> = {
|
||||
Omnivore: "omnivore",
|
||||
Végétarien: "vegetarian",
|
||||
Végan: "vegan",
|
||||
Pescétarien: "pescatarian",
|
||||
"Sans gluten": "gluten_free",
|
||||
};
|
||||
|
||||
const ALLERGEN_KEYS: Record<string, string> = {
|
||||
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<string, string> = {
|
||||
// --- 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<string, string> = {
|
||||
...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);
|
||||
}
|
||||
|
|
@ -5,10 +5,14 @@ import type {
|
|||
IngredientSubcategory,
|
||||
PrismaClient,
|
||||
} from "@prisma/client";
|
||||
import { getEnglishKey } from "./catalog-en-keys.js";
|
||||
|
||||
// Short, optional-to-pick regime list — `UserProfile.dietId` stays
|
||||
// nullable, this is not meant to be exhaustive.
|
||||
const DIETS = ["Omnivore", "Végétarien", "Végan", "Pescétarien", "Sans gluten"];
|
||||
// nullable, this is not meant to be exhaustive. Exported for
|
||||
// `apps/api/scripts/generate-catalog-i18n.ts` (one-off, regenerates
|
||||
// `apps/web`'s `locales/fr/translation.json` `catalog.diets` section from
|
||||
// this same list).
|
||||
export const DIETS = ["Omnivore", "Végétarien", "Végan", "Pescétarien", "Sans gluten"];
|
||||
|
||||
// The 14 allergens EU Regulation 1169/2011 (Annex II) requires food
|
||||
// businesses to declare — a standard, defensible reference list rather than
|
||||
|
|
@ -17,7 +21,7 @@ const DIETS = ["Omnivore", "Végétarien", "Végan", "Pescétarien", "Sans glute
|
|||
// sensitivity) per the product decision discussed in chat: only Gluten and
|
||||
// Sulfites are commonly-recognized intolerances among the 14; the rest are
|
||||
// true allergens.
|
||||
const ALLERGENS: Array<{ name: string; kind: AllergenKind }> = [
|
||||
export const ALLERGENS: Array<{ name: string; kind: AllergenKind }> = [
|
||||
{ name: "Gluten", kind: "INTOLERANCE" },
|
||||
{ name: "Crustacés", kind: "ALLERGY" },
|
||||
{ name: "Œufs", kind: "ALLERGY" },
|
||||
|
|
@ -92,7 +96,7 @@ interface IngredientSeed {
|
|||
// repeating the same values on hundreds of items; only a group's
|
||||
// exceptions (a fish-based stock inside "bases", a cheese inside "produits
|
||||
// laitiers"…) need a per-item override.
|
||||
const INGREDIENT_GROUPS: Array<{
|
||||
export const INGREDIENT_GROUPS: Array<{
|
||||
category: IngredientCategory;
|
||||
subcategory: IngredientSubcategory;
|
||||
defaultDiets: string[];
|
||||
|
|
@ -879,27 +883,38 @@ const INGREDIENTS: Array<
|
|||
/**
|
||||
* Populates the `Diet`/`Category`/`Allergy` reference tables. Idempotent
|
||||
* (safe to call against a database that already has this data — upserts by
|
||||
* `name`, both `@unique`) — used both by `prisma/seed.ts` (the CLI entry
|
||||
* `key`, all `@unique`) — used both by `prisma/seed.ts` (the CLI entry
|
||||
* point, `prisma db seed`) and by `test-support/reset-db.ts` (so every
|
||||
* test starts from the same realistic reference data the real app seeds,
|
||||
* not an empty table).
|
||||
*
|
||||
* Every `name` below (`DIETS`, `ALLERGENS`, `INGREDIENT_GROUPS`) is an
|
||||
* *authoring* label, never written to the database or seen by a client —
|
||||
* {@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<void> {
|
||||
for (const name of DIETS) {
|
||||
await prisma.diet.upsert({ where: { name }, update: {}, create: { name } });
|
||||
const key = getEnglishKey(name);
|
||||
await prisma.diet.upsert({ where: { key }, update: {}, create: { key } });
|
||||
}
|
||||
|
||||
// `Allergy` itself carries no `name` — it's the selectable instance of a
|
||||
// named `Category` (see schema.prisma) — so seeding an allergen means one
|
||||
// Category (upserted by name) plus exactly one Allergy row under it,
|
||||
// `Allergy` itself carries no `key` — it's the selectable instance of a
|
||||
// keyed `Category` (see schema.prisma) — so seeding an allergen means one
|
||||
// Category (upserted by key) plus exactly one Allergy row under it,
|
||||
// created only the first time. `update: { kind }` (not `{}`) — a reseed
|
||||
// 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 = getEnglishKey(name);
|
||||
const category = await prisma.category.upsert({
|
||||
where: { name },
|
||||
where: { key },
|
||||
update: { kind },
|
||||
create: { name, kind },
|
||||
create: { key, kind },
|
||||
});
|
||||
const existing = await prisma.allergy.findFirst({ where: { categoryId: category.id } });
|
||||
if (!existing) {
|
||||
|
|
@ -916,17 +931,18 @@ export async function seedReferenceData(prisma: PrismaClient): Promise<void> {
|
|||
// 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) => getEnglishKey(i.name));
|
||||
const existingIngredients = await prisma.ingredient.findMany({
|
||||
where: { name: { in: INGREDIENTS.map((i) => i.name) } },
|
||||
select: { id: true, name: true, icon: true, category: true, subcategory: true },
|
||||
where: { key: { in: ingredientKeys } },
|
||||
select: { id: true, key: true, icon: true, category: true, subcategory: true },
|
||||
});
|
||||
const existingByName = new Map(existingIngredients.map((i) => [i.name, i]));
|
||||
const existingByKey = new Map(existingIngredients.map((i) => [i.key, i]));
|
||||
|
||||
const missingIngredients = INGREDIENTS.filter((i) => !existingByName.has(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 }) => ({
|
||||
name,
|
||||
key: getEnglishKey(name),
|
||||
icon,
|
||||
category,
|
||||
subcategory,
|
||||
|
|
@ -935,7 +951,7 @@ export async function seedReferenceData(prisma: PrismaClient): Promise<void> {
|
|||
}
|
||||
|
||||
const changed = INGREDIENTS.filter((i) => {
|
||||
const existing = existingByName.get(i.name);
|
||||
const existing = existingByKey.get(getEnglishKey(i.name));
|
||||
return (
|
||||
existing &&
|
||||
(existing.icon !== i.icon ||
|
||||
|
|
@ -944,29 +960,32 @@ export async function seedReferenceData(prisma: PrismaClient): Promise<void> {
|
|||
);
|
||||
});
|
||||
for (const { name, icon, category, subcategory } of changed) {
|
||||
await prisma.ingredient.update({ where: { 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
|
||||
// allergy's id (by its category name) once, then link them in a single
|
||||
// allergy's id (by its category key) once, then link them in a single
|
||||
// bulk insert — same "re-derived every time, not upserted per link"
|
||||
// reasoning as before for `IngredientAllergy` (it has no natural per-row
|
||||
// identity to upsert against), just batched instead of looped.
|
||||
const allIngredients = await prisma.ingredient.findMany({
|
||||
where: { name: { in: INGREDIENTS.map((i) => i.name) } },
|
||||
select: { id: true, name: true },
|
||||
where: { key: { in: ingredientKeys } },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
const ingredientIdByName = new Map(allIngredients.map((i) => [i.name, i.id]));
|
||||
const ingredientIdByKey = new Map(allIngredients.map((i) => [i.key, i.id]));
|
||||
|
||||
const allergies = await prisma.allergy.findMany({ include: { category: true } });
|
||||
const allergyIdByCategoryName = new Map(allergies.map((a) => [a.category.name, a.id]));
|
||||
const allergyIdByCategoryKey = new Map(allergies.map((a) => [a.category.key, a.id]));
|
||||
|
||||
const links: Array<{ ingredientId: number; allergyId: number }> = [];
|
||||
for (const { name, allergenNames } of INGREDIENTS) {
|
||||
const ingredientId = ingredientIdByName.get(name);
|
||||
const ingredientId = ingredientIdByKey.get(getEnglishKey(name));
|
||||
if (ingredientId === undefined) continue;
|
||||
for (const allergenName of allergenNames) {
|
||||
const allergyId = allergyIdByCategoryName.get(allergenName);
|
||||
const allergyId = allergyIdByCategoryKey.get(getEnglishKey(allergenName));
|
||||
if (allergyId !== undefined) links.push({ ingredientId, allergyId });
|
||||
}
|
||||
}
|
||||
|
|
@ -978,14 +997,14 @@ export async function seedReferenceData(prisma: PrismaClient): Promise<void> {
|
|||
// `dietNames` (item override, falling back to its group's `defaultDiets`
|
||||
// in the `INGREDIENTS` flatten step) instead of `allergenNames`.
|
||||
const diets = await prisma.diet.findMany();
|
||||
const dietIdByName = new Map(diets.map((d) => [d.name, d.id]));
|
||||
const dietIdByKey = new Map(diets.map((d) => [d.key, d.id]));
|
||||
|
||||
const dietLinks: Array<{ ingredientId: number; dietId: number }> = [];
|
||||
for (const { name, dietNames } of INGREDIENTS) {
|
||||
const ingredientId = ingredientIdByName.get(name);
|
||||
const ingredientId = ingredientIdByKey.get(getEnglishKey(name));
|
||||
if (ingredientId === undefined) continue;
|
||||
for (const dietName of dietNames) {
|
||||
const dietId = dietIdByName.get(dietName);
|
||||
const dietId = dietIdByKey.get(getEnglishKey(dietName));
|
||||
if (dietId !== undefined) dietLinks.push({ ingredientId, dietId });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,21 +39,21 @@ type IngredientWithDetails = RecipeWithDetails["ingredients"][number]["ingredien
|
|||
function toIngredientView(ingredient: IngredientWithDetails): IngredientView {
|
||||
return {
|
||||
id: ingredient.id,
|
||||
name: ingredient.name,
|
||||
key: ingredient.key,
|
||||
icon: ingredient.icon,
|
||||
category: ingredient.category,
|
||||
subcategory: ingredient.subcategory,
|
||||
allergens: ingredient.allergies.map(({ allergy }) => ({
|
||||
id: allergy.id,
|
||||
name: allergy.category.name,
|
||||
key: allergy.category.key,
|
||||
kind: allergy.category.kind,
|
||||
})),
|
||||
diets: ingredient.diets.map(({ diet }) => ({ id: diet.id, name: diet.name })),
|
||||
diets: ingredient.diets.map(({ diet }) => ({ id: diet.id, key: diet.key })),
|
||||
};
|
||||
}
|
||||
|
||||
function toDietView(diet: { id: number; name: string }): DietView {
|
||||
return { id: diet.id, name: diet.name };
|
||||
function toDietView(diet: { id: number; key: string }): DietView {
|
||||
return { id: diet.id, key: diet.key };
|
||||
}
|
||||
|
||||
/** Deduplicates allergens (by id) across every ingredient of a recipe, for the aggregated "contains" badge — see {@link RecipeSummaryView.allergens}. */
|
||||
|
|
|
|||
|
|
@ -1,35 +1,43 @@
|
|||
import type { AllergyView, DietView, IngredientView } from "@batch-cooking/shared";
|
||||
import { prisma } from "../../db/prisma.js";
|
||||
|
||||
/** All reference dietary regimes, alphabetically — small, static list (see prisma/seed.ts). */
|
||||
/**
|
||||
* All reference dietary regimes, ordered by `key` — small, static list (see
|
||||
* prisma/seed.ts). `key` is a stable slug, not the display label (see
|
||||
* {@link DietView}), so this is an alphabetical-by-slug order rather than a
|
||||
* true French alphabetical one — close enough for a 5-item list, and the
|
||||
* server has no other order to offer now that the label itself only exists
|
||||
* client-side (`apps/web`'s `locales/fr/translation.json`).
|
||||
*/
|
||||
export async function getDiets(): Promise<DietView[]> {
|
||||
return prisma.diet.findMany({ orderBy: { name: "asc" } });
|
||||
return prisma.diet.findMany({ orderBy: { key: "asc" } });
|
||||
}
|
||||
|
||||
/**
|
||||
* All reference allergens, alphabetically. `Allergy` carries no `name` of
|
||||
* its own — it's the selectable instance of a named `Category` (see
|
||||
* schema.prisma) — so this resolves each allergen's display name from its
|
||||
* category and flattens the split away for callers.
|
||||
* All reference allergens, ordered by key (see {@link getDiets} for why key,
|
||||
* not label). `Allergy` carries no `key` of its own — it's the selectable
|
||||
* instance of a keyed `Category` (see schema.prisma) — so this resolves
|
||||
* each allergen's key from its category and flattens the split away for
|
||||
* callers.
|
||||
*/
|
||||
export async function getAllergies(): Promise<AllergyView[]> {
|
||||
const allergies = await prisma.allergy.findMany({
|
||||
include: { category: { select: { name: true, kind: true } } },
|
||||
orderBy: { category: { name: "asc" } },
|
||||
include: { category: { select: { key: true, kind: true } } },
|
||||
orderBy: { category: { key: "asc" } },
|
||||
});
|
||||
return allergies.map((allergy) => ({
|
||||
id: allergy.id,
|
||||
name: allergy.category.name,
|
||||
key: allergy.category.key,
|
||||
kind: allergy.category.kind,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* All reference ingredients, alphabetically, each resolved to its allergens
|
||||
* (see `IngredientAllergy` in schema.prisma) and compatible diet regimes
|
||||
* (see `IngredientDiet`) — same aplattening approach as {@link getAllergies}.
|
||||
* Ingredients with no linked allergen/diet come back with `allergens: []`/
|
||||
* `diets: []`.
|
||||
* All reference ingredients, ordered by key (see {@link getDiets} for why),
|
||||
* each resolved to its allergens (see `IngredientAllergy` in schema.prisma)
|
||||
* and compatible diet regimes (see `IngredientDiet`) — same flattening
|
||||
* approach as {@link getAllergies}. Ingredients with no linked
|
||||
* allergen/diet come back with `allergens: []`/`diets: []`.
|
||||
*/
|
||||
export async function getIngredients(): Promise<IngredientView[]> {
|
||||
const ingredients = await prisma.ingredient.findMany({
|
||||
|
|
@ -37,19 +45,19 @@ export async function getIngredients(): Promise<IngredientView[]> {
|
|||
allergies: { include: { allergy: { include: { category: true } } } },
|
||||
diets: { include: { diet: true } },
|
||||
},
|
||||
orderBy: { name: "asc" },
|
||||
orderBy: { key: "asc" },
|
||||
});
|
||||
return ingredients.map((ingredient) => ({
|
||||
id: ingredient.id,
|
||||
name: ingredient.name,
|
||||
key: ingredient.key,
|
||||
icon: ingredient.icon,
|
||||
category: ingredient.category,
|
||||
subcategory: ingredient.subcategory,
|
||||
allergens: ingredient.allergies.map(({ allergy }) => ({
|
||||
id: allergy.id,
|
||||
name: allergy.category.name,
|
||||
key: allergy.category.key,
|
||||
kind: allergy.category.kind,
|
||||
})),
|
||||
diets: ingredient.diets.map(({ diet }) => ({ id: diet.id, name: diet.name })),
|
||||
diets: ingredient.diets.map(({ diet }) => ({ id: diet.id, key: diet.key })),
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
26
apps/api/src/utils/slugify.ts
Normal file
26
apps/api/src/utils/slugify.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Deterministic slug for a French reference-data label — used as the
|
||||
* stable, storage-safe `key` for `Diet`/`Category`/`Ingredient` rows (see
|
||||
* `db/reference-seed-data.ts`), decoupled from the display label so the
|
||||
* label itself can live in `apps/web`'s `locales/fr/translation.json`
|
||||
* (`catalog.*` namespace) instead of the database. A row's `key` is derived
|
||||
* from its seed-time French name once and then never changes — renaming the
|
||||
* *label* later (a translation fix, a rewording) never touches the key, the
|
||||
* FK-referencing rows, or any code that looks a row up by key.
|
||||
*
|
||||
* Handles the two French ligatures NFD decomposition doesn't touch (`œ`,
|
||||
* `æ` aren't accented letters, they're distinct glyphs) explicitly, then
|
||||
* strips every other accent via NFD decomposition + Unicode "Mark" removal
|
||||
* (`\p{M}`, every combining diacritic NFD can produce), then collapses
|
||||
* whatever isn't `[a-z0-9]` into single underscores.
|
||||
*/
|
||||
export function slugify(label: string): string {
|
||||
return label
|
||||
.toLowerCase()
|
||||
.replace(/œ/g, "oe")
|
||||
.replace(/æ/g, "ae")
|
||||
.normalize("NFD")
|
||||
.replace(/\p{M}/gu, "")
|
||||
.replace(/[^a-z0-9]+/g, "_")
|
||||
.replace(/^_+|_+$/g, "");
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { faker } from "@faker-js/faker";
|
|||
import { expect } from "chai";
|
||||
import request from "supertest";
|
||||
import { createApp } from "../src/app.js";
|
||||
import { getEnglishKey } from "../src/db/catalog-en-keys.js";
|
||||
import { prisma } from "../src/db/prisma.js";
|
||||
import { resetDatabase } from "../test-support/reset-db.js";
|
||||
|
||||
|
|
@ -39,7 +40,9 @@ 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: { name: "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 });
|
||||
|
||||
|
|
@ -50,7 +53,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: { name: "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 });
|
||||
|
|
@ -83,8 +86,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.name === "Arachides");
|
||||
const gluten = allergies.find((a) => a.category.name === "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");
|
||||
|
|
@ -104,8 +107,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.name === "Arachides");
|
||||
const gluten = allergies.find((a) => a.category.name === "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] });
|
||||
|
|
@ -140,8 +143,12 @@ 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: { name: "Tomate" } });
|
||||
const oignon = await prisma.ingredient.findFirstOrThrow({ where: { name: "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([]);
|
||||
|
|
@ -159,8 +166,12 @@ 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: { name: "Tomate" } });
|
||||
const oignon = await prisma.ingredient.findFirstOrThrow({ where: { name: "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")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { faker } from "@faker-js/faker";
|
|||
import { expect } from "chai";
|
||||
import request from "supertest";
|
||||
import { createApp } from "../src/app.js";
|
||||
import { getEnglishKey } from "../src/db/catalog-en-keys.js";
|
||||
import { prisma } from "../src/db/prisma.js";
|
||||
import { resetDatabase } from "../test-support/reset-db.js";
|
||||
|
||||
|
|
@ -19,9 +20,11 @@ function buildSignupPayload(): SignupInput {
|
|||
};
|
||||
}
|
||||
|
||||
/** Resolves a reference ingredient's id by its `reference-seed-data.ts` name. */
|
||||
/** 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<number> {
|
||||
const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { name } });
|
||||
const ingredient = await prisma.ingredient.findFirstOrThrow({
|
||||
where: { key: getEnglishKey(name) },
|
||||
});
|
||||
return ingredient.id;
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +166,9 @@ describe("Recipes", () => {
|
|||
const { agent } = await signup();
|
||||
const tomate = await ingredientId("Tomate");
|
||||
const oeuf = await ingredientId("Œuf");
|
||||
const vegetarien = await prisma.diet.findFirstOrThrow({ where: { name: "Végétarien" } });
|
||||
const vegetarien = await prisma.diet.findFirstOrThrow({
|
||||
where: { key: getEnglishKey("Végétarien") },
|
||||
});
|
||||
|
||||
const res = await agent.post("/recipes").send({
|
||||
name: "Omelette provençale",
|
||||
|
|
@ -183,8 +188,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: { name: string }) => a.name)).to.include("Œufs");
|
||||
expect(res.body.diets.map((d: { name: string }) => d.name)).to.deep.equal(["Végétarien"]);
|
||||
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 () => {
|
||||
|
|
@ -279,7 +288,7 @@ describe("Recipes", () => {
|
|||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.body.name).to.equal("Salade");
|
||||
expect(res.body.ingredients[0].ingredient.name).to.equal("Tomate");
|
||||
expect(res.body.ingredients[0].ingredient.key).to.equal(getEnglishKey("Tomate"));
|
||||
expect(res.body.isFavorite).to.equal(false);
|
||||
});
|
||||
|
||||
|
|
@ -364,7 +373,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.name).to.equal("Oignon");
|
||||
expect(res.body.ingredients[0].ingredient.key).to.equal(getEnglishKey("Oignon"));
|
||||
expect(res.body.steps).to.have.length(2);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { expect } from "chai";
|
||||
import request from "supertest";
|
||||
import { createApp } from "../src/app.js";
|
||||
import { getEnglishKey } from "../src/db/catalog-en-keys.js";
|
||||
import { prisma } from "../src/db/prisma.js";
|
||||
import { resetDatabase } from "../test-support/reset-db.js";
|
||||
|
||||
|
|
@ -21,28 +22,29 @@ describe("Reference data", () => {
|
|||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.body).to.have.length(5);
|
||||
expect(res.body.map((d: { name: string }) => d.name)).to.include("Végétarien");
|
||||
expect(res.body[0]).to.have.keys(["id", "name"]);
|
||||
expect(res.body.map((d: { key: string }) => d.key)).to.include(getEnglishKey("Végétarien"));
|
||||
expect(res.body[0]).to.have.keys(["id", "key"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /reference/allergies", () => {
|
||||
it("returns the seeded allergens with their name resolved, no session required", async () => {
|
||||
it("returns the seeded allergens with their key resolved, no session required", async () => {
|
||||
const res = await request(app).get("/reference/allergies");
|
||||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.body).to.have.length(14);
|
||||
expect(res.body.map((a: { name: string }) => a.name)).to.include("Arachides");
|
||||
expect(res.body[0]).to.have.keys(["id", "name", "kind"]);
|
||||
expect(res.body.map((a: { key: string }) => a.key)).to.include(getEnglishKey("Arachides"));
|
||||
expect(res.body[0]).to.have.keys(["id", "key", "kind"]);
|
||||
});
|
||||
|
||||
it("classifies Gluten and Sulfites as intolerances, the rest as allergies", async () => {
|
||||
const res = await request(app).get("/reference/allergies");
|
||||
|
||||
const byName = (name: string) => res.body.find((a: { name: string }) => a.name === name);
|
||||
expect(byName("Gluten").kind).to.equal("INTOLERANCE");
|
||||
expect(byName("Sulfites").kind).to.equal("INTOLERANCE");
|
||||
expect(byName("Arachides").kind).to.equal("ALLERGY");
|
||||
const byKey = (name: string) =>
|
||||
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");
|
||||
expect(res.body.filter((a: { kind: string }) => a.kind === "INTOLERANCE")).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -53,10 +55,10 @@ describe("Reference data", () => {
|
|||
|
||||
expect(res.status).to.equal(200);
|
||||
expect(res.body.length).to.be.greaterThan(0);
|
||||
expect(res.body.map((i: { name: string }) => i.name)).to.include("Tomate");
|
||||
expect(res.body.map((i: { key: string }) => i.key)).to.include(getEnglishKey("Tomate"));
|
||||
expect(res.body[0]).to.have.keys([
|
||||
"id",
|
||||
"name",
|
||||
"key",
|
||||
"icon",
|
||||
"category",
|
||||
"subcategory",
|
||||
|
|
@ -68,9 +70,12 @@ describe("Reference data", () => {
|
|||
it("resolves each ingredient's linked allergens, empty for one with none", async () => {
|
||||
const res = await request(app).get("/reference/ingredients");
|
||||
|
||||
const byName = (name: string) => res.body.find((i: { name: string }) => i.name === name);
|
||||
expect(byName("Œuf").allergens.map((a: { name: string }) => a.name)).to.include("Œufs");
|
||||
expect(byName("Tomate").allergens).to.deep.equal([]);
|
||||
const byKey = (name: string) =>
|
||||
res.body.find((i: { key: string }) => i.key === getEnglishKey(name));
|
||||
expect(byKey("Œuf").allergens.map((a: { key: string }) => a.key)).to.include(
|
||||
getEnglishKey("Œufs"),
|
||||
);
|
||||
expect(byKey("Tomate").allergens).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ describe("Onboarding wizard (regime → foyer → allergens)", () => {
|
|||
cy.intercept("GET", "**/reference/diets", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{ id: 1, name: "Omnivore" },
|
||||
{ id: 2, name: "Végétarien" },
|
||||
{ id: 1, key: "omnivore" },
|
||||
{ id: 2, key: "vegetarian" },
|
||||
],
|
||||
});
|
||||
cy.intercept("PATCH", "**/profile/diet", {
|
||||
|
|
@ -48,8 +48,8 @@ describe("Onboarding wizard (regime → foyer → allergens)", () => {
|
|||
cy.intercept("GET", "**/reference/allergies", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{ id: 1, name: "Arachides", kind: "ALLERGY" },
|
||||
{ id: 2, name: "Gluten", kind: "INTOLERANCE" },
|
||||
{ id: 1, key: "peanuts", kind: "ALLERGY" },
|
||||
{ id: 2, key: "gluten", kind: "INTOLERANCE" },
|
||||
],
|
||||
});
|
||||
cy.intercept("PATCH", "**/profile/allergies", { statusCode: 200, body: [1] }).as(
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@ describe("Dietary preferences (/parametres/preferences) — hot saving", () => {
|
|||
cy.intercept("GET", "**/reference/diets", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{ id: 1, name: "Omnivore" },
|
||||
{ id: 2, name: "Végétarien" },
|
||||
{ id: 1, key: "omnivore" },
|
||||
{ id: 2, key: "vegetarian" },
|
||||
],
|
||||
});
|
||||
cy.intercept("GET", "**/reference/allergies", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
{ id: 1, name: "Arachides", kind: "ALLERGY" },
|
||||
{ id: 2, name: "Gluten", kind: "INTOLERANCE" },
|
||||
{ id: 1, key: "peanuts", kind: "ALLERGY" },
|
||||
{ id: 2, key: "gluten", kind: "INTOLERANCE" },
|
||||
],
|
||||
});
|
||||
cy.intercept("GET", "**/profile/allergies", { statusCode: 200, body: [2] });
|
||||
|
|
|
|||
178
apps/web/cypress/e2e/recipe-form.cy.ts
Normal file
178
apps/web/cypress/e2e/recipe-form.cy.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale (no
|
||||
// live backend in this CI job; apps/api's own Mocha/Cucumber suites cover
|
||||
// real API behavior against a real database).
|
||||
|
||||
const authenticatedProfile = {
|
||||
id: 1,
|
||||
firstName: "Alice",
|
||||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: 1,
|
||||
dietId: null,
|
||||
};
|
||||
|
||||
const tomato = {
|
||||
id: 1,
|
||||
key: "tomato",
|
||||
icon: "VEGETABLE",
|
||||
category: "PRODUITS_FRAIS",
|
||||
subcategory: "LEGUMES",
|
||||
allergens: [],
|
||||
diets: [{ id: 2, key: "vegetarian" }],
|
||||
};
|
||||
const egg = {
|
||||
id: 2,
|
||||
key: "egg",
|
||||
icon: "EGG",
|
||||
category: "CREMERIE_FROMAGE",
|
||||
subcategory: "OEUFS",
|
||||
allergens: [{ id: 1, key: "eggs", kind: "ALLERGY" }],
|
||||
diets: [],
|
||||
};
|
||||
const carrot = {
|
||||
id: 3,
|
||||
key: "carrot",
|
||||
icon: "VEGETABLE",
|
||||
category: "PRODUITS_FRAIS",
|
||||
subcategory: "LEGUMES",
|
||||
allergens: [],
|
||||
diets: [{ id: 2, key: "vegetarian" }],
|
||||
};
|
||||
|
||||
const diets = [
|
||||
{ id: 1, key: "omnivore" },
|
||||
{ id: 2, key: "vegetarian" },
|
||||
];
|
||||
|
||||
function interceptCatalog() {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||
cy.intercept("GET", "**/reference/ingredients", {
|
||||
statusCode: 200,
|
||||
body: [tomato, egg, carrot],
|
||||
});
|
||||
cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: diets });
|
||||
}
|
||||
|
||||
describe("Recipe form — associating ingredients", () => {
|
||||
beforeEach(() => {
|
||||
interceptCatalog();
|
||||
});
|
||||
|
||||
it("adds an ingredient from the picker, fills its quantity/unit, and creates the recipe", () => {
|
||||
cy.intercept("POST", "**/recipes", { statusCode: 201, body: { id: 42 } }).as("createRecipe");
|
||||
|
||||
cy.visit("/recettes/nouvelle");
|
||||
|
||||
cy.get("#recipe-name").type("Salade de tomates");
|
||||
|
||||
cy.get("input[placeholder='Rechercher un ingrédient…']").type("tomat");
|
||||
cy.contains(".ingredient-picker__card", "Tomate").click();
|
||||
|
||||
// The card disappears from the picker once selected (excludeIds), and
|
||||
// a row for it appears in the recipe's own ingredient list.
|
||||
cy.contains(".ingredient-picker__card", "Tomate").should("not.exist");
|
||||
cy.contains(".ingredient-row__name", "Tomate").should("be.visible");
|
||||
|
||||
cy.get(".ingredient-row .ingredient-row__quantity").type("3");
|
||||
cy.get(".ingredient-row .ingredient-row__unit").type("unité");
|
||||
|
||||
cy.contains("button", "Ajouter une étape").click();
|
||||
cy.get(".step-list-editor__item textarea").type("Couper les tomates.");
|
||||
|
||||
cy.contains("button", "Enregistrer").should("not.be.disabled").click();
|
||||
|
||||
cy.wait("@createRecipe")
|
||||
.its("request.body")
|
||||
.should("deep.include", {
|
||||
name: "Salade de tomates",
|
||||
ingredients: [{ ingredientId: 1, quantity: 3, unit: "unité" }],
|
||||
});
|
||||
cy.url().should("include", "/recettes/42");
|
||||
});
|
||||
|
||||
// Regression test for the exact bug reported: `crypto.randomUUID()` (used
|
||||
// to mint each ingredient/step draft's client-only React key) throws
|
||||
// outside a secure context — https, or literally the hostname
|
||||
// `localhost` — so a LAN IP during on-device testing or a Capacitor
|
||||
// WebView's `capacitor://` origin hit a black screen with "TypeError:
|
||||
// crypto.randomUUID is not a function" the instant an ingredient was
|
||||
// added. Cypress's own origin is secure, so this forces the same failure
|
||||
// by deleting `crypto.randomUUID` before the app boots — see
|
||||
// `apps/web/src/lib/client-key.ts`, which replaced it.
|
||||
it("still works when crypto.randomUUID is unavailable (insecure-context regression)", () => {
|
||||
cy.visit("/recettes/nouvelle", {
|
||||
onBeforeLoad(win) {
|
||||
Object.defineProperty(win.crypto, "randomUUID", {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
cy.get("#recipe-name").type("Recette hors contexte sécurisé");
|
||||
|
||||
cy.contains(".ingredient-picker__card", "Tomate").click();
|
||||
cy.contains(".ingredient-picker__card", "Œuf").click();
|
||||
|
||||
// Both rows rendered with distinct identities — no crash, no React
|
||||
// "same key" warning silently collapsing one of them.
|
||||
cy.get(".ingredient-row").should("have.length", 2);
|
||||
cy.contains(".ingredient-row__name", "Tomate").should("be.visible");
|
||||
cy.contains(".ingredient-row__name", "Œuf").should("be.visible");
|
||||
|
||||
cy.contains("button", "Ajouter une étape").click();
|
||||
cy.contains("button", "Ajouter une étape").click();
|
||||
cy.get(".step-list-editor__item").should("have.length", 2);
|
||||
});
|
||||
|
||||
it("excludes an already-selected ingredient from the picker, and removing it brings it back", () => {
|
||||
cy.visit("/recettes/nouvelle");
|
||||
|
||||
cy.contains(".ingredient-picker__card", "Carotte").click();
|
||||
cy.contains(".ingredient-picker__card", "Carotte").should("not.exist");
|
||||
|
||||
cy.contains(".ingredient-row", "Carotte")
|
||||
.find("button[title='Retirer cet ingrédient']")
|
||||
.click();
|
||||
cy.contains(".ingredient-picker__card", "Carotte").should("be.visible");
|
||||
cy.get(".ingredient-row").should("have.length", 0);
|
||||
});
|
||||
|
||||
it("preloads an existing recipe's ingredients when editing, and lets you add another", () => {
|
||||
const existingRecipe = {
|
||||
id: 7,
|
||||
name: "Omelette",
|
||||
description: null,
|
||||
picture: null,
|
||||
authorId: 1,
|
||||
visibility: "PERSONAL",
|
||||
allergens: [{ id: 1, key: "eggs", kind: "ALLERGY" }],
|
||||
diets: [],
|
||||
isFavorite: false,
|
||||
ingredients: [{ ingredient: egg, quantity: 3, unit: "unité" }],
|
||||
steps: [{ id: 1, description: "Battre les œufs.", picture: null, order: 0 }],
|
||||
};
|
||||
cy.intercept("GET", "**/recipes/7", { statusCode: 200, body: existingRecipe });
|
||||
cy.intercept("PATCH", "**/recipes/7", { statusCode: 200, body: { id: 7 } }).as("updateRecipe");
|
||||
|
||||
cy.visit("/recettes/7/modifier");
|
||||
|
||||
cy.contains(".ingredient-row__name", "Œuf").should("be.visible");
|
||||
cy.get(".ingredient-row .ingredient-row__quantity").should("have.value", "3");
|
||||
|
||||
cy.contains(".ingredient-picker__card", "Tomate").click();
|
||||
cy.get(".ingredient-row").should("have.length", 2);
|
||||
cy.get(".ingredient-row .ingredient-row__quantity").last().type("1");
|
||||
cy.get(".ingredient-row .ingredient-row__unit").last().type("unité");
|
||||
|
||||
cy.contains("button", "Enregistrer").click();
|
||||
|
||||
cy.wait("@updateRecipe")
|
||||
.its("request.body.ingredients")
|
||||
.should("deep.equal", [
|
||||
{ ingredientId: 2, quantity: 3, unit: "unité" },
|
||||
{ ingredientId: 1, quantity: 1, unit: "unité" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
241
apps/web/cypress/e2e/recipes.cy.ts
Normal file
241
apps/web/cypress/e2e/recipes.cy.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale (no
|
||||
// live backend in this CI job; apps/api's own Mocha/Cucumber suites cover
|
||||
// real API behavior against a real database).
|
||||
|
||||
const authenticatedProfile = {
|
||||
id: 1,
|
||||
firstName: "Alice",
|
||||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: 1,
|
||||
dietId: null,
|
||||
};
|
||||
|
||||
const vegetarien = { id: 1, key: "vegetarian" };
|
||||
const gluten = { id: 1, key: "gluten", kind: "INTOLERANCE" };
|
||||
const oeufs = { id: 2, key: "eggs", kind: "ALLERGY" };
|
||||
|
||||
const ratatouille = {
|
||||
id: 1,
|
||||
name: "Ratatouille",
|
||||
description: null,
|
||||
picture: null,
|
||||
authorId: 1,
|
||||
visibility: "PERSONAL",
|
||||
allergens: [],
|
||||
diets: [vegetarien],
|
||||
isFavorite: true,
|
||||
};
|
||||
|
||||
const omelette = {
|
||||
id: 2,
|
||||
name: "Omelette",
|
||||
description: null,
|
||||
picture: null,
|
||||
authorId: 1,
|
||||
visibility: "PERSONAL",
|
||||
allergens: [oeufs],
|
||||
diets: [],
|
||||
isFavorite: false,
|
||||
};
|
||||
|
||||
const omeletteDetail = {
|
||||
...omelette,
|
||||
description: "Une omelette toute simple.",
|
||||
ingredients: [
|
||||
{
|
||||
ingredient: {
|
||||
id: 10,
|
||||
key: "egg",
|
||||
icon: "EGG",
|
||||
category: "CREMERIE_FROMAGE",
|
||||
subcategory: "OEUFS",
|
||||
allergens: [oeufs],
|
||||
diets: [],
|
||||
},
|
||||
quantity: 3,
|
||||
unit: "unité",
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ id: 1, description: "Battre les œufs.", picture: null, order: 1 },
|
||||
{ id: 2, description: "Cuire à la poêle.", picture: null, order: 2 },
|
||||
],
|
||||
};
|
||||
|
||||
function interceptAuth() {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||
cy.intercept("GET", "**/profile/disliked-ingredients", { statusCode: 200, body: [] });
|
||||
}
|
||||
|
||||
describe("Recipe catalog", () => {
|
||||
beforeEach(() => {
|
||||
interceptAuth();
|
||||
});
|
||||
|
||||
it("defaults to the Favoris tab and lists its recipes", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, (req) => {
|
||||
expect(req.url).to.include("tab=favoris");
|
||||
req.reply({ statusCode: 200, body: [ratatouille] });
|
||||
}).as("listRecipes");
|
||||
|
||||
cy.visit("/recettes");
|
||||
cy.wait("@listRecipes");
|
||||
|
||||
cy.contains("h1", "Recettes").should("be.visible");
|
||||
cy.get(".recipe-tabs__tab.active").should("contain.text", "Favoris");
|
||||
cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");
|
||||
// The favorited row carries the ★ fav-mark.
|
||||
cy.contains(".recipe-table__name", "Ratatouille")
|
||||
.find(".recipe-table__fav-mark")
|
||||
.should("exist");
|
||||
cy.contains(".recipe-table__name", "Ratatouille")
|
||||
.parents("tr")
|
||||
.find(".diet-badge")
|
||||
.should("contain.text", "Végétarien");
|
||||
});
|
||||
|
||||
it("shows the empty state when a tab has no recipes", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] });
|
||||
|
||||
cy.visit("/recettes");
|
||||
|
||||
cy.contains("Aucune recette pour le moment.").should("be.visible");
|
||||
});
|
||||
|
||||
it("shows an error state when the catalog fails to load", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 500, body: { code: 5000, message: "boom" } });
|
||||
|
||||
cy.visit("/recettes");
|
||||
|
||||
cy.contains(".recipes-page__status--error", "Impossible de charger").should("be.visible");
|
||||
});
|
||||
|
||||
it("switches tabs, re-fetching each one's own recipes", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, (req) => {
|
||||
const tab = new URL(req.url).searchParams.get("tab");
|
||||
const body = tab === "perso" ? [omelette] : [ratatouille];
|
||||
req.reply({ statusCode: 200, body });
|
||||
}).as("listRecipes");
|
||||
|
||||
cy.visit("/recettes");
|
||||
cy.wait("@listRecipes");
|
||||
cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");
|
||||
|
||||
// Not asserting the specific request URL here — React StrictMode (see
|
||||
// main.tsx) double-invokes mount/update effects in dev, so this can
|
||||
// legitimately fire twice; the rendered result converges either way
|
||||
// (same reasoning as planning-page.cy.ts's week-navigation tests).
|
||||
cy.contains(".recipe-tabs__tab", "Perso").click();
|
||||
cy.get(".recipe-tabs__tab.active").should("contain.text", "Perso");
|
||||
cy.contains(".recipe-table__name", "Omelette").should("be.visible");
|
||||
cy.contains(".recipe-table__name", "Ratatouille").should("not.exist");
|
||||
|
||||
// The disabled "Sources (bientôt)" placeholder never becomes active.
|
||||
cy.contains(".recipe-tabs__tab", "Sources (bientôt)").should("be.disabled");
|
||||
});
|
||||
|
||||
it("searches within the active tab, debounced", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, (req) => {
|
||||
const search = new URL(req.url).searchParams.get("search");
|
||||
req.reply({ statusCode: 200, body: search ? [omelette] : [ratatouille, omelette] });
|
||||
}).as("listRecipes");
|
||||
|
||||
cy.visit("/recettes");
|
||||
cy.wait("@listRecipes");
|
||||
cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");
|
||||
|
||||
// Same "assert the rendered result, not the request count/URL" reasoning
|
||||
// as the tab-switch test above — the 300ms debounce plus StrictMode's
|
||||
// double-invoked effects make the exact number/order of requests an
|
||||
// implementation detail, not something worth pinning down here.
|
||||
cy.get(".recipes-page__search").type("Omel");
|
||||
cy.contains(".recipe-table__name", "Omelette").should("be.visible");
|
||||
cy.contains(".recipe-table__name", "Ratatouille").should("not.exist");
|
||||
});
|
||||
|
||||
it("opens a recipe's detail alongside the table when its row is selected", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [ratatouille, omelette] });
|
||||
cy.intercept("GET", "**/recipes/2", { statusCode: 200, body: omeletteDetail }).as("getRecipe");
|
||||
|
||||
cy.visit("/recettes");
|
||||
cy.contains(".recipe-table__name", "Omelette").click();
|
||||
cy.wait("@getRecipe");
|
||||
|
||||
cy.url().should("include", "/recettes/2");
|
||||
// The table stays mounted (master-detail, not a page navigation) —
|
||||
// both rows are still visible next to the detail panel.
|
||||
cy.contains(".recipe-table__name", "Ratatouille").should("be.visible");
|
||||
cy.get("tr.selected .recipe-table__name").should("contain.text", "Omelette");
|
||||
|
||||
cy.get(".recipe-detail-panel").within(() => {
|
||||
cy.contains("h2", "Omelette").should("be.visible");
|
||||
cy.contains("Une omelette toute simple.").should("be.visible");
|
||||
cy.contains("Battre les œufs.").should("be.visible");
|
||||
cy.contains("Cuire à la poêle.").should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a not-found message for a selected id the API rejects", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] });
|
||||
cy.intercept("GET", "**/recipes/999", {
|
||||
statusCode: 404,
|
||||
// ErrorCode.RECIPE_NOT_FOUND (packages/shared/src/errors/error-codes.ts)
|
||||
// — RecipeDetailPanel only renders the "not found" message for this
|
||||
// exact code, anything else falls into its generic error state.
|
||||
body: { code: 4045, message: "not found" },
|
||||
});
|
||||
|
||||
cy.visit("/recettes/999");
|
||||
|
||||
cy.contains(".recipe-detail-panel", "Cette recette n'existe pas.").should("be.visible");
|
||||
});
|
||||
|
||||
it("toggles a recipe's favorite from the detail panel and reflects it in the table", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [omelette] });
|
||||
cy.intercept("GET", "**/recipes/2", { statusCode: 200, body: omeletteDetail });
|
||||
cy.intercept("POST", "**/recipes/2/favorite", { statusCode: 204 }).as("favorite");
|
||||
|
||||
cy.visit("/recettes/2");
|
||||
|
||||
cy.contains(".recipe-table__name", "Omelette")
|
||||
.find(".recipe-table__fav-mark")
|
||||
.should("not.exist");
|
||||
|
||||
cy.get(".favorite-star-button").click();
|
||||
cy.wait("@favorite");
|
||||
|
||||
cy.get(".favorite-star-button").should("have.class", "is-favorite");
|
||||
cy.contains(".recipe-table__name", "Omelette").find(".recipe-table__fav-mark").should("exist");
|
||||
});
|
||||
|
||||
it("deletes a recipe after a two-step confirmation, then clears the selection", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [omelette] });
|
||||
cy.intercept("GET", "**/recipes/2", { statusCode: 200, body: omeletteDetail });
|
||||
cy.intercept("DELETE", "**/recipes/2", { statusCode: 204 }).as("deleteRecipe");
|
||||
|
||||
cy.visit("/recettes/2");
|
||||
cy.contains(".recipe-detail-panel", "Omelette").should("be.visible");
|
||||
|
||||
cy.contains(".recipe-detail-panel__danger-button", "Supprimer").click();
|
||||
cy.contains(".recipe-detail-panel__danger-button", "Confirmer la suppression").click();
|
||||
cy.wait("@deleteRecipe");
|
||||
|
||||
cy.url().should("match", /\/recettes\/?$/);
|
||||
cy.contains(".recipe-table__name", "Omelette").should("not.exist");
|
||||
cy.contains("Sélectionnez une recette dans le tableau").should("be.visible");
|
||||
});
|
||||
|
||||
it("links the new-recipe button to the recipe form", () => {
|
||||
cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] });
|
||||
|
||||
cy.visit("/recettes");
|
||||
|
||||
cy.contains(".recipes-page__new-button", "Nouvelle recette").should(
|
||||
"have.attr",
|
||||
"href",
|
||||
"/recettes/nouvelle",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
"@batch-cooking/date-tools": "workspace:*",
|
||||
"@batch-cooking/shared": "workspace:*",
|
||||
"i18next": "^26.3.6",
|
||||
"lucide-react": "^1.32.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^17.0.11",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensP
|
|||
import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage";
|
||||
import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage";
|
||||
import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage";
|
||||
import { CreditsPage } from "./pages/settings/CreditsPage";
|
||||
import { HouseholdSettingsPage } from "./pages/settings/HouseholdSettingsPage";
|
||||
import { PreferencesPage } from "./pages/settings/PreferencesPage";
|
||||
import { UserPreferencesPage } from "./pages/settings/UserPreferencesPage";
|
||||
|
|
@ -25,8 +26,8 @@ import { UserPreferencesPage } from "./pages/settings/UserPreferencesPage";
|
|||
* itself redirects to `/login` if needed.
|
||||
*
|
||||
* `/parametres/*` (compte/préférences alimentaires/foyer/préférences
|
||||
* utilisateur) are the settings pages, reachable from the sidebar's bottom
|
||||
* "Paramètres" menu and the account menu (see `AppLayout`) — nested under
|
||||
* utilisateur/crédits) are the settings pages, reachable from the sidebar's
|
||||
* bottom "Paramètres" menu and the account menu (see `AppLayout`) — nested under
|
||||
* `AppLayout` like every other authenticated section. `/foyer` is the old,
|
||||
* pre-split combined page's path; it now just redirects to
|
||||
* `/parametres/foyer` so an existing bookmark/link keeps working.
|
||||
|
|
@ -61,6 +62,7 @@ export function App() {
|
|||
<Route path="/parametres/preferences" element={<PreferencesPage />} />
|
||||
<Route path="/parametres/foyer" element={<HouseholdSettingsPage />} />
|
||||
<Route path="/parametres/preferences-utilisateur" element={<UserPreferencesPage />} />
|
||||
<Route path="/parametres/credits" element={<CreditsPage />} />
|
||||
<Route path="/foyer" element={<Navigate to="/parametres/foyer" replace />} />
|
||||
</Route>
|
||||
<Route
|
||||
|
|
|
|||
41
apps/web/src/components/ui/Checkbox.tsx
Normal file
41
apps/web/src/components/ui/Checkbox.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import type { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* The app-wide "selectable card" checkbox — see `global.scss`'s
|
||||
* `label:has(> input[type="checkbox"])` rule for the actual look (hidden
|
||||
* native input, a `.check-mark` that scales in, `is-selected` driving the
|
||||
* tinted/bordered state). Factors out the JSX triplet (`label` → hidden
|
||||
* `input` → `span.check-mark` → label text) that used to be duplicated
|
||||
* across `AllergySelect`, `DietTagSelect`, `IngredientPicker`'s display
|
||||
* menu, and `UserPreferencesPage`'s theme picker (see {@link RadioOption}
|
||||
* for its `type="radio"` sibling) — one place to get the markup/a11y right
|
||||
* instead of four.
|
||||
*
|
||||
* `is-selected` is applied in JS from the same `checked` boolean the caller
|
||||
* already has, not derived via a CSS `:has(:checked)` chain — that turned
|
||||
* out unreliable across browsers (see the callers this replaces for the
|
||||
* original note).
|
||||
*
|
||||
* `className` is for the *container* layout only (grid item, flex-wrap
|
||||
* chip, stacked list…) — the control's own look never varies, so there's
|
||||
* no `variant` prop here.
|
||||
*/
|
||||
export function CheckboxOption({
|
||||
checked,
|
||||
onChange,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<label className={[className, checked && "is-selected"].filter(Boolean).join(" ")}>
|
||||
<input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} />
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
38
apps/web/src/components/ui/Radio.tsx
Normal file
38
apps/web/src/components/ui/Radio.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import type { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* The `type="radio"` sibling of {@link CheckboxOption} — same "selectable
|
||||
* card" markup/look (see `global.scss`'s `label:has(> input[...])` rule,
|
||||
* shared by both), just a native radio input under the hood so a group of
|
||||
* `RadioOption`s sharing `name` behaves as mutually exclusive (see
|
||||
* `UserPreferencesPage`'s theme picker, the one caller so far).
|
||||
*/
|
||||
export function RadioOption<T extends string>({
|
||||
name,
|
||||
value,
|
||||
checked,
|
||||
onChange,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
name: string;
|
||||
value: T;
|
||||
checked: boolean;
|
||||
onChange: (value: T) => void;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<label className={[className, checked && "is-selected"].filter(Boolean).join(" ")}>
|
||||
<input
|
||||
type="radio"
|
||||
name={name}
|
||||
value={value}
|
||||
checked={checked}
|
||||
onChange={() => onChange(value)}
|
||||
/>
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import type { AllergyView } from "@batch-cooking/shared";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CheckboxOption } from "../../components/ui/Checkbox";
|
||||
import "./profile-forms.scss";
|
||||
|
||||
interface AllergySelectProps {
|
||||
|
|
@ -28,6 +30,8 @@ interface AllergySelectProps {
|
|||
* rationale as `DietSelect`.
|
||||
*/
|
||||
export function AllergySelect({ legend, allergies, value, onChange }: AllergySelectProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
function toggle(id: number) {
|
||||
onChange(value.includes(id) ? value.filter((existing) => existing !== id) : [...value, id]);
|
||||
}
|
||||
|
|
@ -38,19 +42,14 @@ export function AllergySelect({ legend, allergies, value, onChange }: AllergySel
|
|||
{allergies.map((allergy) => {
|
||||
const checked = value.includes(allergy.id);
|
||||
return (
|
||||
<label
|
||||
<CheckboxOption
|
||||
key={allergy.id}
|
||||
// `is-selected` (not a `:has(:checked)` CSS rule) drives the
|
||||
// selected look — chaining `:has(...):has(:checked)` to react to
|
||||
// a *state* change (rather than a DOM mutation) turned out to be
|
||||
// unreliable, so this stays a plain, always-correct React class
|
||||
// instead of relying on CSS to derive it.
|
||||
className={`allergy-select__option${checked ? " is-selected" : ""}`}
|
||||
checked={checked}
|
||||
onChange={() => toggle(allergy.id)}
|
||||
className="allergy-select__option"
|
||||
>
|
||||
<input type="checkbox" checked={checked} onChange={() => toggle(allergy.id)} />
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
{allergy.name}
|
||||
</label>
|
||||
{t(`catalog.allergens.${allergy.key}`)}
|
||||
</CheckboxOption>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function DietSelect({ diets, value, onChange }: DietSelectProps) {
|
|||
<option value="">{t("preferences.form.dietNone")}</option>
|
||||
{diets.map((diet) => (
|
||||
<option key={diet.id} value={diet.id}>
|
||||
{diet.name}
|
||||
{t(`catalog.diets.${diet.key}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export function DislikedIngredientsField({
|
|||
<span aria-hidden="true">
|
||||
<IngredientTypeIcon icon={ingredient.icon} />
|
||||
</span>
|
||||
{ingredient.name}
|
||||
{t(`catalog.ingredients.${ingredient.key}`)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove(ingredient.id)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { AllergyView } from "@batch-cooking/shared";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./recipes.scss";
|
||||
|
||||
/**
|
||||
|
|
@ -10,6 +11,7 @@ import "./recipes.scss";
|
|||
* mount it unconditionally.
|
||||
*/
|
||||
export function AllergenBadges({ allergens }: { allergens: AllergyView[] }) {
|
||||
const { t } = useTranslation();
|
||||
if (allergens.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -17,7 +19,7 @@ export function AllergenBadges({ allergens }: { allergens: AllergyView[] }) {
|
|||
<ul className="allergen-badges">
|
||||
{allergens.map((allergen) => (
|
||||
<li key={allergen.id} className="allergen-badge">
|
||||
{allergen.name}
|
||||
{t(`catalog.allergens.${allergen.key}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { DietView } from "@batch-cooking/shared";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./recipes.scss";
|
||||
|
||||
/**
|
||||
|
|
@ -10,6 +11,7 @@ import "./recipes.scss";
|
|||
* Renders nothing for an empty list, same convention as `AllergenBadges`.
|
||||
*/
|
||||
export function DietBadges({ diets }: { diets: DietView[] }) {
|
||||
const { t } = useTranslation();
|
||||
if (diets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -17,7 +19,7 @@ export function DietBadges({ diets }: { diets: DietView[] }) {
|
|||
<ul className="diet-badges">
|
||||
{diets.map((diet) => (
|
||||
<li key={diet.id} className="diet-badge">
|
||||
{diet.name}
|
||||
{t(`catalog.diets.${diet.key}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { DietView } from "@batch-cooking/shared";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CheckboxOption } from "../../components/ui/Checkbox";
|
||||
import "./recipes.scss";
|
||||
|
||||
/**
|
||||
|
|
@ -29,11 +30,9 @@ export function DietTagSelect({
|
|||
{diets.map((diet) => {
|
||||
const checked = value.includes(diet.id);
|
||||
return (
|
||||
<label key={diet.id} className={checked ? "is-selected" : undefined}>
|
||||
<input type="checkbox" checked={checked} onChange={() => toggle(diet.id)} />
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
{diet.name}
|
||||
</label>
|
||||
<CheckboxOption key={diet.id} checked={checked} onChange={() => toggle(diet.id)}>
|
||||
{t(`catalog.diets.${diet.key}`)}
|
||||
</CheckboxOption>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export function FavoriteStarButton({
|
|||
disabled={isSaving}
|
||||
title={t(isFavorite ? "recipes.detail.unfavorite" : "recipes.detail.favorite")}
|
||||
>
|
||||
<FavoriteIcon />
|
||||
<FavoriteIcon aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from "@batch-cooking/shared";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CheckboxOption } from "../../components/ui/Checkbox";
|
||||
import { SettingsIcon } from "../../layouts/nav-icons";
|
||||
import { AllergenBadges } from "./AllergenBadges";
|
||||
import { DietBadges } from "./DietBadges";
|
||||
|
|
@ -73,8 +74,12 @@ export function IngredientPicker({
|
|||
if (ingredient.category !== category) return false;
|
||||
if (subcategory !== ALL && ingredient.subcategory !== subcategory) return false;
|
||||
}
|
||||
if (normalizedQuery.length > 0 && !ingredient.name.toLowerCase().includes(normalizedQuery)) {
|
||||
return false;
|
||||
// Matches against the *displayed* (translated) label, not the raw slug
|
||||
// key — searching "œuf" should find "Œuf" the way it always has, not
|
||||
// require typing its key.
|
||||
if (normalizedQuery.length > 0) {
|
||||
const label = t(`catalog.ingredients.${ingredient.key}`).toLowerCase();
|
||||
if (!label.includes(normalizedQuery)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
|
@ -101,28 +106,16 @@ export function IngredientPicker({
|
|||
aria-expanded={isDisplayMenuOpen}
|
||||
title={t("recipes.form.displayOptions")}
|
||||
>
|
||||
<SettingsIcon />
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
</button>
|
||||
{isDisplayMenuOpen && (
|
||||
<div className="ingredient-picker__display-menu">
|
||||
<label className={showDiets ? "is-selected" : ""}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showDiets}
|
||||
onChange={(e) => setShowDiets(e.target.checked)}
|
||||
/>
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
<CheckboxOption checked={showDiets} onChange={setShowDiets}>
|
||||
{t("recipes.form.showDietsLabel")}
|
||||
</label>
|
||||
<label className={showAllergens ? "is-selected" : ""}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showAllergens}
|
||||
onChange={(e) => setShowAllergens(e.target.checked)}
|
||||
/>
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
</CheckboxOption>
|
||||
<CheckboxOption checked={showAllergens} onChange={setShowAllergens}>
|
||||
{t("recipes.form.showAllergensLabel")}
|
||||
</label>
|
||||
</CheckboxOption>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -186,7 +179,9 @@ export function IngredientPicker({
|
|||
<span className="ingredient-picker__card-icon" aria-hidden="true">
|
||||
<IngredientTypeIcon icon={ingredient.icon} />
|
||||
</span>
|
||||
<span className="ingredient-picker__card-name">{ingredient.name}</span>
|
||||
<span className="ingredient-picker__card-name">
|
||||
{t(`catalog.ingredients.${ingredient.key}`)}
|
||||
</span>
|
||||
{showAllergens && <AllergenBadges allergens={ingredient.allergens} />}
|
||||
{showDiets && <DietBadges diets={ingredient.diets} />}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export function IngredientRow({
|
|||
<span className="ingredient-row__icon" aria-hidden="true">
|
||||
<IngredientTypeIcon icon={ingredient.icon} />
|
||||
</span>
|
||||
<span className="ingredient-row__name">{ingredient.name}</span>
|
||||
<span className="ingredient-row__name">{t(`catalog.ingredients.${ingredient.key}`)}</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export function RecipeDetailPanel({
|
|||
<ul className="disliked-badges">
|
||||
{dislikedIngredients.map((ingredient) => (
|
||||
<li key={ingredient.id} className="disliked-badge">
|
||||
🚫 {ingredient.name}
|
||||
🚫 {t(`catalog.ingredients.${ingredient.key}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import type { RecipeTab } from "@batch-cooking/shared";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AccountIcon, FavoriteIcon, HouseholdIcon, PublicIcon } from "../../layouts/nav-icons";
|
||||
import "./recipes.scss";
|
||||
|
||||
/** Every functional tab, in display order, with its icon — reuses `AccountIcon`/`HouseholdIcon` from the sidebar's own icon set (see nav-icons.tsx) rather than a second "person"/"house" glyph. */
|
||||
const TABS: Array<{ value: RecipeTab; Icon: () => JSX.Element }> = [
|
||||
const TABS: Array<{ value: RecipeTab; Icon: LucideIcon }> = [
|
||||
{ value: "favoris", Icon: FavoriteIcon },
|
||||
{ value: "perso", Icon: AccountIcon },
|
||||
{ value: "foyer", Icon: HouseholdIcon },
|
||||
|
|
@ -37,7 +38,7 @@ export function RecipeTabs({
|
|||
className={`recipe-tabs__tab${value === active ? " active" : ""}`}
|
||||
onClick={() => onChange(value)}
|
||||
>
|
||||
<Icon />
|
||||
<Icon aria-hidden="true" />
|
||||
{t(`recipes.tabs.${value}`)}
|
||||
</button>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { makeClientKey } from "../../lib/client-key";
|
||||
import "./recipes.scss";
|
||||
|
||||
/** One in-progress preparation step in the recipe form. `key` is a client-only stable identity for React/reordering — the server derives the real `order`/`id` from array position on save (see `schemas/recipe.ts`), never from this. */
|
||||
|
|
@ -24,7 +25,7 @@ export function StepListEditor({
|
|||
const { t } = useTranslation();
|
||||
|
||||
function addStep() {
|
||||
onChange([...steps, { key: crypto.randomUUID(), description: "", picture: "" }]);
|
||||
onChange([...steps, { key: makeClientKey(), description: "", picture: "" }]);
|
||||
}
|
||||
|
||||
function updateStep(key: string, patch: Partial<Pick<StepDraft, "description" | "picture">>) {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,20 @@ import type { ReactNode } from "react";
|
|||
// (schema.prisma), reused across the category chips, subcategory chips and
|
||||
// ingredient cards in `IngredientPicker`/`IngredientRow`. Replaces the
|
||||
// earlier per-ingredient emoji (437 different characters, one per
|
||||
// ingredient) with a small, hand-drawn vocabulary grouped by *what kind of
|
||||
// thing* an ingredient is (a vegetable, a bottle of oil, a wedge of
|
||||
// cheese…) rather than a literal picture of each one — 437 unique,
|
||||
// professional icons isn't a realistic hand-drawn set, but ~20 generic
|
||||
// shapes reused across them is. Same 24×24, stroke-only house style as
|
||||
// `layouts/nav-icons.tsx` (kept separate from that file since these are
|
||||
// ingredient-domain, not app-nav).
|
||||
// ingredient) with a small vocabulary grouped by *what kind of thing* an
|
||||
// ingredient is (a vegetable, a bottle of oil, a wedge of cheese…) rather
|
||||
// than a literal picture of each one — 437 unique, professional icons
|
||||
// isn't a realistic set to draw or license one-by-one, but ~20 generic
|
||||
// shapes reused across them is.
|
||||
//
|
||||
// Most of these (everything using `FilledIcon` below) are solid-fill
|
||||
// glyphs from foodiconpack.com's free "Common ingredient icons"/"Common
|
||||
// Utensils" collections, licensed CC BY 4.0 — see the in-app credits page
|
||||
// (`pages/CreditsPage.tsx`) for the required attribution. The three with
|
||||
// no good match in that pack (`BreadIcon`, `DoughIcon`, `SproutIcon`) stay
|
||||
// hand-drawn, using the stroke-based `Icon` wrapper that used to cover all
|
||||
// 22 — both wrappers render at the same on-screen size (sized entirely via
|
||||
// CSS, no width/height baked into either), so the mix reads as one set.
|
||||
function Icon({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -32,111 +39,138 @@ function Icon({ children }: { children: ReactNode }) {
|
|||
);
|
||||
}
|
||||
|
||||
/** Carrot — root vegetables, leafy greens, and produce generally (`PRODUITS_FRAIS`/`LEGUMES`). */
|
||||
/** Wrapper for the foodiconpack.com icons — solid-fill glyphs (2048×2048 source viewBox) rather than `Icon`'s stroke-only ones. `fill="currentColor"` on the root cascades to every child `<path>` (none set their own), so these theme/recolor exactly like the hand-drawn set. */
|
||||
function FilledIcon({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<svg viewBox="0 0 2048 2048" fill="currentColor" aria-hidden="true">
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/** Carrot (foodiconpack.com, CC BY 4.0 — see the credits page) — root vegetables, leafy greens, and produce generally (`PRODUITS_FRAIS`/`LEGUMES`). */
|
||||
export function VegetableIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M12 3c1.5 1 2 2.5 1.3 4M15 2c1 1.2 1 2.6 0 4" />
|
||||
<path d="M13.3 7c2.8 0 4.7 2.9 4.1 5.6L15.8 20a2 2 0 0 1-3.9.2L9.2 12.6C8.3 9.9 10.3 7 13.3 7z" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1372.78 269.269 C 1451.37 259.868 1460.99 372.996 1461.44 425.661 C 1581.16 368.032 1695.37 431.416 1620.86 576.71 C 1685.93 569.075 1808.81 603.967 1777.39 694.986 C 1758.86 748.652 1687.55 747.579 1641.06 748.465 C 1580.54 749.618 1520.86 763.068 1465.02 788.033 C 1534.06 891.041 1509.32 1016.25 1431.63 1106.97 C 1379.12 1166.91 1315.36 1215.45 1253.34 1264.87 C 1058.07 1420.5 847.167 1562.19 628.521 1682.86 C 575.245 1711.66 526.145 1740.43 468.866 1762.97 C 390.259 1795.21 302.898 1750 274.227 1672.21 C 244.804 1591.18 312.531 1477.87 352.017 1402.88 C 476.741 1166.04 615.662 937.768 779.446 725.68 C 906.883 560.659 1021.56 455.144 1235.99 558.243 C 1264.92 502.026 1277.99 486.974 1284.17 416.13 C 1287.17 381.847 1282.64 350.416 1304.56 321.09 C 1322.93 296.516 1341.19 275.069 1372.78 269.269 z M 950.76 616.439 L 1013.63 678.467 C 1029.66 694.066 1071.68 724.095 1046.31 747.333 C 1024.75 767.082 1004.77 741.305 990.319 727.133 C 964.593 701.906 939.427 675.525 913.717 650.33 C 880.748 676.906 827.077 745.279 800.804 781.139 L 854.416 832.616 C 865.235 843.052 882.868 854.945 884.017 870.127 C 885.534 890.17 863.677 905.592 846.353 893.305 C 828.849 880.245 786.026 838.832 771.219 820.954 C 714.393 891.651 650.945 992.221 601.996 1070.21 C 624.959 1095 681.374 1144.78 697.215 1170.14 C 708.227 1187.77 690.155 1203.7 673.807 1207.19 C 653.583 1201.54 592.75 1131.47 575.668 1114.3 C 559.969 1135.77 548.023 1159.96 533.882 1182.5 C 510.398 1219.92 485.868 1260.5 465.325 1299.77 C 502.429 1335.29 536.293 1374.32 572.651 1410.85 C 616.262 1454.67 564.989 1469.73 552.538 1459.88 C 545.792 1454.54 539.672 1447.84 533.725 1441.61 C 507.768 1414.38 462.092 1372.67 441.283 1344.75 C 406.237 1391.7 337.706 1542.08 320.289 1594.46 C 307.509 1635.07 326.359 1680.01 360.377 1704.01 C 404.141 1734.89 437.219 1724.27 480.998 1702.92 C 588.332 1650.35 692.162 1590.27 793.354 1526.86 C 814.319 1513.73 835.284 1498.97 857.352 1487.95 C 835.987 1470.56 796.488 1430.13 780.352 1410.17 C 763.323 1389.11 795.041 1355.92 821.643 1382.07 C 847.186 1408.16 873.398 1433.7 899.028 1459.69 C 963.823 1420.72 1057.58 1356.01 1116.07 1307.2 C 1077.11 1269.13 1037.68 1230.01 1000.71 1190.1 C 982.941 1170.92 1012.3 1138.95 1033.56 1154.94 C 1048.09 1165.87 1062.35 1182.66 1075.26 1195.74 L 1155.51 1275.86 C 1166.47 1265.89 1185.51 1254.03 1198.05 1244 L 1260.92 1194.32 C 1274.19 1183.64 1289.57 1170.23 1302.77 1160.32 C 1278.8 1138.72 1256.47 1114.14 1233.14 1092.69 C 1217.61 1078.42 1202.94 1062.56 1221.87 1044.16 C 1249.46 1017.35 1315.96 1106.48 1340.25 1126.29 C 1444.56 1048.18 1496.33 911.829 1413.1 796.434 C 1406.16 786.802 1391.42 772.44 1382.72 763.523 L 1275.05 654.549 C 1222.53 602.189 1141.05 555.834 1065.3 567.094 C 1021.92 572.869 984.684 588.342 950.76 616.439 z M 1378.48 317.89 C 1330.92 337.14 1337 380.739 1333.96 418.637 C 1328.72 484.036 1311.97 533.086 1277.38 588.051 C 1293.37 600.283 1316.84 625.274 1331.99 640.442 L 1435.43 745.774 C 1500.7 717.893 1548.44 703.336 1620.74 699.414 C 1645.59 698.066 1687.15 701.875 1708.39 694.233 C 1721.56 688.881 1730.42 684.198 1734.14 669.597 C 1725.34 623.524 1643.09 621.963 1607.48 627.006 C 1580.34 630.85 1546.06 647.219 1546.46 602.242 C 1552.37 591.19 1562.16 583.393 1568.67 573.333 C 1587.79 543.812 1600.89 501.106 1592 466.268 C 1482.33 418.046 1449.83 520.39 1414.54 505.673 C 1378.89 490.801 1434.89 402.689 1400.54 340.129 C 1396.73 333.184 1387.45 318.271 1378.48 317.89 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Apple — `PRODUITS_FRAIS`/`FRUITS`. */
|
||||
/** Apple (foodiconpack.com, CC BY 4.0) — `PRODUITS_FRAIS`/`FRUITS`. */
|
||||
export function FruitIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M12 8c-.5-1.3-.2-3 1-4" />
|
||||
<path d="M12 8c-4 0-6.5 3-6.5 7a6.5 6.5 0 0 0 13 0C18.5 11 16 8 12 8z" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 639.796 267.013 C 781.434 262.103 956.112 338.089 1022.53 468.256 C 1028.76 480.476 1033.68 492.986 1039.98 505.103 C 1079.28 423.136 1119.59 362.725 1191.86 304.793 C 1207.29 295.22 1235.79 265.497 1254.81 269.055 C 1274.69 272.773 1277.84 292.064 1274.6 308.547 C 1266.47 318.866 1248.2 329.649 1237.12 338.285 C 1131.9 420.35 1060.45 546.672 1048.57 682.02 C 1107.04 669.285 1129.39 657.187 1183.99 637.63 C 1536.21 511.481 1760.44 792.38 1734.89 1120.39 C 1718.81 1354.66 1590.75 1616.21 1387.43 1738.88 C 1311.69 1785.07 1214.98 1792.8 1134.52 1754.06 C 1082.6 1729.07 1073.4 1712.81 1004.38 1718.28 C 917.695 1725.14 884.149 1804.95 750.621 1775.01 C 601.141 1741.49 473.114 1590.49 406.265 1456.56 C 327.693 1299.13 287.874 1104.16 324.793 930.29 C 366.48 733.959 526.268 590.689 731.532 609.409 C 820.954 617.563 903.048 662.498 993.64 682.02 C 995.341 671.368 996.474 657.971 997.837 647 C 980.828 646.119 962.537 647.498 945.443 646.853 C 794.823 641.173 687.284 529.291 639.45 392.227 C 628.619 361.191 596.29 281.598 639.796 267.013 z M 730.753 1713.44 C 832.193 1747.12 856.182 1715.04 934.669 1681.28 C 995.523 1655.1 1082.34 1658.82 1138.75 1694.17 C 1249.82 1763.75 1371.52 1705.24 1453.51 1620.95 C 1636.34 1440.7 1756.94 1086.45 1634.33 845.186 C 1571.31 719.014 1460.98 652.165 1319.22 660.997 C 1247.81 670.321 1204.55 687.291 1138.29 714.935 C 1006.74 769.809 916.081 711.143 793.279 675.575 C 667.76 638.278 533.232 676.049 451.257 780.278 C 245.567 1041.81 435.376 1604.58 730.753 1713.44 z M 1002.96 588.644 L 1004.74 588.061 C 1005.7 582.463 1002.62 570.998 1001.38 565.365 C 964.943 401.816 825.31 343.477 675.894 322.716 C 703.263 417.984 739.351 507.827 831.847 559.58 C 882.452 587.895 945.913 600.64 1002.96 588.644 z" />
|
||||
<path d="M 1447.63 774.31 C 1466.99 773.829 1479.84 789.768 1492.65 802.065 C 1598.84 903.998 1609.83 1072.78 1575.18 1207.76 C 1569.82 1228.55 1564.73 1260.7 1541.05 1267.95 C 1513.84 1272.68 1511.65 1247.61 1517.21 1228.75 C 1555.27 1099.42 1556.15 950.748 1461.25 844.839 C 1446.49 828.371 1414.28 815.596 1424.71 790.193 C 1429.12 779.44 1436.68 776.036 1447.63 774.31 z" />
|
||||
<path d="M 921.833 797.237 C 951.095 795.04 989.613 858.005 1079.32 816.787 C 1104.69 805.129 1153.42 774.482 1150.9 831.101 C 1144.48 842.746 1139.52 845.664 1128.52 851.943 C 974.142 937.063 833.621 814.285 921.833 797.237 z" />
|
||||
<path d="M 1497.5 1305.04 C 1537.83 1303.8 1534.41 1331.11 1520.2 1360.63 C 1508.41 1385.13 1460.58 1486.18 1434.24 1491.8 C 1413.92 1495.19 1397.1 1475.37 1403.39 1458.8 C 1409.29 1443.25 1426.07 1419.47 1435.14 1405 C 1448.3 1384.01 1479.36 1312.89 1497.5 1305.04 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A leaf sprig — `PRODUITS_FRAIS`/`HERBES_FRAICHES`. */
|
||||
/** Basil sprig (foodiconpack.com, CC BY 4.0) — `PRODUITS_FRAIS`/`HERBES_FRAICHES`. */
|
||||
export function HerbIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M12 21V7" />
|
||||
<path d="M12 8c0-3 2-5 5-5 0 3-2 5-5 5z" />
|
||||
<path d="M12 13c0-3-2-5-5-5 0 3 2 5 5 5z" />
|
||||
<path d="M12 18c0-2.5 1.7-4 4-4 0 2.5-1.7 4-4 4z" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1297 375.152 C 1297.31 375.122 1297.62 375.092 1297.93 375.062 L 1299.5 374.946 C 1324.25 372.99 1330.3 393.76 1340.29 411.428 C 1445.38 597.423 1466.58 822.011 1335.29 1001.07 C 1538.83 1039.4 1693.43 1206.7 1764.63 1395.49 C 1780.62 1440.99 1802.85 1460.72 1755.17 1486.63 C 1511.01 1619.32 1256.57 1642.53 1054.51 1420.27 C 1044.41 1409.16 1035.39 1396.78 1024.69 1386.22 L 1023.41 1384.96 C 1020.62 1390.15 1017.8 1396.04 1015.36 1401.29 C 954.186 1533 852.17 1597.02 721.738 1643.36 C 635.872 1669.89 545.702 1676.59 456.49 1670 C 444.114 1669.08 424.62 1670.5 415.057 1660.86 C 399.096 1644.75 396.531 1553 396.403 1526.65 C 395.692 1380.85 439.29 1221.82 549.469 1120.14 C 556.91 1113.28 564.765 1107.03 571.903 1099.81 C 414.431 1016.69 311.984 867.702 267.408 696.354 C 260.292 669.001 292.765 660.426 312.264 650.538 C 464.952 573.114 662.94 553.712 812.003 649.983 C 839.647 667.809 858.12 688.164 882.624 708.423 C 952.609 553.039 1138.14 422.318 1297 375.152 z M 1058.31 1158.5 C 1086.64 1176.01 1110.56 1194.94 1141.8 1210.5 L 1143.45 1210.05 C 1161.11 1205.39 1177.37 1205.57 1195.39 1199.49 C 1211.62 1194.02 1222.58 1185.39 1238.1 1179.36 C 1265.26 1112.37 1353.03 1181.94 1236.12 1237.58 C 1226.69 1242.23 1215.48 1244.9 1205.92 1249.34 C 1271.27 1282.67 1347.14 1304.65 1419.69 1312.71 C 1444.43 1315.46 1471.38 1314.5 1495.71 1318.23 C 1516.07 1321.35 1518.21 1362.24 1492.81 1365.18 C 1396.9 1372.42 1308.35 1347.66 1221.61 1310.93 C 1176.05 1291.64 1092.58 1247.44 1054 1213.44 C 1054.22 1219.16 1054.24 1223.36 1054 1229.09 C 1054.21 1269.53 1049.24 1289.92 1043.83 1328.33 C 1055.08 1336.9 1066.99 1355.53 1076.09 1366.7 C 1168.79 1480.51 1310.86 1552.36 1459.54 1534.46 C 1539.73 1524.81 1652.75 1481.64 1726.47 1444.05 C 1719.96 1417.1 1708.08 1392.76 1696.41 1367.77 C 1628.45 1222.19 1504.2 1091.36 1343.67 1053.67 C 1329.31 1050.3 1305.36 1049.82 1293.46 1045.94 C 1216.88 1115.83 1148.01 1127.06 1058.31 1158.5 z M 1008.88 1104.51 C 1029.08 1016.58 1066.85 970.902 994.607 892.42 C 986.451 883.338 973.212 876.324 967.474 865.403 C 951.773 835.52 1009.39 801.066 1062.57 897.391 C 1074.35 864.076 1146.81 683.543 1177.85 667.485 C 1194.73 615.664 1245.36 655.028 1229.77 677.355 C 1162.43 773.807 1144.66 834.804 1103.59 935.529 C 1128.61 924.476 1194.49 891.272 1208.89 931.16 C 1219.69 961.081 1175.36 962.839 1160.07 967.194 C 1092.16 983.544 1071.27 1043.61 1062.57 1104.51 C 1114.58 1093.5 1194.46 1057 1237.44 1026.78 C 1282.73 994.928 1323.48 933.824 1344.58 883.971 C 1399.15 754.991 1376.26 618.983 1326.7 493.407 C 1319.33 474.74 1300.37 449.876 1294.56 430.498 L 1293.46 430.013 C 1073.91 508.331 826.989 716.516 924.55 976.739 C 949.002 1041.96 966.404 1054.78 1008.88 1104.51 z M 911.322 1220.42 C 908.583 1225.42 905.868 1230.48 903.012 1235.41 C 879.204 1305.9 892.966 1298.41 890.98 1355.46 C 869.513 1387.78 840.809 1367.92 837.943 1335.49 C 835.735 1310.5 834.461 1287.82 840.494 1263 C 772.153 1312.77 716.487 1369.47 664.741 1435.89 C 654.728 1448.75 636.703 1476.18 624.209 1484.63 C 603.237 1487.23 593.985 1482.23 588.611 1461.25 C 605.45 1395.46 769.662 1249.54 829.728 1210.5 C 804.37 1201.73 744.257 1193.55 731.185 1179.36 C 729.217 1161.72 731.122 1142.64 752.398 1142.95 C 790.952 1143.52 852.187 1158.43 882.624 1179.36 C 912.06 1159.77 938.23 1152.97 966.613 1137.46 C 933.936 1081.82 841.879 1065.99 783.499 1067 C 518.61 1094.25 425.858 1371.6 451.438 1602.06 L 453.382 1618.13 C 582.728 1636.62 748.134 1597.01 851.931 1521.4 C 953.222 1447.61 1028.59 1305.85 996.187 1179.36 C 969.744 1193.75 934.896 1205.5 911.322 1220.42 z M 624.209 1067 C 663.756 1024.24 801.711 1004.42 859.585 1024.71 C 834.882 993.777 768.557 949.403 734.582 928.067 C 689.329 896.195 626.684 867.979 574.45 850.664 C 565.801 847.797 555.964 845.051 548.231 840.425 C 529.357 829.134 535.202 795.346 557.806 793.29 C 574.762 791.747 594.243 803.066 610.101 808.112 C 686.088 836.217 805.124 901.186 863.317 957.456 C 852.613 903.863 843.249 823.055 863.317 771.526 C 803.078 672.149 665.648 629.126 555.357 635.989 C 500.936 641.624 449.889 649.695 398.311 669.264 C 372.256 679.149 347.839 692.36 322.412 703.094 C 351.371 820.526 445.376 959.627 543.493 1025.77 C 568.268 1042.47 598.946 1052.4 624.209 1067 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A cut of meat — `BOUCHERIE_POISSONNERIE`/`VIANDES`. */
|
||||
/** Cut of beef (foodiconpack.com, CC BY 4.0) — `BOUCHERIE_POISSONNERIE`/`VIANDES`. */
|
||||
export function MeatIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M8 16 17 7a3 3 0 1 1 4 4l-9 9a5 5 0 0 1-8-2 5 5 0 0 1 4-4z" />
|
||||
<circle cx="9" cy="15" r="1.2" fill="currentColor" stroke="none" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1308.14 523.992 L 1312.6 523.924 C 1454.43 522.805 1610.61 548.182 1716.75 650.075 C 1775.28 706.261 1782.93 751.789 1783.01 826.54 L 1783.04 917.353 C 1783.06 1010.55 1771.33 1066.78 1700.13 1137.95 C 1596.62 1241.42 1403.81 1296.7 1259.76 1326.93 C 1209.71 1337.43 1160.48 1346.4 1120.16 1380.19 C 1012.81 1470.17 871.679 1517.01 732.041 1517.05 C 595.68 1521.18 446.238 1484.54 349.138 1386.9 C 249.032 1286.24 267.821 1219.77 264.451 1097.37 C 263.781 988.817 261.11 910.336 340.139 821.149 C 474.113 669.955 688.1 616.603 883.588 631.743 C 957.665 637.48 921.629 639.588 980.005 611.665 C 1080 554.854 1194.6 532.022 1308.14 523.992 z M 706.25 1286.5 C 818.707 1286.65 934.051 1253.71 1024.51 1185.44 C 1077.93 1145.12 1101.9 1119.99 1171.74 1105.09 C 1337.6 1069.69 1557.82 1016.23 1684.73 894.779 C 1750.71 831.638 1750.35 747.664 1684.27 684.643 C 1595.11 603.34 1458.42 569.51 1340.93 571.393 L 1333.37 571.545 C 1221.14 574.763 1097.45 597.84 999.775 655.417 C 940.411 690.411 951.287 685.266 883.111 678.262 C 713.082 660.793 509.707 714.149 388.559 839.132 C 285.627 945.322 291.099 1078.2 397.649 1178.69 C 480.66 1255.97 594.96 1285.65 706.25 1286.5 z M 311.865 1150.04 C 311.618 1193.12 310.214 1233.21 327.929 1274 C 360.387 1349.74 426.019 1399.76 500.238 1429.77 C 669.049 1498.04 907.387 1480.12 1056.81 1368.31 C 1144.06 1303.01 1155.66 1298.02 1263.78 1277.08 C 1411.68 1248.44 1661.21 1169.28 1720.81 1024.04 C 1727.44 1007.91 1741.85 923.985 1734.47 909.711 C 1629.45 1029.38 1445.12 1090.38 1294.98 1126.72 C 1243.16 1139.26 1192.05 1145.3 1142.31 1163.55 C 1106.31 1176.9 1076.19 1206.59 1044.46 1229.82 C 846.531 1374.72 495.37 1379.86 331.246 1177.56 C 324.617 1169.39 318.964 1158.23 311.865 1150.04 z" />
|
||||
<path d="M 1315.3 625.51 C 1403.29 621.006 1500.96 638.275 1580.27 677.164 C 1633.56 703.294 1709.65 758.76 1674.46 826.622 C 1617.4 936.633 1349.51 1014.12 1219.34 1042.33 C 1150.83 1057.18 1089.02 1071.24 1031.01 1112.69 C 1007.54 1129.45 986.187 1148.63 962.088 1164.65 C 832.042 1251.11 632.613 1256.92 496.247 1184.82 C 391.696 1129.54 330.391 1025.89 391.816 914.601 C 406.591 887.832 433.393 895.208 439.338 922.263 C 439.253 922.61 439.167 922.957 439.082 923.304 L 438.609 925.025 C 431.523 951.486 410.012 974.111 413.43 1009.19 C 415.396 1029.38 424.15 1059.52 439.338 1072.9 C 444.902 1083.93 450.491 1091.78 459.648 1100.35 C 584.804 1217.49 818.215 1211.08 952.793 1113.46 C 1069.9 1022 1106.29 1021.56 1252.02 986.806 C 1361.4 960.723 1533.36 906.124 1616.42 824.398 C 1680.03 768.986 1552.92 713.717 1512.33 700.09 C 1363.35 650.077 1194.11 669.799 1053.38 735.651 C 1022.03 750.322 997.224 723.169 1021.34 697.751 C 1072.9 656.981 1251.49 628.454 1315.3 625.51 z" />
|
||||
<path d="M 646.825 886.362 C 702.457 879.407 786.447 922.225 794.75 982.778 C 801.648 1033.08 763.457 1065.59 718.244 1072.9 C 567.004 1081.28 502.772 901.463 646.825 886.362 z M 718.244 1024.83 C 793.647 1002.49 716.16 931.349 646.825 932.999 C 561.064 956.042 655.25 1036.09 718.244 1024.83 z" />
|
||||
<path d="M 802.826 729.574 C 828.698 729.076 924.149 731.655 942.262 748.115 C 956.777 761.306 943.306 787.771 925.012 787.846 C 907.327 787.919 888.961 782.426 871.344 780.184 C 846.828 777.066 821.805 776.935 797.139 776.997 C 760.02 779.065 724.895 784.441 688.234 789.836 C 663.605 793.461 645.396 771.717 661.968 753.577 C 679.575 734.304 774.344 730.066 802.826 729.574 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Drumstick — `BOUCHERIE_POISSONNERIE`/`VOLAILLES`. */
|
||||
/** Chicken (foodiconpack.com, CC BY 4.0) — `BOUCHERIE_POISSONNERIE`/`VOLAILLES`. */
|
||||
export function PoultryIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M13 4c2.5 0 4.5 2 4.5 4.5 0 2.8-2.3 4.8-4 6.4l-3.2 3.2a2.3 2.3 0 0 1-3.3-3.3l3.2-3.2C11.7 10 13.7 7.7 13 4z" />
|
||||
<path d="M6.5 21 5 22.5" />
|
||||
<path d="M9 18.5 7.5 20" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 675.126 266.323 C 967.235 260.048 1214.44 528.773 1251.89 805.204 C 1266.26 911.248 1251.46 943.654 1312.46 1036.29 C 1335.55 1072.85 1332.23 1115.27 1333 1156.17 C 1341.12 1168.54 1356.96 1182.77 1368.02 1193.66 L 1523.48 1347.01 C 1571.93 1308.81 1626.77 1294.25 1686.33 1320.03 C 1837.19 1385.35 1796.26 1620.31 1605.98 1612.96 C 1607.1 1734 1500.17 1813.09 1387.71 1760.71 C 1313.41 1722.49 1280.75 1625.77 1323.94 1552.68 C 1330.42 1541.72 1340.23 1529.7 1347.93 1519.26 C 1328.45 1504.48 1304.03 1476.31 1285.74 1458.28 C 1242.55 1415.72 1196.92 1372.39 1155.75 1327.99 C 1115.44 1335.98 1066.84 1330.84 1034.25 1304.74 C 952.169 1239.01 919.31 1249.46 819.432 1240.34 C 552.64 1215.97 290.736 994.396 273.345 717.722 C 258.595 483.062 440.327 279.156 675.126 266.323 z M 1012.42 1226.73 C 1064.37 1255.02 1057.57 1277.45 1119.99 1283.74 L 1119.73 1282.45 C 1117.6 1272.41 1113.71 1263.24 1111.83 1252.97 C 1096.94 1171.59 1162.38 1099.72 1244.97 1108.95 C 1257.47 1110.35 1269.74 1114.56 1282.14 1115.55 C 1281.25 1066.26 1263.39 1056.6 1241.35 1018.29 C 1209.13 962.295 1212.42 888.346 1204.72 825.685 C 1172.36 562.105 946.595 307.481 667.216 315.994 C 336.567 353.769 220.698 717.712 423.74 970.752 C 524.402 1096.2 678.735 1180.6 839.46 1192.03 C 911.572 1197.16 943.937 1193.67 1012.42 1226.73 z M 1555.01 1604.22 C 1549.48 1592.78 1542.83 1579.52 1548.04 1566.93 C 1561.96 1533.24 1596 1559.93 1616.77 1561.54 C 1731.75 1570.5 1773.06 1412.29 1665.9 1366.04 C 1579.53 1328.72 1563.03 1415.3 1503.62 1392.76 C 1483.85 1385.26 1452.8 1348.4 1437.09 1332.91 L 1318.07 1213.59 C 1293.75 1189.22 1266.91 1153.69 1227.77 1159.34 C 1167.96 1167.95 1138.15 1231.74 1174.36 1275.3 C 1192.34 1296.95 1216.41 1318.31 1236.62 1338.38 L 1352.4 1454.75 C 1401.76 1503.76 1423.84 1509.57 1371.55 1571.75 C 1356.84 1590.46 1352.85 1621.38 1357.31 1643.95 C 1383.05 1774.38 1573.03 1740.94 1555.01 1604.22 z" />
|
||||
<path d="M 455.532 531 C 499.531 529.749 482.303 565.435 472.293 588.075 C 368.207 819.036 626.45 1051.94 846.232 1066.11 C 854.945 1066.67 864.015 1066.09 872.296 1067.15 C 898.816 1070.54 905.178 1109.95 875.701 1115.55 C 815.373 1120.97 737.3 1099.73 683.214 1075.56 C 484.697 986.826 328.482 768.729 435.173 551.515 C 440.697 540.27 443.403 535.311 455.532 531 z" />
|
||||
<path d="M 595.867 416.13 C 613.538 413.493 629.489 429.31 625.29 446.941 C 621.69 462.056 580.796 475.159 563.709 484.618 C 542.722 487.614 527.439 464.903 536.564 448.282 C 543.497 435.655 582.641 420.266 595.867 416.13 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Fish — `BOUCHERIE_POISSONNERIE`/`POISSONS`. */
|
||||
/** Salmon (foodiconpack.com, CC BY 4.0) — `BOUCHERIE_POISSONNERIE`/`POISSONS`. */
|
||||
export function FishIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M3 12c3-4 8-6 13-4 2.5 1 5 3 5 4s-2.5 3-5 4c-5 2-10 0-13-4z" />
|
||||
<path d="M16 9v6" />
|
||||
<circle cx="6.5" cy="11" r=".6" fill="currentColor" stroke="none" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 848.8 611.959 C 956.068 608.708 1044.51 631.421 1131.39 698.914 C 1148.25 712.014 1165.22 725.562 1177.7 742.966 C 1227.5 742.17 1299.29 739.642 1347.16 745.629 C 1475.32 761.658 1584.27 822.795 1683.11 902.874 C 1728.9 939.97 1819.43 997.284 1769.04 1067.17 C 1695.69 1166.69 1489.7 1273.94 1375.76 1298.94 C 1360.87 1302.21 1346.37 1303.87 1331.48 1306.84 C 1329.74 1307.19 1324.42 1329.42 1322.71 1331.44 C 1312.15 1353.61 1273.12 1434.41 1240.09 1416.29 C 1205.22 1397.16 1161.41 1373.48 1131.54 1347.44 C 1077.87 1401.88 959.556 1430.72 883.367 1434.04 C 864.642 1434.85 841.26 1438.14 830.499 1420.07 C 824.192 1403.88 839.343 1383.72 845.775 1368.4 C 859.93 1334.68 868.734 1300.99 871.477 1264.51 C 811.517 1248.31 719.051 1202.21 660.649 1176.38 C 635.966 1165.47 610.267 1149.44 584.486 1142.76 C 569.097 1138.77 527.699 1150.22 516.836 1159.32 C 477.658 1192.15 410.834 1284.93 366.594 1293.68 C 328.759 1301.16 292.698 1283.67 276.582 1248.61 C 257.157 1207.52 278.639 1177.56 298.038 1142.08 L 333.333 1078.66 C 358.335 1034.54 356.085 1028.74 329.607 985.464 C 313.639 959.826 300.153 929.214 283.839 904.045 C 234.677 828.199 326.659 715.631 421.756 816.857 L 467.491 866.032 C 528.722 932.931 541.448 935.655 629.953 894.25 C 675.649 872.872 818.201 795.24 861.408 788.49 C 869.971 739.897 865.6 713.87 843.414 669.403 C 836.835 652.967 807.955 621.582 848.8 611.959 z M 1035.23 1260.01 C 1084.67 1266.26 1121.21 1264.44 1170.4 1264.52 C 1268.29 1264.66 1372.07 1266.46 1462.35 1221.55 C 1318.94 1111.49 1314.37 931.696 1452.56 819.114 C 1376.92 794.541 1343.35 788.251 1265.28 788.502 L 1181.4 788.532 C 1154.8 788.512 1108.16 785.885 1084.93 788.49 C 912.171 783.433 754.681 894.896 602.061 958.678 C 542.216 981.383 542.976 1064.44 597.361 1093.98 C 632.316 1112.96 667.984 1128.68 704.288 1144.7 C 784.236 1179.98 949.211 1257.44 1035.23 1260.01 z M 1513.21 1199.7 C 1538.49 1186.5 1565.23 1169.12 1588.88 1157.14 L 1590.18 1156 C 1637 1115.8 1692.77 1091.87 1732.84 1037.59 C 1761.99 998.107 1583.28 886.745 1555.05 868.355 C 1541.64 859.615 1517.43 849.614 1506.72 840.812 C 1354.84 928.932 1366.55 1109.04 1513.21 1199.7 z M 538.028 1100.01 C 508.305 1065.26 497.667 1001.63 528.261 965.1 C 463.125 943.727 445.497 907.33 398.237 859.615 C 383.478 844.714 370.882 820.78 347.424 822.164 C 278.723 836.198 344.977 918.628 362.722 950.128 C 411.207 1029.13 405.5 1044.14 363.451 1120.08 L 331.581 1177.83 C 318.823 1201.08 302.259 1225.54 331.962 1244.78 C 361.159 1263.68 384.283 1227.56 401.021 1210.56 C 422.042 1188.15 474.084 1130.68 497.432 1116.97 C 510.269 1109.44 524.88 1106.52 538.028 1100.01 z M 1207.23 1311.42 L 1162.01 1311.42 C 1184.76 1333.38 1215.11 1349.18 1241.53 1366.8 C 1243.02 1366.96 1246.95 1367.54 1248.19 1366.8 C 1261.56 1345.86 1269.05 1333.98 1280.47 1311.42 L 1207.23 1311.42 z M 1111.06 742.966 C 1058.95 692.943 961.972 655.509 888.733 656.971 C 908.887 698.018 911.838 724.895 910.908 769.027 C 958.966 755.726 1031.03 738.7 1080.72 742.966 C 1094.34 741.284 1097.55 741.422 1111.06 742.966 z M 917.364 1281.01 C 910.429 1325.55 901.194 1346.02 888.733 1386.83 C 940.753 1385.27 990.679 1371.4 1038.67 1352.01 C 1060.75 1342.49 1086.55 1329.1 1103.09 1311.42 C 1030.94 1309.91 986.962 1301.35 917.364 1281.01 z" />
|
||||
<path d="M 1138.71 911.138 C 1141.01 911.319 1146.16 911.143 1148.69 911.138 C 1179.13 922.018 1172.09 950.887 1143.5 958.005 C 1080.14 973.783 1025.18 1007.98 985.657 1060.08 C 976.182 1072.57 973.205 1084.48 959.841 1094.55 C 936.953 1100.24 920.042 1077.29 934.135 1054.15 C 980.888 977.368 1054.04 933.806 1138.71 911.138 z" />
|
||||
<path d="M 935.933 915.073 C 953.045 914.272 967.591 929.458 961.074 946.565 C 955.463 961.292 877.195 1000.11 851.021 1070.08 C 846.765 1081.41 842.067 1089.7 830.499 1094.55 C 792.917 1099.31 802.165 1062.07 811.947 1040.99 C 832.235 997.279 888.251 932.996 935.933 915.073 z" />
|
||||
<path d="M 1562.6 950.18 C 1573.55 948.189 1584.28 954.59 1587.73 965.166 C 1589.08 969.304 1589.19 973.747 1588.04 977.944 C 1585.68 986.54 1578.42 992.9 1569.59 994.105 C 1563.05 994.998 1556.45 992.937 1551.57 988.479 C 1546.7 984.021 1544.06 977.628 1544.36 971.03 C 1544.85 960.681 1552.41 952.035 1562.6 950.18 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Shrimp — `BOUCHERIE_POISSONNERIE`/`CRUSTACES_FRUITS_DE_MER`. */
|
||||
/** Shrimp (foodiconpack.com, CC BY 4.0) — `BOUCHERIE_POISSONNERIE`/`CRUSTACES_FRUITS_DE_MER`. */
|
||||
export function ShellfishIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M6 6c6-2 12 1 13 7-4 2-9 2-12-1C5 10 4.5 8 6 6z" />
|
||||
<path d="M6 6 4 4M7 8 5 7M8 11l-2.2.6" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 406.938 329.975 C 420.456 328.426 461.606 329.929 478.111 329.968 L 1131.51 329.551 C 1470.33 330.801 1773.34 636.729 1768.97 977.539 C 1764.28 1343.03 1477.67 1613.49 1117.55 1611 C 1016.13 1682.55 903.221 1736.61 783.079 1768.46 C 758.473 1774.04 730.58 1783.31 705.357 1777.76 C 660.154 1767.83 630.173 1723.58 639.98 1678 C 647.173 1644.57 671.241 1628.08 695.497 1606.99 L 753.833 1556.35 C 766.873 1545.02 790.931 1521.56 803.533 1513.61 C 790.311 1499.24 771.674 1488.17 755.927 1475.94 C 715.955 1444.91 649.476 1381.54 635.493 1332.75 C 627.423 1290.39 652.422 1253.62 692.825 1240.15 C 753.277 1220.01 936.175 1304.57 988.999 1331.68 C 1020.6 1347.9 1050.88 1366.76 1082.58 1383.33 C 1098.43 1377.29 1127.57 1377.77 1146.56 1371.73 C 1207.35 1352.41 1259.45 1319.26 1291.51 1263.48 C 1337.45 1180.16 1370.08 1123.42 1342.98 1021.19 C 1316.94 922.937 1222.75 846.004 1121.11 839.379 C 1097.59 837.846 1033.24 836.114 1012.94 842.367 C 953.966 860.532 924.33 939.719 967.505 988.121 C 993.163 1016.89 1013.51 1022.95 1040.23 1043.51 C 1064.01 1062.28 1038.26 1106.8 1000.82 1081.01 C 904.072 1014.36 858.029 948.67 929.502 838.946 C 864.678 837.299 799.62 883.674 812.342 953.152 C 821.604 1003.73 876.964 1022.7 892.321 1055.77 L 892.93 1057.12 C 883.291 1126.99 828.577 1062.01 807.666 1042.42 C 746.966 985.54 742.644 897.667 803.533 838.946 C 770.437 837.973 735.885 840.112 702.975 838.728 C 512.664 830.723 353.129 724.246 298.564 537.94 C 282.773 484.022 265.073 438.935 302.954 387.995 C 327.141 355.47 367.174 335.163 406.938 329.975 z M 359.088 401.252 C 381.815 416.537 394.861 433.427 415.235 451.468 C 467.804 498.019 526.934 511.39 594.352 518.344 C 592.133 535.017 589.861 552.495 589.477 569.33 C 513.374 562.715 444.814 542.9 385.02 493.222 C 368.174 479.227 346.168 454.633 330.488 443.841 C 330.307 514.756 386.704 628.592 434.824 676.604 C 507.857 749.474 621.768 788.396 723.801 788.689 L 843.532 788.441 C 860.28 788.413 882.053 787.742 898.5 788.49 L 1038.36 788.472 C 1140.07 788.443 1215.01 788.544 1299.22 861.535 C 1441.2 811.164 1518.19 711.434 1544.44 565.358 C 1402.87 437.853 1295.28 380.584 1095.5 380.613 L 693.374 380.636 L 433.545 380.606 C 406.526 380.464 381.311 384.734 359.088 401.252 z M 1338.86 903.254 C 1379.94 947.312 1405.12 1027.41 1402.49 1086.62 C 1431.25 1098.02 1443.96 1104.03 1475.01 1111.46 C 1567.78 1128.49 1642.63 1113.9 1713 1048.13 C 1737.19 927.145 1693.35 769.641 1627.82 668.707 C 1620.68 657.712 1597.84 623.903 1589.87 616.435 C 1585.79 627.183 1582.78 638.577 1579.17 649.494 C 1537.83 774.365 1453.88 850.262 1338.86 903.254 z M 1237.15 1388.06 C 1188.44 1418.7 1089.96 1431.25 1072.26 1448.24 C 1033.97 1485 1054.23 1546.57 1105.82 1555.71 C 1127.29 1559.52 1143.48 1559.29 1164.89 1558.99 C 1217.37 1553.15 1303.79 1539.24 1351.4 1513.61 C 1305.52 1486.7 1268.61 1456.41 1246.45 1406.36 C 1244.91 1402.89 1239.04 1389.72 1237.15 1388.06 z M 1696.63 1125.81 C 1619.77 1182.53 1484.66 1181.57 1398.81 1140.65 C 1380.05 1217.41 1341.24 1299.85 1284.26 1355.35 C 1290.07 1379.84 1300.31 1401.23 1315.82 1421.15 C 1344.11 1455.26 1377.21 1471.4 1419.23 1481.63 C 1518.71 1443.09 1623.53 1309.89 1665.65 1216.82 C 1671.57 1203.75 1698.88 1137.41 1696.63 1125.81 z M 713.193 1287.13 C 701.618 1289.96 695.911 1294.19 689.674 1304.42 C 663.742 1346.97 811.935 1456.07 841.285 1472.47 C 900.389 1505.48 885.289 1511.25 835.595 1553.03 C 815.59 1569.86 702.246 1665.68 692.995 1681.95 C 684.317 1697.21 693.107 1716.72 707.541 1724.7 C 725.346 1734.55 814.134 1707.12 840.752 1696.69 C 887.26 1680.39 1018.53 1623.86 1054.87 1588.48 C 982.295 1545.74 989.462 1476.4 1031.52 1412.55 C 939.345 1365.12 862.757 1323.81 759.293 1296.88 C 748.377 1294.04 723.137 1286.46 713.193 1287.13 z" />
|
||||
<path d="M 663.454 505.915 C 691.733 504.193 716.17 525.462 718.366 553.708 C 720.562 581.954 699.707 606.745 671.502 609.415 C 652.83 611.183 634.657 602.742 623.963 587.335 C 613.268 571.929 611.715 551.952 619.9 535.077 C 628.084 518.202 644.734 507.055 663.454 505.915 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Wheat ear — grains, pasta, rice, flour (`EPICERIE_SECHE`/`FECULENTS`, and flours under `AIDES_CULINAIRES`/`BASES`). */
|
||||
/** Bowl of rice (foodiconpack.com, CC BY 4.0) — grains, pasta, rice, flour (`EPICERIE_SECHE`/`FECULENTS`, and flours under `AIDES_CULINAIRES`/`BASES`). */
|
||||
export function GrainIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M12 22V6" />
|
||||
<path d="M12 7c-1-1-2.5-1-3.5 0 1 1 2.5 1 3.5 0zM12 7c1-1 2.5-1 3.5 0-1 1-2.5 1-3.5 0z" />
|
||||
<path d="M12 11c-1-1-2.5-1-3.5 0 1 1 2.5 1 3.5 0zM12 11c1-1 2.5-1 3.5 0-1 1-2.5 1-3.5 0z" />
|
||||
<path d="M12 15c-1-1-2.5-1-3.5 0 1 1 2.5 1 3.5 0zM12 15c1-1 2.5-1 3.5 0-1 1-2.5 1-3.5 0z" />
|
||||
<path d="M12 6c0-1.5.8-2.8 2-3.5" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 910.895 546.004 C 963.142 539.715 976.976 568.025 1015.6 572.175 C 1067.55 579.738 1143.11 497.126 1227.25 585.607 C 1265.7 626.042 1281.37 619.731 1333.45 613.476 C 1383.16 607.852 1437.33 637.737 1461.72 680.486 C 1492.16 733.835 1478.5 737.073 1547.2 751.088 C 1597.77 761.402 1642.54 804.035 1653.56 854.79 C 1665.08 907.857 1662.91 902.985 1711.69 916.532 C 1799.22 942.499 1808.65 1051.5 1730.7 1105.25 C 1727.55 1124.49 1717.68 1147.67 1709.51 1165.97 C 1652.44 1293.6 1556 1408.56 1434.94 1479.6 C 1399.88 1500.17 1350.75 1495.6 1311.33 1495.59 L 1166.25 1495.54 L 723.987 1495.58 C 638.136 1495.6 617.325 1495.43 547.632 1438.36 C 439.637 1349.93 360.588 1238.54 320.186 1105.25 C 312.672 1099 303.794 1093.43 296.379 1086.6 C 244.517 1038.86 257.881 948.348 324.422 920.61 C 391.508 892.645 373.956 917.684 398.634 843.682 C 412.775 801.278 451.966 762.448 495.675 751.506 C 559.266 735.587 546.838 742.839 577.749 688.391 C 606.277 638.141 663.732 605.766 721.87 613.943 C 767.387 620.345 778.161 626.969 813.557 592.239 C 844.736 562.253 867.315 547.981 910.895 546.004 z M 1123.75 593.348 C 1084.78 598.665 1072.65 620.665 1024.21 620.51 C 969.014 620.334 957.472 587.74 904.098 595.381 C 848.119 603.395 838.626 672.347 751.279 667.428 C 704.28 664.781 671.206 644.723 631.072 697.809 C 616.411 717.201 609.97 741.06 592.558 758.55 C 559.137 801.379 512.288 783.678 474.207 817.233 C 408.119 875.466 479.926 921.396 341.041 966.749 C 318.781 974.018 305.366 1019.28 319.921 1037.81 C 341.458 1075.3 386.538 1066.19 423.368 1066.19 L 899.75 1066.17 L 1656.66 1066.27 C 1667.48 1066.37 1677.69 1066.49 1688.01 1066.01 C 1737.09 1063.73 1767.97 972.945 1671.43 957.275 C 1606.38 946.716 1612.76 858.359 1586.19 827.757 C 1544.59 779.835 1485.94 815.998 1438.22 741.598 C 1430.31 729.264 1425.74 714.695 1418.47 702.944 C 1361 610.666 1286.49 715.282 1206.1 632.781 C 1182.1 608.147 1161.13 589.594 1123.75 593.348 z M 1549.74 1321.46 C 1604.47 1258.73 1646.54 1193.93 1677.04 1115.97 C 1648.29 1115.12 1615.43 1115.97 1586.23 1115.97 L 422.623 1115.92 C 411.331 1115.9 386.023 1114.98 376.085 1115.97 C 407.412 1231.51 523.469 1368.3 622.564 1429.74 C 649.154 1446.22 669.529 1445.25 699.498 1445.35 L 1328.1 1445.41 C 1341.98 1445.41 1382.15 1446.27 1393.43 1442.45 C 1444.74 1425.09 1514.43 1360.06 1549.74 1321.46 z" />
|
||||
<path d="M 1141.2 687.994 C 1152.75 687.403 1193.43 686.078 1203.08 691.804 C 1220.91 702.389 1217.43 733.715 1193.16 737.983 C 1183.34 738.44 1167.74 737.105 1157.57 738.156 C 1108.99 743.18 1107.03 692.946 1141.2 687.994 z" />
|
||||
<path d="M 560.473 908.045 C 619.234 904.862 574.313 970.708 540.825 986.982 C 518.475 987.906 510.767 975.204 511.027 954.689 C 517.878 941.655 546.665 912.691 560.473 908.045 z" />
|
||||
<path d="M 779.781 947.003 C 793.571 946.901 828.187 944.959 838.316 951.744 C 856.542 963.953 848.523 992.185 826.199 996.97 C 815.153 997.062 780.364 998.789 771.828 994.339 C 750.301 983.115 754.338 953.94 779.781 947.003 z" />
|
||||
<path d="M 1056.75 865.234 C 1113.77 869.736 1066.37 938.797 1032.46 943.013 C 974.711 946.264 1028.33 872.554 1056.75 865.234 z" />
|
||||
<path d="M 936.348 737.983 C 958.3 731.257 1033.13 789.941 976.649 809.248 C 951.821 813.134 884.764 753.056 936.348 737.983 z" />
|
||||
<path d="M 1231.76 918.185 C 1252.8 919.574 1319.83 980.518 1263.2 996.97 C 1239.04 996.092 1175.04 929.033 1231.76 918.185 z" />
|
||||
<path d="M 1342.56 781.027 C 1369.08 780.782 1429.65 832.584 1379.48 858.001 C 1356.36 858.94 1292.24 799.262 1342.56 781.027 z" />
|
||||
<path d="M 1472.67 943.013 C 1485.27 942.888 1509.89 941.296 1520.38 945.509 C 1544.12 955.043 1537.31 985.279 1514.98 993.49 C 1502.43 993.656 1468.83 995.776 1460.6 989.535 C 1440.72 974.456 1448.11 947.945 1472.67 943.013 z" />
|
||||
<path d="M 709.837 784.339 C 734.276 782.429 793.224 845.928 739.437 858.001 C 711.809 858.665 655.635 792.466 709.837 784.339 z" />
|
||||
<path d="M 1503.8 1239.21 C 1533.06 1239.99 1537.22 1267.62 1517.1 1287.65 C 1436.03 1368.35 1341.5 1414.67 1226.01 1414.01 C 1220.84 1414.36 1216.6 1414.17 1211.46 1414.01 C 1195.42 1398.13 1192.26 1372.96 1219.01 1365.73 C 1229.64 1362.85 1247.87 1365.62 1259.95 1364.16 C 1337.23 1354.87 1410.36 1318.8 1467.44 1266.26 C 1478.54 1256.05 1488.75 1242.33 1503.8 1239.21 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Bean pod — `EPICERIE_SECHE`/`LEGUMINEUSES`. */
|
||||
/** Chickpeas (foodiconpack.com, CC BY 4.0) — `EPICERIE_SECHE`/`LEGUMINEUSES`. */
|
||||
export function LegumeIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M5 13c0-5 3.5-9 9-9 1 3-1 5-1 5s3 0 5 3c0 5-4 9-9 9-3 0-4-4-4-8z" />
|
||||
<circle cx="9.5" cy="14.5" r="1" fill="currentColor" stroke="none" />
|
||||
<circle cx="13" cy="11" r="1" fill="currentColor" stroke="none" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1013.51 267.432 C 1054.43 264.207 1100.24 278.034 1122.05 315.518 C 1180.56 423.993 1241.02 436.027 1342.11 494.876 C 1471.74 570.336 1530.28 681.13 1486.64 829.74 C 1545.75 839.209 1566.11 892.267 1599.8 933.697 C 1671.83 1022.28 1715.25 1055.05 1758.15 1175.38 C 1841.93 1412.28 1678.03 1672.39 1450.72 1750.57 C 1308.73 1799.41 1173.53 1789.15 1039.67 1724.49 C 1033.19 1721.15 1031.84 1719.92 1026.34 1715.26 C 991.374 1733.25 959.676 1746.61 921.88 1758.18 C 494.809 1888.38 94.9953 1429.32 345.529 1050.14 C 361.138 1026.52 380.493 1006.19 399.199 985.2 L 459.204 916.706 C 496.056 874.184 500.503 852.333 555.689 829.74 C 553.61 813.771 548.9 797.541 547.223 780.596 C 535.851 665.686 596.471 557.081 693.829 497.779 C 775.149 450.088 855.239 428.656 910.864 340.514 C 940.561 293.459 948.947 274.699 1013.51 267.432 z M 603.727 818.271 C 656.652 822.916 692.575 834.894 725.311 879.589 C 742 902.376 784.999 948.72 789.685 973.144 C 786.727 981.272 784.008 987.91 775.434 991.692 C 729.185 1012.41 703.046 876.839 618.793 869.227 C 570.664 864.878 545.066 886.976 517.381 922.752 C 464.085 991.627 394.366 1049.45 357.354 1129.84 C 193.102 1485.16 574.214 1833.93 933.689 1702.98 C 946.781 1698.22 964.505 1691.1 976.179 1684.07 C 952.103 1667.53 938.458 1655.36 917.845 1634.5 C 744.671 1462.32 720.803 1208.92 873.734 1012.23 C 892.823 987.684 916.515 966.264 935.633 941.851 C 957.672 913.706 977.482 885.57 1001.01 858.542 C 1017.98 839.054 1039.72 835.474 1054.3 822.633 C 1082.47 752.252 1063.19 669.654 1042.05 599.577 C 1035.1 576.532 1012.05 548.352 1026.34 525.437 C 1041.77 515.724 1060.34 514.993 1068.52 533.407 C 1106.84 619.641 1132.29 725.908 1108.23 818.271 C 1202.19 821.085 1218.89 888.402 1272.32 942.071 C 1315.86 881.786 1339.44 833.735 1419.84 820.857 L 1436.6 818.271 C 1456.63 760.852 1462.07 712.001 1433.76 653.521 C 1376.68 535.596 1237.13 516.899 1150.84 435.265 C 1134.31 419.621 1120.85 404.766 1107.08 386.506 C 1081.94 353.152 1071.33 305.846 1013.51 316.999 C 969.085 322.72 966.599 349.547 944.081 380.567 C 880.308 468.417 812.995 487.203 728.093 534.855 C 623.605 594.958 577.059 702.123 603.727 818.271 z M 1528.15 1658.14 C 1627.22 1594.53 1696.94 1496.13 1723.35 1380.9 C 1762.22 1211.25 1665.02 1075.29 1557.48 958.821 C 1542.9 943.033 1532.25 922.398 1518.81 905.496 C 1486.7 865.13 1425.17 857.059 1382.59 885.343 C 1336.85 915.728 1307.92 1010.81 1265.44 1008.94 C 1253.38 1002.37 1245.86 990.451 1237.49 979.5 L 1198.79 928.664 C 1167.97 887.694 1148.14 864.117 1089.75 868.031 C 1026.85 877.015 1005.09 937.006 967.48 979.741 C 947.131 1002.86 924.883 1024.48 906.383 1049.2 C 617.866 1445.08 1108.89 1920.89 1528.15 1658.14 z" />
|
||||
<path d="M 803.837 570.263 C 816.631 568.791 828.258 576.677 831.432 589.333 C 842.771 634.54 728.622 623.76 711.544 730.095 C 708.383 749.772 710.529 763.493 688.265 769.926 C 612.6 773.1 694.982 592.905 803.837 570.263 z" />
|
||||
<path d="M 541.29 1433.12 C 592.324 1430.63 559.382 1475.46 570.723 1502.01 C 578.27 1519.67 633.086 1547.81 584.185 1569.25 C 532.537 1578.43 488.747 1458.63 541.29 1433.12 z" />
|
||||
<path d="M 497.481 1073.17 C 542.167 1068.57 514.288 1121.55 524.535 1142.28 C 531.879 1158.98 584.927 1184.58 537.784 1205.85 C 479.931 1210.95 445.843 1093.14 497.481 1073.17 z" />
|
||||
<path d="M 460.572 1258.23 C 478.638 1255.82 486.678 1260.75 492.994 1277.94 C 476.761 1349.19 512.767 1321.04 523.439 1358.64 C 521.051 1370.57 519.267 1378.45 507.165 1383.62 C 448.938 1399.11 412.727 1273.75 460.572 1258.23 z" />
|
||||
<path d="M 600.018 1258.23 C 618.259 1255.06 625.732 1263.03 633.327 1277.94 C 624.777 1322.36 625.753 1319.44 660.777 1346.4 C 669.325 1363.88 664.223 1374.38 647.895 1383.62 C 590.88 1393.42 551.674 1281.29 600.018 1258.23 z" />
|
||||
<path d="M 682.829 1466.11 C 722.586 1464.19 702.352 1506.18 715.124 1525.8 C 731.787 1551.4 754.706 1540.44 750.813 1578.22 C 745.417 1586.64 742.894 1589.39 733.455 1592.05 C 674.449 1594.11 630.88 1478.9 682.829 1466.11 z" />
|
||||
<path d="M 1521.8 1333.05 C 1604.54 1329.65 1516.57 1582.4 1323.12 1597.61 C 1283.49 1602.06 1282.02 1552.74 1320.79 1550.14 C 1411.56 1544.06 1487.33 1464.11 1502.59 1376.73 C 1505.99 1357.39 1500.88 1341.19 1521.8 1333.05 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Acorn — nuts, seeds, dried fruit (`EPICERIE_SECHE`/`GRAINES_FRUITS_SECS`). */
|
||||
/** Almonds (foodiconpack.com, CC BY 4.0) — nuts, seeds, dried fruit (`EPICERIE_SECHE`/`GRAINES_FRUITS_SECS`). */
|
||||
export function NutSeedIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M8 9c0-3 1.8-5 4-5s4 2 4 5" />
|
||||
<path d="M8 9c-1.3.5-2 1.7-2 3 0 4 2.7 8 6 8s6-4 6-8c0-1.3-.7-2.5-2-3z" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 519.112 267.004 C 702.645 256.911 911.605 357.081 1054.42 464.673 C 1072.21 478.076 1088.34 493.554 1106.8 506.701 C 1245.69 449.076 1395.16 403.644 1547.04 409.112 C 1629.93 412.095 1660.51 492.578 1689.44 554.52 C 1857.55 927.686 1771.06 1348.1 1451.5 1602.11 C 1339.67 1689.88 1222.33 1748.42 1080.95 1770.26 C 771.484 1818.07 513.153 1603.38 513.066 1285.81 C 502.419 1269.82 489.949 1256.71 477.939 1241.8 C 284.456 1001.56 225.698 709.39 317.313 414.961 C 353.481 298.726 401.491 280.595 519.112 267.004 z M 808.738 1686.51 C 979.643 1758.72 1155.35 1723.52 1311.77 1634.91 C 1683.71 1424.22 1814.95 995.958 1657.65 609.48 C 1628.44 537.714 1608.99 455.036 1518.77 459.076 C 1470.96 458.583 1411.28 469.23 1364.38 478.775 C 987.814 554.845 568.571 857.845 563.56 1275.76 C 561.466 1450.38 646.303 1612.43 808.738 1686.51 z M 519.112 1210.8 C 522.564 1130.73 555.869 1031.88 594.69 962.196 C 703.455 761.458 852.304 636.013 1051.95 530.327 C 935.6 416.368 695.525 316.867 538.275 318.468 L 535.514 318.51 C 448.008 322.098 397.228 334.307 369.794 418.571 C 308.175 607.84 308.507 825.828 393.368 1008.1 C 422.163 1069.95 470.683 1160.88 519.112 1210.8 z" />
|
||||
<path d="M 1598.68 637.027 C 1634.96 636.008 1631.43 677.59 1633.38 703.782 C 1660.05 1061.69 1409.99 1438.78 1084.43 1578.07 C 1064.07 1586.81 1041.58 1592.74 1021.7 1602.11 C 990.429 1606.96 975.681 1570.64 1001.28 1554.63 C 1009.08 1549.75 1029.21 1545.21 1039.32 1541.41 C 1365.76 1418.7 1617.24 1037.19 1580.86 690.388 C 1578.23 665.352 1572.45 648.646 1598.68 637.027 z" />
|
||||
<path d="M 1389.55 513.084 C 1420.88 510.153 1436.47 548.477 1389.63 565.042 C 1089.32 671.257 809.481 970.365 747.434 1282.07 C 742.975 1304.47 747.472 1327.49 723.119 1337.73 C 681.185 1342.02 692.713 1296.79 697.737 1271.86 C 761.679 954.621 1070.79 601.146 1389.55 513.084 z" />
|
||||
<path d="M 1494.44 587.89 C 1524.5 579.666 1540.77 618.733 1515.32 632.093 C 1372.83 706.936 1263.57 829.757 1171.91 959.7 C 1139.08 1006.24 1014.2 1175.99 970.965 1198.24 C 947.407 1203.31 937.48 1187.92 938.726 1166.33 C 943.263 1158.9 948.465 1153.71 954.576 1147.64 C 1044.16 1059.82 1109.56 953.698 1185.75 855.011 C 1236.48 789.295 1408.64 609.858 1494.44 587.89 z" />
|
||||
<path d="M 1401 947.194 C 1418.44 945.106 1439.07 954.077 1432.88 975.625 C 1421.27 1015.98 1145.22 1271.37 1107.5 1304.52 C 1042.37 1361.77 937.778 1453.82 860.358 1494.78 C 831.809 1503.03 814.986 1466.14 838.867 1449.69 C 1033.05 1315.88 1206.91 1153.58 1366.06 980.106 C 1377.42 967.723 1385.63 955.132 1401 947.194 z" />
|
||||
<path d="M 527.238 530.327 C 549.916 527.673 555.705 542.414 564.717 559.516 C 588.26 604.191 625.411 639.87 670.146 662.994 C 694.395 675.528 698.057 700.841 670.807 714.082 C 626.871 720.144 456.047 564.148 527.238 530.327 z" />
|
||||
<path d="M 660.624 459.076 C 692.344 451.902 788.132 507.422 813.942 524.957 C 838.38 541.56 870.027 569.964 826.773 587.89 C 807.313 588.78 792.835 572.687 776.913 563.127 C 746.27 544.726 715.103 527.143 681.523 514.651 C 670.352 510.495 655.348 506.303 645.654 499.785 C 635.539 480.478 638.726 465.073 660.624 459.076 z" />
|
||||
<path d="M 482.823 714.082 C 516.153 713.195 527.206 774.263 543.132 805.241 C 552.817 824.079 600.887 864.414 554.102 881.663 C 513.988 888.85 477.292 790.388 467.37 760.79 C 460.711 740.926 458.572 720.487 482.823 714.082 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -161,34 +195,46 @@ export function DoughIcon() {
|
|||
);
|
||||
}
|
||||
|
||||
/** A glass of milk — `CREMERIE_FROMAGE`/`PRODUITS_LAITIERS` (non-cheese items). */
|
||||
/** Milk carton (foodiconpack.com, CC BY 4.0) — `CREMERIE_FROMAGE`/`PRODUITS_LAITIERS` (non-cheese items). */
|
||||
export function MilkIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M9 2h6l1 4-1 2v11a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2V8L8 6z" />
|
||||
<path d="M8 12h8" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 779.86 268.928 C 786.444 267.776 808.108 268.835 816.425 268.869 L 1239.47 268.892 C 1251.42 268.887 1268.83 268.222 1280.22 270.224 C 1339.49 280.641 1344.2 346.945 1336.34 394.552 C 1333.06 414.436 1314.38 431.172 1297.92 441.343 C 1343.66 501.777 1394.45 593.378 1433.99 659.263 C 1437.18 664.566 1436.96 682.486 1436.96 688.894 L 1436.9 1658.43 C 1436.9 1715.13 1419.86 1772.69 1350.61 1776.96 C 1324.54 1778.56 1292.75 1777 1265.75 1776.99 L 753.887 1777.04 C 732.737 1777.04 709.252 1778.28 688.335 1775.73 C 599.133 1764.87 608.939 1673.48 609.023 1609.46 L 608.924 696.324 C 608.915 673.385 605.675 665.681 618.805 645.763 L 726.465 482.971 C 733.856 471.575 743.878 455.001 752.327 444.99 C 733.226 431.892 711.239 414.788 710.597 389.124 C 709.27 336.038 711.774 275.09 779.86 268.928 z M 655.997 1095.86 C 657.228 1141.69 655.977 1193.52 655.975 1239.96 L 656.084 1623.69 C 655.355 1718.28 650.722 1730.19 740.845 1730.1 L 816.5 1730 L 1290.37 1730.04 C 1386.14 1730.08 1389.84 1729.99 1389.77 1623.23 L 1389.71 1148.13 C 1326.18 1144.56 1312.21 1136.26 1263.63 1103.33 C 1228.78 1079.71 1200.2 1069.82 1158.5 1074.03 C 1070.35 1066.06 1015.65 1198.45 855.662 1123.37 C 838.033 1115.1 822.796 1101.08 805.94 1091.4 C 757.478 1065.7 701.947 1067.96 655.767 1096.35 L 655.997 1095.86 z M 655.997 690.535 C 655.415 763.136 655.026 841.111 655.997 913.692 C 746.649 887.151 780.349 903.836 853.481 945.995 C 930.868 991.394 983.241 907.17 1087.85 963.051 C 1113.47 976.738 1106.78 1005.09 1077.6 1006.99 C 1066.09 1006.09 1056.19 1001.04 1045.4 997.401 C 1009.64 985.352 979.087 995.291 943.904 1003.48 C 839.891 1027.7 809.781 942.719 713.57 950.335 C 693.938 951.889 673.206 956.794 655.997 966.626 C 656.723 992.751 655.768 1015.71 655.997 1041.3 C 694.992 1023.56 752.692 1022.39 793.757 1034.87 C 869.483 1061.19 899.87 1121.11 996.655 1089.42 C 1022.81 1081.16 1043.67 1061.28 1067.84 1048.91 C 1135 1012.98 1229.18 1018.3 1290.63 1064.07 C 1328.35 1092.16 1342.12 1096.6 1389.71 1101.24 L 1389.71 690.535 L 831.485 690.535 L 655.997 690.535 z M 811.491 314.999 C 747.852 317.269 756.708 337.816 757.996 390.049 L 767.34 398.009 C 780.479 399.085 802.178 398.047 816.382 398.026 L 1238.5 398.009 C 1261.58 398.307 1274.96 401.836 1292.06 385.935 C 1293.53 346.238 1300.37 314.504 1250.44 314.994 L 811.491 314.999 z M 1247.5 642.987 L 1370.52 642.987 C 1343.23 602.272 1316.23 557.822 1290.1 516.091 L 1268.91 482.018 C 1260.74 469.147 1253.32 459.132 1247.5 444.99 L 805.895 444.99 C 768.179 506.706 711.428 581.218 679.918 642.987 C 742.577 643.629 806.436 642.983 869.179 642.982 L 1247.5 642.987 z" />
|
||||
<path d="M 1292.06 1165.05 C 1304.47 1164.51 1314.69 1166.59 1319.57 1179.63 C 1322.97 1188.7 1321.03 1350.47 1321.04 1369.31 L 1321.05 1461.22 C 1321.05 1481.43 1329.33 1524.23 1304.49 1531.79 C 1260.85 1538.42 1274.43 1475.3 1274.42 1445 L 1274.43 1237.1 C 1274.44 1210.88 1265.14 1174.39 1292.06 1165.05 z" />
|
||||
<path d="M 1292.06 1574.06 C 1335.71 1570.9 1327.92 1660.46 1304.49 1669.63 C 1268.65 1672.77 1273.3 1645.42 1274.39 1620.96 C 1275.27 1601.16 1267.84 1583.13 1292.06 1574.06 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A wedge of cheese with holes — `CREMERIE_FROMAGE`/`PRODUITS_LAITIERS` (cheese items). */
|
||||
/** Wedge of cheddar (foodiconpack.com, CC BY 4.0) — `CREMERIE_FROMAGE`/`PRODUITS_LAITIERS` (cheese items). */
|
||||
export function CheeseIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M3 18 20 8l1 3-3 9H4z" />
|
||||
<circle cx="10" cy="15" r="1" fill="currentColor" stroke="none" />
|
||||
<circle cx="15" cy="13" r="1" fill="currentColor" stroke="none" />
|
||||
<circle cx="14" cy="17" r="1" fill="currentColor" stroke="none" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 499.059 422.998 C 555.378 418.116 619.709 441.465 674.623 455.762 L 996.114 542.998 C 1031.04 552.641 1041.28 552.872 1077.38 553.146 L 1616.68 553.023 C 1682.85 553.02 1770.16 548.867 1778.65 640.355 C 1780.01 655.048 1779.18 672.498 1779.17 687.571 L 1779.16 1476.37 C 1779.25 1597.67 1748.78 1621.04 1630.31 1620.91 L 1465.34 1620.9 C 1443.94 1620.93 1414.19 1625.68 1399.87 1607.61 C 1398.05 1604.38 1394.76 1596.66 1393.72 1593.03 C 1377.2 1535.41 1297.2 1530.02 1271.36 1582.6 C 1248.88 1628.34 1248.64 1620.97 1198.19 1620.66 L 427.301 1620.9 C 358.787 1620.91 283.221 1625.78 266.731 1535.13 C 264.224 1521.36 265.662 1500.67 265.734 1486.47 L 265.757 1263.78 C 265.759 1237.69 263.842 1189.05 268.742 1163.04 C 271.121 1150.41 300.728 1146.42 313.047 1140.68 C 369.139 1114.52 366.57 1036.24 306.189 1016.72 C 262.308 1002.37 265.669 999.106 265.716 951.744 L 265.77 737.682 C 265.716 702.461 264.337 660.984 270.354 626.626 C 291.571 505.489 383.535 437.517 499.059 422.998 z M 324.231 960.286 C 424.782 991.538 442.508 1139.67 345.468 1188.98 C 338.454 1192.54 331.35 1195.88 324.231 1199.2 L 324.133 1487.36 C 324.051 1514.94 321.811 1559.19 360.901 1561.89 C 390.466 1563.93 421.893 1562.96 451.923 1562.94 L 1215.09 1562.94 C 1251.18 1483.5 1357.74 1460.52 1418.55 1523.85 C 1430.27 1536.56 1437.26 1547.46 1445.09 1562.94 C 1494.56 1564.28 1550.54 1562.91 1600.43 1562.92 L 1652.1 1563.01 C 1695.43 1563.09 1720.53 1558.29 1720.97 1507.06 L 1721.02 796.34 L 700.446 523.084 C 646.538 508.564 558.008 475.4 503.232 480.643 C 403.945 488.725 329.744 562.227 324.905 662.706 C 323.854 684.536 324.21 706.469 324.206 728.326 L 324.253 884.955 C 324.264 905.156 325.564 942.061 324.231 960.286 z M 1721.02 735.631 L 1721.29 682.195 C 1722.41 598.797 1694.33 611.308 1624.05 611.291 L 1259.29 610.969 C 1258.13 610.958 1256.06 611.184 1254.84 611.306 C 1292.78 627.458 1364.28 641.475 1405.34 652.014 L 1665.84 720.616 C 1672.26 722.287 1717.93 735.862 1721.02 735.631 z" />
|
||||
<path d="M 734.395 1158.49 C 787.928 1151.1 837.236 1188.72 844.242 1242.31 C 851.249 1295.89 813.273 1344.93 759.641 1351.55 C 706.549 1358.11 658.114 1320.6 651.178 1267.55 C 644.241 1214.51 681.403 1165.81 734.395 1158.49 z M 752.487 1294.89 C 774.162 1292.22 789.602 1272.53 787.02 1250.85 C 784.437 1229.16 764.805 1213.65 743.111 1216.15 C 721.302 1218.67 705.69 1238.43 708.286 1260.23 C 710.882 1282.03 730.698 1297.56 752.487 1294.89 z" />
|
||||
<path d="M 1483.12 874.242 C 1536.37 868.906 1583.87 907.74 1589.22 960.991 C 1594.58 1014.24 1555.76 1061.76 1502.51 1067.13 C 1449.24 1072.51 1401.69 1033.66 1396.34 980.387 C 1390.98 927.109 1429.84 879.58 1483.12 874.242 z M 1500.64 1007.39 C 1521.24 1002.81 1534.16 982.322 1529.42 961.758 C 1524.69 941.194 1504.1 928.432 1483.57 933.333 C 1463.27 938.18 1450.68 958.491 1455.37 978.826 C 1460.06 999.162 1480.27 1011.91 1500.64 1007.39 z" />
|
||||
<path d="M 990.465 825.063 C 1043.7 818.146 1092.42 855.837 1099.09 909.108 C 1105.76 962.379 1067.84 1010.92 1014.54 1017.34 C 961.589 1023.72 913.435 986.109 906.808 933.185 C 900.181 880.262 937.573 831.934 990.465 825.063 z M 1008.88 959.34 C 1029.88 955.91 1044.22 936.238 1041.06 915.196 C 1037.91 894.153 1018.42 879.555 997.342 882.436 C 983.457 884.334 971.668 893.555 966.482 906.574 C 961.297 919.593 963.517 934.394 972.294 945.319 C 981.071 956.243 995.047 961.6 1008.88 959.34 z" />
|
||||
<path d="M 1531.75 1315.16 C 1567.83 1310.92 1581.26 1372.96 1537.23 1379.82 C 1496.94 1381.97 1490.57 1325.05 1531.75 1315.16 z" />
|
||||
<path d="M 1113.52 1341.48 C 1124.59 1339.75 1135.73 1344.09 1142.71 1352.86 C 1149.68 1361.63 1151.41 1373.46 1147.23 1383.86 C 1143.05 1394.25 1133.62 1401.6 1122.51 1403.11 C 1105.59 1405.4 1089.98 1393.64 1087.52 1376.74 C 1085.05 1359.85 1096.65 1344.12 1113.52 1341.48 z" />
|
||||
<path d="M 1308.82 1150.12 C 1325.59 1147.1 1341.64 1158.21 1344.71 1174.97 C 1347.78 1191.73 1336.72 1207.82 1319.97 1210.95 C 1303.14 1214.1 1286.96 1202.97 1283.87 1186.13 C 1280.79 1169.29 1291.97 1153.15 1308.82 1150.12 z" />
|
||||
<path d="M 786.604 693.812 C 803.652 692.684 818.375 705.618 819.451 722.67 C 820.528 739.722 807.549 754.405 790.494 755.43 C 773.512 756.451 758.905 743.54 757.833 726.56 C 756.761 709.581 769.628 694.935 786.604 693.812 z" />
|
||||
<path d="M 501.378 1363.47 C 517.816 1361.46 532.869 1372.89 535.346 1389.26 C 537.823 1405.64 526.824 1421.01 510.528 1423.95 C 499.565 1425.93 488.393 1421.8 481.36 1413.16 C 474.328 1404.52 472.546 1392.74 476.709 1382.41 C 480.871 1372.07 490.321 1364.82 501.378 1363.47 z" />
|
||||
<path d="M 502.61 674.93 C 513.474 673.325 524.359 677.705 531.083 686.388 C 537.807 695.071 539.323 706.706 535.05 716.823 C 530.777 726.939 521.379 733.964 510.467 735.198 C 493.988 737.061 479.058 725.37 476.914 708.926 C 474.77 692.481 486.204 677.353 502.61 674.93 z" />
|
||||
<path d="M 640.602 933.6 C 650.874 930.436 662.055 933.036 669.876 940.408 C 677.698 947.781 680.954 958.788 678.402 969.229 C 675.85 979.67 667.884 987.935 657.543 990.868 C 641.802 995.333 625.399 986.3 620.758 970.61 C 616.117 954.92 624.965 938.417 640.602 933.6 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** An egg — `CREMERIE_FROMAGE`/`OEUFS`. */
|
||||
/** Eggs (foodiconpack.com, CC BY 4.0) — `CREMERIE_FROMAGE`/`OEUFS`. */
|
||||
export function EggIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M12 3C8 8 6 12.5 6 15.5a6 6 0 0 0 12 0C18 12.5 16 8 12 3z" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 672.028 452.989 C 802.231 431.822 916.044 572.595 976.073 668.56 C 982.025 662.6 990.897 647.221 996.637 639.262 C 1056.03 556.903 1134.48 467.569 1240.98 454.261 C 1440.34 428.671 1578.12 667.305 1635.4 824.202 C 1650.71 866.151 1669.24 919.792 1674.32 963.863 C 1796.44 1050.34 1809.04 1195.17 1745.42 1324.69 C 1688.22 1441.14 1595.52 1527.32 1473.73 1571.05 C 1298.56 1633.57 1065.66 1573.42 958.295 1417.43 C 945.299 1429.42 925.899 1441.05 910.854 1450.18 C 632.974 1618.83 279.351 1421.44 265.261 1103.37 C 257.624 930.964 357.165 692.936 470.568 565.306 C 519.686 510.026 595.86 455.977 672.028 452.989 z M 662.243 1464 C 757.01 1464.06 858.31 1447.38 929.301 1378.54 C 814.231 1173.44 836.302 990.945 923.922 774.62 C 931.423 756.102 942.19 737.296 948.796 718.683 C 900.647 639.142 870.484 578.847 783.776 525.855 C 762.865 513.075 703.956 491.421 679.695 500.115 C 592.129 496.732 497.313 595.998 452.999 664.544 C 376.729 782.52 321.573 926.983 311.732 1067.93 C 300.626 1267.53 463.183 1450.54 662.243 1464 z M 1557.87 1477.91 C 1576.89 1460.83 1595.24 1447.76 1613.92 1429.68 C 1709.13 1337.16 1790.75 1153.1 1687.79 1036.91 C 1631.4 973.276 1537.02 953.186 1461.36 993.787 C 1448.89 1000.48 1438.13 1009.84 1426.48 1017.79 C 1369.1 1056.81 1294.02 1057.84 1233.37 1025.8 C 1180.17 997.693 1161.91 968.725 1087.78 972.993 C 807.16 1011.04 898.602 1337.03 1073.46 1465.96 C 1200.69 1561.72 1376.3 1576.66 1517.03 1502.16 C 1526.94 1496.91 1548.72 1480.65 1557.87 1477.91 z M 906.453 1017.56 C 979.391 933.074 1088.63 900.45 1194.17 947.19 C 1205.36 952.146 1219.05 961.719 1229.41 968.383 C 1288.55 1006.6 1355.65 1018.41 1413.05 970.324 C 1468.19 924.129 1552.4 915.263 1619.91 938.773 C 1597.48 779.347 1431.48 482.377 1253.19 500.115 C 1048.63 534.486 925.124 838.141 906.453 1017.56 z" />
|
||||
<path d="M 1641.18 1164.04 C 1655.96 1163.87 1667.24 1172.92 1666.31 1187.92 C 1655.64 1360.64 1490.45 1471.56 1326.72 1477.91 C 1313.35 1477.35 1308.32 1475.65 1301.24 1464 C 1298.93 1437.3 1309.46 1432.84 1334.84 1431.93 C 1467.26 1427.15 1593.28 1334.34 1618.18 1200.99 C 1619.88 1191.88 1620.69 1182.45 1623.87 1173.73 C 1630.25 1167.66 1632.69 1166.33 1641.18 1164.04 z" />
|
||||
<path d="M 1571.63 1070.83 C 1584.72 1067.48 1598.05 1075.41 1601.34 1088.51 C 1604.64 1101.62 1596.65 1114.91 1583.52 1118.14 C 1570.49 1121.36 1557.3 1113.43 1554.03 1100.41 C 1550.75 1087.39 1558.62 1074.17 1571.63 1070.83 z" />
|
||||
<path d="M 1141.5 1043.64 C 1154.47 1043.06 1165.52 1052.98 1166.35 1065.94 C 1167.17 1078.91 1157.46 1090.14 1144.52 1091.21 C 1136.66 1091.85 1128.99 1088.57 1124.04 1082.45 C 1119.08 1076.32 1117.47 1068.14 1119.74 1060.59 C 1122.65 1050.89 1131.38 1044.1 1141.5 1043.64 z" />
|
||||
<path d="M 1036.59 1114.73 C 1049.08 1110.47 1062.65 1117.2 1066.8 1129.73 C 1070.95 1142.26 1064.08 1155.77 1051.52 1159.8 C 1039.12 1163.77 1025.83 1157.02 1021.73 1144.66 C 1017.64 1132.3 1024.27 1118.95 1036.59 1114.73 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -203,67 +249,69 @@ export function SproutIcon() {
|
|||
);
|
||||
}
|
||||
|
||||
/** A shaker — dried spices/herbs (`CONDIMENTS_EPICES`/`EPICES`). */
|
||||
/** Cinnamon sticks (foodiconpack.com, CC BY 4.0) — dried spices/herbs (`CONDIMENTS_EPICES`/`EPICES`). */
|
||||
export function SpiceIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<rect x="8" y="8" width="8" height="13" rx="3" />
|
||||
<path d="M10 8V5a2 2 0 0 1 4 0v3" />
|
||||
<path d="M11 12h.01M13 12h.01M10.5 15h.01M13.5 15h.01M12 18h.01" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1299.23 499.315 C 1359.06 491.799 1390.3 536.11 1425.43 572.134 C 1496.5 553.162 1542.93 603.387 1572.87 661.811 C 1661.4 617.869 1693.59 665.685 1739.02 734.286 C 1766.76 776.167 1793.52 800.671 1780.45 855.484 C 1769.46 901.584 1737.1 918.753 1700.9 943.085 L 956.229 1453.57 C 881.032 1505.41 824.394 1564.64 727.781 1541.31 C 622.715 1509.59 598.438 1427.43 620.532 1331.46 C 593.107 1328.96 556.54 1312.76 537.915 1293.61 C 371.504 1425.39 178.623 1225.58 306.96 1081.41 C 326.186 1059.81 363.923 1042.27 388.969 1028.76 L 1109.88 640.263 C 1131.52 628.662 1150.34 620.99 1170.36 606.2 C 1223.61 566.87 1210.47 510.761 1299.23 499.315 z M 698.223 1153.03 C 685.564 1124.01 644.122 1103.62 652.014 1083.32 C 656.117 1072.77 665.7 1067.69 676.173 1065.41 L 677.523 1065.87 C 730.347 1084.36 771.371 1175.97 760.119 1229.24 L 760.916 1230.44 C 774.747 1226.74 829.275 1193.14 844.192 1184.62 L 1535.71 800.412 C 1597.34 766.704 1560.33 730.548 1538.73 696.859 C 1516.84 662.716 1510.46 619.145 1455.71 615.368 C 1443.29 616.688 1414.94 634.577 1402.39 641.277 L 879.346 923.682 C 841.372 943.965 556.595 1088.5 539.86 1109.98 C 489.374 1177.33 562.598 1300.61 654.807 1286.88 C 719.275 1277.29 729.816 1200 698.223 1153.03 z M 1598.52 704.214 C 1629.22 745.717 1610.85 806.574 1570.27 832.144 C 1528.59 858.413 1480.95 881.992 1438 906.347 L 748.377 1294.08 C 726.513 1306.49 700.23 1324.6 676.173 1331.46 C 642.753 1368.48 656.169 1436.47 691.565 1468.81 C 756.913 1528.51 865.349 1504.56 850.662 1403.24 C 846.258 1385.45 842.637 1374.38 832.194 1358.52 C 820.284 1340.43 782.796 1327.27 797.906 1301.05 C 804.787 1289.11 820.472 1287.3 830.907 1294.95 C 880.916 1329.97 899.477 1376.66 898.999 1435.05 C 919.996 1424.24 936.911 1410.37 956.218 1397.18 L 1666.61 909.295 C 1783.99 829.202 1730.19 819.511 1676.19 724.443 C 1665.66 705.918 1654.98 693.562 1631.02 693.416 C 1617.19 695.209 1610.81 697.532 1598.52 704.214 z M 424.954 1289.68 C 456.981 1288.58 481.153 1279.67 505.982 1258.74 C 482.024 1207.75 464.9 1174.25 485.217 1114.52 C 501.004 1068.11 539.721 1052.14 579.79 1030.9 L 1208.36 692.006 C 1262.31 662.286 1332.68 630.795 1383.21 598.302 C 1360.55 570.383 1339.51 542.641 1299.23 546.102 C 1275.97 551.717 1257.21 564.624 1246.66 586.131 C 1218.85 642.791 1177.24 657.382 1124.55 685.304 L 459.432 1043.62 C 436.067 1056.26 356.059 1096.31 341.245 1113.44 C 276.781 1187.98 331.053 1287.67 424.954 1289.68 z" />
|
||||
<path d="M 1360.08 777.002 C 1380.21 776.904 1390.76 790.877 1384.27 811.255 C 1381.62 819.58 1275.59 878.124 1250.15 886.907 C 1226.82 891.473 1218 863.959 1226.13 851.7 C 1233.68 840.3 1344.34 781.861 1360.08 777.002 z" />
|
||||
<path d="M 589.734 1153.03 C 603.824 1152.09 645.581 1173.62 661.273 1178.77 C 683.217 1185.98 681.408 1216.89 657.535 1224.03 C 632.942 1225.26 519.247 1177.53 589.734 1153.03 z" />
|
||||
<path d="M 723.057 1360.97 C 731.948 1360.68 812.223 1392.94 814.451 1405.67 C 817.702 1424.25 811.966 1434.23 794.522 1438.58 C 774.923 1438.59 648.922 1386.08 723.057 1360.97 z" />
|
||||
<path d="M 379.771 1163.13 C 388.374 1162.08 439.518 1180.94 450.159 1186.2 C 472.912 1198.35 467.313 1225.91 442.076 1230.67 C 427.597 1229.29 389.88 1214.43 374.813 1208.91 C 351.499 1200.37 351.948 1171.93 379.771 1163.13 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A condiment jar — sauces, pickles, tinned/preserved goods (`CONDIMENTS_EPICES`/`SAUCES` and the `EPICERIE_SECHE`/`AUTRES` catch-all). */
|
||||
/** Honey jar (foodiconpack.com, CC BY 4.0) — sauces, pickles, tinned/preserved goods (`CONDIMENTS_EPICES`/`SAUCES` and the `EPICERIE_SECHE`/`AUTRES` catch-all). */
|
||||
export function JarIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<rect x="6" y="9" width="12" height="12" rx="2" />
|
||||
<path d="M9 9V6a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
|
||||
<path d="M6 13h12" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 441.421 842.864 C 447.786 842.313 464.211 842.793 471.395 842.807 L 1019.7 842.764 C 1032.89 842.741 1048.9 841.881 1061.84 843.353 C 1130.81 851.198 1163.98 918.556 1130.28 977.676 C 1193.73 1004.99 1195.17 1074.2 1153.32 1125.1 C 1169.13 1142.38 1192.59 1189.25 1202.41 1211.85 C 1250.22 1321.88 1252.68 1457.38 1205.46 1568.25 C 1179.01 1630.35 1126.32 1716.02 1060.36 1743.03 C 1018.13 1757.99 939.663 1751.84 893.846 1751.84 L 558.891 1751.86 C 485.108 1751.86 456.534 1757.33 399.168 1702.94 C 252.041 1563.46 226.559 1333.79 330.44 1161.63 C 336.605 1151.42 346.62 1133.29 354.405 1125.1 C 309.176 1074.05 316.393 1016.01 373.247 977.676 C 341.618 912.507 363.601 854.3 441.421 842.864 z M 675.49 1147.95 C 674.654 1202.31 686.841 1260.99 645.515 1301.98 C 605.095 1342.08 544.747 1339.55 505.417 1299.64 C 465.481 1259.11 483.176 1198.65 479.076 1147.95 L 402.038 1147.95 C 273.352 1299.41 297.324 1545.93 446.301 1676.73 C 482.42 1708.44 508.476 1701.08 553.305 1701.08 L 904.022 1701.09 C 937.651 1701.09 987.565 1702.82 1019.49 1700.23 C 1092.21 1694.33 1158.11 1565.46 1173.88 1504.48 C 1208.54 1389.77 1185.08 1240.15 1104.25 1147.95 L 1006.64 1147.91 C 955.106 1147.75 964.099 1136.4 950.944 1168.76 C 917.416 1251.22 805.109 1248.39 785.535 1147.95 C 750.82 1147.92 709.692 1146.79 675.49 1147.95 z M 830.367 1101.07 L 837.774 1107.07 C 837.983 1134.23 832.163 1176.53 873.289 1174.99 L 874.247 1174.95 C 927.062 1157.23 886.505 1109.61 920.058 1097.59 C 945.687 1088.41 1065.03 1106.22 1103.86 1094.32 C 1140.07 1083.22 1145.85 1020.86 1080.75 1017.52 C 1055.39 1016.22 1029.65 1017.07 1004.25 1017.07 L 448.493 1017.07 C 427.043 1018.23 402.017 1013.88 386.352 1031.96 C 371.738 1048.83 372.852 1073.39 390.862 1087.43 C 419.378 1109.66 500.067 1085.09 523.258 1103.42 C 535.236 1112.88 531.224 1139.24 531.155 1152.37 L 530.839 1214.29 C 530.615 1307.53 614.177 1284.58 621.411 1240.4 C 623.374 1228.41 622.701 1213.42 622.655 1201.04 L 622.434 1142.74 C 622.287 1095.93 627.961 1096.54 672.476 1096.79 L 719.123 1097 C 742.002 1097.03 812.594 1092.57 830.367 1101.07 z M 1064.76 965.18 C 1108.65 949.333 1095.92 898.437 1057.26 895.318 C 1035.28 893.545 1006.38 894.91 983.609 894.932 L 441.421 894.985 C 396.1 901.206 401.684 964.819 454.169 964.945 L 990.398 965.186 C 1007.59 965.192 1049.73 966.614 1064.76 965.18 z" />
|
||||
<path d="M 1289.39 292.367 C 1326.12 288.265 1352.75 308.707 1374.41 335.781 C 1418.32 306.712 1471.33 336.401 1495.2 378.493 C 1544.75 348.864 1592.39 377.915 1621.27 421.001 C 1671.12 420.78 1704.66 417.497 1743.36 455.769 C 1792.33 504.191 1793.6 591.975 1743.75 640.152 C 1705.04 677.57 1670.58 674.943 1621.27 674.832 C 1596.59 737.221 1552.79 745.834 1495.2 729.12 C 1472.47 770.532 1441.21 787.563 1394.57 778.379 C 1360.92 771.752 1380.82 770.173 1357.74 794.106 C 1310.02 844.096 1227.55 821.602 1211.46 754.37 C 1206.62 734.136 1208.65 704.247 1208.65 682.677 L 1208.7 581.596 L 1035.75 581.596 L 377.727 581.595 C 350.11 581.589 322.276 581.811 294.694 581.208 C 260.937 580.471 261.039 531.119 291.666 530.646 C 324.929 529.931 358.287 530.245 391.571 530.245 L 1208.7 530.263 C 1209.51 520.792 1208.8 501.437 1208.77 491.101 L 1208.73 381.027 C 1208.97 333.971 1241.73 296.636 1289.39 292.367 z M 1298.86 770.005 C 1313 767.514 1329.13 756.138 1329.26 740.59 L 1329.03 442.72 C 1329.03 411.9 1342.02 337.471 1292.72 343.188 C 1246.23 352.56 1259.98 412.832 1259.97 452.092 L 1259.96 688.959 C 1259.96 719.956 1252.41 770.674 1298.86 770.005 z M 1379.63 708.828 C 1390.61 719.865 1399.84 729.244 1416.52 729.12 C 1456.74 728.388 1450.53 683.254 1450.87 655.181 L 1451.07 469.27 C 1451.05 439.366 1460.98 378.539 1416.52 378.493 C 1372.3 382.068 1379.61 426.387 1379.61 459.837 L 1379.6 654.043 C 1379.58 669.976 1378.68 693.565 1379.63 708.828 z M 1541.6 687.466 C 1578 681.587 1574.03 642.844 1574.04 615.382 L 1574.03 489.826 C 1574.01 470.817 1572.28 404.792 1534.72 421.001 C 1506.93 420.198 1502.06 439.052 1501.89 462.447 L 1501.79 631.805 C 1501.87 659.021 1504.66 689.717 1541.6 687.466 z M 1624.49 624.418 C 1656.15 624.69 1683.84 627.86 1707.91 603.745 C 1736.48 570.656 1737.04 517.576 1703.82 486.797 C 1682.99 467.498 1651.28 471.716 1624.49 471.965 L 1624.45 572.781 C 1624.45 588.207 1623.7 609.679 1624.49 624.418 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A bottle — oils, vinegars (`CONDIMENTS_EPICES`/`ASSAISONNEMENTS`, the pourable subset). */
|
||||
/** Oil bottle (foodiconpack.com, CC BY 4.0) — oils, vinegars (`CONDIMENTS_EPICES`/`ASSAISONNEMENTS`, the pourable subset). */
|
||||
export function BottleIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M10 2h4v3.5l1.5 2V20a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2V7.5L10 5.5z" />
|
||||
<path d="M9.5 11h5" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1113 267.774 C 1118.93 267.838 1127.18 268.144 1132.79 267.575 C 1212.81 259.459 1291.26 334.177 1283.92 411.9 C 1320.46 426.872 1328.11 431.256 1348.99 466.537 C 1460.44 475.628 1555.19 437.956 1647.13 535.703 C 1705.58 597.853 1723.55 687.084 1698.19 767.766 C 1677.99 832.062 1578.77 923.958 1525.17 973.224 C 1578.32 1063.57 1591.03 1155.94 1591.05 1257.58 L 1591.36 1579.29 C 1591.7 1675.52 1530.2 1758.25 1434.53 1774.01 C 1404.53 1778.95 1376.38 1777.9 1346.23 1777.89 L 868.5 1777.85 C 817.993 1777.93 770.594 1783.03 726.147 1753.63 C 714.384 1745.84 702.133 1731.64 691.185 1725.99 C 670.808 1725.81 650.472 1726.52 630.147 1724.67 C 462.136 1709.37 366.732 1559.53 343.149 1404.74 C 325.158 1286.65 348.66 1166.97 389.895 1055.99 C 399.753 1029.46 408.922 999.898 429.075 979.14 C 503.845 902.125 585.964 984.301 642.107 1039.44 C 671.069 971.711 701.229 920.107 761.04 872.291 C 835.931 812.42 889.919 772.757 892.018 668.347 C 848.185 630.588 849.467 598.554 850.18 545.65 C 851.157 473.261 847.573 441.474 922.991 411.9 C 916.203 374.993 935.26 330.694 960.986 304.454 C 1004.3 260.277 1056.4 267.649 1113 267.774 z M 943.863 676.997 C 935.593 780.389 891.969 841.391 811.421 898.559 C 738.881 950.044 717.919 999.669 683.124 1075.69 C 718.508 1107.43 763.536 1151.02 793.91 1187.54 C 965.064 1029.92 1243.58 1024.81 1407.43 1196.22 C 1496.92 1289.84 1465.7 1413.13 1376.97 1494.79 C 1265.66 1597.23 1103.91 1616.32 962.658 1573.91 C 943.497 1567.53 925.733 1559.93 907.115 1552.11 C 874.749 1630.34 827.889 1676.83 750.715 1710.27 C 773.015 1721.48 788.554 1726.23 813.901 1726.13 L 1278.76 1725.99 C 1319.97 1726 1361.16 1726.13 1402.31 1725.57 C 1470.93 1724.62 1532.2 1669.6 1537.89 1600.7 C 1539.51 1581.09 1539.03 1561.64 1539.04 1541.91 L 1539.02 1298 C 1539.02 1153 1528.08 1021.46 1412.67 917.064 C 1373.18 881.342 1338.53 868.866 1305.8 822.534 C 1269.26 770.804 1262.58 738.027 1257.7 676.997 C 1240.38 680.459 1195.72 676.996 1176.1 676.986 L 1065.74 677.04 C 1051.68 677.053 1024.61 677.931 1011.73 676.997 C 993.467 677.85 961.651 678.239 943.863 676.997 z M 1495.04 929.233 C 1498.73 925.689 1502.55 921.909 1506.35 918.51 C 1522.16 905.383 1542.38 881.856 1557.55 867.044 C 1607.92 817.833 1649.8 786.169 1656.89 710.328 C 1661.24 663.759 1647.67 616.675 1618.03 580.681 C 1568.2 520.175 1514.1 518.813 1445.06 518.785 L 1351.01 518.841 L 1351.01 586.996 L 1446.99 586.952 C 1489.39 586.937 1526.58 582.18 1559.47 614.942 C 1606.7 661.976 1598.63 732.85 1552.52 778.096 L 1448.79 878.929 C 1461.81 889.697 1485.88 914.78 1495.04 929.233 z M 936.782 463.972 C 887.714 471.621 902.402 557.987 904.235 597.116 C 906.068 633.523 962.311 625.555 985.507 625.53 L 1203.16 625.533 C 1223.95 625.552 1246.82 626.359 1267.25 625.51 C 1292.35 624.448 1300.31 610.245 1300.03 586.659 C 1298.91 492.084 1326.13 463.961 1221.92 463.944 L 937.416 463.9 C 937.204 463.924 936.993 463.948 936.782 463.972 z M 1406.94 846.536 C 1423.38 832.363 1438.29 815.198 1454.25 800.985 C 1480.96 774.681 1538.13 728.203 1539.12 694.413 C 1540.91 632.943 1480.07 640.114 1440.08 640.261 L 1348.99 640.346 L 1337.81 640.346 C 1329.89 654.324 1323.13 658.656 1310.42 668.347 C 1308.51 755.891 1341.97 798.976 1406.94 846.536 z M 1154.01 411.9 C 1179.4 411.398 1205.04 411.981 1230.48 411.9 C 1234.21 366.538 1199.22 322.81 1152.61 321.161 C 1124.22 320.156 1079 318.458 1051.13 320.788 C 996.889 323.351 974.266 363.057 975.002 411.9 C 992.225 412.919 1016.43 411.918 1034.46 411.904 L 1154.01 411.9 z M 1077.65 1119.02 C 1006.5 1126.09 919.413 1149.46 863.243 1196.96 C 938.217 1212.28 999.725 1254.37 1070.65 1280.46 C 1108.58 1294.42 1137.83 1297.54 1176.69 1302 C 1259.65 1302.52 1312.94 1289.81 1387.05 1253.24 C 1329.02 1166.36 1182.39 1112.07 1077.65 1119.02 z M 840.44 1243.81 C 845.432 1252.5 853.131 1260.87 859.13 1268.88 C 897.565 1320.2 923.184 1382.53 922.872 1447.31 C 922.791 1464.28 921.612 1482.96 922.991 1499.54 C 934.619 1509.64 955.708 1517.09 970.31 1521.94 C 1105.09 1566.65 1256.66 1544.09 1358.35 1440.01 C 1397.17 1394.75 1410.91 1360.52 1410.01 1302 L 1408.42 1302.37 C 1395.32 1305.59 1377.94 1314.66 1365.06 1320.03 C 1294.67 1349.36 1212.93 1361.15 1137.19 1351.3 C 1041.82 1338.9 943.039 1272.16 840.44 1243.81 z M 495.507 999.144 C 453.53 1008.01 440.935 1065.13 428.748 1100.97 C 371.204 1269.09 360.088 1532 533.268 1638.59 C 598.933 1679 675.355 1687.18 746.437 1655.94 C 859.966 1603.37 901.891 1465.68 850.084 1355.36 C 800.354 1249.47 644.644 1104.07 549.506 1027.25 C 535.226 1015.89 514.258 997.224 495.507 999.144 z" />
|
||||
<path d="M 490.113 1171.08 C 526.074 1169.48 523.674 1192.77 517.597 1218.79 C 501.423 1288.06 501.968 1347.56 517.363 1416.46 C 514 1429.79 511.1 1433.52 498.914 1440.01 C 429.934 1456.78 461.2 1243.09 466.534 1210.88 C 469.864 1190.77 469.321 1178.34 490.113 1171.08 z" />
|
||||
<path d="M 1067.99 1178.67 C 1084.54 1178.69 1113.69 1177.22 1129 1179.34 C 1158.36 1183.39 1158.55 1230.38 1120.86 1230.63 C 1104.98 1230.58 1058.92 1234.3 1047.71 1223.62 C 1028.23 1205.06 1038.37 1179.18 1067.99 1178.67 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A glass — juices, coffee/tea, cooking alcohols, water (`CONDIMENTS_EPICES`/`ASSAISONNEMENTS`'s drinkable subset). */
|
||||
/** Glass (foodiconpack.com, CC BY 4.0) — juices, coffee/tea, cooking alcohols, water (`CONDIMENTS_EPICES`/`ASSAISONNEMENTS`'s drinkable subset). */
|
||||
export function DrinkIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M6 3h12l-1.5 16.5A2 2 0 0 1 14.5 21h-5a2 2 0 0 1-2-1.5z" />
|
||||
<path d="M7 9h10" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 1267.05 274.485 C 1325.51 271.562 1335.62 290.356 1378.17 311.904 C 1386.06 307.975 1394.21 301.843 1401.71 297.203 C 1487.29 244.219 1616.96 297.102 1629.14 392.932 C 1734.88 398.312 1783.1 466.039 1783.5 563.848 L 1783.53 722.652 C 1783.49 830.259 1784.98 944.69 1727.69 1040.56 C 1659.77 1154.23 1656 1272.82 1655.97 1400.89 L 1656.05 1650.68 C 1656.02 1758.75 1621.13 1773.1 1521.07 1773.05 L 1259.5 1772.99 C 1231.98 1773.01 1203.87 1773.38 1176.4 1772.76 C 1160.67 1772.41 1144.87 1763.27 1128.16 1763.17 L 721.75 1763.02 L 588.607 1763 C 566.914 1763 544.536 1763.73 522.85 1763 C 456.776 1760.77 396.557 1710.05 383.737 1645.07 C 379.527 1623.73 380.955 1598.24 380.976 1576.49 L 381.024 1300.03 C 381.034 1273.38 380.023 1242.55 381.015 1216.37 C 333.984 1195.75 300.304 1175.9 277.68 1126.87 C 243.634 1053.08 280.583 957.367 355.016 924.877 C 355.565 907.942 353.79 884.25 356.109 868.341 C 372.867 753.37 467.596 697.043 578.822 710.005 C 632.246 644.377 685.672 601.154 778.393 611.55 C 850.674 619.653 890.27 657.13 932.09 710.005 L 964.049 710.005 C 963.638 624.745 947.76 498.792 1007.33 438.183 C 1037.36 407.629 1072.52 393.572 1114.67 392.932 C 1139.14 323.206 1192.38 279.094 1267.05 274.485 z M 1730 694.012 L 1016.32 694.012 C 1012.45 844.295 1013.09 909.673 1082.42 1040.58 C 1136.35 1152.04 1146.02 1252.12 1146.03 1374.77 L 1145.97 1611.06 C 1146.01 1682.73 1128.6 1719.82 1210.62 1719.9 L 1529.39 1719.92 C 1568.14 1719.97 1601.9 1723.64 1602.95 1675.39 C 1603.31 1647.44 1603.01 1619.46 1603.02 1591.5 L 1602.99 1414.19 C 1603.01 1277.52 1605.79 1170.54 1664.47 1044.8 C 1672.02 1028.62 1682.56 1012.72 1690.2 996.562 C 1732.11 907.991 1734.92 792.128 1730 694.012 z M 1064.3 1143.88 C 1060.97 1106.23 1018.67 1037.53 1001.98 1000.28 C 966.107 920.223 962.451 850.873 964.049 765.017 C 954.263 765.07 944.392 765.287 934.535 765.178 C 887.998 764.665 900.936 757.388 876.025 725.296 C 849.552 689.761 785.191 651.167 740.498 664.397 C 636.802 675.565 623.761 764.277 597.178 766.129 C 584.972 767.073 571.518 762.206 558.876 760.856 C 472.166 751.595 401.323 821.727 408.888 908 C 410.726 966.867 422.178 951.207 373.4 976.923 C 250.053 1041.95 354.688 1216.46 466.223 1151.84 C 499.004 1133 512.803 1095.67 562.276 1135.27 C 592.735 1159.65 588.097 1198.25 588.077 1232.88 L 587.986 1314.65 C 587.942 1343.37 583.657 1394.57 632.277 1379.55 C 658.53 1361.57 654.122 1349.31 654.111 1319.44 L 654.033 1252.24 C 654.004 1207.85 650.169 1176.52 691.026 1145.51 C 737.98 1109.87 775.436 1148.14 817.218 1149.31 C 841.254 1149.99 852.643 1149.72 875.92 1141.99 C 926.399 1125.21 906.257 1119.18 955.5 1138.03 C 992.889 1152.34 1026.34 1156.21 1064.3 1143.88 z M 433.999 1216.37 L 433.999 1550.43 C 475.685 1551.17 519.63 1550.43 561.541 1550.43 L 921.821 1550.38 C 938.878 1550.35 956.176 1550.16 973.027 1550.52 C 997.119 1551.04 1008.11 1578.93 993.12 1595.17 C 981.426 1607.83 945.098 1603.81 928.188 1603.81 L 565.302 1603.82 C 522.605 1603.82 476.507 1602.95 433.999 1603.84 C 434.202 1669.45 473.124 1710.64 541.776 1710.03 L 765.4 1709.63 L 1054.87 1709.55 C 1066.79 1709.42 1084.27 1708.4 1095.35 1709.63 C 1091.23 1685.26 1093.01 1631.26 1093.02 1603.84 C 1080.62 1604.81 1070.37 1606.45 1059.82 1598.09 C 1034.03 1577.67 1055.71 1549.13 1093.02 1550.43 L 1093.05 1401.69 C 1093.08 1325.3 1094.42 1270.1 1078.85 1193.74 C 1027.34 1219.45 971.168 1195.86 922.099 1183.84 C 909.765 1185.27 893.686 1193.02 880.867 1196.36 C 828.579 1209.97 789.396 1203.07 740.498 1183.84 C 713.843 1188.12 707.598 1201.37 706.941 1227 C 705.854 1269.36 711.046 1322.31 705.54 1363.54 C 697.442 1424.18 618.55 1456.12 569.879 1419.16 C 528.58 1389.91 533.915 1354.11 533.976 1309.77 L 534.185 1227.98 C 534.411 1208.95 539.307 1188.09 526.31 1172.61 C 495.427 1200.14 474.835 1210.36 433.999 1216.37 z M 1267.05 327.463 C 1149.56 339.214 1179.51 443.167 1135.59 443.939 C 996.152 443.732 1022.17 546.05 1016.32 641.005 C 1071.8 641.819 1129.63 641.006 1185.28 641.004 L 1520.25 641.005 L 1730 641.005 C 1726.75 542.326 1749.52 446.998 1610.37 443.425 C 1581.03 442.672 1585.35 410.202 1575.44 391.464 C 1549.22 336.931 1486.59 311.569 1431.89 340.646 C 1419.5 346.18 1392.44 368.293 1379.03 368.873 C 1353.16 369.99 1325.15 326.369 1267.05 327.463 z" />
|
||||
<path d="M 856.545 913.549 C 866.417 911.644 876.552 915.188 883.085 922.83 C 889.618 930.472 891.543 941.034 888.127 950.49 C 884.71 959.946 876.478 966.838 866.569 968.539 C 851.467 971.131 837.092 961.08 834.344 946.004 C 831.596 930.929 841.499 916.452 856.545 913.549 z" />
|
||||
<path d="M 728.404 789.654 C 737.003 786.898 746.405 788.493 753.615 793.928 C 760.826 799.364 764.946 807.964 764.664 816.989 C 764.296 828.745 756.569 838.996 745.369 842.586 C 730.752 847.272 715.103 839.221 710.419 824.603 C 705.734 809.985 713.786 794.338 728.404 789.654 z" />
|
||||
<path d="M 625.937 963.277 C 640.819 961.847 654.064 972.7 655.588 987.572 C 657.111 1002.44 646.341 1015.76 631.478 1017.37 C 616.484 1019 603.027 1008.12 601.49 993.113 C 599.953 978.109 610.923 964.72 625.937 963.277 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A stockpot — broths, stocks, water bases (`AIDES_CULINAIRES`/`BASES`'s liquid-base subset). */
|
||||
/** Stockpot (foodiconpack.com, CC BY 4.0) — broths, stocks, water bases (`AIDES_CULINAIRES`/`BASES`'s liquid-base subset). */
|
||||
export function StockPotIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<rect x="4" y="10" width="16" height="10" rx="2" />
|
||||
<path d="M2 12h2M20 12h2" />
|
||||
<path d="M9 10V7a3 3 0 0 1 6 0v3" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 956.689 498.026 L 1074.61 497.958 C 1137.01 497.691 1174.39 532.451 1174.48 597.104 C 1265.87 599.475 1358.96 595.285 1450.52 597.066 C 1551.82 599.036 1640.84 682.526 1638.99 785.253 C 1622.4 813.785 1611.98 785.183 1613 828.678 C 1639.21 825.311 1672.61 823.696 1698.78 818.712 C 1763.14 806.46 1805.62 869.267 1772.1 924.572 C 1750.8 959.702 1653.92 968.017 1613 976.35 L 1613.05 1323.99 C 1613.06 1352.99 1614.47 1381.48 1607.44 1409.65 C 1580.36 1518.21 1497.47 1548.07 1399.95 1547.95 L 1089.99 1547.91 L 663.723 1547.96 C 564.944 1547.94 471.975 1528.47 441.558 1414.71 C 432.646 1381.38 435.528 1339.45 435.545 1304.36 L 435.707 1017.76 C 435.822 956.555 438.134 978.73 368.191 964.482 C 334.482 957.614 301.664 960.737 279.03 929.523 C 256.702 898.732 261.782 855.965 292.641 833.135 C 320.337 812.91 350.476 820.964 381.769 826.296 L 435.591 835.521 C 435.902 826.211 435.618 815.518 435.591 806.105 C 421.651 801.018 412.723 797.094 412.559 780.488 C 410.831 699.26 472.524 621.061 551.615 603.033 C 592.371 593.743 648.136 597.11 691.291 597.11 L 875.146 597.104 C 871.062 542.426 901.02 503.078 956.689 498.026 z M 478.871 806.105 C 477.554 845.634 478.872 892.42 478.876 932.718 L 478.841 1313.78 C 478.773 1369.65 473.787 1415.83 516.462 1459.99 C 546.181 1490.75 580.38 1505.29 623.081 1504.86 L 1419.04 1505.01 C 1452.51 1505.2 1469.64 1502.53 1499.23 1486.78 C 1571.64 1439.94 1569.41 1384.54 1569.36 1306.19 L 1569.36 929.821 C 1569.37 891.098 1570.64 844.093 1569.34 806.105 L 528.372 806.105 C 512.279 806.198 494.852 806.61 478.871 806.105 z M 1264.5 762.998 L 1594.19 762.998 C 1586.34 721.917 1562.92 688.244 1528.97 664.169 C 1487.57 634.821 1441.84 640.706 1393.89 640.809 L 788.11 640.848 L 665.057 640.812 C 633.374 640.813 587.735 638.189 558.195 646.073 C 508.836 659.248 463.194 707.895 457.828 759.35 C 457.629 761.31 457.844 761.459 459.043 762.615 C 489.947 763.762 525.361 763.005 556.59 763.009 L 1264.5 762.998 z M 325.946 863.171 C 316.949 867.888 313.801 870.39 309.051 879.906 C 307.696 930.337 378.413 921.02 421.834 930.35 C 426.15 930.259 431.368 929.994 435.591 930.35 C 434.898 913.263 435.666 896.899 435.591 879.906 C 417.924 875.099 342.207 861.758 325.946 863.171 z M 956.689 541.052 C 922.748 546.685 916.547 566.328 917.645 597.104 L 1093.5 597.104 L 1130.73 597.104 C 1129.13 534.666 1094.11 540.855 1046.78 541.012 L 956.689 541.052 z M 1613 873.877 L 1613 930.35 C 1622.19 929.498 1629.94 928.246 1638.99 926.739 C 1666.38 921.093 1695.84 916.564 1722.96 910.501 C 1748.41 904.812 1745.27 855.434 1711.78 863.171 C 1684.18 864.363 1640.88 870.069 1613 873.877 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
/** A sugar cube — `AIDES_CULINAIRES`/`SUCRES`. */
|
||||
/** Sugar (foodiconpack.com, CC BY 4.0) — `AIDES_CULINAIRES`/`SUCRES`. */
|
||||
export function SugarIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M4 8 12 4l8 4-8 4z" />
|
||||
<path d="M4 8v8l8 4 8-4V8" />
|
||||
<path d="M12 12v8" />
|
||||
</Icon>
|
||||
<FilledIcon>
|
||||
<path d="M 563.343 266.546 C 579.356 265.999 598.229 266.52 614.537 266.523 L 1165.31 266.51 C 1214.01 266.5 1267.02 255.617 1296.26 302.682 C 1323.29 346.167 1275.54 419.411 1264.41 467.576 C 1285.74 508.561 1315.63 552.485 1339.39 592.89 C 1380.58 662.943 1373.02 694.734 1373.02 771.075 L 1373.04 1068.44 C 1373.05 1091.57 1373.19 1114.08 1372.67 1137.74 C 1373.67 1157.45 1347.53 1169.18 1333.48 1156.58 C 1319.4 1143.95 1323.15 1103.91 1323.13 1086.51 L 1323.23 998.001 C 1323.35 921.011 1321.79 841.992 1323.04 765.249 C 1302.66 765.841 1263.43 767.077 1243.62 763.124 C 1209.14 756.243 1194.07 747.655 1156.63 747.651 C 1097.23 747.646 1050.15 776.567 981.876 761.345 C 948.843 753.98 929.738 747.792 895.257 747.199 C 833.498 746.136 788.155 775.837 714.942 762.372 C 621.546 745.195 631.078 743.658 537.012 759.443 C 519.46 762.388 489.969 762.19 471.997 762.221 L 471.995 1371.67 C 472.002 1396.15 472.97 1424.92 471.997 1448.84 L 597.5 1448.84 L 936.881 1448.84 C 951.994 1448.71 966.891 1448.41 981.989 1448.91 C 1013.24 1449.97 1014.95 1496.54 985.493 1498.58 C 968.334 1499.76 948.038 1498.99 930.633 1498.99 L 471.997 1498.96 C 471.9 1615.07 488.067 1658.29 618.58 1658.04 L 920.519 1658.05 C 941.436 1658 962.351 1657.71 983.263 1657.98 C 1023.72 1658.51 1019.41 1701.53 1001.19 1705 C 973.159 1710.33 929.732 1708.06 902.015 1708.05 L 632.237 1708.04 C 610.876 1708.04 589.378 1708.56 568.066 1707.31 C 490.732 1702.79 424.454 1638.6 422.594 1560.23 C 422.045 1537.12 422.175 1514.18 422.199 1491.06 L 422.163 798.425 C 422.067 611.542 423.495 654.442 516.951 491.098 C 522.459 481.473 529.099 472.824 533.608 462.528 C 524.397 441.605 516.805 417.28 507.32 397.071 C 479.406 337.599 491.407 270.834 563.343 266.546 z M 581.2 444.248 L 858.635 444.207 C 877.707 444.202 918.616 439.037 917.922 470.882 C 917.289 499.919 877.231 495.793 856.563 495.719 L 575.303 495.627 C 555.982 521.64 542.019 551.259 524.773 578.28 C 494.345 625.954 472.748 652.065 471.997 711.023 C 508.075 714.232 541.943 706.338 576.962 699.622 C 653.495 684.942 727.737 724.071 802.658 710.056 C 841.73 700.588 902.783 691.423 942.696 700.048 C 970.712 706.101 991.978 712.195 1020.99 713.991 C 1085.47 714.159 1144.93 686.96 1210.17 702.913 C 1261.33 714.301 1273.21 710.564 1323.04 713.991 C 1322.45 696.464 1322.69 677.961 1317.12 661.13 C 1311.61 644.465 1231.69 507.757 1221.07 497.37 C 1197.05 488.286 1118.19 504.122 1091.8 492.457 C 1072.38 483.875 1074.24 452.977 1094.37 445.85 C 1105.49 441.914 1200.19 444.253 1218.49 444.248 C 1224.39 414.175 1250.62 365.264 1255.09 334.51 C 1240.34 308.577 1197.61 318.459 1168.14 318.51 L 615.35 318.531 C 600.872 318.528 580.593 317.824 566.686 318.553 C 513.479 321.249 572.707 415.367 581.2 444.248 z" />
|
||||
<path d="M 979.84 444.248 C 1053.88 437.921 1046.87 490.04 1011.25 495.627 C 929.626 498.405 950.269 448.02 979.84 444.248 z" />
|
||||
<path d="M 1396.75 1217.47 C 1443.62 1216.15 1464.72 1245.77 1494.53 1275.78 C 1565.63 1347.33 1623.96 1384.38 1535.89 1472 L 1539.17 1471.74 C 1583.19 1468.48 1626.03 1501.91 1626.71 1547.84 C 1627.54 1603.5 1637.63 1714.43 1605.34 1752.92 C 1575.25 1788.59 1526.31 1777.12 1485.51 1778.93 C 1441.76 1778.97 1394.42 1787.64 1359.68 1755.43 C 1348.72 1763.2 1342.39 1767.96 1330.25 1773.29 C 1298.34 1783.83 1255.57 1778.81 1222.29 1778.9 C 1181.48 1779.02 1140.16 1786.51 1108.24 1755.67 C 1072.31 1720.97 1081.98 1654.29 1082 1607.15 C 1082.01 1583.57 1079.27 1553.57 1085.21 1530.78 C 1105.39 1453.44 1204.35 1472.15 1264.41 1472 C 1175.55 1397.29 1237.85 1339.22 1296.83 1283.61 C 1333.08 1249.42 1344.73 1222.99 1396.75 1217.47 z M 1247.12 1728.54 C 1321.35 1728.84 1337.7 1737.1 1336.56 1662.79 C 1336.02 1627.15 1337.61 1591.34 1335.69 1555.74 C 1333.59 1516.93 1302.77 1521.22 1275.51 1521.35 L 1218.49 1521.43 C 1198.62 1521.35 1133.22 1514.22 1132.35 1546.35 C 1131.11 1592.23 1132.44 1638.8 1132.04 1684.78 C 1131.18 1732.14 1158.14 1728.7 1194.19 1728.63 L 1247.12 1728.54 z M 1273.02 1405.46 C 1290.13 1431.7 1353.26 1490.83 1377.71 1512.41 C 1385.03 1518.87 1394.84 1519.46 1404.08 1519.51 C 1424.76 1511.3 1510.19 1429.67 1526.99 1405.46 L 1526.91 1403.58 C 1525.97 1379.71 1527.17 1378.58 1508.46 1360.21 C 1486.32 1338.47 1425.85 1265.45 1396.75 1267.2 C 1373.17 1272.79 1305.16 1345.42 1286.86 1364.36 C 1272.58 1379.15 1271.92 1385.74 1273.02 1405.46 z M 1385.12 1569.55 L 1385.1 1660 C 1385.17 1745.39 1402.36 1728.92 1477.75 1728.54 C 1544.14 1728.7 1578.04 1744.59 1578.07 1671.14 L 1578.06 1591.77 C 1577.93 1508.63 1563.04 1518.16 1483.56 1519.51 C 1457.92 1546.93 1437.27 1567.87 1396.85 1569.25 L 1385.12 1569.55 z" />
|
||||
</FilledIcon>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -237,6 +237,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
// App version — a quiet diagnostic footnote below the account menu, not
|
||||
// an interactive element (hence `aria-hidden` on the `<p>` in
|
||||
// AppLayout.tsx). Hidden whenever space is at a premium — the icon-only
|
||||
// rail and the mobile horizontal bar (see the `.collapsed` block and the
|
||||
// `@media (max-width: 640px)` block below).
|
||||
&__version {
|
||||
margin: var(--space-xs) 0 0;
|
||||
padding: 0 var(--space-sm);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// --- Collapsed (icon-only rail) state -----------------------------------
|
||||
// A single class toggle on the root element — every nested rule below
|
||||
// just hides labels/chevrons and re-centers icons via CSS, no child
|
||||
|
|
@ -282,6 +295,10 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.app-sidebar__version {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// The popover would otherwise shrink to the icon rail's own width,
|
||||
// squashing "Mon compte"/"Se déconnecter" — give it a normal,
|
||||
// comfortable width instead, still anchored to the rail's left edge.
|
||||
|
|
@ -342,6 +359,11 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
// No room for this on a horizontal bar either.
|
||||
&__version {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import "./AppLayout.scss";
|
|||
import {
|
||||
AccountIcon,
|
||||
ChevronLeftIcon,
|
||||
CreditsIcon,
|
||||
DietPreferencesIcon,
|
||||
HouseholdIcon,
|
||||
PlanningIcon,
|
||||
|
|
@ -38,6 +39,7 @@ const SETTINGS_ITEMS = [
|
|||
{ to: "/parametres/preferences", key: "preferences", Icon: DietPreferencesIcon },
|
||||
{ to: "/parametres/foyer", key: "household", Icon: HouseholdIcon },
|
||||
{ to: "/parametres/preferences-utilisateur", key: "userPreferences", Icon: UserPreferencesIcon },
|
||||
{ to: "/parametres/credits", key: "credits", Icon: CreditsIcon },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
|
@ -85,7 +87,7 @@ export function AppLayout() {
|
|||
onClick={toggleCollapsed}
|
||||
title={t(isCollapsed ? "layout.sidebar.expand" : "layout.sidebar.collapse")}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
<ChevronLeftIcon aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -102,7 +104,7 @@ export function AppLayout() {
|
|||
className={({ isActive }) => (isActive ? "active" : undefined)}
|
||||
title={t(`layout.nav.${key}`)}
|
||||
>
|
||||
<Icon />
|
||||
<Icon aria-hidden="true" />
|
||||
<span className="label">{t(`layout.nav.${key}`)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
|
|
@ -110,6 +112,9 @@ export function AppLayout() {
|
|||
|
||||
<SettingsMenu />
|
||||
<AccountMenu />
|
||||
<p className="app-sidebar__version" aria-hidden="true">
|
||||
v{__APP_VERSION__}
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<main className="app-content">
|
||||
|
|
@ -146,7 +151,7 @@ function SettingsMenu() {
|
|||
title={t("layout.settings.toggle")}
|
||||
>
|
||||
<span className="app-sidebar__settings-toggle-left">
|
||||
<SettingsIcon />
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
<span className="label">{t("layout.settings.toggle")}</span>
|
||||
</span>
|
||||
<span className="chevron" aria-hidden="true">
|
||||
|
|
@ -163,7 +168,7 @@ function SettingsMenu() {
|
|||
className={({ isActive }) => (isActive ? "active" : undefined)}
|
||||
title={t(`layout.settings.nav.${key}`)}
|
||||
>
|
||||
<Icon />
|
||||
<Icon aria-hidden="true" />
|
||||
<span className="label">{t(`layout.settings.nav.${key}`)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,130 +1,28 @@
|
|||
import type { ReactNode } from "react";
|
||||
|
||||
// Small, hand-drawn line-icon set for the sidebar nav (24×24 viewBox,
|
||||
// matches the reviewed mockup — see the plan/PR description) rather than
|
||||
// pulling in an icon library for a handful of glyphs. Sized entirely via
|
||||
// CSS (`.app-sidebar__nav svg` etc., see AppLayout.scss) — no width/height
|
||||
// attribute here, so the same markup works at any size the caller picks.
|
||||
// `aria-hidden` on every icon: each one is always paired with visible text
|
||||
// (the nav label, or a `title` tooltip when collapsed) that already
|
||||
// conveys the meaning — the icon itself is decorative.
|
||||
|
||||
function Icon({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlanningIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||||
<path d="M16 2v4M8 2v4M3 10h18" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function RecipesIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
||||
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function ShoppingListIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="9" cy="21" r="1" />
|
||||
<circle cx="20" cy="21" r="1" />
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function DietPreferencesIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z" />
|
||||
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function HouseholdIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
<path d="M9 22V12h6v10" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserPreferencesIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="13.5" cy="6.5" r=".5" />
|
||||
<circle cx="17.5" cy="10.5" r=".5" />
|
||||
<circle cx="8.5" cy="7.5" r=".5" />
|
||||
<circle cx="6.5" cy="12.5" r=".5" />
|
||||
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.9 0 1.5-.7 1.5-1.5 0-.4-.2-.8-.4-1.1-.2-.3-.4-.6-.4-1 0-.8.7-1.5 1.5-1.5H16c3.3 0 6-2.7 6-6 0-4.4-4-8-10-8z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChevronLeftIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Favorites — the recipe catalog's "Favoris" tab (`RecipeTabs`) and the recipe detail panel's favorite toggle (`FavoriteStarButton`). */
|
||||
export function FavoriteIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
/** Public recipes — the recipe catalog's "Publique" tab (`RecipeTabs`). */
|
||||
export function PublicIcon() {
|
||||
return (
|
||||
<Icon>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M2 12h20" />
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
// Sidebar/nav icon set — thin, named re-exports of lucide-react glyphs
|
||||
// rather than importing `lucide-react` directly in every consumer. Keeps a
|
||||
// single place documenting "which glyph stands for which app concept"
|
||||
// (the mapping itself, decided once, isn't obvious from a bare `Settings`
|
||||
// or `Home` import) and keeps consumer diffs small if a glyph ever needs
|
||||
// to change. Previously a hand-drawn custom SVG set (24×24, stroke-based,
|
||||
// no fill) — lucide-react uses the same stroke-based "line icon" language
|
||||
// (default `strokeWidth={2}`, round caps/joins) so the switch is visually
|
||||
// a no-op, just swaps who draws the paths.
|
||||
//
|
||||
// Every one of these is always paired with visible text (the nav label, or
|
||||
// a `title` tooltip when the sidebar is collapsed) — purely decorative, so
|
||||
// every consumer passes `aria-hidden="true"` itself (unlike the old custom
|
||||
// `Icon` wrapper, lucide doesn't set this by default).
|
||||
export {
|
||||
Calendar as PlanningIcon,
|
||||
BookOpen as RecipesIcon,
|
||||
ShoppingCart as ShoppingListIcon,
|
||||
Settings as SettingsIcon,
|
||||
User as AccountIcon,
|
||||
Leaf as DietPreferencesIcon,
|
||||
Home as HouseholdIcon,
|
||||
Palette as UserPreferencesIcon,
|
||||
Info as CreditsIcon,
|
||||
ChevronLeft as ChevronLeftIcon,
|
||||
Star as FavoriteIcon,
|
||||
Globe as PublicIcon,
|
||||
} from "lucide-react";
|
||||
|
|
|
|||
24
apps/web/src/lib/client-key.ts
Normal file
24
apps/web/src/lib/client-key.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Generates a client-only, ephemeral identity for a draft list row (a
|
||||
* recipe's ingredient lines/steps while editing — see `RecipeFormPage`,
|
||||
* `StepListEditor`) — used purely as a React `key`/local identity to keep
|
||||
* each row's own input state stable across reorders, never sent to the
|
||||
* server or persisted anywhere.
|
||||
*
|
||||
* `crypto.randomUUID()` looks like the obvious choice, but the Web Crypto
|
||||
* API only defines it in a "secure context" — https, or the literal
|
||||
* hostname `localhost` — and throws `TypeError: crypto.randomUUID is not a
|
||||
* function` everywhere else: a LAN IP while testing on a real device, a
|
||||
* Capacitor WebView's `capacitor://` origin (this app's eventual mobile
|
||||
* wrapper — see `AllergySelect`'s doc comment for the same "mobile via
|
||||
* Capacitor" context), plain `http://` on a real domain before TLS is set
|
||||
* up. None of that matters for a value that only ever needs to be *unique
|
||||
* within this one render session*, not cryptographically random, so this
|
||||
* never touches `crypto` at all.
|
||||
*/
|
||||
let counter = 0;
|
||||
|
||||
export function makeClientKey(): string {
|
||||
counter += 1;
|
||||
return `${counter}-${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
|
|
@ -78,7 +78,8 @@
|
|||
"account": "Compte",
|
||||
"preferences": "Préférences alimentaires",
|
||||
"household": "Foyer",
|
||||
"userPreferences": "Préférences utilisateur"
|
||||
"userPreferences": "Préférences utilisateur",
|
||||
"credits": "Crédits"
|
||||
}
|
||||
},
|
||||
"accountMenu": {
|
||||
|
|
@ -260,6 +261,14 @@
|
|||
"SYSTEM": "Système"
|
||||
}
|
||||
},
|
||||
"credits": {
|
||||
"title": "Crédits",
|
||||
"icons": {
|
||||
"title": "Icônes d'ingrédients",
|
||||
"description": "La plupart des icônes d'ingrédients de l'application proviennent des collections gratuites « Common ingredient icons » et « Common Utensils » de Food Icon Pack, sous licence Creative Commons Attribution 4.0. Elles ont été recolorées pour s'adapter aux thèmes clair et sombre de l'application.",
|
||||
"licenseLabel": "Licence CC BY 4.0"
|
||||
}
|
||||
},
|
||||
"household": {
|
||||
"title": "Foyer",
|
||||
"form": {
|
||||
|
|
@ -288,5 +297,469 @@
|
|||
"confirmButton": "Confirmer la suppression",
|
||||
"cancelButton": "Annuler"
|
||||
}
|
||||
},
|
||||
"catalog": {
|
||||
"diets": {
|
||||
"omnivore": "Omnivore",
|
||||
"vegetarian": "Végétarien",
|
||||
"vegan": "Végan",
|
||||
"pescatarian": "Pescétarien",
|
||||
"gluten_free": "Sans gluten"
|
||||
},
|
||||
"allergens": {
|
||||
"gluten": "Gluten",
|
||||
"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",
|
||||
"molluscs": "Mollusques"
|
||||
},
|
||||
"ingredients": {
|
||||
"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",
|
||||
"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",
|
||||
"fresh_green_chili": "Piment vert frais",
|
||||
"lemon": "Citron",
|
||||
"lime": "Citron vert",
|
||||
"apple": "Pomme",
|
||||
"pear": "Poire",
|
||||
"banana": "Banane",
|
||||
"orange": "Orange",
|
||||
"clementine": "Clémentine",
|
||||
"grapefruit": "Pamplemousse",
|
||||
"strawberry": "Fraise",
|
||||
"raspberry": "Framboise",
|
||||
"blueberry": "Myrtille",
|
||||
"blackberry": "Mûre",
|
||||
"cherry": "Cerise",
|
||||
"apricot": "Abricot",
|
||||
"peach": "Pêche",
|
||||
"plum": "Prune",
|
||||
"grape": "Raisin",
|
||||
"melon": "Melon",
|
||||
"watermelon": "Pastèque",
|
||||
"pineapple": "Ananas",
|
||||
"mango": "Mangue",
|
||||
"kiwi": "Kiwi",
|
||||
"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",
|
||||
"ham": "Jambon blanc",
|
||||
"cured_ham": "Jambon cru",
|
||||
"sausage": "Saucisse",
|
||||
"chorizo": "Chorizo",
|
||||
"merguez": "Merguez",
|
||||
"prosciutto": "Prosciutto",
|
||||
"pancetta": "Pancetta",
|
||||
"mortadella": "Mortadelle",
|
||||
"salami": "Salami",
|
||||
"chicken": "Poulet",
|
||||
"turkey": "Dinde",
|
||||
"duck": "Canard",
|
||||
"duck_breast": "Magret de canard",
|
||||
"salmon": "Saumon",
|
||||
"tuna": "Thon",
|
||||
"cod": "Cabillaud",
|
||||
"trout": "Truite",
|
||||
"sardine": "Sardine",
|
||||
"anchovy": "Anchois",
|
||||
"whiting": "Merlan",
|
||||
"surimi": "Surimi",
|
||||
"sea_bass": "Bar (loup de mer)",
|
||||
"sea_bream": "Dorade",
|
||||
"sole": "Sole",
|
||||
"turbot": "Turbot",
|
||||
"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",
|
||||
"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",
|
||||
"bulgur": "Boulgour",
|
||||
"polenta": "Polenta",
|
||||
"quinoa": "Quinoa",
|
||||
"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",
|
||||
"tagliatelle": "Tagliatelles",
|
||||
"lasagna_sheets": "Lasagnes (feuilles)",
|
||||
"gnocchi": "Gnocchi",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"liquid_cream": "Crème liquide",
|
||||
"cheese": "Fromage",
|
||||
"emmental": "Emmental",
|
||||
"gruyere": "Gruyère",
|
||||
"parmesan": "Parmesan",
|
||||
"mozzarella": "Mozzarella",
|
||||
"goat_cheese": "Chèvre (fromage)",
|
||||
"feta": "Feta",
|
||||
"comte": "Comté",
|
||||
"fromage_blanc": "Fromage blanc",
|
||||
"mascarpone": "Mascarpone",
|
||||
"yogurt": "Yaourt",
|
||||
"burrata": "Burrata",
|
||||
"ricotta": "Ricotta",
|
||||
"pecorino": "Pecorino",
|
||||
"gorgonzola": "Gorgonzola",
|
||||
"cheddar": "Cheddar",
|
||||
"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",
|
||||
"silken_tofu": "Tofu soyeux",
|
||||
"herbes_de_provence": "Herbes de Provence",
|
||||
"black_pepper": "Poivre noir",
|
||||
"paprika": "Paprika",
|
||||
"espelette_pepper": "Piment d'Espelette",
|
||||
"cayenne_pepper": "Piment de Cayenne",
|
||||
"cumin": "Cumin",
|
||||
"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",
|
||||
"nigella": "Nigelle",
|
||||
"allspice": "Quatre épices",
|
||||
"colombo_powder": "Colombo (poudre)",
|
||||
"baharat": "Baharat",
|
||||
"horseradish": "Raifort",
|
||||
"herb_salt": "Sel aux herbes",
|
||||
"celery_salt": "Sel de céleri",
|
||||
"fleur_de_sel": "Fleur de sel",
|
||||
"salt": "Sel",
|
||||
"five_spice": "Cinq épices",
|
||||
"garam_masala": "Garam masala",
|
||||
"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",
|
||||
"zaatar": "Za'atar",
|
||||
"soy_sauce": "Sauce soja",
|
||||
"mustard": "Moutarde",
|
||||
"mayonnaise": "Mayonnaise",
|
||||
"ketchup": "Ketchup",
|
||||
"tabasco": "Tabasco",
|
||||
"worcestershire_sauce": "Sauce Worcestershire",
|
||||
"fish_sauce": "Sauce nuoc-mâm",
|
||||
"wasabi": "Wasabi",
|
||||
"harissa": "Harissa",
|
||||
"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",
|
||||
"red_pesto": "Pesto rouge (tomates séchées)",
|
||||
"pesto": "Pesto",
|
||||
"oyster_sauce": "Sauce huître",
|
||||
"hoisin_sauce": "Sauce hoisin",
|
||||
"sriracha": "Sauce sriracha",
|
||||
"sweet_chili_sauce": "Sauce sweet chili",
|
||||
"miso": "Miso",
|
||||
"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",
|
||||
"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",
|
||||
"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": "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",
|
||||
"rum": "Rhum",
|
||||
"whisky": "Whisky",
|
||||
"vodka": "Vodka",
|
||||
"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": "Dashi (bouillon japonais)",
|
||||
"shellfish_bisque": "Bisque de crustacés",
|
||||
"tapioca_flour": "Farine de tapioca",
|
||||
"masa_harina": "Masa harina",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { IngredientPicker } from "../features/recipes/IngredientPicker";
|
|||
import { IngredientRow } from "../features/recipes/IngredientRow";
|
||||
import { type StepDraft, StepListEditor } from "../features/recipes/StepListEditor";
|
||||
import "../features/recipes/recipes.scss";
|
||||
import { makeClientKey } from "../lib/client-key";
|
||||
import { errorMessageService } from "../services/error-message.service";
|
||||
|
||||
/** In display order — mirrors `RecipeVisibility` (schema.prisma/shared types). */
|
||||
|
|
@ -82,7 +83,7 @@ export function RecipeFormPage() {
|
|||
setDietIds(recipe.diets.map((diet) => diet.id));
|
||||
setIngredientLines(
|
||||
recipe.ingredients.map((line) => ({
|
||||
key: crypto.randomUUID(),
|
||||
key: makeClientKey(),
|
||||
ingredient: line.ingredient,
|
||||
quantity: String(line.quantity),
|
||||
unit: line.unit,
|
||||
|
|
@ -90,7 +91,7 @@ export function RecipeFormPage() {
|
|||
);
|
||||
setSteps(
|
||||
recipe.steps.map((step) => ({
|
||||
key: crypto.randomUUID(),
|
||||
key: makeClientKey(),
|
||||
description: step.description,
|
||||
picture: step.picture ?? "",
|
||||
})),
|
||||
|
|
@ -110,7 +111,7 @@ export function RecipeFormPage() {
|
|||
function addIngredient(ingredient: IngredientView) {
|
||||
setIngredientLines((lines) => [
|
||||
...lines,
|
||||
{ key: crypto.randomUUID(), ingredient, quantity: "", unit: "" },
|
||||
{ key: makeClientKey(), ingredient, quantity: "", unit: "" },
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function RecipesPage() {
|
|||
const { id } = useParams<{ id: string }>();
|
||||
const selectedId = id !== undefined ? Number(id) : null;
|
||||
|
||||
const [activeTab, setActiveTab] = useState<RecipeTab>("publique");
|
||||
const [activeTab, setActiveTab] = useState<RecipeTab>("favoris");
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch, setDebouncedSearch] = useState("");
|
||||
const [listState, setListState] = useState<RecipeListState>({ status: "loading" });
|
||||
|
|
|
|||
37
apps/web/src/pages/settings/CreditsPage.tsx
Normal file
37
apps/web/src/pages/settings/CreditsPage.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import "./settings-pages.scss";
|
||||
|
||||
/**
|
||||
* Third-party attribution — routed at `/parametres/credits`. Exists solely
|
||||
* to satisfy the CC BY 4.0 requirement on the ingredient icon set (see
|
||||
* `features/recipes/ingredient-icons.tsx`'s `FilledIcon`-based components):
|
||||
* most of it comes from foodiconpack.com's free "Common ingredient icons"
|
||||
* and "Common Utensils" collections, which require crediting the source,
|
||||
* linking the license, and noting that the icons were adapted (recolored
|
||||
* to `currentColor` for theming, original size/style attributes dropped —
|
||||
* see the generation note in `ingredient-icons.tsx`). Nothing else in the
|
||||
* app currently needs a credits entry — grow this page if that changes.
|
||||
*/
|
||||
export function CreditsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="settings-page">
|
||||
<h1>{t("credits.title")}</h1>
|
||||
|
||||
<div className="settings-page__section">
|
||||
<h2 className="settings-page__section-title">{t("credits.icons.title")}</h2>
|
||||
<p className="settings-page__hint">{t("credits.icons.description")}</p>
|
||||
<p className="settings-page__hint">
|
||||
<a href="https://foodiconpack.com" target="_blank" rel="noreferrer">
|
||||
foodiconpack.com
|
||||
</a>
|
||||
{" — "}
|
||||
<a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noreferrer">
|
||||
{t("credits.icons.licenseLabel")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { ErrorCode, THEME_PREFERENCES, type ThemePreference } from "@batch-cooki
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ApiError } from "../../api/client";
|
||||
import { RadioOption } from "../../components/ui/Radio";
|
||||
import { useTheme } from "../../features/theme/ThemeContext";
|
||||
import { errorMessageService } from "../../services/error-message.service";
|
||||
import "./settings-pages.scss";
|
||||
|
|
@ -46,23 +47,16 @@ export function UserPreferencesPage() {
|
|||
{THEME_PREFERENCES.map((option) => {
|
||||
const checked = theme === option;
|
||||
return (
|
||||
<label
|
||||
<RadioOption
|
||||
key={option}
|
||||
// `is-selected` (not a `:has(:checked)` CSS rule) drives the
|
||||
// selected look — see AllergySelect.tsx for why this stays
|
||||
// in JS rather than pure CSS.
|
||||
className={`theme-select__option${checked ? " is-selected" : ""}`}
|
||||
name="theme"
|
||||
value={option}
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
className="theme-select__option"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
value={option}
|
||||
checked={checked}
|
||||
onChange={() => handleChange(option)}
|
||||
/>
|
||||
<span className="check-mark" aria-hidden="true" />
|
||||
{t(`userPreferences.theme.${option}`)}
|
||||
</label>
|
||||
</RadioOption>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
// household/regime/allergies page was split in three.
|
||||
// =============================================================================
|
||||
|
||||
// Centered column, not pinned to the content area's left edge — on a wide
|
||||
// desktop viewport that left plenty of unused space beside these forms.
|
||||
// Wide enough to give grids (the allergy checkboxes, the ingredient
|
||||
// picker's category chips) real room, still well short of `.app-content`'s
|
||||
// full width so line lengths/inputs don't stretch edge-to-edge.
|
||||
.settings-page {
|
||||
max-width: 56rem;
|
||||
margin: 0 auto;
|
||||
|
||||
&__status {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-md);
|
||||
|
|
@ -21,9 +29,10 @@
|
|||
|
||||
// Each setting lives in its own card, same surface treatment used
|
||||
// elsewhere (see .planning-table in HomePage.scss) — reads as a distinct,
|
||||
// self-contained unit rather than one long form.
|
||||
// self-contained unit rather than one long form. Width comes from
|
||||
// `.settings-page` above, not its own cap, now that it's the one
|
||||
// establishing the centered column every section sits in.
|
||||
.settings-page__section {
|
||||
max-width: 32rem;
|
||||
margin-top: var(--space-lg);
|
||||
padding: var(--space-lg);
|
||||
background: var(--color-surface);
|
||||
|
|
@ -127,15 +136,21 @@
|
|||
// visually distinct, consistent "danger" treatment wherever they appear.
|
||||
.settings-page__danger-zone {
|
||||
margin-top: var(--space-lg);
|
||||
border-color: var(--color-error);
|
||||
border: 1.5px solid var(--color-error);
|
||||
background: color-mix(in srgb, var(--color-error) 8%, var(--color-surface));
|
||||
}
|
||||
|
||||
.settings-page__danger-button {
|
||||
// `button.` prefix (not just `.settings-page__danger-button`) to outrank
|
||||
// the generic `.settings-page button` rule above — a class selector alone
|
||||
// has lower specificity than "class + element", so without it the generic
|
||||
// gray fill/border was winning and this never actually rendered red (same
|
||||
// fix as `button.settings-page__link-button` below, same reason).
|
||||
button.settings-page__danger-button {
|
||||
color: #fff;
|
||||
background: var(--color-error);
|
||||
border-color: var(--color-error);
|
||||
|
||||
&:hover {
|
||||
&:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,15 +60,16 @@ h1 {
|
|||
// `display: none`) and the whole label row it lives in becomes the
|
||||
// interactive surface instead: a flat bordered box that fills in with a
|
||||
// tinted background + primary border once selected, with a checkmark
|
||||
// fading in on the trailing edge.
|
||||
// fading in on the leading edge.
|
||||
//
|
||||
// The base (unselected) look below is detected structurally with `:has()`
|
||||
// — safe, since "does this label contain a checkbox/radio" never changes
|
||||
// after mount. The *selected* look is instead driven by the `is-selected`
|
||||
// class each caller (AllergySelect.tsx, UserPreferencesPage.tsx) toggles
|
||||
// in JS from the same boolean it already passes to `checked` — chaining a
|
||||
// second `:has(:checked)` to react to that live state turned out to be
|
||||
// unreliable across browsers, so this only needs one always-true `:has()`.
|
||||
// class {@link CheckboxOption}/{@link RadioOption} (components/ui/) toggle
|
||||
// in JS from the same boolean their caller already passes to `checked` —
|
||||
// chaining a second `:has(:checked)` to react to that live state turned
|
||||
// out to be unreliable across browsers, so this only needs one
|
||||
// always-true `:has()`.
|
||||
//
|
||||
// Every checkbox/radio in the app goes through this one place (the allergy
|
||||
// grid, the theme picker, anywhere future) rather than each feature styling
|
||||
|
|
@ -126,15 +127,15 @@ input[type="radio"] {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
// The checkmark — a real element (see AllergySelect.tsx /
|
||||
// UserPreferencesPage.tsx) shown via the same `is-selected` class as the
|
||||
// label's own look above, not a separate CSS-only trigger. Scaled in from
|
||||
// nothing so toggling has a bit of motion. Same mark for both checkbox and
|
||||
// radio: one consistent "selected" language app-wide rather than a
|
||||
// checkmark here and a dot there.
|
||||
// The checkmark — a real element (see components/ui/Checkbox.tsx /
|
||||
// Radio.tsx) shown via the same `is-selected` class as the label's own
|
||||
// look above, not a separate CSS-only trigger. Scaled in from nothing so
|
||||
// toggling has a bit of motion. Same mark for both checkbox and radio: one
|
||||
// consistent "selected" language app-wide rather than a checkmark here and
|
||||
// a dot there. Sits first in the row (before the label text, per DOM
|
||||
// order) — a classic "control on the left" layout rather than trailing.
|
||||
.check-mark {
|
||||
flex: none;
|
||||
margin-left: auto;
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
background: var(--color-primary);
|
||||
|
|
|
|||
5
apps/web/src/vite-env.d.ts
vendored
Normal file
5
apps/web/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
// Injected by `define` in vite.config.ts, sourced from package.json's
|
||||
// version field — see AppLayout.tsx for where it's rendered.
|
||||
declare const __APP_VERSION__: string;
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// Read once at config-eval time — cheaper than a plugin hook, and this file
|
||||
// only ever runs in Node (dev server / build), never bundled into the app.
|
||||
const pkg = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf-8"));
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
css: {
|
||||
|
|
@ -13,4 +18,10 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
},
|
||||
// Exposes package.json's version as a compile-time constant — see
|
||||
// src/vite-env.d.ts for the matching ambient declaration, and
|
||||
// AppLayout.tsx for where it's rendered.
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(pkg.version),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
/**
|
||||
* A dietary regime, as returned by `GET /reference/diets` — reference data
|
||||
* (`Diet`, seeded via `apps/api/prisma/seed.ts`), not user-specific.
|
||||
*
|
||||
* `key` is a stable slug (e.g. `"vegetarien"`), not a display label — it
|
||||
* never changes once seeded, unlike the label it stands in for. Callers
|
||||
* resolve the label themselves via i18n (`t(\`catalog.diets.${key}\`)`,
|
||||
* `apps/web`'s `locales/fr/translation.json`), the same way
|
||||
* `IngredientCategory`/`IngredientSubcategory` enum values already do (see
|
||||
* `recipes.form.category.*`/`recipes.form.subcategory.*` in that file).
|
||||
*/
|
||||
export interface DietView {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -18,17 +25,19 @@ export type AllergenKind = "ALLERGY" | "INTOLERANCE";
|
|||
/**
|
||||
* A selectable allergen, as returned by `GET /reference/allergies`.
|
||||
*
|
||||
* `name` is resolved server-side from the parent `Category` — the `Allergy`
|
||||
* table itself carries no name of its own (see `schema.prisma`), so this
|
||||
* flattens that split away: callers just get `{id, name}` and never need to
|
||||
* know a `Category` exists underneath. `kind` groups allergens into two
|
||||
* `key` is resolved server-side from the parent `Category` — the `Allergy`
|
||||
* table itself carries no key of its own (see `schema.prisma`), so this
|
||||
* flattens that split away: callers just get `{id, key}` and never need to
|
||||
* know a `Category` exists underneath. Like {@link DietView.key}, it's a
|
||||
* stable slug (e.g. `"gluten"`), not a display label — resolved via
|
||||
* `t(\`catalog.allergens.${key}\`)`. `kind` groups allergens into two
|
||||
* separate lists client-side (`AllergySelect`, `apps/web`) rather than one
|
||||
* flat "allergies & intolérances" list — a single `PATCH /profile/allergies`
|
||||
* call still covers both, this is a display grouping only.
|
||||
*/
|
||||
export interface AllergyView {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
kind: AllergenKind;
|
||||
}
|
||||
|
||||
|
|
@ -164,10 +173,13 @@ export type IngredientIcon = (typeof INGREDIENT_ICONS)[number];
|
|||
* catalog (`apps/web`'s recipe form and detail page) to pick ingredients and
|
||||
* to surface which allergens/regimes a recipe contains, aggregated across
|
||||
* its ingredients.
|
||||
*
|
||||
* `key` is a stable slug (e.g. `"tomate"`), not a display label — like
|
||||
* {@link DietView.key}, resolved via `t(\`catalog.ingredients.${key}\`)`.
|
||||
*/
|
||||
export interface IngredientView {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
icon: IngredientIcon;
|
||||
category: IngredientCategory;
|
||||
subcategory: IngredientSubcategory;
|
||||
|
|
|
|||
|
|
@ -99,6 +99,9 @@ importers:
|
|||
i18next:
|
||||
specifier: ^26.3.6
|
||||
version: 26.3.6(typescript@5.9.3)
|
||||
lucide-react:
|
||||
specifier: ^1.32.0
|
||||
version: 1.32.0(react@18.3.1)
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
|
|
@ -2098,6 +2101,11 @@ packages:
|
|||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz}
|
||||
|
||||
lucide-react@1.32.0:
|
||||
resolution: {integrity: sha512-txX56hMFnRxPi1f9/nH69YN8uvAO6a7Y1KSWKjCDAtdD9+soEgmWuCt6iRm1pkxUZo2+YntSdsE1L6bIuKoY8Q==, tarball: https://registry.npmjs.org/lucide-react/-/lucide-react-1.32.0.tgz}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
luxon@3.7.2:
|
||||
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==, tarball: https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -4764,6 +4772,10 @@ snapshots:
|
|||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
||||
lucide-react@1.32.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
luxon@3.7.2: {}
|
||||
|
||||
make-dir@3.1.0:
|
||||
|
|
|
|||
Loading…
Reference in a new issue