/** * 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 { const map: Record = {}; const seenKeys = new Map(); for (const label of labels) { const key = slugify(label); const clashingLabel = seenKeys.get(key); if (clashingLabel !== undefined && clashingLabel !== label) { throw new Error(`Slug collision: "${clashingLabel}" and "${label}" both slugify to "${key}"`); } seenKeys.set(key, label); map[key] = label; } 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`);