Files
HealthLog/src/lib/mental-health/instrument-wording.ts
T
Marc-André Bombeck 3843705296 feat(mental-health): keep questionnaire wording in code, one cited file per language
The PHQ-9, GAD-7, WHO-5 and SCI now have text in every app language
instead of falling back to the English original. Each instrument and
language is one file under src/lib/mental-health/wording/ that declares
what it is: a published validated translation reproduced word for word,
with its source and terms in `source`, or a translation written for
HealthLog, faithful to the English and on the same scale.

Validated versions added: the Pfizer PHQ-9 for France, the Netherlands,
Korea and Brazil, the Pfizer GAD-7 for the Netherlands, Korea and
Brazil, the PRU Dutch WHO-5, the Korean WHO-5 validation (J Korean Geriatr Psychiatry 2010) and
the Polish SCI validation (Sci Rep 2026). The SCI in the other
languages and the WHO-5 in Brazilian Portuguese carry HealthLog's own
translation; each file names any validated version found and why it is
not used.

Where the text is unvalidated, the check-in shows a note that the result
is orientation and a one-tap switch to the English original. Each
language loads as its own chunk when a questionnaire surface needs it.

The item text, answers, stems and the PHQ-9 follow-up leave every message
bundle, so a translator never sees them; the i18n guard now requires that
no bundle carries them, and the registry test requires every instrument
in every language with the item count and anchor scores the scoring
expects. CONTRIBUTING explains where the wording lives and how to propose
a validated version.
2026-10-23 08:13:56 +02:00

57 lines
2.2 KiB
TypeScript

/**
* How a surface reads questionnaire text out of one language's wording: the
* answer label of an item, the stem above it. Data-free, so a client surface
* imports it without pulling every language in; the text itself comes from
* `./wording/load.ts` on the client and `./wording/resolve.ts` on the server.
*
* Every instrument has wording in every app language (`./wording/`). Where a
* validated translation is published, that translation is reproduced word for
* word; where none is, a translation written for HealthLog stands in, and the
* surfaces show a note beside it with a one-tap switch to the English
* original. The text never lives in `messages/*.json`, so a translator editing
* the bundles cannot reword an item by accident (`i18n-locale-integrity.test.ts`
* holds every bundle clear of it).
*/
import { localeFromLanguageTag, type Locale } from "@/lib/i18n/config";
import { INSTRUMENTS, type InstrumentId } from "./instruments";
import { SINGLE_ANCHOR_GROUP, type InstrumentWording } from "./wording/types";
/**
* The app language whose questionnaire text a reader of `locale` is shown;
* a language the app does not ship reads the English original.
*/
export function wordingLocale(locale: string): Locale {
return localeFromLanguageTag(locale) ?? "en";
}
/** Instruments whose form carries the unscored functional follow-up. */
export function hasFunctionalItem(id: InstrumentId): boolean {
return id === "PHQ9";
}
/** The anchor group an item's answers come from. */
export function anchorGroup(id: InstrumentId, itemIndex: number): string {
return INSTRUMENTS[id].optionGroups?.[itemIndex] ?? SINGLE_ANCHOR_GROUP;
}
/** The label of answer `value` to item `itemIndex`. */
export function anchorText(
id: InstrumentId,
wording: InstrumentWording,
itemIndex: number,
value: number,
): string {
return wording.anchors[anchorGroup(id, itemIndex)][value];
}
/** The stem the source form prints above item `itemIndex`, or null. */
export function stemText(
id: InstrumentId,
wording: InstrumentWording,
itemIndex: number,
): string | null {
const stemId = INSTRUMENTS[id].stemIds[itemIndex];
return stemId ? (wording.stems[stemId] ?? null) : null;
}