import type { ParsedRecipe, ParsedRecipeStep } from "./recipe-source-adapter.js"; import { type TechStepMappingRule, loadTechStepMappingRules, matchTechSteps, } from "./tech-step-matcher.js"; /** * The "Traduction en étapes" stage of the import pipeline described in * specs/batch-cooking-architecture.md (Import depuis source → **Traduction * en étapes** → Sauvegarde) — takes a source-agnostic {@link ParsedRecipe} * (recipe-source-adapter.ts's `parse()` output) and declares each step's * technique sequence, the same `techStepIds: number[]` shape * `Step.techSteps`/`StepTechStep` (schema.prisma) will eventually persist. * * Deliberately doesn't touch ingredients — resolving free-text ingredient * lines against our `Ingredient`/`Unit` catalogs is a separate, not-yet-built * concern (see `ParsedRecipeIngredient`'s doc comment) — and doesn't turn * the result into a saveable `Recipe` either (no `dietIds`/`visibility`/ * author, a source can't know those). This is one step of the pipeline, not * the whole thing. * * `translateRecipeSteps` is pure (takes `techStepMappings` as a plain * argument, same convention as `matchTechSteps` itself) so it's unit-testable * without a database; `translateRecipe` is the DB-backed convenience wrapper * a caller reaches for in practice, mirroring `tech-step-matcher.ts`'s own * pure/DB-touching split. */ /** A {@link ParsedRecipeStep}, after tech-step detection — declares its technique sequence alongside the description/picture it already had. */ export interface TranslatedRecipeStep extends ParsedRecipeStep { /** Ordered sequence of detected `TechStep` ids (see `matchTechSteps`) — empty if this step doesn't mention any known technique. */ techStepIds: number[]; } /** A {@link ParsedRecipe} whose `steps` have been translated — everything else (name, ingredients, portions, …) passes through unchanged. */ export interface TranslatedRecipe extends Omit { steps: TranslatedRecipeStep[]; } /** * Declares each of `recipe`'s steps' technique sequence against * `techStepMappings`, leaving everything else about the recipe untouched. * Pure — testable with a hand-built mapping list, no database involved (see * `translateRecipe` for the DB-backed loader). `techStepMappings` should * already be filtered to the locale the caller cares about, same * requirement `matchTechSteps` itself has. */ export function translateRecipeSteps( recipe: ParsedRecipe, techStepMappings: TechStepMappingRule[], ): TranslatedRecipe { return { ...recipe, steps: recipe.steps.map((step) => ({ ...step, techStepIds: matchTechSteps(step.description, techStepMappings), })), }; } /** * Convenience wrapper around {@link translateRecipeSteps} that loads * `locale`'s mapping catalog itself — what a caller reaches for when * translating a single recipe on its own (e.g. the eventual "import this * one recipe" endpoint). A caller translating many recipes at once should * call `loadTechStepMappingRules` once and reuse it across * `translateRecipeSteps` calls instead, the same "don't requery per item" * reasoning `recipe.service.ts`'s `createRecipe`/`updateRecipe` already * follow for manually-authored recipes. * * No user- or recipe-level language preference exists anywhere in the app * yet (see `tech-step-matcher.ts`'s `loadTechStepMappingRules`) — callers * pass a locale explicitly rather than this module guessing one. Note that * an English-language source (e.g. TheMealDB) translated against `"fr"` * mappings will currently get an empty `techStepIds` sequence on every * step — matching-language mappings for that source's language don't exist * yet, this stage doesn't invent them. */ export async function translateRecipe( recipe: ParsedRecipe, locale: string, ): Promise { const techStepMappings = await loadTechStepMappingRules(locale); return translateRecipeSteps(recipe, techStepMappings); }