Navi's web client speaks en / uk / ru. The rules themselves live in the platform handbook (handbook.gnexus → 10-platform/i18n); this file only records how navi follows them and what is still untranslated.
locale_effective from GET /auth/me — the choice made in Settings (navi_users.locale_override) → the gnexus-auth account locale (navi_users.locale, a straight mirror kept by the user.profile_updated webhook) → en.
The picker is the first block of Settings → Account (webclient/src/components/settings/AccountPanel.vue) and offers Auto / English / Українська / Русский — each language named in its own language. A choice is written with PATCH /auth/me, so it follows the account to other devices and survives logout.
With NAVI_AUTH_ENABLED=false there is no account to follow: the picker drops Auto, the choice is kept in localStorage under navi.locale, and the same key is used when the visitor is not signed in.
webclient/src/i18n/ — vue-i18n is deliberately not pulled in:
| File | Role |
|---|---|
index.js |
locale ref, t(slug, params), setLocale, normalizeTag, pluralCategory, and the navi.locale storage helpers |
messages/en.js, uk.js, ru.js |
flat dictionaries, the same slugs in all three |
t() interpolates {param} and falls back to the English string when the chosen language has no slug yet (or to the slug itself when nobody has it).{ one, few, many, other }) driven only by params.n; ru/uk use one/few/many, en uses one/other.<area>.<name> and an area is a screen or a component group — settings.*, sidebar.*, messages.*, chat.*, artifacts.*, ui.*, profile.*, app.*, common.*. Slugs are shared with the other gnexus services where they mean the same thing.SUPPORTED_LOCALES is the single list: adding a language means adding messages/<code>.js there, plus navi/locales.py on the backend (and the same slug in navi_users.locale_override constraints on the client side).The backend side is navi/locales.py (normalize_locale, effective_locale), mirroring the frontend's normalisation: en-US/en_US → en, anything not shipped → None.
Phase 1 shipped the framework and the language block only. Everything else is still hard-coded English and is translated area by area, biggest first:
| Area | Strings | Status |
|---|---|---|
settings.* |
~118 | language block only |
artifacts.* |
~45 | pending |
sidebar.* |
~29 | pending |
messages.* |
~27 | pending |
chat.* |
~21 | pending |
ui.* |
~20 | pending |
The remaining strings are counted in templates (text nodes, placeholder, title, aria-label, label, alt) plus keyed strings in <script setup>.
Backend-generated text (tool results, system prompts, WS event payloads) is still English and is out of scope for now — the interface language does not change what the agent answers.