Introduces a locale bundle system that makes UI language switchable at
runtime and trivially extensible to any future language.
Architecture:
- static/i18n.js: LOCALES object with 'en' and 'zh' bundles, t(key)
helper with English fallback, setLocale()/loadLocale() for persistence
via localStorage. Adding a new language = adding one object.
- api/config.py: 'language' setting (default 'en'), BCP-47 validation
- api/routes.py: _LOGIN_LOCALE dict for server-rendered login page;
template placeholders substituted at request time from saved setting
- static/index.html: loads i18n.js first (before other scripts); adds
Language dropdown to Settings panel, auto-populated from LOCALES
Wiring:
- boot.js: applies server-persisted locale at startup (after /api/settings
fetch); speech recognition lang follows _locale._speech
- panels.js: populates Language dropdown from LOCALES on settings open;
saves + applies locale on Save Settings
- All JS files: hardcoded user-facing strings replaced with t() calls
Coverage:
- test_sprint20.py: relaxed recognition.lang assertion to accept dynamic
locale-driven assignment (behavior unchanged for English default)
- 499/499 tests pass
Closes#177 (incorporates Chinese translations as a proper locale bundle
rather than hardcoded strings, so English default is fully preserved)