Commit Graph

3 Commits

Author SHA1 Message Date
Nathan Esquenazi
b979b4c443 feat: pluggable i18n with English/Chinese language switcher in Settings
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)
2026-04-08 18:57:50 -07:00
Nathan Esquenazi
46fdf3513f fix: mic appends to existing textarea text instead of replacing it
Previously, tapping the mic button would reset the textarea each time,
clobbering anything the user had already typed or previously dictated.

Fix:
- Capture _prefix = ta.value when recording starts (btn.onclick)
- onresult writes _prefix + (final || interim) so live interim text
  appears after the existing content, not replacing it
- onend commits _prefix + _finalText with smart space insertion:
  if the prefix doesn't end with a space or newline, a space is added
  before the new transcript so words don't run together
- _prefix is reset to '' in _setRecording(false) so each new recording
  session starts with a fresh snapshot

Behaviour now: tap mic, speak, tap again (or wait for auto-stop) ->
transcript is appended to whatever was in the textarea. Tap mic again
-> continues appending further. Text stays fully editable before send.

tests/test_sprint20.py: 6 new tests covering prefix capture, onresult
prepend, onend commit, reset, and smart spacing (52 total, 382 overall).
2026-04-03 14:13:29 +00:00
Nathan Esquenazi
efb7293ae8 feat: add voice input mic button via Web Speech API
- index.html: add #btnMic (hidden by default, shown if browser supports
  SpeechRecognition) and #micStatus listening indicator inside .composer-box

- boot.js: IIFE-scoped mic handler wired to Web Speech API
  * recognition.continuous=false (auto-stops after ~2s silence)
  * recognition.interimResults=true (live transcript preview in textarea)
  * Toggles .recording class + shows #micStatus while active
  * Handles 'not-allowed', 'no-speech', 'network' errors via showToast()
  * btnSend.onclick stops active recognition before sending
  * Entire feature disabled/hidden gracefully when API unavailable

- style.css: .mic-btn, .mic-btn.recording (red pulse animation),
  .mic-status, .mic-dot, @keyframes mic-pulse

- tests/test_sprint20.py: 46 tests covering HTML structure, CSS rules,
  JS logic, error handling, and regression checks (376 total, all pass)

No API keys, no external libraries, no server changes. Browser-only.
Works in Chrome, Edge, Safari (partial). Firefox unsupported (hides button).
2026-04-03 14:04:03 +00:00