From fe13fa966b5877526c2f1c3295d63af2549aefe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 1 Oct 2026 22:09:30 +0200 Subject: [PATCH] fix(labs): print range bars and lab values in the reader's number format Range bars and the lab list, detail rows, chart tooltip and reference ranges printed numbers with a plain `String()`, so a German reader saw "1.8 mIU/L" and "61.3 kg" under a German interface. The same was true of the mean, GMI, eA1C and the advanced indices on the glucose panel. `RangeBar` now prints value, bounds and delta through the locale number formatter and takes an optional `formatValue` for surfaces with their own precision. Lab values keep their trim rule (whole numbers bare, at most two decimals) in `formatLabValue`, which now hands the digits to a caller-supplied locale formatter; `useLabNumber` wires it to the reader's locale for every lab surface. Inputs are unchanged. --- .../__tests__/glucose-clinical-panel.test.tsx | 5 +++ .../glucose/glucose-advanced-disclosure.tsx | 5 +-- .../glucose/glucose-clinical-panel.tsx | 6 ++-- .../lab-reference-range-bar.test.tsx | 14 +++++++- src/components/labs/biomarker-manager.tsx | 5 +-- src/components/labs/lab-biomarker-chart.tsx | 9 +++--- src/components/labs/lab-form.tsx | 11 +++---- src/components/labs/lab-history-list.tsx | 6 ++-- src/components/labs/lab-list.tsx | 10 +++--- .../labs/lab-reference-range-bar.tsx | 10 ++++-- src/components/labs/source-range-note.tsx | 7 ++-- src/components/labs/use-lab-format.ts | 18 +++++++++++ .../targets/__tests__/range-bar.test.tsx | 20 ++++++++++-- src/components/targets/range-bar.tsx | 31 ++++++++++++------ src/lib/labs/__tests__/format-value.test.ts | 25 +++++++++++++++ src/lib/labs/format-value.ts | 32 +++++++++++++------ 16 files changed, 161 insertions(+), 53 deletions(-) create mode 100644 src/components/labs/use-lab-format.ts create mode 100644 src/lib/labs/__tests__/format-value.test.ts diff --git a/src/components/insights/glucose/__tests__/glucose-clinical-panel.test.tsx b/src/components/insights/glucose/__tests__/glucose-clinical-panel.test.tsx index 6dbb55925..bc39ce83f 100644 --- a/src/components/insights/glucose/__tests__/glucose-clinical-panel.test.tsx +++ b/src/components/insights/glucose/__tests__/glucose-clinical-panel.test.tsx @@ -162,6 +162,11 @@ describe(" asserted state", () => { const de = render(, "de"); expect(de).toContain("(3,9–10,0 mmol/L ist das Ziel)"); expect(de).not.toMatch(/mg\/dl/i); + // Mean, GMI and eA1C read with the German decimal comma. + expect(de).not.toMatch(/>\d+\.\d+\d+,\d<\/span>mmol\/L/, + ); authMock.mockReturnValue({ user: { glucoseUnit: "mg/dL" }, diff --git a/src/components/insights/glucose/glucose-advanced-disclosure.tsx b/src/components/insights/glucose/glucose-advanced-disclosure.tsx index 7fbe90613..e28b2b68d 100644 --- a/src/components/insights/glucose/glucose-advanced-disclosure.tsx +++ b/src/components/insights/glucose/glucose-advanced-disclosure.tsx @@ -4,7 +4,7 @@ import { useId, useState } from "react"; import { ChevronDown } from "lucide-react"; import { InfoPopover } from "@/components/ui/info-popover"; -import { useTranslations } from "@/lib/i18n/context"; +import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import type { GlucoseClinicalMetrics } from "@/lib/analytics/glucose-metrics"; import type { DataSummary } from "@/lib/analytics/trends"; @@ -49,11 +49,12 @@ export function GlucoseAdvancedDisclosure({ fractionDigits = 1, }: GlucoseAdvancedDisclosureProps) { const { t } = useTranslations(); + const number = useFormatters().number; const [open, setOpen] = useState(false); const regionId = useId(); const fmt = (v: number | null) => - v === null ? "—" : v.toFixed(fractionDigits); + v === null ? "—" : number(v, fractionDigits); const contexts = Object.entries(byContext ?? {}).filter( ([, s]) => s && s.count > 0, diff --git a/src/components/insights/glucose/glucose-clinical-panel.tsx b/src/components/insights/glucose/glucose-clinical-panel.tsx index 48e440497..56b0cd653 100644 --- a/src/components/insights/glucose/glucose-clinical-panel.tsx +++ b/src/components/insights/glucose/glucose-clinical-panel.tsx @@ -82,7 +82,7 @@ export function GlucoseClinicalPanel() { const formatGlucoseValue = (mgdl: number): string => { const v = convertGlucose(mgdl, glucoseUnit); - return isMmol ? v.toFixed(1) : String(Math.round(v)); + return fmt.number(v, isMmol ? 1 : 0); }; // Learning state — calm, never asserting TIR / GMI off thin data. @@ -189,7 +189,7 @@ export function GlucoseClinicalPanel() { icon={