From 316ba3cb620c0d47e99a87fa60c28b8f9ab02011 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 20:36:11 +0000 Subject: [PATCH] feat(i18n): server-genererad prosa -> nyckel+params (min dag, ombokning, scan-diff) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Min-dag-noten: noteKey (estimateNote/computedNote) bredvid svensk note; klient t(noteKey). - Ombokningsskäl (planning.ts): rescheduleReasonKey + params bredvid rescheduleReasonSv; klient t(key, {title}). Workerns två andra ombokningstexter lämnade på svensk fallback. - Scan-diff: reasonCode-enum (6 koder) bredvid reasonSv; klient t(scanDiff.reason.). - 8 nya nycklar i alla 12 språk. Ingen beslutslogik rörd; alla Sv-fält kvar som fallback. - Kvar som egna spår: recept-säkerhetsmeddelanden + substitutions-data (översättningstabell). - typecheck grönt (20/20), vakt grön, inventory-engine 53/53 tester. Co-Authored-By: Claude --- apps/api/src/routes/meals.ts | 3 ++ apps/api/src/routes/planning.ts | 28 +++++++++++++++---- apps/mobile/src/app/(tabs)/my-day.tsx | 6 ++-- apps/mobile/src/app/(tabs)/plan.tsx | 9 +++++- .../src/app/scan-diff-review/[jobId].tsx | 4 ++- apps/mobile/src/locales/da/common.json | 10 ++++++- apps/mobile/src/locales/de/common.json | 10 ++++++- apps/mobile/src/locales/en/common.json | 10 ++++++- apps/mobile/src/locales/es/common.json | 10 ++++++- apps/mobile/src/locales/fi/common.json | 10 ++++++- apps/mobile/src/locales/fr/common.json | 10 ++++++- apps/mobile/src/locales/it/common.json | 10 ++++++- apps/mobile/src/locales/nb/common.json | 10 ++++++- apps/mobile/src/locales/nl/common.json | 10 ++++++- apps/mobile/src/locales/pl/common.json | 10 ++++++- apps/mobile/src/locales/pt/common.json | 10 ++++++- apps/mobile/src/locales/sv/common.json | 10 ++++++- packages/inventory-engine/src/scan-diff.ts | 23 ++++++++++++++- packages/shared-types/src/entities.ts | 3 ++ 19 files changed, 174 insertions(+), 22 deletions(-) diff --git a/apps/api/src/routes/meals.ts b/apps/api/src/routes/meals.ts index a4f4b29..ccd662e 100644 --- a/apps/api/src/routes/meals.ts +++ b/apps/api/src/routes/meals.ts @@ -184,9 +184,12 @@ export async function mealRoutes(app: FastifyInstance) { title: m.recipeId ? (titleMap.get(m.recipeId) ?? m.titleSv) : m.titleSv, })), summary, + // Svenska strängen behålls som fallback; noteKey låter klienten översätta + // (samma två fall som förr – noten visas alltid, exakt som tidigare). note: hasEstimates ? "Dagen innehåller uppskattade värden från foto – justera gärna vid behov." : "Värdena är beräknade ur recept och livsmedelsdata och visas som uppskattningar.", + noteKey: hasEstimates ? "myday.estimateNote" : "myday.computedNote", }; }); diff --git a/apps/api/src/routes/planning.ts b/apps/api/src/routes/planning.ts index 2f54a0c..d3fa926 100644 --- a/apps/api/src/routes/planning.ts +++ b/apps/api/src/routes/planning.ts @@ -19,6 +19,14 @@ import { loadLocalePreferences } from "../lib/localeContext.js"; * (GENERATE_WEEK_PLAN) som väger lager, utgångsdatum, matlådor, budget, * variation och mål – med deterministisk kärna och AAMOS som rådgivare. */ +/** + * Kanonisk svensk motivering vid dynamisk omplanering (spec §25). Samma funktion + * används både när planposten skrivs och för att härleda den översättbara i18n- + * nyckeln vid läsning (så att klienten kan visa den på användarens språk). + */ +export const rescheduleMovedReasonSv = (title: string): string => + `${title} flyttades hit eftersom råvarorna bör användas först.`; + export async function planningRoutes(app: FastifyInstance) { const auth = { preHandler: [app.authenticate] }; @@ -54,10 +62,20 @@ export async function planningRoutes(app: FastifyInstance) { ); const result = plansWithEntries.map(({ plan, entries }) => ({ ...plan, - entries: entries.map((e) => ({ - ...e, - title: e.recipeId ? (titleMap.get(e.recipeId) ?? e.titleSv) : e.titleSv, - })), + entries: entries.map((e) => { + const title = e.recipeId ? (titleMap.get(e.recipeId) ?? e.titleSv) : e.titleSv; + // Behåll rescheduleReasonSv (fallback) men lägg till översättbar nyckel + + // parametrar för just omplanerings-meningen (andra motiveringar, t.ex. + // matlåde-noteringar från workern, faller kvar på svenska). + const structured = + e.rescheduleReasonSv && e.rescheduleReasonSv === rescheduleMovedReasonSv(e.titleSv) + ? { + rescheduleReasonKey: "plan.rescheduleReason", + rescheduleReasonParams: { title }, + } + : {}; + return { ...e, title, ...structured }; + }), })); return { plans: result }; }); @@ -167,7 +185,7 @@ export async function planningRoutes(app: FastifyInstance) { recipeId: entry.recipeId, titleSv: entry.titleSv, status: "moved", - rescheduleReasonSv: `${entry.titleSv} flyttades hit eftersom råvarorna bör användas först.`, + rescheduleReasonSv: rescheduleMovedReasonSv(entry.titleSv), }) .where(eq(schema.weekPlanEntries.id, later[0].id)); } diff --git a/apps/mobile/src/app/(tabs)/my-day.tsx b/apps/mobile/src/app/(tabs)/my-day.tsx index 6bf1321..486ec94 100644 --- a/apps/mobile/src/app/(tabs)/my-day.tsx +++ b/apps/mobile/src/app/(tabs)/my-day.tsx @@ -64,6 +64,8 @@ interface DayResponse { saltWarning: boolean; }; note: string; + /** i18n-nyckel för noten; klienten översätter, `note` är svensk fallback. */ + noteKey?: string; } /** Måltidstypernas etiketter bor i i18n-katalogen: myday.mealType. (12 språk). */ @@ -82,7 +84,7 @@ export default function MyDayScreen() { if (query.isLoading) return ; if (query.isError || !query.data) return void query.refetch()} />; - const { summary, meals, note } = query.data; + const { summary, meals, note, noteKey } = query.data; const remaining = summary.remaining.kcal; const macros = [ @@ -186,7 +188,7 @@ export default function MyDayScreen() {