fix(i18n): say what the Coach reads of older lab results when the lookback has no limit

The no-limit option promised every lab result, and the German detail
sentence ended on a dangling clause. The Coach keeps the latest value
of the most recently measured markers and, for a marker asked about,
its five previous results with older ones summarised per year for ten
years; years before that are only counted. Every language now says
that in complete sentences, with the two numbers pinned to the lab
history constants.
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 18:21:58 +02:00
1 parent 3354631a85
commit e78d5f86a6
12 files changed
+73 -24

No files matched your search

+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} Tage",
"last90days": "{days} Tage",
"lastYear": "{months} Monate",
"allTime": "{months} Monate im Detail, alle Laborwerte"
"allTime": "{months} Monate im Detail, Labor länger"
},
"detailDays": "Der Coach liest nichts, was älter als {days} Tage ist, in keiner Antwort.",
"detailMonths": "Der Coach liest nichts, was älter als {months} Monate ist, in keiner Antwort.",
"detailAll": "Alles, was du erfasst hast: {dailyMonths} Monate tagesgenau, ältere Werte als Monatswerte bis {years} Jahre zurück und alle Laborwerte, ältere pro Jahr zusammengefasst.",
"detailAll": "Ohne Begrenzung liest der Coach {dailyMonths} Monate tagesgenau und ältere Werte als Monatswerte bis {years} Jahre zurück. Von Laborwerten liest er den jüngsten Wert der zuletzt gemessenen Marker und, wenn du nach einem Marker fragst, dessen {labRecent} vorherige Werte; ältere Werte sind pro Jahr zusammengefasst, bis {labYears} Jahre zurück, noch frühere Jahre werden nur gezählt.",
"note": "Dein Profil, aktuelle Medikamente, eine laufende Erkrankung und anstehende Termine bleiben sichtbar; Auswertungen, die mehr Verlauf brauchen, werden gekürzt oder weggelassen. Antworten, die der Coach früher in älteren Unterhaltungen gegeben hat, können noch ältere Werte nennen."
},
"showConversations": "Unterhaltungen anzeigen",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} days",
"last90days": "{days} days",
"lastYear": "{months} months",
"allTime": "{months} months in detail, all lab results"
"allTime": "{months} months in detail, longer for labs"
},
"detailDays": "The Coach reads nothing older than {days} days, in any answer.",
"detailMonths": "The Coach reads nothing older than {months} months, in any answer.",
"detailAll": "Everything you recorded: {dailyMonths} months in daily detail, older readings as monthly values up to {years} years back, and every lab result, older ones summarised per year.",
"detailAll": "Without a limit, the Coach reads {dailyMonths} months of readings in daily detail and older readings as monthly values up to {years} years back. For lab results it reads the latest value of the most recently measured markers and, for a marker you ask about, its {labRecent} previous results, with older ones summarised per year up to {labYears} years back; earlier years are only counted.",
"note": "Your profile, current medications, an ongoing illness and upcoming visits stay visible; summaries that need more history are shortened or left out. Answers the Coach gave earlier in older conversations may still quote older values."
},
"showConversations": "Show conversations",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} días",
"last90days": "{days} días",
"lastYear": "{months} meses",
"allTime": "{months} meses en detalle, todos los análisis"
"allTime": "{months} meses en detalle, más para los análisis"
},
"detailDays": "El Coach no lee nada con más de {days} días de antigüedad, en ninguna respuesta.",
"detailMonths": "El Coach no lee nada con más de {months} meses de antigüedad, en ninguna respuesta.",
"detailAll": "Todo lo que has registrado: {dailyMonths} meses al detalle diario, las mediciones más antiguas como valores mensuales hasta {years} años atrás y todos los análisis, los más antiguos resumidos por año.",
"detailAll": "Sin límite, el Coach lee {dailyMonths} meses de mediciones al detalle diario y las más antiguas como valores mensuales hasta {years} años atrás. De los análisis lee el último valor de los marcadores medidos más recientemente y, de un marcador por el que preguntes, sus {labRecent} resultados anteriores; los más antiguos se resumen por año hasta {labYears} años atrás y los años anteriores solo se cuentan.",
"note": "Tu perfil, tu medicación actual, una enfermedad en curso y las próximas citas siguen visibles; los resúmenes que necesitan más historial se acortan o se omiten. Las respuestas que el Coach dio antes en conversaciones anteriores pueden seguir citando valores más antiguos."
},
"showConversations": "Mostrar conversaciones",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} jours",
"last90days": "{days} jours",
"lastYear": "{months} mois",
"allTime": "{months} mois en détail, toutes les analyses"
"allTime": "{months} mois en détail, plus pour les analyses"
},
"detailDays": "Le Coach ne lit rien de plus ancien que {days} jours, dans aucune réponse.",
"detailMonths": "Le Coach ne lit rien de plus ancien que {months} mois, dans aucune réponse.",
"detailAll": "Tout ce que vous avez enregistré : {dailyMonths} mois au jour près, les mesures plus anciennes en valeurs mensuelles jusqu’à {years} ans en arrière, et toutes les analyses, les plus anciennes résumées par année.",
"detailAll": "Sans limite, le Coach lit {dailyMonths} mois de mesures au jour près et les mesures plus anciennes en valeurs mensuelles jusqu’à {years} ans en arrière. Pour les analyses, il lit la dernière valeur des marqueurs mesurés le plus récemment et, pour un marqueur sur lequel vous l’interrogez, ses {labRecent} résultats précédents ; les plus anciens sont résumés par année jusqu’à {labYears} ans en arrière, les années antérieures sont seulement comptées.",
"note": "Votre profil, vos médicaments actuels, une maladie en cours et vos prochains rendez-vous restent visibles ; les synthèses qui demandent plus d’historique sont raccourcies ou omises. Les réponses données plus tôt par le Coach dans d’anciennes conversations peuvent encore citer des valeurs plus anciennes."
},
"showConversations": "Afficher les conversations",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} giorni",
"last90days": "{days} giorni",
"lastYear": "{months} mesi",
"allTime": "{months} mesi in dettaglio, tutti gli esami"
"allTime": "{months} mesi in dettaglio, di più per gli esami"
},
"detailDays": "Il Coach non legge nulla di più vecchio di {days} giorni, in nessuna risposta.",
"detailMonths": "Il Coach non legge nulla di più vecchio di {months} mesi, in nessuna risposta.",
"detailAll": "Tutto ciò che hai registrato: {dailyMonths} mesi giorno per giorno, le misurazioni più vecchie come valori mensili fino a {years} anni fa e tutti gli esami, quelli più vecchi riassunti per anno.",
"detailAll": "Senza limite, il Coach legge {dailyMonths} mesi di misurazioni giorno per giorno e quelle più vecchie come valori mensili fino a {years} anni fa. Degli esami legge l’ultimo valore dei marcatori misurati più di recente e, per un marcatore su cui chiedi, i suoi {labRecent} risultati precedenti; quelli più vecchi sono riassunti per anno fino a {labYears} anni fa, gli anni precedenti vengono solo contati.",
"note": "Il tuo profilo, i farmaci attuali, una malattia in corso e le prossime visite restano visibili; i riepiloghi che richiedono più storico vengono accorciati o omessi. Le risposte date in precedenza dal Coach nelle conversazioni più vecchie possono ancora citare valori più vecchi."
},
"showConversations": "Mostra conversazioni",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days}일",
"last90days": "{days}일",
"lastYear": "{months}개월",
"allTime": "{months}개월 상세, 모든 검사 결과"
"allTime": "{months}개월 상세, 검사 결과는 더 길게"
},
"detailDays": "코치는 어떤 답변에서도 {days}일보다 오래된 기록은 읽지 않아요.",
"detailMonths": "코치는 어떤 답변에서도 {months}개월보다 오래된 기록은 읽지 않아요.",
"detailAll": "기록한 모든 것: 최근 {dailyMonths}개월은 하루 단위로, 그 이전 측정값은 최대 {years}년 전까지 월별 값으로, 검사 결과는 전부 읽고 오래된 결과는 연도별로 요약해요.",
"detailAll": "제한이 없으면 코치는 최근 {dailyMonths}개월은 하루 단위로, 그 이전 측정값은 최대 {years}년 전까지 월별 값으로 읽어요. 검사 결과는 가장 최근에 측정한 항목들의 최신 값을 읽고, 물어본 항목은 이전 결과 {labRecent}개도 읽어요. 더 오래된 결과는 최대 {labYears}년 전까지 연도별로 요약하고, 그보다 이전 연도는 개수만 세요.",
"note": "프로필, 현재 복용 중인 약, 진행 중인 질병, 예정된 진료는 계속 보여요. 더 긴 기록이 필요한 요약은 줄이거나 빼요. 예전 대화에서 코치가 이미 한 답변에는 더 오래된 값이 남아 있을 수 있어요."
},
"showConversations": "대화 보기",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} dagen",
"last90days": "{days} dagen",
"lastYear": "{months} maanden",
"allTime": "{months} maanden in detail, alle labuitslagen"
"allTime": "{months} maanden in detail, labuitslagen langer"
},
"detailDays": "De Coach leest in geen enkel antwoord iets dat ouder is dan {days} dagen.",
"detailMonths": "De Coach leest in geen enkel antwoord iets dat ouder is dan {months} maanden.",
"detailAll": "Alles wat je hebt vastgelegd: {dailyMonths} maanden per dag, oudere metingen als maandwaarden tot {years} jaar terug en alle labuitslagen, oudere per jaar samengevat.",
"detailAll": "Zonder grens leest de Coach {dailyMonths} maanden aan metingen per dag en oudere metingen als maandwaarden tot {years} jaar terug. Van labuitslagen leest hij de laatste waarde van de meest recent gemeten markers en, voor een marker waar je naar vraagt, ook de {labRecent} vorige uitslagen; oudere worden per jaar samengevat tot {labYears} jaar terug, nog oudere jaren alleen geteld.",
"note": "Je profiel, je huidige medicatie, een lopende ziekte en komende afspraken blijven zichtbaar; samenvattingen die meer geschiedenis nodig hebben, worden ingekort of weggelaten. Antwoorden die de Coach eerder in oudere gesprekken gaf, kunnen nog oudere waarden noemen."
},
"showConversations": "Gesprekken tonen",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} dni",
"last90days": "{days} dni",
"lastYear": "{months} miesięcy",
"allTime": "{months} miesięcy szczegółowo, wszystkie wyniki badań"
"allTime": "{months} miesięcy szczegółowo, wyniki badań dłużej"
},
"detailDays": "Coach nie czyta niczego starszego niż {days} dni, w żadnej odpowiedzi.",
"detailMonths": "Coach nie czyta niczego starszego niż {months} miesięcy, w żadnej odpowiedzi.",
"detailAll": "Wszystko, co zostało zapisane: {dailyMonths} miesięcy z dokładnością do dnia, starsze pomiary jako wartości miesięczne do {years} lat wstecz oraz wszystkie wyniki badań, starsze podsumowane rocznie.",
"detailAll": "Bez limitu Coach czyta {dailyMonths} miesięcy pomiarów z dokładnością do dnia, a starsze pomiary jako wartości miesięczne do {years} lat wstecz. Z wyników badań czyta ostatnią wartość ostatnio mierzonych markerów, a dla markera, o który pytasz, także {labRecent} poprzednich wyników; starsze są podsumowane rocznie do {labYears} lat wstecz, a wcześniejsze lata tylko zliczane.",
"note": "Twój profil, obecne leki, trwająca choroba i nadchodzące wizyty pozostają widoczne; podsumowania wymagające dłuższej historii są skracane lub pomijane. Odpowiedzi, których Coach udzielił wcześniej w starszych rozmowach, mogą nadal przytaczać starsze wartości."
},
"showConversations": "Pokaż rozmowy",
+2 -2
View File
@@ -3231,11 +3231,11 @@
"last30days": "{days} dias",
"last90days": "{days} dias",
"lastYear": "{months} meses",
"allTime": "{months} meses em detalhe, todos os exames"
"allTime": "{months} meses em detalhe, exames por mais tempo"
},
"detailDays": "O Coach não lê nada mais antigo que {days} dias, em nenhuma resposta.",
"detailMonths": "O Coach não lê nada mais antigo que {months} meses, em nenhuma resposta.",
"detailAll": "Tudo o que você registrou: {dailyMonths} meses em detalhe diário, leituras mais antigas como valores mensais até {years} anos atrás, e todos os resultados de exames, os mais antigos resumidos por ano.",
"detailAll": "Sem limite, o Coach lê {dailyMonths} meses de medições em detalhe diário e as mais antigas como valores mensais até {years} anos atrás. Dos exames, lê o valor mais recente dos marcadores medidos por último e, para um marcador sobre o qual você perguntar, os {labRecent} resultados anteriores; os mais antigos são resumidos por ano até {labYears} anos atrás, e os anos anteriores são apenas contados.",
"note": "Seu perfil, medicamentos atuais, uma doença em curso e consultas futuras permanecem visíveis; resumos que precisam de mais histórico são encurtados ou omitidos. Respostas que o Coach deu antes em conversas mais antigas ainda podem citar valores antigos."
},
"showConversations": "Mostrar conversas",
@@ -151,7 +151,7 @@ describe("<SourcesRail>", () => {
// `allTime` is the default: the sentence carries the numbers the
// readers use (`history-reach.ts`).
expect(html).toContain(
"Everything you recorded: 12 months in daily detail, older readings as monthly values up to 10 years back, and every lab result, older ones summarised per year.",
"Without a limit, the Coach reads 12 months of readings in daily detail and older readings as monthly values up to 10 years back. For lab results it reads the latest value of the most recently measured markers and, for a marker you ask about, its 5 previous results, with older ones summarised per year up to 10 years back; earlier years are only counted.",
);
expect(html).not.toContain("stay visible");
});
@@ -21,9 +21,17 @@ vi.mock("@/lib/tz/resolver", () => ({
resolveUserTimezone: () => Promise.resolve("UTC"),
}));
import { locales } from "@/lib/i18n/config";
import { getServerTranslator } from "@/lib/i18n/server-translator";
import {
LAB_HISTORY_MAX_YEARS,
LAB_HISTORY_RECENT_POINTS,
} from "@/lib/ai/coach/labs-snapshot";
import {
COACH_ALL_TIME_TABLE_DAYS,
COACH_DAILY_DETAIL_DAYS,
COACH_LAB_HISTORY_YEARS,
COACH_LAB_RECENT_POINTS,
COACH_HISTORY_WINDOWS,
HISTORY_REACH_DAYS,
UNBOUNDED_REACH,
@@ -122,13 +130,43 @@ describe("the numbers the settings text states", () => {
});
expect(lookbackText("lastYear").detailParams).toEqual({ months: 12 });
expect(lookbackText("allTime")).toEqual({
// "12 months in detail, every lab result": the option says what it
// "12 months in detail, longer for labs": the option says what it
// reads, not "All" (#1263).
optionParams: { months: 12 },
detailKey: "detailAll",
detailParams: { dailyMonths: 12, years: 10 },
detailParams: { dailyMonths: 12, years: 10, labRecent: 5, labYears: 10 },
});
});
it("states the lab history the named-marker read actually carries", () => {
expect(COACH_LAB_RECENT_POINTS).toBe(LAB_HISTORY_RECENT_POINTS);
expect(COACH_LAB_HISTORY_YEARS).toBe(LAB_HISTORY_MAX_YEARS);
});
it("words the no-limit detail as a complete sentence that claims no more than is read", () => {
const { detailParams } = lookbackText("allTime");
for (const locale of locales) {
const { t } = getServerTranslator(locale);
const option = t("insights.coach.lookback.option.allTime", {
months: 12,
});
const detail = t("insights.coach.lookback.detailAll", detailParams);
// Every number reaches the sentence, none stays a raw placeholder.
expect(detail, locale).not.toMatch(/\{\w+\}/);
for (const n of ["12", "10", "5"]) expect(detail, locale).toContain(n);
// Ends as a sentence, not on a dangling clause.
expect(detail.trim(), locale).toMatch(/[.。]$|요\.?$/);
// "every lab result" was a claim the bounded history does not keep.
expect(`${option} ${detail}`, locale).not.toMatch(
/every lab|all lab|alle Laborwerte|toutes les analyses|todos los análisis|tutti gli esami|wszystkie wyniki|모든 검사|alle labuitslagen|todos os exames/i,
);
}
const de = getServerTranslator("de").t(
"insights.coach.lookback.detailAll",
detailParams,
);
expect(de).toContain("pro Jahr zusammengefasst");
});
});
describe("a history beyond the limit", () => {
+14 -3
View File
@@ -163,8 +163,17 @@ export const COACH_DAILY_DETAIL_DAYS = 365;
/** How far back an all-time table reaches, as monthly values. */
export const COACH_ALL_TIME_TABLE_DAYS = 3_650;
// Lab results are not held to a window of their own: without a limit the
// Coach reads all of them, summarised per marker (`labs-snapshot.ts`, #1263).
// Lab results are not held to a window of their own (#1263). Without a limit
// the snapshot carries the latest value of the most recently measured
// markers; a read for one named marker adds its previous results in full and
// older ones summarised per year (`labs-snapshot.ts`). The two numbers the
// sentence states are pinned to that module's constants by the test.
/** Earlier results a named-marker read lists in full (`LAB_HISTORY_RECENT_POINTS`). */
export const COACH_LAB_RECENT_POINTS = 5;
/** Years of per-year lab summaries before years are only counted (`LAB_HISTORY_MAX_YEARS`). */
export const COACH_LAB_HISTORY_YEARS = 10;
/** The i18n key and parameters describing one lookback option. */
export function lookbackText(window: CoachDefaultWindow): {
@@ -176,13 +185,15 @@ export function lookbackText(window: CoachDefaultWindow): {
if (days === null) {
const dailyMonths = Math.round(COACH_DAILY_DETAIL_DAYS / 30.4);
// The option names what "no limit" actually reads (#1263): the dense
// readings in daily detail for a year, the lab results in full.
// readings in daily detail for a year, the lab results further back.
return {
optionParams: { months: dailyMonths },
detailKey: "detailAll",
detailParams: {
dailyMonths,
years: Math.round(COACH_ALL_TIME_TABLE_DAYS / 365),
labRecent: COACH_LAB_RECENT_POINTS,
labYears: COACH_LAB_HISTORY_YEARS,
},
};
}