fix(measurements): drop the stray space in a unit-less range message

A score, rating, count or ratio has no unit label, so the range
message for a manual pain score read "Enter a value between 0 and 10 ."
with a space before the stop, and the same in every locale. A second
sentence without the unit now answers those types.
This commit is contained in:
Marc-André Bombeck committed 2026-11-12 12:49:18 +01:00
1 parent 048dbbee9f
commit 3ed266e281
12 files changed
+58 -19

No files matched your search

+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Trainingsbereitschaft",
"typeAcuteTrainingLoad": "Akute Trainingsbelastung",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Gib einen Wert zwischen {min} und {max} {unit} ein."
"valueOutOfRange": "Gib einen Wert zwischen {min} und {max} {unit} ein.",
"valueOutOfRangeNoUnit": "Gib einen Wert zwischen {min} und {max} ein."
},
"metricCatalog": {
"title": "Was HealthLog erfassen kann",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Training readiness",
"typeAcuteTrainingLoad": "Acute training load",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Enter a value between {min} and {max} {unit}."
"valueOutOfRange": "Enter a value between {min} and {max} {unit}.",
"valueOutOfRangeNoUnit": "Enter a value between {min} and {max}."
},
"metricCatalog": {
"title": "What HealthLog can track",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Preparación para entrenar",
"typeAcuteTrainingLoad": "Carga de entrenamiento aguda",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Introduce un valor entre {min} y {max} {unit}."
"valueOutOfRange": "Introduce un valor entre {min} y {max} {unit}.",
"valueOutOfRangeNoUnit": "Introduce un valor entre {min} y {max}."
},
"metricCatalog": {
"title": "Lo que HealthLog puede registrar",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Disponibilité à l'entraînement",
"typeAcuteTrainingLoad": "Charge d'entraînement aiguë",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Saisissez une valeur entre {min} et {max} {unit}."
"valueOutOfRange": "Saisissez une valeur entre {min} et {max} {unit}.",
"valueOutOfRangeNoUnit": "Saisissez une valeur entre {min} et {max}."
},
"metricCatalog": {
"title": "Ce que HealthLog peut suivre",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Prontezza all'allenamento",
"typeAcuteTrainingLoad": "Carico di allenamento acuto",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Inserisci un valore tra {min} e {max} {unit}."
"valueOutOfRange": "Inserisci un valore tra {min} e {max} {unit}.",
"valueOutOfRangeNoUnit": "Inserisci un valore tra {min} e {max}."
},
"metricCatalog": {
"title": "Cosa può monitorare HealthLog",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "트레이닝 준비도",
"typeAcuteTrainingLoad": "급성 훈련 부하",
"sourceGarmin": "Garmin",
"valueOutOfRange": "{min}에서 {max} {unit} 사이의 값을 입력해 주세요."
"valueOutOfRange": "{min}에서 {max} {unit} 사이의 값을 입력해 주세요.",
"valueOutOfRangeNoUnit": "{min}에서 {max} 사이의 값을 입력해 주세요."
},
"metricCatalog": {
"title": "HealthLog가 기록할 수 있는 것",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Trainingsgereedheid",
"typeAcuteTrainingLoad": "Acute trainingsbelasting",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Vul een waarde in tussen {min} en {max} {unit}."
"valueOutOfRange": "Vul een waarde in tussen {min} en {max} {unit}.",
"valueOutOfRangeNoUnit": "Vul een waarde in tussen {min} en {max}."
},
"metricCatalog": {
"title": "Wat HealthLog kan bijhouden",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Gotowość do treningu",
"typeAcuteTrainingLoad": "Ostre obciążenie treningowe",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Wpisz wartość od {min} do {max} {unit}."
"valueOutOfRange": "Wpisz wartość od {min} do {max} {unit}.",
"valueOutOfRangeNoUnit": "Wpisz wartość od {min} do {max}."
},
"metricCatalog": {
"title": "Co HealthLog potrafi śledzić",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Prontidão para treinar",
"typeAcuteTrainingLoad": "Carga de treino aguda",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Informe um valor entre {min} e {max} {unit}."
"valueOutOfRange": "Informe um valor entre {min} e {max} {unit}.",
"valueOutOfRangeNoUnit": "Informe um valor entre {min} e {max}."
},
"metricCatalog": {
"title": "O que o HealthLog pode acompanhar",
+2 -1
View File
@@ -1033,7 +1033,8 @@
"typeTrainingReadiness": "Готовность к тренировке",
"typeAcuteTrainingLoad": "Острая тренировочная нагрузка",
"sourceGarmin": "Garmin",
"valueOutOfRange": "Введите значение от {min} до {max} {unit}."
"valueOutOfRange": "Введите значение от {min} до {max} {unit}.",
"valueOutOfRangeNoUnit": "Введите значение от {min} до {max}."
},
"metricCatalog": {
"title": "Что умеет отслеживать HealthLog",
@@ -64,7 +64,11 @@ import { MeasurementList } from "../measurement-list";
import type { UnitDisplay } from "@/hooks/use-unit-display";
import { convertGlucose, toCanonicalMgdl } from "@/lib/glucose";
import { getUnitForType } from "@/lib/measurements/unit-map";
import { editSeedValue, storedValueFor } from "../list/use-measurement-edit";
import {
editSeedValue,
outOfRangeMessage,
storedValueFor,
} from "../list/use-measurement-edit";
import { rowDisplayFor } from "../list/use-measurement-row-display";
/** A metric reader on the given glucose unit, as the list's row display. */
@@ -140,3 +144,28 @@ describe("MeasurementList — blood-glucose display unit", () => {
expect(storedValueFor(display, glucose(95.48), 95.48)).toBe(95.48);
});
});
describe("the out-of-range sentence", () => {
const t = ((key: string, params?: Record<string, unknown>) =>
`${key} ${JSON.stringify(params)}`) as unknown as Parameters<
typeof outOfRangeMessage
>[2];
const reading = (type: string, unit: string) =>
({ id: "m1", type, value: 1, unit, source: "MANUAL" }) as never;
const shows = (unit: string) => ({
rowDisplay: (m: { value: number }) => ({ value: m.value, unit }),
});
it("names the unit where the type has one", () => {
expect(
outOfRangeMessage(shows("kg"), reading("WEIGHT", "kg"), t, String),
).toMatch(/^measurements\.valueOutOfRange \{.*"unit":"kg"/);
});
it("leaves the unit out for a score, which has no label", () => {
// "Enter a value between 0 and 10 ." read with a space before the stop.
expect(
outOfRangeMessage(shows(""), reading("PAIN_NRS", "score"), t, String),
).toBe('measurements.valueOutOfRangeNoUnit {"min":"0","max":"10"}');
});
});
@@ -103,20 +103,20 @@ export function editValueRange(
type Translate = ReturnType<typeof useTranslations>["t"];
/** The sentence for a value outside its band, in the unit the input shows. */
function outOfRangeMessage(
export function outOfRangeMessage(
display: Pick<MeasurementRowDisplay, "rowDisplay">,
measurement: Measurement,
t: Translate,
formatNumber: (value: number) => string,
): string {
const range = editValueRange(display, measurement);
return range
? t("measurements.valueOutOfRange", {
min: formatNumber(range.min),
max: formatNumber(range.max),
unit: range.unit,
})
: t("measurements.invalidValue");
if (!range) return t("measurements.invalidValue");
const bounds = { min: formatNumber(range.min), max: formatNumber(range.max) };
// A score, rating, count or ratio has no unit label, which left a space
// before the full stop.
return range.unit
? t("measurements.valueOutOfRange", { ...bounds, unit: range.unit })
: t("measurements.valueOutOfRangeNoUnit", bounds);
}
/**