fix(ai): accept every app language on the chat and status requests

The Coach chat, the document chat and the fenced chat declared their
locale field as de/en, so a body naming French, Korean, Brazilian
Portuguese or any other shipped language was refused with 422 before
resolveServerLocale, which carries every locale, ever saw it. The same
pair sat on the profile update, so an account could not store any other
language there either, and the stored language is what the cookie-blind
background work (the Coach nudge, notification texts) writes in.

All four now take z.enum(locales), and the published contract for the
metric, insight and biomarker status queries and the profile update
lists every UI locale instead of two.
This commit is contained in:
Marc-André Bombeck committed 2026-10-29 09:19:56 +01:00
1 parent 65fe5c34e3
commit 6f60812132
10 files changed
+197 -42

No files matched your search

+13 -6
View File
@@ -8,6 +8,7 @@
import { recoveryEmptySchema } from "../recovery-empty";
import { trendSentiment, trendVerdict } from "../trend-verdict";
import { z } from "zod/v4";
import { locales } from "@/lib/i18n/config";
import { measurementSourceEnum, measurementTypeEnum } from "../shared";
import { METRIC_STATUS_IDS } from "@/lib/insights/metric-status-registry";
import {
@@ -45,9 +46,11 @@ export const metricStatusQuery = z
"HealthKit metric id to assess (e.g. RESTING_HEART_RATE, SLEEP_DURATION). Closed enum: an unknown id 422s. The seven specialised metrics are served by their own routes and are not accepted here.",
),
locale: z
.enum(["de", "en"])
.enum(locales)
.optional()
.describe("Optional UI-locale override; defaults to the session locale."),
.describe(
"Optional UI-locale override, any language the app ships; defaults to the session locale.",
),
})
.meta({ id: "MetricStatusQuery" });
@@ -792,9 +795,11 @@ export const glp1PlateauResponse = z
export const insightStatusQuery = z
.object({
locale: z
.enum(["de", "en"])
.enum(locales)
.optional()
.describe("Optional UI-locale override; defaults to the session locale."),
.describe(
"Optional UI-locale override, any language the app ships; defaults to the session locale.",
),
})
.meta({ id: "InsightStatusQuery" });
@@ -856,9 +861,11 @@ export const biomarkerAssessmentQuery = z
"User-scoped biomarker id to assess. A cross-user or unknown id returns an `insufficient` envelope, not a 404 (existence sealed).",
),
locale: z
.enum(["de", "en"])
.enum(locales)
.optional()
.describe("Optional UI-locale override; defaults to the session locale."),
.describe(
"Optional UI-locale override, any language the app ships; defaults to the session locale.",
),
})
.meta({ id: "BiomarkerAssessmentQuery" });
+1 -1
View File
@@ -554,7 +554,7 @@ export const profileUpdateRequest = z
.describe(
"Trimmed before its bound is checked. Null, an empty string or only spaces clears it; an omitted field is left untouched.",
),
locale: z.enum(["de", "en"]).nullable().optional(),
locale: z.enum(locales).nullable().optional(),
timezone: z.string().min(1).max(64).optional(),
timeFormat: z
.enum(["AUTO", "H12", "H24"])