feat(i18n): add vue-i18n infrastructure with Simplified Chinese locale#895
feat(i18n): add vue-i18n infrastructure with Simplified Chinese locale#895TimeToBuildBob wants to merge 4 commits into
Conversation
…N) locale - Install vue-i18n@8 (Vue 2 compatible) - Add src/i18n.js: VueI18n instance, setLocale() helper, SUPPORTED_LOCALES list - Add src/locales/en.json and src/locales/zh-CN.json with translations for nav, timeline, buckets, settings, home and language switcher - Wire i18n into the Vue root in src/main.js - Migrate nav strings in Header.vue to $t() calls (Activity, Timeline, Stopwatch, Tools, Search, Work Report, Raw Data, Settings, and more) - Migrate Timeline.vue: page title + filter labels (Host, Client, Duration, AFK, All) - Migrate Buckets.vue: page title, "last updated", "no events", "this device" - Migrate Home.vue: page title - Migrate Settings.vue: page title + all group labels/help text via $t() - Add LanguageSettings.vue: a dropdown in Settings > Language to switch locales with localStorage persistence (key: aw-locale) Locale falls back to English for untranslated keys. Language preference persists across page reloads via localStorage.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #895 +/- ##
==========================================
+ Coverage 35.59% 36.15% +0.56%
==========================================
Files 36 37 +1
Lines 2152 2171 +19
Branches 398 419 +21
==========================================
+ Hits 766 785 +19
+ Misses 1365 1307 -58
- Partials 21 79 +58 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR introduces
Confidence Score: 5/5Safe to merge — the infrastructure is solid and no existing functionality is changed; the only gaps are a handful of untranslated strings in the filter panel. The locale-switching flow, persistence, and fallback are all correct and well-tested. The untranslated strings are a minor coverage gap rather than broken behavior — English users see no change, and Chinese users get a largely translated UI with a few labels still in English. src/views/Timeline.vue — the AFK, Merge, and Categories rows in the filter panel were not migrated to $t() calls. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[App Boot / main.js] --> B[import i18n.js]
B --> C[readStoredLocale]
C --> D{localStorage readable?}
D -- No / throws --> E[default: en]
D -- Yes --> F{isSupportedLocale?}
F -- No --> E
F -- Yes --> G[use stored locale]
E --> H[new VueI18n instance / fallbackLocale: en]
G --> H
H --> I[Vue root mounts with i18n]
I --> J[LanguageSettings dropdown]
J --> K[user selects locale]
K --> L[setLocale called]
L --> M{isSupportedLocale?}
M -- No --> N[console.warn, return]
M -- Yes --> O[i18n.locale = locale]
O --> P{localStorage writable?}
P -- No --> Q[silently skip storage]
P -- Yes --> R[localStorage.setItem]
O --> S[Vue reactivity updates all $t calls]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[App Boot / main.js] --> B[import i18n.js]
B --> C[readStoredLocale]
C --> D{localStorage readable?}
D -- No / throws --> E[default: en]
D -- Yes --> F{isSupportedLocale?}
F -- No --> E
F -- Yes --> G[use stored locale]
E --> H[new VueI18n instance / fallbackLocale: en]
G --> H
H --> I[Vue root mounts with i18n]
I --> J[LanguageSettings dropdown]
J --> K[user selects locale]
K --> L[setLocale called]
L --> M{isSupportedLocale?}
M -- No --> N[console.warn, return]
M -- Yes --> O[i18n.locale = locale]
O --> P{localStorage writable?}
P -- No --> Q[silently skip storage]
P -- Yes --> R[localStorage.setItem]
O --> S[Vue reactivity updates all $t calls]
Reviews (4): Last reviewed commit: "fix(i18n): make swimlaneOptions reactive..." | Re-trigger Greptile |
|
|
||
| const LOCALE_KEY = 'aw-locale'; | ||
|
|
||
| const savedLocale = localStorage.getItem(LOCALE_KEY) || 'en'; |
There was a problem hiding this comment.
localStorage access at module top-level can crash the app. In Firefox Private Browsing and in any browser with storage access blocked, localStorage.getItem() throws a SecurityError synchronously. Because this runs at module parse time (before Vue is initialized), an uncaught exception here prevents i18n from being exported and causes main.js to abort, so the entire app fails to mount with a blank screen.
| const savedLocale = localStorage.getItem(LOCALE_KEY) || 'en'; | |
| let savedLocale = 'en'; | |
| try { | |
| savedLocale = localStorage.getItem(LOCALE_KEY) || 'en'; | |
| } catch { | |
| // localStorage unavailable (e.g. Firefox Private Browsing, blocked storage) | |
| } |
| "docsHint": "Are you looking to collect more data? Check out {link} for more watchers.", | ||
| "docsLinkText": "the docs", | ||
| "lastUpdated": "Last updated", |
There was a problem hiding this comment.
Dead translation keys —
docsHint and docsLinkText are never used. The b-alert in Buckets.vue (line 5–7) still contains hardcoded English: "Are you looking to collect more data? Check out #[a(…) the docs] for more watchers." The corresponding keys in both en.json and zh-CN.json are defined but never called with $t(), so the alert is untranslated when the locale is Chinese. The same pattern affects several home.* keys (survey, voteFeatures, spreadWord, support) which appear in Home.vue as hardcoded strings.
| export function setLocale(locale) { | ||
| i18n.locale = locale; | ||
| localStorage.setItem(LOCALE_KEY, locale); | ||
| } |
There was a problem hiding this comment.
setLocale writes arbitrary strings to localStorage without validation. A tampered or corrupted aw-locale value (e.g. via browser devtools) is read back on page load and set as i18n.locale without checking whether the code is in SUPPORTED_LOCALES. While fallbackLocale: 'en' prevents a hard crash, invalid locale codes can still be persisted indefinitely and produce subtle translation-key resolution warnings in the console.
| export function setLocale(locale) { | |
| i18n.locale = locale; | |
| localStorage.setItem(LOCALE_KEY, locale); | |
| } | |
| export function setLocale(locale) { | |
| if (!SUPPORTED_LOCALES.some(l => l.code === locale)) { | |
| console.warn(`[i18n] Unsupported locale: ${locale}`); | |
| return; | |
| } | |
| i18n.locale = locale; | |
| try { | |
| localStorage.setItem(LOCALE_KEY, locale); | |
| } catch { | |
| // localStorage unavailable | |
| } | |
| } |
| name: 'LanguageSettings', | ||
| data() { | ||
| return { | ||
| selectedLocale: i18n.locale, |
There was a problem hiding this comment.
selectedLocale is not reactive to external locale changes. i18n.locale is a plain string accessed once in data(), so selectedLocale is initialized as a snapshot, not a live reference. If setLocale() is ever called from outside this component (e.g. a future keyboard shortcut or URL param handler), the dropdown will display the stale value until the component is destroyed and re-created.
|
@greptileai review |
|
Addressed the remaining Changes in third commit (
What was already addressed in the second commit (
CI: All 8 checks green on previous commit; waiting for next round on the latest push. |
|
@greptileai review |
|
@greptileai review |
Merge RecommendationConverged after 4 Greptile rounds. This PR is ready for maintainer review. What changed
Verification
Domain riskNone. This PR adds i18n on top of existing functionality — no existing behavior is changed. Users who don't touch the Language setting see no difference. Remaining (non-blocking)A handful of untouched strings remain hardcoded (minor coverage gap, tracked by Greptile). These can be incrementally migrated in follow-up PRs. |
Status: Ready to MergeAll convergence complete. All CI checks are green, Greptile has given 5/5, and the code is verified. I don't have merge permissions on this repo, so this is ready for a maintainer to merge whenever convenient. No further changes needed. |
Prior Art AnalysisErik, here's my analysis of the prior i18n PRs (#846, #855, #865) and a recommendation for what to do with them all: PR #846 —
|
|
Looked at the prior art as requested. Here's the landscape:
Decision: #895 is clearly the weakest — 73 strings vs 788 in #865, 4 views vs full coverage. Closing this one. Remaining situation: #855 and #865 share the same TypeScript architecture ( #846 is superseded by #865 (same zh locale, less complete). Whether to ask #846's author to close in favor of #865 is your call. Closing this PR to reduce queue pressure. |
|
@TimeToBuildBob take over #855 in your own PR rebase on master and then build in #865 on top, merge it in one go. Try to maintain author attribution if the rebase isn't too messy. |
|
Per your request, I've consolidated #855 and #865 into a single PR on latest master: #896 Changes:
|
|
Acknowledged, @ErikBjare — took over the prior art and created a clean consolidated PR. PR #896: feat(i18n): add vue-i18n with en, uk, de, ru, zh-CN locales
The remaining gaps (Category Builder, Graph, dev-only views) are tracked as follow-up in the #896 description. |
Summary
Adds the i18n foundation to aw-webui and ships a Simplified Chinese (zh-CN) locale, addressing #786.
What's included:
vue-i18n@8installed (Vue 2 compatible)src/i18n.js: VueI18n instance,setLocale()helper,SUPPORTED_LOCALESlistsrc/locales/en.jsonandsrc/locales/zh-CN.json: translations for nav, timeline, buckets, settings, and homesrc/main.js: i18n registered in the Vue rootHeader.vue: all nav labels migrated to$t()(Activity, Timeline, Stopwatch, Tools dropdown items, Raw Data, Settings, error states)Timeline.vue: page title + filter labels (Host, Client, Duration, AFK, All)Buckets.vue: page title, "last updated", "no events recorded yet", "this device" badgeHome.vue: page titleSettings.vue: page title + all group labels/help text via$t()LanguageSettings.vue: new Settings > Language panel with a dropdown that persists the selection tolocalStorage(key:aw-locale)Behavior:
localStorageTest plan
npm run build)en.json)