Skip to content

feat(i18n): Windows app bilingual support (zh-Hans / English)#109

Merged
debugtheworldbot merged 24 commits intomainfrom
feat/win-i18n
Apr 29, 2026
Merged

feat(i18n): Windows app bilingual support (zh-Hans / English)#109
debugtheworldbot merged 24 commits intomainfrom
feat/win-i18n

Conversation

@debugtheworldbot
Copy link
Copy Markdown
Owner

@debugtheworldbot debugtheworldbot commented Apr 29, 2026

Closes #108

Summary

  • Bilingual UI support (zh-Hans / English) for KeyStats Windows. 158 localization keys total. App auto-detects system language at startup (strict simplified Chinese rule: only zh-CN/zh-Hans*/zh → 中文; everything else → English) with manual override in Settings (restart to apply).
  • Approach: .resx + hand-written strongly-typed Strings accessor (no Visual Studio Designer dependency). LocalizationManager.ApplyAtStartup runs first in App.OnStartup to set Thread.CurrentUICulture; mutex check has a 3-attempt 500ms-apart retry to handle the language-switch relaunch race window.
  • 25 source files migrated (9 windows + 3 dialogs + 3 ViewModels + 3 custom controls + 3 services + App.xaml.cs). All ~358 hardcoded Chinese strings now flow through Strings.<Key> accessors. Console logs translated to English in the same pass.

Changes

New files

  • Properties/Strings.resx / Strings.zh-Hans.resx — 158 key bilingual resources
  • Properties/Strings.cs — hand-written strongly-typed accessor class
  • Helpers/LocalizationManager.cs — culture detection + application
  • docs/superpowers/specs/2026-04-29-windows-i18n-design.md — design spec
  • docs/superpowers/plans/2026-04-29-windows-i18n.md — implementation plan

Key changes

  • App.xaml.cs: startup ordering adds LocalizationManager.ApplyAtStartup; mutex retry loop; tray menu / toasts / import-export dialogs all routed through Strings; catch block error message localized
  • Models/AppSettings.cs: new LanguagePreference string field (default "system", backward-compatible with old settings.json)
  • Views/SettingsWindow.xaml + .xaml.cs: new Language card (System / 中文 / English); switching shows a restart confirmation dialog → Process.Start + Application.Shutdown
  • KeyStats.csproj: explicit <ManifestResourceName> overrides ensure .zh-Hans.resx compiles to a satellite assembly correctly

Resource strategy

  • Neutral Strings.resx = English (also serves as fallback; non-Chinese systems auto-resolve here)
  • Strings.zh-Hans.resx = Simplified Chinese
  • Physical key names (Backspace/Tab/Ctrl+Shift+A etc.), JSON field names, PostHog events, and unit suffixes (m/km/px) stay in English by design (not user-facing UI copy)
  • Restart-on-switch design — no runtime live-switch complexity, per spec decision

Test plan

(dotnet is unavailable on the macOS host; verify the items below on Windows.)

Build / packaging

  • dotnet build KeyStats/KeyStats.csproj -c Debug succeeds
  • After dotnet build -c Release, bin/Release/net48/zh-Hans/KeyStats.resources.dll satellite assembly exists
  • build.ps1 -Configuration Release produces a zip that includes the satellite folder

Startup language detection (clear or set %LOCALAPPDATA%\KeyStats\settings.json to languagePreference: "system")

  • zh-CN system → Chinese UI
  • zh-Hans-CN → Chinese UI
  • zh-TW / zh-HK → English UI (strict simplified rule)
  • en-US / ja-JP / de-DE → English UI

Manual override

  • Settings → Language switched to English/中文 → restart prompt (in current language) → click OK → app exits and relaunches → new language applied
  • Same, but click Cancel → ComboBox reverts, settings.json unchanged, no restart
  • settings.json languagePreference: "fr" (invalid) → falls back to auto-detect, no crash
  • Old settings.json (missing field) → defaults to "system", runs normally

UI completeness (each window, both languages)

  • StatsPopup (left-click tray icon)
  • Settings (right-click tray → Settings)
  • NotificationSettings, MouseCalibration, AppStats, KeyboardHeatmap, KeyHistory
  • ImportModeDialog (Settings → Import), ConfirmDialog
  • 5 tray menu items, Toast notifications, import/export error messages

Edge cases

  • Launch two instances: the second shows the localized "already running" dialog, then exits
  • During a language-switch relaunch, mutex collision is resolved by the new process's 1–2 retries
  • Long English strings don't break SettingsWindow card layout
  • Number formatting still follows system locale (CurrentCulture was deliberately not modified)

🤖 Generated with Claude Code

debugtheworldbot and others added 20 commits April 29, 2026 10:59
Bilingual zh-Hans/en support for KeyStats Windows: auto-detect from
system locale at startup with manual override in Settings (restart
to apply). Uses .resx + strongly-typed Strings class; reuses macOS
translations where overlapping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
17 tasks covering resource bootstrap, LocalizationManager,
AppSettings field, App startup wiring with mutex retry, Settings
Language card with restart flow, and per-window/control/VM/service
migration. Each task has explicit code, .resx key tables, and
verification gates (build + manual smoke).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…remove 16 orphan keys

Task 17 verification cleanup:
- Migrate hardcoded "KeyStats is already running." dialog in App.xaml.cs
  to Strings.Error_AppAlreadyRunning + Strings.App_Name (LocalizationManager
  is applied earlier in OnStartup, so the prior English-only fallback was
  no longer necessary).
- Remove orphan keys defined in resx but never referenced by code or XAML:
  Common_Ok, Common_Close, Common_Retry (pre-staged but unused);
  Stats_PeakKpsLabel, Stats_PeakCpsLabel (superseded by *_TooltipLabel);
  Calibration_Instruction, Calibration_StartTitle, Calibration_StartMessage,
  Calibration_FinishTitle, Calibration_FinishMessage, Calibration_SuccessTitle,
  Calibration_SuccessMessageFormat, Calibration_FailureTitle,
  Calibration_FailureMessage, Calibration_StatusWaiting,
  Calibration_StatusMovingFormat (planned for macOS-style dialog flow but
  Windows UI uses inline status text instead).
- Final tally: 158 keys across Strings.resx, Strings.zh-Hans.resx, and
  Strings.cs; en/zh key sets identical; cs/resx names aligned; zero orphans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 29, 2026 07:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds bilingual UI localization (zh-Hans / English) to the KeyStats Windows app, with startup language detection and a Settings-based manual override (restart-to-apply), using .resx resources and a strongly-typed Strings accessor.

Changes:

  • Introduces .resx-based localization infrastructure (Strings.resx, Strings.zh-Hans.resx, Strings.cs) plus LocalizationManager and a persisted LanguagePreference.
  • Migrates multiple WPF windows/controls/view-models/services from hardcoded Chinese strings to Strings.<Key> lookups, and converts remaining Chinese console logs/comments to English.
  • Updates startup flow to apply CurrentUICulture early and adds a single-instance mutex retry to support “restart after language switch”.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
docs/superpowers/specs/2026-04-29-windows-i18n-design.md i18n design spec for Windows implementation
docs/superpowers/plans/2026-04-29-windows-i18n.md Step-by-step implementation plan and verification gates
KeyStats.Windows/KeyStats/App.xaml.cs Apply UI culture at startup; mutex retry; localize tray menu/toasts/dialog titles/errors
KeyStats.Windows/KeyStats/Helpers/LocalizationManager.cs Culture resolution (system/zh-Hans/en) and startup application
KeyStats.Windows/KeyStats/Models/AppSettings.cs Persist languagePreference setting with default "system"
KeyStats.Windows/KeyStats/KeyStats.csproj Explicit manifest resource names for resx embedding/satellite build
KeyStats.Windows/KeyStats/Properties/Strings.resx English (neutral) resource set
KeyStats.Windows/KeyStats/Properties/Strings.zh-Hans.resx Simplified Chinese resource set
KeyStats.Windows/KeyStats/Properties/Strings.cs Hand-written strongly-typed resource accessor
KeyStats.Windows/KeyStats/Views/SettingsWindow.xaml Localize Settings UI and add Language selection card
KeyStats.Windows/KeyStats/Views/SettingsWindow.xaml.cs Handle language selection + restart; localize version and GitHub error
KeyStats.Windows/KeyStats/Views/StatsPopupWindow.xaml Localize stats popup XAML labels/buttons
KeyStats.Windows/KeyStats/Views/StatsPopupWindow.xaml.cs Translate comments (no functional behavior change in shown diff)
KeyStats.Windows/KeyStats/Views/NotificationSettingsWindow.xaml Localize notification settings UI strings
KeyStats.Windows/KeyStats/Views/MouseCalibrationWindow.xaml Localize calibration window XAML
KeyStats.Windows/KeyStats/Views/MouseCalibrationWindow.xaml.cs Localize status/labels and formatting strings
KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml Localize heatmap window XAML
KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml.cs Localize heatmap UI, summary, and date display formatting
KeyStats.Windows/KeyStats/Views/KeyHistoryWindow.xaml Localize key history window UI strings
KeyStats.Windows/KeyStats/Views/ImportModeDialog.xaml Localize import-mode dialog UI strings
KeyStats.Windows/KeyStats/Views/ConfirmDialog.xaml Localize default confirm dialog title/message/buttons
KeyStats.Windows/KeyStats/Views/ConfirmDialog.xaml.cs Localize default confirm dialog fallbacks in Show(...)
KeyStats.Windows/KeyStats/Views/AppStatsWindow.xaml Localize AppStats window UI strings
KeyStats.Windows/KeyStats/Views/Controls/KeyBreakdownControl.xaml Localize empty-state text
KeyStats.Windows/KeyStats/Views/Controls/StatsChartControl.xaml.cs Translate internal comments (no functional behavior change in shown diff)
KeyStats.Windows/KeyStats/Views/Controls/KeyDistributionPieChartControl.xaml.cs Localize pie chart center/empty-state strings
KeyStats.Windows/KeyStats/ViewModels/StatsPopupViewModel.cs Localize history summary string formatting
KeyStats.Windows/KeyStats/ViewModels/KeyHistoryViewModel.cs Localize empty/summary strings
KeyStats.Windows/KeyStats/ViewModels/AppStatsViewModel.cs Localize headers, summary, empty text, and unknown-app label
KeyStats.Windows/KeyStats/Services/StatsManager.cs Localize import exception messages; translate comments
KeyStats.Windows/KeyStats/Services/NotificationService.cs Localize toast title/body for threshold notifications
KeyStats.Windows/KeyStats/Services/InputMonitorService.cs Translate internal comments (no functional behavior change in shown diff)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/superpowers/specs/2026-04-29-windows-i18n-design.md
Comment thread docs/superpowers/specs/2026-04-29-windows-i18n-design.md
Comment thread docs/superpowers/specs/2026-04-29-windows-i18n-design.md
Comment thread KeyStats.Windows/KeyStats/Views/ConfirmDialog.xaml.cs
Comment thread KeyStats.Windows/KeyStats/Views/KeyboardHeatmapWindow.xaml.cs
Comment thread KeyStats.Windows/KeyStats/Properties/Strings.zh-Hans.resx Outdated
SaveSettings() is debounced 2s, so RestartApp launched the new process
before the new LanguagePreference hit disk — the relaunched app read the
old value and stayed in the previous language. Force a synchronous flush
before spawning the new process. Also silence CS8601 with newPref! since
the early IsNullOrEmpty return already guarantees non-null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ead of localized resx keys

Agent-Logs-Url: https://github.com/debugtheworldbot/keyStats/sessions/110e43ee-722c-4aca-91d3-294dbf249636

Co-authored-by: debugtheworldbot <62830430+debugtheworldbot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/debugtheworldbot/keyStats/sessions/77b4ab04-6b8e-4d41-9753-b46885d2247a

Co-authored-by: debugtheworldbot <62830430+debugtheworldbot@users.noreply.github.com>
- Merge NotifSettings_{EveryPrefix,TimesSuffix} into single
  NotifSettings_EveryFormat so translators maintain a coherent sentence
  instead of a brittle prefix/suffix pair (word order differs across
  languages). XAML now binds two named TextBlocks that the code-behind
  fills by splitting the format string around {0}.
- Drop AppStats_SortIndicator resource — the ↓ glyph is identical in both
  cultures, so it lives as a const in AppStatsViewModel.
- Track language switch / cancel via TrackClick (matches the project
  convention of emitting analytics for new settings cards).
- Log the exception when RestartApp fails to relaunch, so a bug report
  can pinpoint why the app didn't come back after a language switch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@debugtheworldbot debugtheworldbot merged commit 2f7f048 into main Apr 29, 2026
@debugtheworldbot debugtheworldbot deleted the feat/win-i18n branch April 29, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Report]: App is in Chinese language

3 participants