Skip to content

feat(i18n): add vue-i18n with en, uk, de, ru, zh-CN locales#896

Merged
ErikBjare merged 5 commits into
ActivityWatch:masterfrom
TimeToBuildBob:i18n-all-locales
Jul 3, 2026
Merged

feat(i18n): add vue-i18n with en, uk, de, ru, zh-CN locales#896
ErikBjare merged 5 commits into
ActivityWatch:masterfrom
TimeToBuildBob:i18n-all-locales

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Summary

Consolidates i18n work from PRs #855 (vue-i18n with uk/de/ru) and #865 (zh-CN translations) into a single clean PR on latest master, per Erik's request (see comments on #895).

Includes:

  • vue-i18n@8 infrastructure with TypeScript locale files
  • Language picker in Settings with localStorage persistence
  • Browser locale detection on first visit
  • 5 locale files: en, uk, de, ru, zh-CN
  • Core views migrated to $t() calls (Header, Buckets, Home, Timeline, Activity, Settings, Footer, and more)
  • Locale validation script and unit tests
  • Author attribution preserved: NureRykushBohdan (uk/de/ru) and JhihJian (zh-CN)

Follow-up (not in scope):

Closes #855, #865
Supersedes #895

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
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds full vue-i18n v8 infrastructure to aw-webui, migrating core views (Header, Footer, Home, Buckets, Timeline, Activity, Settings, and more) from hard-coded English strings to $t() calls, with locale files for en, uk, de, ru, and zh-CN.

  • Infrastructure: src/i18n/index.ts wires up VueI18n, browser-locale detection, localStorage/backend persistence via setAppLocale, and moment locale sync; LanguageSettings.vue exposes a working language picker in Settings \u2192 General.
  • Locale files: Five complete locale files (412 keys each) with consistent key structure; validated by the new npm run check:locales script that checks key parity and placeholder consistency against the English baseline.
  • Test coverage: Unit tests for isAppLocale, browser detection, localStorage restore, and settings-store locale loading round-trips are included and cover the main code paths.

Confidence Score: 5/5

The change is safe to merge; it adds an opt-in i18n layer without altering any existing data paths or store contracts.

All five locale files are fully wired up including zh-CN, the settings store locale lifecycle is correct, and the new functionality is well-covered by unit tests. The only gaps are cosmetic: document.documentElement.lang is not set when the locale comes from browser detection alone, and the installed vue-i18n v8 carries a deprecation notice — neither affects runtime correctness.

The browser-detection-only path in src/i18n/index.ts and src/stores/settings.ts is the one place where the document.documentElement.lang attribute does not get updated.

Important Files Changed

Filename Overview
src/i18n/index.ts New i18n module: registers VueI18n, exports isAppLocale, getInitialLocale, setAppLocale, and the i18n instance with all 5 locales wired up. Browser locale detection and localStorage persistence work correctly; moment locale is synced. Minor gap: document.documentElement.lang is not set during browser-detection-based init.
src/stores/settings.ts Adds locale to the settings state and integrates it into the load/save lifecycle. Invalid locales from server and localStorage are validated and warned on. Server locale takes priority over localStorage, which takes priority over browser detection. The only subtle gap is that setAppLocale is not called in the browser-detection-only path.
src/views/settings/LanguageSettings.vue New settings panel with a dropdown for all 5 supported locales. Reads from the store, validates via isAppLocale, and calls both update (persists to backend) and setAppLocale (updates i18n, moment, DOM, localStorage) on change. Loading guard prevents stale display.
scripts/check-locales.mjs New validation script that checks key parity, placeholder consistency, and untranslated strings across all 5 locale files against the en baseline. Uses new Function to parse static TS object literals (intentional, with eslint-disable comment). Hooked up as npm run check:locales.
src/components/UncategorizedNotification.vue Migrated to i18n: period labels now use translation keys, and the body string is built via $t('uncategorized.body', { duration, percent, period }). Replaced `
src/i18n/locales/zh-CN.ts Simplified Chinese locale, now properly imported and registered in index.ts and selectable in the language picker. Key structure matches en.ts. Included in check-locales.mjs validation.
test/unit/i18n.test.js Unit tests covering isAppLocale, localStorage-based restore, browser locale detection (including zh-CN and fallback to en), and setAppLocale side-effects. Tests are thorough and use module isolation correctly.
test/unit/store/settings.locale.test.js Integration tests for locale loading in the settings store: valid/invalid server locale, valid/invalid localStorage locale, and the sync with the live i18n instance. Covers the main code paths correctly.
src/main.js Imports the i18n instance from the new module and passes it to the root Vue constructor. Clean integration with no side-effects.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Browser
    participant i18n as i18n/index.ts
    participant Store as settings.ts
    participant LS as localStorage
    participant Server as aw-server

    Browser->>i18n: module init
    i18n->>LS: getItem('locale')
    alt stored locale found
        LS-->>i18n: 'de'
        i18n->>i18n: "initialLocale = 'de'"
    else no stored locale
        i18n->>Browser: navigator.language
        Browser-->>i18n: 'de-DE'
        i18n->>i18n: detectBrowserLocale() returns 'de'
    end
    i18n->>i18n: "new VueI18n({ locale })"
    i18n->>i18n: moment.locale('de')

    Browser->>Store: settings.load()
    Store->>Server: get_settings()
    Server-->>Store: "{ locale } or {}"
    alt locale in server or localStorage
        Store->>i18n: setAppLocale('de')
        i18n->>LS: setItem('locale', 'de')
        i18n->>Browser: "document.lang = 'de'"
    else browser-detected only
        Store->>Store: "patch({ locale: i18n.locale })"
        Note over Store,Browser: document.lang NOT updated here
    end

    Browser->>Store: user picks 'ru' in LanguageSettings
    Store->>i18n: setAppLocale('ru')
    i18n->>LS: setItem('locale', 'ru')
    i18n->>Browser: "document.lang = 'ru'"
    Store->>Server: POST /0/settings/locale 'ru'
Loading
%%{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"}}}%%
sequenceDiagram
    participant Browser
    participant i18n as i18n/index.ts
    participant Store as settings.ts
    participant LS as localStorage
    participant Server as aw-server

    Browser->>i18n: module init
    i18n->>LS: getItem('locale')
    alt stored locale found
        LS-->>i18n: 'de'
        i18n->>i18n: "initialLocale = 'de'"
    else no stored locale
        i18n->>Browser: navigator.language
        Browser-->>i18n: 'de-DE'
        i18n->>i18n: detectBrowserLocale() returns 'de'
    end
    i18n->>i18n: "new VueI18n({ locale })"
    i18n->>i18n: moment.locale('de')

    Browser->>Store: settings.load()
    Store->>Server: get_settings()
    Server-->>Store: "{ locale } or {}"
    alt locale in server or localStorage
        Store->>i18n: setAppLocale('de')
        i18n->>LS: setItem('locale', 'de')
        i18n->>Browser: "document.lang = 'de'"
    else browser-detected only
        Store->>Store: "patch({ locale: i18n.locale })"
        Note over Store,Browser: document.lang NOT updated here
    end

    Browser->>Store: user picks 'ru' in LanguageSettings
    Store->>i18n: setAppLocale('ru')
    i18n->>LS: setItem('locale', 'ru')
    i18n->>Browser: "document.lang = 'ru'"
    Store->>Server: POST /0/settings/locale 'ru'
Loading

Reviews (3): Last reviewed commit: "fix(i18n): fix lint error - replace empt..." | Re-trigger Greptile

Comment thread src/i18n/index.ts Outdated
Comment thread src/i18n/index.ts
Comment thread scripts/check-locales.mjs Outdated
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.87500% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.42%. Comparing base (17d4a4b) to head (aea2642).

Files with missing lines Patch % Lines
src/stores/settings.ts 86.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #896      +/-   ##
==========================================
+ Coverage   35.58%   38.42%   +2.84%     
==========================================
  Files          36       42       +6     
  Lines        2161     2225      +64     
  Branches      401      436      +35     
==========================================
+ Hits          769      855      +86     
+ Misses       1371     1292      -79     
- Partials       21       78      +57     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

✅ Ready to Merge

All checks passing:

  • ✅ CI green (all 8 jobs pass)
  • ✅ Greptile 5/5 confidence — "Safe to merge"
  • ✅ Review threads resolved (zh-CN locale wired, TypeScript null guard, and duplicate entries all addressed in follow-up commits)
  • ✅ Merge status: CLEAN

Summary: This PR consolidates i18n work (vue-i18n@8 with en, uk, de, ru, zh-CN) from PRs #855 and #865 into a single clean implementation. The changes are additive, fall back cleanly to English on any missing key, and all five locale files are fully wired and validated.

Ready for merge by maintainer.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ErikBjare ErikBjare merged commit 948be8d into ActivityWatch:master Jul 3, 2026
9 checks passed
TimeToBuildBob added a commit to TimeToBuildBob/aw-webui that referenced this pull request Jul 3, 2026
…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
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.

2 participants