diff --git a/messages/de.json b/messages/de.json
index 365f608ae..6b2f2bf51 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "Gehgeschwindigkeit",
"description": "Deine durchschnittliche Gehgeschwindigkeit im Zeitverlauf, in km/h.",
- "chartTitle": "Gehgeschwindigkeit (km/h)",
+ "chartTitle": "Gehgeschwindigkeit",
"emptyState": {
"title": "Noch keine Gehgeschwindigkeitsdaten",
"description": "Werte erscheinen hier, sobald Apple Health synchronisiert."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "Handgelenktemperatur",
"description": "Deine nächtliche Handgelenktemperatur, gemessen an deinem eigenen Ausgangswert.",
- "chartTitle": "Handgelenktemperatur (°C)",
+ "chartTitle": "Handgelenktemperatur",
"emptyState": {
"title": "Noch keine Handgelenktemperatur-Daten",
"description": "Werte erscheinen hier, sobald Apple Health das nächtliche Tragen synchronisiert."
@@ -8286,9 +8286,6 @@
"unitSteps": "Schritte",
"unitBpm": "S/min",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "Pkt.",
"confidenceLow": "Geringe Sicherheit",
"confidenceMedium": "Mittlere Sicherheit",
@@ -8463,7 +8460,6 @@
"flow": "Stärke",
"symptoms": "Symptome",
"temperature": "Basaltemperatur",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "Messwert als gestört markieren",
"temperatureDisturbedHint": "Fieber, Krankheit, Alkohol oder eine späte Messung — wird von der Temperaturauswertung ausgeschlossen.",
"ovulationTest": "Ovulationstest",
@@ -8555,7 +8551,7 @@
"empty": "Noch keine Temperaturwerte",
"emptyHint": "Miss deine Basaltemperatur jeden Morgen, um den Temperaturanstieg deines Zyklus zu sehen.",
"ovulationEstimate": "Gesch. Eisprung",
- "tooltipTemp": "{temp} °C",
+ "tooltipTemp": "{temp} {unit}",
"ovulationConfirmed": "Bestätigter Eisprung"
},
"fieldInfo": {
diff --git a/messages/en.json b/messages/en.json
index e04b37a24..90e0d58ea 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "Walking Speed",
"description": "Your average walking speed over time, in km/h.",
- "chartTitle": "Walking speed (km/h)",
+ "chartTitle": "Walking speed",
"emptyState": {
"title": "No walking-speed data yet",
"description": "Walking-speed readings appear here once Apple Health syncs."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "Wrist Temperature",
"description": "Your overnight wrist temperature, tracked against your own baseline.",
- "chartTitle": "Wrist temperature (°C)",
+ "chartTitle": "Wrist temperature",
"emptyState": {
"title": "No wrist-temperature data yet",
"description": "Wrist-temperature readings appear here once Apple Health syncs overnight wear."
@@ -8286,9 +8286,6 @@
"unitSteps": "steps",
"unitBpm": "bpm",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "pts",
"confidenceLow": "Low confidence",
"confidenceMedium": "Medium confidence",
@@ -8463,7 +8460,6 @@
"flow": "Flow",
"symptoms": "Symptoms",
"temperature": "Basal body temperature",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "Mark reading as disturbed",
"temperatureDisturbedHint": "Fever, illness, alcohol or a late measurement — excluded from the temperature evaluation.",
"ovulationTest": "Ovulation test",
@@ -8555,7 +8551,7 @@
"empty": "No temperature readings yet",
"emptyHint": "Log your basal body temperature each morning to see your cycle's temperature shift.",
"ovulationEstimate": "Est. ovulation",
- "tooltipTemp": "{temp} °C",
+ "tooltipTemp": "{temp} {unit}",
"ovulationConfirmed": "Confirmed ovulation"
},
"fieldInfo": {
diff --git a/messages/es.json b/messages/es.json
index 97027af1f..8e850ae00 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "Velocidad al andar",
"description": "Tu velocidad media al andar en el tiempo, en km/h.",
- "chartTitle": "Velocidad al andar (km/h)",
+ "chartTitle": "Velocidad al andar",
"emptyState": {
"title": "Aún no hay datos",
"description": "Las lecturas aparecen aquí cuando Apple Health sincroniza."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "Temperatura de muñeca",
"description": "Tu temperatura de muñeca durante la noche, comparada con tu valor de referencia.",
- "chartTitle": "Temperatura de muñeca (°C)",
+ "chartTitle": "Temperatura de muñeca",
"emptyState": {
"title": "Aún no hay datos de temperatura de muñeca",
"description": "Los valores aparecen aquí cuando Apple Health sincroniza el uso nocturno."
@@ -8286,9 +8286,6 @@
"unitSteps": "pasos",
"unitBpm": "lpm",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "pts",
"confidenceLow": "Confianza baja",
"confidenceMedium": "Confianza media",
@@ -8463,7 +8460,6 @@
"flow": "Flujo",
"symptoms": "Síntomas",
"temperature": "Temperatura basal",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "Marcar la medición como alterada",
"temperatureDisturbedHint": "Fiebre, enfermedad, alcohol o una medición tardía: se excluye de la evaluación de temperatura.",
"ovulationTest": "Test de ovulación",
@@ -8555,7 +8551,7 @@
"empty": "Aún no hay mediciones de temperatura",
"emptyHint": "Registra tu temperatura basal cada mañana para ver el cambio de temperatura de tu ciclo.",
"ovulationEstimate": "Ovulación est.",
- "tooltipTemp": "{temp} °C",
+ "tooltipTemp": "{temp} {unit}",
"ovulationConfirmed": "Ovulación confirmada"
},
"fieldInfo": {
diff --git a/messages/fr.json b/messages/fr.json
index ffb3f280a..7cf020417 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "Vitesse de marche",
"description": "Votre vitesse de marche moyenne au fil du temps, en km/h.",
- "chartTitle": "Vitesse de marche (km/h)",
+ "chartTitle": "Vitesse de marche",
"emptyState": {
"title": "Aucune donnée",
"description": "Les relevés apparaissent ici dès qu’Apple Health se synchronise."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "Température du poignet",
"description": "Votre température du poignet pendant la nuit, comparée à votre valeur de référence.",
- "chartTitle": "Température du poignet (°C)",
+ "chartTitle": "Température du poignet",
"emptyState": {
"title": "Pas encore de données de température du poignet",
"description": "Les valeurs apparaissent ici dès qu'Apple Santé synchronise le port nocturne."
@@ -8286,9 +8286,6 @@
"unitSteps": "pas",
"unitBpm": "bpm",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "pts",
"confidenceLow": "Confiance faible",
"confidenceMedium": "Confiance moyenne",
@@ -8463,7 +8460,6 @@
"flow": "Flux",
"symptoms": "Symptômes",
"temperature": "Température basale",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "Marquer la mesure comme perturbée",
"temperatureDisturbedHint": "Fièvre, maladie, alcool ou mesure tardive — exclue de l'évaluation de la température.",
"ovulationTest": "Test d'ovulation",
@@ -8555,7 +8551,7 @@
"empty": "Pas encore de relevés de température",
"emptyHint": "Notez votre température basale chaque matin pour voir le décalage de température de votre cycle.",
"ovulationEstimate": "Ovulation est.",
- "tooltipTemp": "{temp} °C",
+ "tooltipTemp": "{temp} {unit}",
"ovulationConfirmed": "Ovulation confirmée"
},
"fieldInfo": {
diff --git a/messages/it.json b/messages/it.json
index 66e7ee9db..83b14de00 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "Velocità di cammino",
"description": "La tua velocità media di cammino nel tempo, in km/h.",
- "chartTitle": "Velocità di cammino (km/h)",
+ "chartTitle": "Velocità di cammino",
"emptyState": {
"title": "Nessun dato",
"description": "Le letture appaiono qui quando Apple Health si sincronizza."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "Temperatura del polso",
"description": "La temperatura del polso durante la notte, rispetto al tuo valore di riferimento.",
- "chartTitle": "Temperatura del polso (°C)",
+ "chartTitle": "Temperatura del polso",
"emptyState": {
"title": "Nessun dato sulla temperatura del polso",
"description": "I valori compaiono qui quando Apple Health sincronizza l'uso notturno."
@@ -8286,9 +8286,6 @@
"unitSteps": "passi",
"unitBpm": "bpm",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "pts",
"confidenceLow": "Affidabilità bassa",
"confidenceMedium": "Affidabilità media",
@@ -8463,7 +8460,6 @@
"flow": "Flusso",
"symptoms": "Sintomi",
"temperature": "Temperatura basale",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "Segna la misurazione come disturbata",
"temperatureDisturbedHint": "Febbre, malattia, alcol o una misurazione tardiva: esclusa dalla valutazione della temperatura.",
"ovulationTest": "Test di ovulazione",
@@ -8555,7 +8551,7 @@
"empty": "Ancora nessuna misurazione della temperatura",
"emptyHint": "Registra la temperatura basale ogni mattina per vedere il rialzo termico del tuo ciclo.",
"ovulationEstimate": "Ovulazione stim.",
- "tooltipTemp": "{temp} °C",
+ "tooltipTemp": "{temp} {unit}",
"ovulationConfirmed": "Ovulazione confermata"
},
"fieldInfo": {
diff --git a/messages/ko.json b/messages/ko.json
index fd7eb9f96..bb57177ef 100644
--- a/messages/ko.json
+++ b/messages/ko.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "보행 속도",
"description": "시간에 따른 평균 보행 속도예요(km/h).",
- "chartTitle": "보행 속도(km/h)",
+ "chartTitle": "보행 속도",
"emptyState": {
"title": "아직 보행 속도 데이터가 없어요",
"description": "Apple Health가 동기화되면 보행 속도 측정값이 여기에 나타나요."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "손목 온도",
"description": "내 기준값과 비교한 밤사이 손목 온도예요.",
- "chartTitle": "손목 온도 (°C)",
+ "chartTitle": "손목 온도",
"emptyState": {
"title": "아직 손목 온도 데이터가 없어요",
"description": "Apple Health가 밤사이 착용 기록을 동기화하면 손목 온도 측정값이 여기에 표시돼요."
@@ -8286,9 +8286,6 @@
"unitSteps": "걸음",
"unitBpm": "bpm",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "점",
"confidenceLow": "신뢰도 낮음",
"confidenceMedium": "신뢰도 보통",
@@ -8463,7 +8460,6 @@
"flow": "생리량",
"symptoms": "증상",
"temperature": "기초 체온",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "측정값을 방해받음으로 표시",
"temperatureDisturbedHint": "발열, 아픈 날, 음주, 늦은 측정 — 체온 평가에서 제외돼요.",
"ovulationTest": "배란 테스트",
@@ -8555,7 +8551,7 @@
"empty": "아직 체온 기록이 없어요",
"emptyHint": "매일 아침 기초 체온을 기록하면 주기의 체온 변화를 볼 수 있어요.",
"ovulationEstimate": "예상 배란",
- "tooltipTemp": "{temp}°C",
+ "tooltipTemp": "{temp}{unit}",
"ovulationConfirmed": "확인된 배란"
},
"fieldInfo": {
diff --git a/messages/pl.json b/messages/pl.json
index 122c19159..90fcc23d4 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -3739,7 +3739,7 @@
"walkingSpeed": {
"title": "Prędkość chodu",
"description": "Twoja średnia prędkość chodu w czasie, w km/h.",
- "chartTitle": "Prędkość chodu (km/h)",
+ "chartTitle": "Prędkość chodu",
"emptyState": {
"title": "Brak danych",
"description": "Odczyty pojawią się tutaj po synchronizacji Apple Health."
@@ -4142,7 +4142,7 @@
"wristTemperature": {
"title": "Temperatura nadgarstka",
"description": "Nocna temperatura nadgarstka, odniesiona do Twojej wartości bazowej.",
- "chartTitle": "Temperatura nadgarstka (°C)",
+ "chartTitle": "Temperatura nadgarstka",
"emptyState": {
"title": "Brak danych o temperaturze nadgarstka",
"description": "Wartości pojawią się tutaj, gdy Apple Health zsynchronizuje nocne noszenie."
@@ -8286,9 +8286,6 @@
"unitSteps": "kroki",
"unitBpm": "ud./min",
"unitMs": "ms",
- "unitKg": "kg",
- "unitCelsius": "°C",
- "unitGlucose": "mg/dL",
"unitMood": "pkt",
"confidenceLow": "Niska pewność",
"confidenceMedium": "Średnia pewność",
@@ -8463,7 +8460,6 @@
"flow": "Krwawienie",
"symptoms": "Objawy",
"temperature": "Podstawowa temperatura ciała",
- "temperaturePlaceholder": "°C",
"temperatureDisturbed": "Oznacz pomiar jako zakłócony",
"temperatureDisturbedHint": "Gorączka, choroba, alkohol lub spóźniony pomiar — wykluczony z oceny temperatury.",
"ovulationTest": "Test owulacyjny",
@@ -8555,7 +8551,7 @@
"empty": "Brak pomiarów temperatury",
"emptyHint": "Zapisuj podstawową temperaturę ciała każdego ranka, aby zobaczyć przesunięcie temperatury w cyklu.",
"ovulationEstimate": "Szac. owulacja",
- "tooltipTemp": "{temp} °C",
+ "tooltipTemp": "{temp} {unit}",
"ovulationConfirmed": "Potwierdzona owulacja"
},
"fieldInfo": {
diff --git a/src/__tests__/helpers/unit-display-mock.ts b/src/__tests__/helpers/unit-display-mock.ts
new file mode 100644
index 000000000..60a90a914
--- /dev/null
+++ b/src/__tests__/helpers/unit-display-mock.ts
@@ -0,0 +1,42 @@
+/**
+ * A real `UnitDisplay` for one fixed preference, for component tests that
+ * render without a QueryClient (`useUnitDisplay` reads the account through
+ * react-query). Built on the production transforms, so a test asserts the
+ * same numbers and symbols the hook would give.
+ *
+ * vi.mock("@/hooks/use-unit-display", async () => {
+ * const { unitDisplayFor } = await import("@/__tests__/helpers/unit-display-mock");
+ * return { useUnitDisplay: () => unitDisplayFor("metric") };
+ * });
+ */
+import type { UnitDisplay } from "@/hooks/use-unit-display";
+import type { GlucoseUnit } from "@/lib/glucose";
+import {
+ applyDisplayTransform,
+ applyDisplayTransformDelta,
+ getDisplayTransform,
+ hasDisplayTransform,
+ invertDisplayTransform,
+ type UnitPreference,
+} from "@/lib/measurements/display-transform";
+
+export function unitDisplayFor(
+ preference: UnitPreference,
+ glucoseUnit: GlucoseUnit = "mg/dL",
+): UnitDisplay {
+ // The hook resolves mass, length and temperature through the
+ // metric/imperial branch alone; glucose is converted where it renders.
+ const forType = (type: string) => getDisplayTransform(type, preference);
+ return {
+ preference,
+ glucoseUnit,
+ transformFor: forType,
+ toDisplay: (type, value) => applyDisplayTransform(value, forType(type)),
+ fromDisplay: (type, value) => invertDisplayTransform(value, forType(type)),
+ toDisplayDelta: (type, value) =>
+ applyDisplayTransformDelta(value, forType(type)),
+ unitFor: (type) => forType(type).displayUnit,
+ decimalsFor: (type) => forType(type).decimals,
+ isTransformed: (type) => hasDisplayTransform(type),
+ };
+}
diff --git a/src/__tests__/i18n-english-leak-guard.test.ts b/src/__tests__/i18n-english-leak-guard.test.ts
index dc9d79fac..897cd03bb 100644
--- a/src/__tests__/i18n-english-leak-guard.test.ts
+++ b/src/__tests__/i18n-english-leak-guard.test.ts
@@ -87,7 +87,6 @@ const LEGIT_IDENTICAL = new Set
- {t("cycle.bbt.tooltipTemp", { temp: fmt.number(p.temp) })} + {t("cycle.bbt.tooltipTemp", { + temp: fmt.number(p.temp), + unit: unitDisplay.unitFor("BODY_TEMPERATURE"), + })}
{p.mucus ? (
diff --git a/src/components/cycle/cycle-phase-crosstab.tsx b/src/components/cycle/cycle-phase-crosstab.tsx
index 0a9fe400d..9dd962d90 100644
--- a/src/components/cycle/cycle-phase-crosstab.tsx
+++ b/src/components/cycle/cycle-phase-crosstab.tsx
@@ -55,14 +55,16 @@ const METRIC_LABEL_KEY: Record