i18n(measurements): say how a duration entry was read

Refs #1101
This commit is contained in:
Marc-André Bombeck committed 2026-10-03 18:35:26 +02:00
1 parent 313d8491de
commit 51e2d68a88
9 files changed
+108 -7

No files matched your search

+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "Verhältnis",
"unitRating": "Stufe",
"typeWho5Score": "WHO-5-Wert",
"typeSciScore": "SCI-Wert"
"typeSciScore": "SCI-Wert",
"durationReadAs": "Gelesen als {duration}",
"durationHoursMinutes": "{hours} Std. {minutes} Min.",
"durationHoursOnly": "{hours} Std.",
"durationMinutesOnly": "{minutes} Min.",
"durationInvalid": "Gib eine Dauer ein, zum Beispiel 7,5, 7:30 oder 7h 30m.",
"durationTooLong": "Ein Eintrag kann nicht länger als 24 Stunden sein."
},
"metricCatalog": {
"title": "Was HealthLog erfassen kann",
+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "ratio",
"unitRating": "rating",
"typeWho5Score": "WHO-5 score",
"typeSciScore": "SCI score"
"typeSciScore": "SCI score",
"durationReadAs": "Read as {duration}",
"durationHoursMinutes": "{hours} h {minutes} min",
"durationHoursOnly": "{hours} h",
"durationMinutesOnly": "{minutes} min",
"durationInvalid": "Enter a duration like 7.5, 7:30 or 7h 30m.",
"durationTooLong": "One entry can't be longer than 24 hours."
},
"metricCatalog": {
"title": "What HealthLog can track",
+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "ratio",
"unitRating": "nivel",
"typeWho5Score": "Puntuación WHO-5",
"typeSciScore": "Puntuación SCI"
"typeSciScore": "Puntuación SCI",
"durationReadAs": "Se lee como {duration}",
"durationHoursMinutes": "{hours} h {minutes} min",
"durationHoursOnly": "{hours} h",
"durationMinutesOnly": "{minutes} min",
"durationInvalid": "Escribe una duración como 7,5, 7:30 o 7h 30m.",
"durationTooLong": "Una entrada no puede durar más de 24 horas."
},
"metricCatalog": {
"title": "Lo que HealthLog puede registrar",
+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "ratio",
"unitRating": "niveau",
"typeWho5Score": "Score WHO-5",
"typeSciScore": "Score SCI"
"typeSciScore": "Score SCI",
"durationReadAs": "Compris comme {duration}",
"durationHoursMinutes": "{hours} h {minutes} min",
"durationHoursOnly": "{hours} h",
"durationMinutesOnly": "{minutes} min",
"durationInvalid": "Saisis une durée comme 7,5, 7:30 ou 7h 30m.",
"durationTooLong": "Une saisie ne peut pas dépasser 24 heures."
},
"metricCatalog": {
"title": "Ce que HealthLog peut suivre",
+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "rapporto",
"unitRating": "livello",
"typeWho5Score": "Punteggio WHO-5",
"typeSciScore": "Punteggio SCI"
"typeSciScore": "Punteggio SCI",
"durationReadAs": "Letto come {duration}",
"durationHoursMinutes": "{hours} h {minutes} min",
"durationHoursOnly": "{hours} h",
"durationMinutesOnly": "{minutes} min",
"durationInvalid": "Inserisci una durata come 7,5, 7:30 o 7h 30m.",
"durationTooLong": "Una voce non può superare le 24 ore."
},
"metricCatalog": {
"title": "Cosa può monitorare HealthLog",
+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "비율",
"unitRating": "단계",
"typeWho5Score": "WHO-5 점수",
"typeSciScore": "SCI 점수"
"typeSciScore": "SCI 점수",
"durationReadAs": "{duration}(으)로 읽었어요",
"durationHoursMinutes": "{hours}시간 {minutes}분",
"durationHoursOnly": "{hours}시간",
"durationMinutesOnly": "{minutes}분",
"durationInvalid": "7.5, 7:30, 7h 30m처럼 시간을 입력해 주세요.",
"durationTooLong": "한 번에 24시간보다 길게 입력할 수 없어요."
},
"metricCatalog": {
"title": "HealthLog가 기록할 수 있는 것",
+7 -1
View File
@@ -798,7 +798,13 @@
"unitRatio": "stosunek",
"unitRating": "poziom",
"typeWho5Score": "Wynik WHO-5",
"typeSciScore": "Wynik SCI"
"typeSciScore": "Wynik SCI",
"durationReadAs": "Odczytano jako {duration}",
"durationHoursMinutes": "{hours} godz. {minutes} min",
"durationHoursOnly": "{hours} godz.",
"durationMinutesOnly": "{minutes} min",
"durationInvalid": "Wpisz czas trwania, np. 7,5, 7:30 lub 7h 30m.",
"durationTooLong": "Jeden wpis nie może przekraczać 24 godzin."
},
"metricCatalog": {
"title": "Co HealthLog potrafi śledzić",
@@ -99,6 +99,8 @@ const LEGIT_IDENTICAL = new Set<string>([
"labs.catalog.hs-crp",
"measurements.typeHrvRmssd",
"measurements.typeVo2Max",
// "{hours} h {minutes} min": the SI symbols for hour and minute.
"measurements.durationHoursMinutes",
"settings.sections.sources.metrics.vo2Max",
// Brand / product / proper nouns.
"settings.googleHealth",
@@ -0,0 +1,57 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nProvider } from "@/lib/i18n/context";
import { parseDurationEntry } from "@/lib/measurements/parse-duration";
import { DurationReadAs } from "../duration-read-as";
/**
* The hint under a duration field says how the entry was read, so "7.5",
* "7:30" and "7h30" can each be checked before saving.
*/
function render(minutes: number, locale: "en" | "de" | "ko" = "en") {
return renderToStaticMarkup(
<I18nProvider initialLocale={locale}>
<DurationReadAs minutes={minutes} />
</I18nProvider>,
);
}
function readAs(raw: string): string {
const read = parseDurationEntry(raw, "h");
if (!read.ok) throw new Error(`refused ${raw}`);
return render(read.minutes);
}
describe("<DurationReadAs>", () => {
it.each(["7.5", "7,5", "7:30", "7h30", "7 h 30 min"])(
"shows %j as seven and a half hours",
(raw) => {
expect(readAs(raw)).toContain("Read as 7 h 30 min");
},
);
it("leaves out a zero part", () => {
expect(render(480)).toContain("Read as 8 h<");
expect(render(45)).toContain("Read as 45 min");
});
it("speaks the reader's language", () => {
expect(render(450, "de")).toContain("7 Std. 30 Min.");
expect(render(450, "ko")).toContain("7시간 30분");
});
it("is wired under the measurement form's value field", () => {
const form = readFileSync(
join(process.cwd(), "src/components/measurements/measurement-form.tsx"),
"utf8",
);
expect(form).toMatch(
/hint=\{\s*durationRead\?\.ok === true \? \(\s*<DurationReadAs minutes=\{durationRead\.minutes\} \/>/,
);
// A refused entry is an inline field error, never a toast.
expect(form).toMatch(/setValueError\(\s*t\(/);
});
});