Weblate writes an empty string for every untranslated key. Paraglide
compiles a present-but-empty value into a message function that returns
"", so the site rendered a blank instead of falling back to English. A
key that is absent from a locale, by contrast, is re-exported from the
base locale and falls back correctly.
Add scripts/strip-empty-locales.mjs to delete empty/whitespace-only
leaves from the non-base locales/*/common.json before paraglide compiles
them, turning "present-but-empty" into "absent". Idempotent, base en
untouched, key order and formatting preserved. Wired as predev/prebuild
so builds stay correct if empties reappear from a later Weblate sync;
deliberately not a lint/CI gate, since empties are a benign expected
input to be neutralised, not forbidden.
Cleans 125 existing empties across 10 locales.
Claude-Session: https://claude.ai/code/session_014Cwk5Yyze9yPvkYkUtMAP4
Introduce full i18n for the Svelte client_v2 using svelte-i18n (ICU/formatjs),
mirroring the old client's language support.
- i18n setup in src/lib/i18n/ (lazy per-language loaders, init + waitLocale boot
guard, setLocale persisted to localStorage, Intl locale helper); 26-language
registry ported from the old client.
- Language picker on the Settings page.
- scripts/convert-locales.mjs seeds static/locales/<lang>/common.json from the
old client's Weblate files: {{var}} -> {var}, escapes literal braces, merges
i18next plural suffixes into ICU plurals. Existing translations are reused via
the same keys; new v2-only strings are added to en/common.json only and fall
back to English until Weblate fills them.
- Extracted all hardcoded UI strings across routes and components to $_ lookups;
DateTimeField derives month/weekday names from Intl in the active locale.
- static/locales is prettier-ignored (mirrors the old client's public/locales).
Follow-up (maintainer): add a Weblate component pointing at
client_v2/static/locales (nested JSON, ICU MessageFormat, English source).
Claude-Session: https://claude.ai/code/session_01Ne7RsfHejKZX97uAq3aVMn