From 484474e2c9c060573db286aa830bccc7b5924061 Mon Sep 17 00:00:00 2001 From: hell Date: Mon, 26 Oct 2026 10:49:27 +0700 Subject: [PATCH] feat: UI localization with vue-i18n and a per-user language setting Adds vue-i18n with the English string as the message key, so templates read $t("Report incident") and English needs no message file; Russian lives in src/locales/ru.json. Vuetify's own strings follow the same switch through its vue-i18n adapter, and relative dates use the active locale. A custom messageResolver looks the whole key up before walking dots as a path, so keys like "Loading... Please wait" resolve while Vuetify's $vuetify.* messages keep working. The language is chosen in the profile menu (avatar > Language) with a button toggle instead of a select: a select's dropdown renders under the profile menu overlay. The choice is kept in localStorage and in the new dispatch_user_settings.locale column (migration c6baea6d5ae8) and applied when /auth/me loads; without a saved choice the UI follows the browser. This pass translates the shell: toolbar, profile menu, navigation (route titles and groups), login/register, incident and case tables. Table headers moved from data() to computed so they re-render on switch. src/tests/setup.js installs i18n for every mount; i18n.spec.js covers the resolver and the switch. Co-Authored-By: Claude Fable 5.1 --- .../administration/contributing/index.mdx | 20 +++ src/dispatch/auth/models.py | 5 +- .../core/versions/2026-10-26_c6baea6d5ae8.py | 31 ++++ .../static/dispatch/package-lock.json | 83 +++++++++++ src/dispatch/static/dispatch/package.json | 1 + .../static/dispatch/src/auth/Login.vue | 12 +- .../static/dispatch/src/auth/Register.vue | 11 +- .../static/dispatch/src/auth/userSettings.js | 2 + .../static/dispatch/src/case/Table.vue | 42 +++--- .../dispatch/src/components/AppDrawer.vue | 18 +-- .../dispatch/src/components/AppToolbar.vue | 57 ++++++-- src/dispatch/static/dispatch/src/filters.js | 3 +- .../static/dispatch/src/i18n/index.js | 54 +++++++ .../static/dispatch/src/incident/Table.vue | 48 ++++--- .../static/dispatch/src/locales/ru.json | 132 ++++++++++++++++++ src/dispatch/static/dispatch/src/main.js | 3 + .../static/dispatch/src/tests/i18n.spec.js | 27 ++++ .../static/dispatch/src/tests/setup.js | 6 + .../static/dispatch/src/vuetify/index.js | 6 + 19 files changed, 483 insertions(+), 78 deletions(-) create mode 100644 src/dispatch/database/revisions/core/versions/2026-10-26_c6baea6d5ae8.py create mode 100644 src/dispatch/static/dispatch/src/i18n/index.js create mode 100644 src/dispatch/static/dispatch/src/locales/ru.json create mode 100644 src/dispatch/static/dispatch/src/tests/i18n.spec.js diff --git a/docs/docs/administration/contributing/index.mdx b/docs/docs/administration/contributing/index.mdx index 1224bae13cc7..5608164ecf4c 100644 --- a/docs/docs/administration/contributing/index.mdx +++ b/docs/docs/administration/contributing/index.mdx @@ -59,3 +59,23 @@ Push the tag: ``` git push origin latest ``` + +## Localization + +The UI is translated with [vue-i18n](https://vue-i18n.intlify.dev/). The English string is the +message key, so templates read `{{ $t("Report incident") }}` or `:label="$t('Search')"` and English +needs no message file: a key without a translation renders as itself. Translations live in +`src/dispatch/static/dispatch/src/locales/.json` as a flat map from the English string to +the translated one. + +- Keys may contain dots and punctuation; `src/i18n/index.js` looks the whole key up before treating + dots as a nested path (Vuetify's own `$vuetify.*` messages still use the nested form). +- Table headers and other strings built in `data()` or plain functions must move to a `computed` + property (Options API) or call `t()` from `useI18n()` (`