Localization¶
Stratum ships a fully translatable interface. All user-facing strings are routed
through the i18next layer (t(...)), and the active UI language is a persisted
setting — no restart required.
Supported Languages¶
| Code | Language | Status |
|---|---|---|
en |
English | Source of truth |
de |
German | Full translation |
it |
Italian | Full translation |
fr |
French | Full translation |
es |
Spanish | Full translation |
zh |
Chinese | Placeholder (en values) |
ja |
Japanese | Placeholder (en values) |
el |
Greek | Placeholder (en values) |
en is always available as the fallback: a missing key never renders the raw
key or crashes the app. zh, ja, and el currently mirror English values
(marked in progress in _meta.status) — they render correctly and only need
translation work.
Changing the Language¶
- Open Settings → Appearance
- Select a language from the Language dropdown
- The interface updates immediately — no restart required
The choice is persisted to .pkm/config.toml under the locale key
(ISO 639-1 code, default en) and is read at startup. locale serializes at
the top level of the config file (no section wrapper):
Locale-Aware Formatting¶
Dates, numbers, and calendar labels follow the active locale's conventions via
the Intl helpers in src/i18n/ (formatDate, formatNumber,
localizedMonthNames, localizedDayHeaders). The Journal calendar and any
number/date rendering use these helpers automatically.
Developer Workflow¶
Adding a new UI string¶
Never hardcode user-facing copy. Use t(...):
import { useTranslation } from 'react-i18next';
const { t } = useTranslation();
return <Button>{t('common.save')}</Button>;
Add the key (and all translations) to src/i18n/locales/<lang>.json under the
appropriate section.
The i18n gate¶
src/i18n/i18nGate.test.ts is a static AST scan that fails npm run test
if a user-facing English string is hardcoded as JSX text or a
label/placeholder/title/aria-label literal outside t(). It walks src/
(excluding tests, workers, and i18n/ itself) and reports each violation as
path:line — 'literal'.
Run it in isolation:
The gate's allowlist covers non-UI literals (CSS, units, URLs, enum tokens, imports, SVG paths, LaTeX). It is intentionally conservative: when the scan flags something that IS user-facing, translate it — do not extend the allowlist.
Locale parity gate¶
src/i18n/localeParity.test.ts enforces the release contract:
- all 8 locale files expose the identical flattened key set;
- every key exists in
en(extra keys in any locale fail); - placeholder locales (
zh/ja/el) mirrorenvalues — no missing-key path; - full locales (
de/it/fr/es) contain no untranslated English except a reviewed allowlist of brands, endonyms, and technical loanwords.
When adding a locale key, add it to all 8 files in the same commit. When translating a full locale, never leave an English value behind.
Runtime fallback behavior¶
The runtime i18next fallback chain is active locale → en. There is no
raw-key rendering: if a key is missing from the active locale but present in
en, the en value is used. Only a key missing from en itself would surface
as the key string — the parity gate prevents that.