- 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>
99 lines
4.2 KiB
TypeScript
99 lines
4.2 KiB
TypeScript
/**
|
|
* One-off generator, run by hand whenever the catalog's reference data
|
|
* changes (a new ingredient/diet/allergen added to
|
|
* `db/reference-seed-data.ts`): derives every row's slug `key` from its
|
|
* French name (see `slugify.ts`), fails loudly on any collision, and
|
|
* regenerates `apps/web/src/locales/fr/translation.json`'s
|
|
* `catalog.{diets,allergens,ingredients}` sections (key -> French label),
|
|
* merged in without touching the rest of the file.
|
|
*
|
|
* Also (re-)writes `backfill.sql` alongside itself — the `UPDATE ... SET
|
|
* key = ...` statements a migration adding a brand new item needs to carry
|
|
* forward, in case that ever happens again; the one for this refactor's own
|
|
* migration (`prisma/migrations/20260818190000_catalog_labels_to_keys/`)
|
|
* was generated once and copied in by hand, and `backfill.sql` itself is
|
|
* gitignored scratch output, not the source of truth.
|
|
*
|
|
* 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 { ALLERGENS, DIETS, INGREDIENT_GROUPS } from "../src/db/reference-seed-data.js";
|
|
import { slugify } from "../src/utils/slugify.js";
|
|
|
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
|
|
function toKeyLabelMap(labels: string[]): Record<string, string> {
|
|
const map: Record<string, string> = {};
|
|
const seenKeys = new Map<string, string>();
|
|
for (const label of labels) {
|
|
const key = slugify(label);
|
|
const clashingLabel = seenKeys.get(key);
|
|
if (clashingLabel !== undefined && clashingLabel !== label) {
|
|
throw new Error(`Slug collision: "${clashingLabel}" and "${label}" both slugify to "${key}"`);
|
|
}
|
|
seenKeys.set(key, label);
|
|
map[key] = label;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
const dietLabels = DIETS;
|
|
const allergenLabels = ALLERGENS.map((a) => a.name);
|
|
const ingredientLabels = INGREDIENT_GROUPS.flatMap((g) => g.items.map((i) => i.name));
|
|
|
|
const diets = toKeyLabelMap(dietLabels);
|
|
const allergens = toKeyLabelMap(allergenLabels);
|
|
const ingredients = toKeyLabelMap(ingredientLabels);
|
|
|
|
console.log(
|
|
`diets: ${Object.keys(diets).length}, allergens: ${Object.keys(allergens).length}, ingredients: ${Object.keys(ingredients).length}`,
|
|
);
|
|
|
|
// --- Merge into the fr locale file -----------------------------------------
|
|
const localePath = here + "../../web/src/locales/fr/translation.json";
|
|
const locale = JSON.parse(readFileSync(localePath, "utf8"));
|
|
locale.catalog = { diets, allergens, ingredients };
|
|
writeFileSync(localePath, JSON.stringify(locale, null, 2) + "\n");
|
|
console.log(`wrote ${localePath}`);
|
|
|
|
// --- Emit the migration's backfill SQL --------------------------------------
|
|
function escapeSql(value: string): string {
|
|
return value.replace(/'/g, "''");
|
|
}
|
|
|
|
// The migration this feeds renames each table's "name" column to "key"
|
|
// *before* running this backfill — so by the time these UPDATEs run, the
|
|
// "key" column still holds the old French label value (just under its new
|
|
// column name), which is exactly what the WHERE clause below matches on.
|
|
const dietUpdates = Object.entries(diets)
|
|
.map(([key, label]) => `UPDATE "diet" SET "key" = '${escapeSql(key)}' WHERE "key" = '${escapeSql(label)}';`)
|
|
.join("\n");
|
|
const categoryUpdates = Object.entries(allergens)
|
|
.map(
|
|
([key, label]) => `UPDATE "category" SET "key" = '${escapeSql(key)}' WHERE "key" = '${escapeSql(label)}';`,
|
|
)
|
|
.join("\n");
|
|
const ingredientUpdates = Object.entries(ingredients)
|
|
.map(
|
|
([key, label]) =>
|
|
`UPDATE "ingredients" SET "key" = '${escapeSql(key)}' WHERE "key" = '${escapeSql(label)}';`,
|
|
)
|
|
.join("\n");
|
|
|
|
const sql = `-- Auto-generated by scripts/generate-catalog-i18n.ts — do not hand-edit.
|
|
-- Backfills the "key" column (just renamed from "name" by this migration's
|
|
-- preceding statement, so it still holds the old French label) to its slug
|
|
-- value, for every row already seeded in a database this migration runs
|
|
-- against. A fresh database has none of these rows yet (the seed script
|
|
-- inserts by "key" from the start), so this is a no-op there.
|
|
|
|
${dietUpdates}
|
|
|
|
${categoryUpdates}
|
|
|
|
${ingredientUpdates}
|
|
`;
|
|
writeFileSync(here + "backfill.sql", sql);
|
|
console.log(`wrote ${here}backfill.sql`);
|