feat: add Chinese localization#865
Conversation
Greptile SummaryThis PR adds a lightweight custom i18n layer to aw-webui, introducing English and Simplified Chinese locale files, a persisted
Confidence Score: 4/5The change is safe to merge; the core i18n module, locale files, settings persistence, and template migrations are all solid. The one concrete defect — activity view tab names staying in the old language after a language switch — is a UX regression on the changed path but does not affect data integrity or break any existing functionality. The i18n module is well-designed and thoroughly tested. Settings store language validation handles edge cases (unsupported locales, boolean-like values) from both server and localStorage. The main defect is that localizeView bakes translated view names into Pinia state at load time; when the user switches language mid-session, activity view tab labels remain in the pre-switch language because viewsStore.load() is never re-invoked. Everything else — templates, tooltips, D3 status messages, and the settings UI — works as intended. src/stores/views.ts — the localizeView/serializeView design needs either a reload hook on language change or a template-side computed approach to avoid stale tab names. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User selects language
LanguageSettings.vue] --> B[setLocale - i18n state]
A --> C[useSettingsStore.update language value]
B --> D[Vue.observable state.locale updated]
D --> E[$t and t calls in Vue templates re-evaluate]
D --> F[D3 imperative renders NOT re-triggered]
C --> G[settingsStore.save then .load]
G --> H[Settings reloaded from server/localStorage]
H --> I[App.vue watcher fires setLocale again]
H --> J[viewsStore NOT reloaded, view.name strings stay stale]
J --> K[Activity view tab names show old language until page reload]
E --> L[Nav, settings, tooltips update immediately]
Reviews (1): Last reviewed commit: "feat: localize remaining interface text" | Re-trigger Greptile |
| function localizeView(view: View): View { | ||
| return { | ||
| ...view, | ||
| elements: view.elements.map(element => ({ ...element })), | ||
| name: view.nameKey ? t(view.nameKey) : view.name, | ||
| }; | ||
| } | ||
|
|
||
| function serializeView(view: View): View { | ||
| return { | ||
| ...view, | ||
| elements: view.elements.map(element => ({ ...element })), | ||
| name: view.nameKey ? translate(view.nameKey, 'en') : view.name, | ||
| }; | ||
| } |
There was a problem hiding this comment.
View names baked into state — stale after a language switch
localizeView calls t(view.nameKey) once at load time and stores the result as a plain string in Pinia state. Because Pinia does not re-run that call when the i18n locale changes, the activity-view tab labels (Summary, Window, Browser, Editor) remain in the old language after the user changes language in Settings — they only refresh if the page is reloaded or viewsStore.load() is called again explicitly. LanguageSettings.vue only triggers useSettingsStore().update(), which reloads the settings store but does not re-invoke viewsStore.load(), so the stale names persist for the entire session.
| { | ||
| label: 'Total time', | ||
| labelKey: 'visualizationStatus.totalTime', |
There was a problem hiding this comment.
The
label field here is what Chart.js uses directly in legends and tooltips. Unless the consuming component explicitly reads labelKey and calls $t(), the chart will always display "Total time" regardless of the active locale. The labelKey mechanism only works if every consumer respects it, making the translation incomplete by default.
| { | |
| label: 'Total time', | |
| labelKey: 'visualizationStatus.totalTime', | |
| { | |
| label: 'visualizationStatus.totalTime', | |
| labelKey: 'visualizationStatus.totalTime', |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const state = Vue.observable<{ locale: SupportedLocale }>({ | ||
| locale: DEFAULT_LOCALE, | ||
| }); |
There was a problem hiding this comment.
D3-rendered text is not reactively updated on locale change
t() is called at draw time inside D3 imperative functions (summary.ts, periodusage.ts, timeline.ts, timeline-simple.ts). Unlike Vue templates, Vue.observable reactivity does not cause these D3 renders to re-run when the locale changes. SVG status messages ("No data", "Today", etc.) stay in the old language after a language switch until the next data update or manual refresh. This is an inherent limitation of calling t() imperatively outside the Vue rendering cycle — worth documenting so future contributors know to trigger a re-render after locale changes.
| import type en from './en'; | ||
|
|
||
| type DeepPartial<T> = { | ||
| [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; | ||
| }; | ||
|
|
There was a problem hiding this comment.
test namespace present in English but intentionally absent from zh-CN
The English locale defines a test.onlyEnglish key specifically to exercise the English-fallback path in i18n.test.node.ts. This is fine by design — satisfies DeepPartial<typeof en> allows it — but if anyone adds real UI-facing keys under test in the future without checking zh-CN, they will silently fall back to English. A brief comment inside en.ts (e.g. // test-only keys, not shown in the UI) would prevent accidental production use of this namespace.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Consolidates i18n work from PR ActivityWatch#855 (vue-i18n with uk/de/ru) and PR ActivityWatch#865 (zh-CN translations) into a single clean PR on latest master. Includes: - vue-i18n@8 infrastructure with TypeScript locale files - Language picker in Settings with localStorage persistence - Browser locale detection on first visit - Locale files: en, uk, de, ru, zh-CN - Core views migrated to $t() calls - Locale validation script and unit tests Co-Authored-By: NureRykushBohdan Co-Authored-By: JhihJian
|
Thanks for the Chinese localization work! It was incorporated with author attribution into PR #896, which consolidates #865 and #855 (uk/de/ru) into a single clean vue-i18n implementation per Erik's request. #896 is CI-green and ready for review/merge. If you're happy with the attribution, feel free to close this PR — the work lives on in #896. |
* feat(i18n): add vue-i18n with en, uk, de, ru, zh-CN locales Consolidates i18n work from PR #855 (vue-i18n with uk/de/ru) and PR #865 (zh-CN translations) into a single clean PR on latest master. Includes: - vue-i18n@8 infrastructure with TypeScript locale files - Language picker in Settings with localStorage persistence - Browser locale detection on first visit - Locale files: en, uk, de, ru, zh-CN - Core views migrated to $t() calls - Locale validation script and unit tests Co-Authored-By: NureRykushBohdan <bohdan.rykush@nure.ua> Co-Authored-By: JhihJian <jhihjian@foxmail.com> * fix(i18n): update package-lock.json with vue-i18n@8 entry * style(i18n): apply prettier formatting (single quotes, trailing commas) * fix(i18n): wire zh-CN locale * fix(i18n): fix lint error - replace empty arrow function with jest.fn() in locale test
|
Merged as part of #896, thanks for contributing! ❤️ |
…Watch#896) * feat(i18n): add vue-i18n with en, uk, de, ru, zh-CN locales Consolidates i18n work from PR ActivityWatch#855 (vue-i18n with uk/de/ru) and PR ActivityWatch#865 (zh-CN translations) into a single clean PR on latest master. Includes: - vue-i18n@8 infrastructure with TypeScript locale files - Language picker in Settings with localStorage persistence - Browser locale detection on first visit - Locale files: en, uk, de, ru, zh-CN - Core views migrated to $t() calls - Locale validation script and unit tests Co-Authored-By: NureRykushBohdan <bohdan.rykush@nure.ua> Co-Authored-By: JhihJian <jhihjian@foxmail.com> * fix(i18n): update package-lock.json with vue-i18n@8 entry * style(i18n): apply prettier formatting (single quotes, trailing commas) * fix(i18n): wire zh-CN locale * fix(i18n): fix lint error - replace empty arrow function with jest.fn() in locale test
Summary
Test Plan
npm test -- --runInBandnpm run buildnpm run tauri build -- --bundles nsisfromaw-tauri(builtaw-tauri_0.1.0_x64-setup.exefor Windows verification)Notes
aw-tauri cargo clippy -- -D warningscurrently fails on pre-existing Rust warnings (child_pid,collapsible_match), unrelated to this web UI localization PR.