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() {