Write the Coach read strip's second line in the reader's language

On a German metric page the strip printed its own summary in German and
the pattern line under it in English. The pattern line is a finished
sentence the server writes, and the route never told the server who was
reading, so it wrote English.

The route now resolves the reader's language the same way the
correlations and analytics routes do and passes it down. The language is
a required argument the whole way through — it used to be optional with
an English fallback, which is why a caller could stay silent and still
get a sentence back. The cycle page's lagged rows had the same gap and
are fixed with it.

The strip's browser cache is keyed by language too, so switching
language no longer re-uses the sentence written for the previous one.
This commit is contained in:
Marc-André Bombeck committed 2026-07-31 08:52:23 +02:00
1 parent 340d1db6d9
commit 4ed6f7d1ce
30 files changed
+394 -161

No files matched your search

+7
View File
@@ -20,6 +20,7 @@ import { annotate } from "@/lib/logging/context";
import { apiSuccess, apiError } from "@/lib/api-response";
import { requireCycleEnabled } from "@/lib/cycle/gate";
import { checkRateLimit } from "@/lib/rate-limit";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
import { predictCycle, type NightlyTempInput } from "@/lib/cycle";
import { LUTEAL_DEFAULT } from "@/lib/cycle/types";
import {
@@ -208,10 +209,16 @@ export const GET = apiHandler(async () => {
userPriorityJson,
});
const headline = selectHeadlinePhaseRow(rows);
// The lagged rows are served with their narrated `interpretation` intact, so
// the reader's language is resolved here the same way the correlations route
// resolves it. This surface had the same silent-English gap the metric-page
// strip had.
const locale = await resolveServerLocale({ userLocale: user.locale ?? null });
const lagged = discoverPhaseCorrelations({
phaseByDay,
measurements,
userPriorityJson,
locale,
});
// Cycle-NATIVE insight: where each logged symptom clusters across the phases.
+10 -1
View File
@@ -23,6 +23,7 @@ import { requireModuleEnabled } from "@/lib/modules/gate";
import { requireAssistantSurface } from "@/lib/feature-flags";
import { measurementTypeEnum } from "@/lib/validations/measurement";
import { buildCoachReadStrip } from "@/lib/insights/derived/coach-read";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
import type { MeasurementType } from "@/generated/prisma/client";
export const dynamic = "force-dynamic";
@@ -60,12 +61,20 @@ export const GET = apiHandler(async (request: NextRequest) => {
}
const metric = parsed.data.metric as MeasurementType;
const strip = await buildCoachReadStrip(user.id, metric);
// Line 2 of the strip is a finished sentence the clients print verbatim, so
// the reader's language is decided here — same resolution the correlations
// and analytics routes use (cookie, then the stored preference, then the
// Accept-Language header). Without it the strip answered a German page in
// English, one line under the other.
const locale = await resolveServerLocale({ userLocale: user.locale ?? null });
const strip = await buildCoachReadStrip(user.id, metric, locale);
annotate({
action: { name: "insights.coach-read" },
meta: {
metric,
locale,
has_baseline: strip.baseline !== null,
learning: strip.learning,
has_driver: strip.driver !== null,