.js` there, plus `navi/locales.py` on the backend (and the
same slug in `navi_users.locale_override` constraints on the client side).
@@ -50,20 +54,31 @@
## Translation status
-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:
+The framework and the language block shipped first; from there the interface is
+translated area by area, biggest first, one commit per area. An area not yet
+done is hard-coded English and looks exactly as it did before — the two kinds of
+screen sit side by side without either noticing.
| Area | Strings | Status |
|---|---|---|
-| `settings.*` | ~118 | language block only |
+| `settings.*` | 125 (+ 41 in `common.*`, `col.*`, `confirm.*`, `toast.*`) | done |
| `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 `
-
+
+
diff --git a/webclient/dist/sw.js b/webclient/dist/sw.js
index d647c60..63bc75f 100644
--- a/webclient/dist/sw.js
+++ b/webclient/dist/sw.js
@@ -8,13 +8,13 @@
* - /images/*: cache-first, capped — except the app's own artwork (below);
* - everything dynamic (/api, /ws, /auth, /push, /content, ...): pass-through.
*
- * 784e239ff091bb09 is stamped at build time (vite.config.js closeBundle)
+ * bbd4943df8d73fe6 is stamped at build time (vite.config.js closeBundle)
* with a digest of index.html + the hashed asset filenames — so sw.js bytes
* change on every build, the browser picks the new SW up (served no-store),
* and activation evicts caches from previous builds.
*/
-const VERSION = '784e239ff091bb09';
+const VERSION = 'bbd4943df8d73fe6';
const SHELL_CACHE = `navi-shell-${VERSION}`;
const ASSETS_CACHE = `navi-assets-${VERSION}`;
const IMAGES_CACHE = 'navi-images';
diff --git a/webclient/src/components/settings/AdminToolsPanel.vue b/webclient/src/components/settings/AdminToolsPanel.vue
index 07adb99..24a1b38 100644
--- a/webclient/src/components/settings/AdminToolsPanel.vue
+++ b/webclient/src/components/settings/AdminToolsPanel.vue
@@ -1,38 +1,39 @@
- Tools
+ {{ t('settings.adminToolsTitle') }}
- {{ running ? 'Reloading…' : 'Reload tools' }}
+ {{ t(running ? 'settings.adminToolsReloading' : 'settings.adminToolsReload') }}
- Re-reads the tools in tools/, the context providers, and reconnects
- every MCP server — no restart, no downtime. New tools are callable from the next
- message of every session. Admin only: it swaps the toolset all profiles resolve against.
+ {{ t('settings.adminToolsDescriptionBefore') }} tools/
+ {{ t('settings.adminToolsDescriptionAfter') }}
- {{ report.tools.length }} tool{{ report.tools.length === 1 ? '' : 's' }} loaded
+ {{ t('settings.adminToolsLoaded', { n: report.tools.length }) }}
— {{ report.tools.join(', ') }}
- ({{ report.total_tools }} in the registry)
+
+ {{ t('settings.adminToolsRegistry', { n: report.total_tools }) }}
+
- enabled.json names {{ report.missing_enabled.join(', ') }} — no such tool
- exists, so it is dropped from every profile.
+ enabled.json
+ {{ t('settings.adminToolsMissing', { names: report.missing_enabled.join(', ') }) }}
@@ -43,13 +44,13 @@
- MCP reload failed: {{ report.mcp_error }}
+ {{ t('settings.adminToolsMcpFailed', { error: report.mcp_error }) }}
- {{ report.mcp_tools.length }} MCP tool{{ report.mcp_tools.length === 1 ? '' : 's' }} —
- {{ report.providers.length }} context provider{{ report.providers.length === 1 ? '' : 's' }}
+ {{ t('settings.adminToolsMcpTools', { n: report.mcp_tools.length }) }} —
+ {{ t('settings.adminToolsProviders', { n: report.providers.length }) }}
@@ -58,6 +59,7 @@
diff --git a/webclient/src/components/settings/AppPanel.vue b/webclient/src/components/settings/AppPanel.vue
index 3e9202e..2d6d7b0 100644
--- a/webclient/src/components/settings/AppPanel.vue
+++ b/webclient/src/components/settings/AppPanel.vue
@@ -1,13 +1,8 @@
- App
+ {{ t('settings.appTitle') }}
-
- Navi is a web app, but it can live on your device like a native one — either
- installed into this browser, or as the Android app, which wraps this same
- interface. They are not exclusive: the Android app is worth having on a phone,
- the installed web app everywhere else.
-
+ {{ t('settings.appDescription') }}
-
- Safari: tap Share, then
- Add to Home Screen. Notifications there need iOS 16.4 or newer and
- work only in the installed app, not in a Safari tab.
+ {{ t('settings.appBrowserSafari') }}
+ {{ t('settings.appIosTap') }}
+ {{ t('settings.appIosShare') }}
+ {{ t('settings.appIosAddHome') }}. {{ t('settings.appIosNote') }}
-
- Chrome or Edge: click the install icon at the right end of the
- address bar, or open the menu and
- choose Install Navi.
+ {{ t('settings.appBrowserChrome') }}
+ {{ t('settings.appChromeClick') }}
+ {{ t('settings.appChromeMenu') }}
+ {{ t('settings.appChromeInstall') }}.
-
- Android: open the menu and
- choose Install app (sometimes “Add to Home screen”).
+ {{ t('settings.appBrowserAndroid') }}
+ {{ t('settings.appAndroidOpen') }}
+ {{ t('settings.appAndroidMenu') }}
+ {{ t('settings.appAndroidInstall') }} {{ t('settings.appAndroidAlso') }}
-
- Browsers only offer this over HTTPS or on localhost — over a plain
-
http:// address the option never appears, and neither do
- offline mode and notifications.
+ {{ t('settings.appHttpsBefore') }} http://
+ {{ t('settings.appHttpsAfter') }}
@@ -76,27 +69,21 @@
- Android app
+ {{ t('settings.appAndroidTitle') }}
-
- The same Navi in a native shell. What a browser tab cannot give you:
- notifications while the phone is locked — a background service keeps the
- connection open — plus a native camera and photo picker, and links that open
- in your normal browser. Sessions, profiles and settings are the server's, so
- the app and the web client stay in step.
-
+ {{ t('settings.appAndroidText') }}
- The APK is not published on this server — build it with
+ {{ t('settings.appApkMissing') }}
android-client/tools/publish-apk.sh.
- - Version
- {{ apk.versionName }}
- - Size
- {{ sizeText }}
- - Built
- {{ dateText }}
+ - {{ t('settings.appVersion') }}
- {{ apk.versionName }}
+ - {{ t('settings.appSize') }}
- {{ sizeText }}
+ - {{ t('settings.appBuilt') }}
- {{ dateText }}
- SHA-256
{{ apk.sha256 }}
@@ -104,24 +91,17 @@
- Download APK
+ {{ t('settings.appDownloadApk') }}
+ - {{ t('settings.appAndroidStep1') }}
-
- Open the downloaded file on the phone. Android will ask whether to allow
- installing apps from this source — allow it.
-
- -
- On first launch the app asks for your Navi address. Enter
+ {{ t('settings.appAndroidStep2') }}
{{ defaultOrigin }}, then log in through the browser when it opens; the app returns on its
- own.
+ >{{ t('settings.appAndroidStep2Tail') }}
- -
- A newer APK installs over the old one and keeps your settings. You would
- only have to uninstall first if the signing key ever changed.
-
+ - {{ t('settings.appAndroidStep3') }}
@@ -131,6 +111,7 @@
diff --git a/webclient/src/components/settings/NotificationSettingsPanel.vue b/webclient/src/components/settings/NotificationSettingsPanel.vue
index 810f76d..d99d611 100644
--- a/webclient/src/components/settings/NotificationSettingsPanel.vue
+++ b/webclient/src/components/settings/NotificationSettingsPanel.vue
@@ -1,42 +1,29 @@
- Notifications
+ {{ t('settings.notificationsTitle') }}
-
- The app keeps its connection open in the background and raises a native
- notification when a reply finishes while Navi is minimized. Notifications
- stop if you close the app itself.
-
-
- Push notifications let Navi ping you when a reply is ready — even when the
- tab is closed. You get one notification per finished reply, but only when
- no Navi window is open on that session.
-
+ {{ t('settings.notificationsBridgeText') }}
+ {{ t('settings.notificationsWebText') }}
-
- This browser does not support web push (needs HTTPS, a service worker
- and the Push API). Try Chrome or a recent Firefox.
-
+ {{ t('settings.notificationsUnsupported') }}
- Push notifications
+ {{ t('settings.notificationsRowTitle') }}
-
- Enabled — background mode holds the connection open when minimized.
-
- Enabled — this device is subscribed.
+ {{ t('settings.notificationsOnBackground') }}
+ {{ t('settings.notificationsOn') }}
- {{ bridgeMode ? 'Blocked in the system app settings.' : 'Blocked in browser site settings.' }}
+ {{ t(bridgeMode ? 'settings.notificationsBlockedApp' : 'settings.notificationsBlockedBrowser') }}
- Disabled.
+ {{ t('settings.notificationsOff') }}
import { onMounted } from 'vue'
+import { t } from '@/i18n/index.js'
import { usePush } from '@/composables/usePush.js'
import { useToast } from 'gnexus-ui-kit/vue'
@@ -66,19 +54,24 @@
const ok = await enable()
if (ok === false && permission.value === 'denied') {
toast.error({
- title: 'Permission denied',
- text: bridgeMode.value
- ? 'Allow notifications in the system app settings.'
- : 'Allow notifications in the browser site settings.',
+ title: t('toast.notificationsDenied'),
+ text: t(
+ bridgeMode.value
+ ? 'toast.notificationsAllowApp'
+ : 'toast.notificationsAllowBrowser'
+ ),
})
} else if (ok === false) {
- toast.error({ title: 'Push unavailable', text: 'The server does not accept push subscriptions right now.' })
+ toast.error({
+ title: t('toast.notificationsUnavailable'),
+ text: t('toast.notificationsUnavailableText'),
+ })
}
} else {
await disable()
}
} catch (err) {
- toast.error({ title: 'Push error', text: err.message })
+ toast.error({ title: t('toast.notificationsFailed'), text: err.message })
}
}
diff --git a/webclient/src/components/settings/SettingsView.vue b/webclient/src/components/settings/SettingsView.vue
index fc84ed5..7882676 100644
--- a/webclient/src/components/settings/SettingsView.vue
+++ b/webclient/src/components/settings/SettingsView.vue
@@ -4,21 +4,27 @@
- Settings
+ {{ t('settings.title') }}
-
+
-
+
@@ -38,11 +44,12 @@
diff --git a/webclient/src/components/settings/SynapseTargetsPanel.vue b/webclient/src/components/settings/SynapseTargetsPanel.vue
index 32578b5..bc8744d 100644
--- a/webclient/src/components/settings/SynapseTargetsPanel.vue
+++ b/webclient/src/components/settings/SynapseTargetsPanel.vue
@@ -1,28 +1,23 @@
- Synapse deliveries
+ {{ t('settings.synapseTargetsTitle') }}
- Add Target
+ {{ t('settings.synapseTargetAdd') }}
-
- Deliveries from the GNEXUS Synapse notification hub arrive at this Navi's
- webhook and are verified against a per-user target secret. Create the
- target in the Synapse admin panel, then register it here to let Navi
- accept its deliveries.
-
+ {{ t('settings.synapseTargetsDescription') }}
-
+
{{ formatDate(row.created_at) }}
@@ -30,7 +25,7 @@
@@ -46,7 +41,8 @@
diff --git a/webclient/src/i18n/messages/en.js b/webclient/src/i18n/messages/en.js
index 6625b1a..4771bb3 100644
--- a/webclient/src/i18n/messages/en.js
+++ b/webclient/src/i18n/messages/en.js
@@ -13,6 +13,30 @@
// shared vocabulary, reused wherever the meaning is the same
'common.error': 'Something went wrong',
+ 'common.menu': 'Menu',
+ 'common.save': 'Save',
+ 'common.cancel': 'Cancel',
+ 'common.create': 'Create',
+ 'common.add': 'Add',
+ 'common.done': 'Done',
+ 'common.never': 'Never',
+ 'common.always': 'Always',
+ 'common.you': 'You',
+ 'common.revoke': 'Revoke',
+ 'common.copyToClipboard': 'Copy to clipboard',
+
+ // table columns
+ 'col.name': 'Name',
+ 'col.key': 'Key',
+ 'col.created': 'Created',
+ 'col.lastUsed': 'Last used',
+
+ // confirmation dialogs
+ 'confirm.revokeKey': 'Revoke API key "{name}"? This cannot be undone.',
+ 'confirm.revokeTarget': 'Revoke Synapse target "{target}"? This cannot be undone.',
+ 'confirm.mcpResetAdmin': 'Reset the key for "{server}" to the shared default?',
+ 'confirm.mcpReset':
+ 'Remove your key for "{server}"? The server stops being connected for you.',
// the account itself
'profile.languageAuto': 'Auto (from account)',
@@ -24,12 +48,206 @@
// toasts — a title, sometimes a line of text under it
'toast.languageSaved': 'Language saved',
+ 'toast.keyCreateFailed': 'Failed to create key',
+ 'toast.keyRevoked': 'API key revoked',
+ 'toast.keyRevokeFailed': 'Failed to revoke key',
+ 'toast.notificationsDenied': 'Permission denied',
+ 'toast.notificationsAllowApp': 'Allow notifications in the system app settings.',
+ 'toast.notificationsAllowBrowser': 'Allow notifications in the browser site settings.',
+ 'toast.notificationsUnavailable': 'Push unavailable',
+ 'toast.notificationsUnavailableText':
+ 'The server does not accept push subscriptions right now.',
+ 'toast.notificationsFailed': 'Push error',
+ 'toast.mcpKeySaved': 'Key saved for {server}',
+ 'toast.mcpKeySaveFailed': 'Failed to save key',
+ 'toast.mcpKeyReset': 'Key reset for {server}',
+ 'toast.mcpKeyResetFailed': 'Failed to reset key',
+ 'toast.synapseTargetAdded': 'Target added',
+ 'toast.synapseTargetAddFailed': 'Failed to add target',
+ 'toast.synapseTargetRevoked': 'Target revoked',
+ 'toast.synapseTargetRevokeFailed': 'Failed to revoke target',
+ 'toast.reactionsLoadFailed': 'Failed to load reaction settings',
+ 'toast.saveFailed': 'Failed to save',
+ 'toast.instructionsSaved': 'Instructions saved',
+ 'toast.instructionsFailed': 'Failed to save instructions',
+ 'toast.toolsReloaded': { one: 'Reloaded {n} tool', other: 'Reloaded {n} tools' },
+ 'toast.toolsReloadErrors': 'Reload finished with errors — see the panel',
+ 'toast.toolsReloadFailed': 'Failed to reload tools',
- // settings — the language row of the Account tab, the first area to be moved
- // off hard-coded English
+ // settings — the shell, then one block per tab
+ 'settings.title': 'Settings',
+ 'settings.sectionsLabel': 'Settings sections',
+ 'settings.tabApp': 'App',
+ 'settings.tabAccount': 'Account',
+ 'settings.tabNotifications': 'Notifications',
+ 'settings.tabSynapse': 'Synapse',
+ 'settings.tabMcp': 'MCP',
+
'settings.languageToolbar': 'Interface language',
'settings.languageDescription':
'Navi follows the language of your gnexus-auth account by default; a choice here overrides it for Navi only. Auto goes back to the account language.',
'settings.languageDescriptionOpen':
'Sign-in is switched off on this Navi, so the language is kept in this browser only.',
+
+ // settings — App
+ 'settings.appTitle': 'App',
+ 'settings.appDescription':
+ 'Navi is a web app, but it can live on your device like a native one — either installed into this browser, or as the Android app, which wraps this same interface. They are not exclusive: the Android app is worth having on a phone, the installed web app everywhere else.',
+ 'settings.appInstallTitle': 'Install in this browser',
+ 'settings.appInstallText':
+ 'Installing puts Navi in its own window with no address bar, adds an icon to your dock or home screen, and caches the interface so the app opens instantly and still shows your sessions offline. It also enables notifications for finished replies — including when the tab is closed.',
+ 'settings.appAndroidAlert':
+ 'You are reading this inside the Android app, which is already installed. To update it, download the APK below and install it over the old one.',
+ 'settings.appInstalledAlert':
+ 'Installed — Navi is running in its own window, not in a browser tab.',
+ 'settings.appInstallAction': 'Install',
+ 'settings.appInstallHint': 'Your browser will ask you to confirm.',
+ 'settings.appBrowserSafari': 'Safari:',
+ 'settings.appIosTap': 'tap',
+ 'settings.appIosShare': 'Share, then',
+ 'settings.appIosAddHome': 'Add to Home Screen',
+ 'settings.appIosNote':
+ 'Notifications there need iOS 16.4 or newer and work only in the installed app, not in a Safari tab.',
+ 'settings.appBrowserChrome': 'Chrome or Edge:',
+ 'settings.appChromeClick':
+ 'click the install icon at the right end of the address bar, or open the',
+ 'settings.appChromeMenu': 'menu and choose',
+ 'settings.appChromeInstall': 'Install Navi',
+ 'settings.appBrowserAndroid': 'Android:',
+ 'settings.appAndroidOpen': 'open the',
+ 'settings.appAndroidMenu': 'menu and choose',
+ 'settings.appAndroidInstall': 'Install app',
+ 'settings.appAndroidAlso': '(sometimes “Add to Home screen”)',
+ 'settings.appHttpsBefore':
+ 'Browsers only offer this over HTTPS or on localhost — over a plain',
+ 'settings.appHttpsAfter':
+ 'address the option never appears, and neither do offline mode and notifications.',
+ 'settings.appAndroidTitle': 'Android app',
+ 'settings.appAndroidText':
+ "The same Navi in a native shell. What a browser tab cannot give you: notifications while the phone is locked — a background service keeps the connection open — plus a native camera and photo picker, and links that open in your normal browser. Sessions, profiles and settings are the server's, so the app and the web client stay in step.",
+ 'settings.appApkMissing': 'The APK is not published on this server — build it with',
+ 'settings.appVersion': 'Version',
+ 'settings.appSize': 'Size',
+ 'settings.appBuilt': 'Built',
+ 'settings.appDownloadApk': 'Download APK',
+ 'settings.appAndroidStep1':
+ 'Open the downloaded file on the phone. Android will ask whether to allow installing apps from this source — allow it.',
+ 'settings.appAndroidStep2': 'On first launch the app asks for your Navi address. Enter',
+ 'settings.appAndroidStep2Tail':
+ ', then log in through the browser when it opens; the app returns on its own.',
+ 'settings.appAndroidStep3':
+ 'A newer APK installs over the old one and keeps your settings. You would only have to uninstall first if the signing key ever changed.',
+
+ // settings — API keys
+ 'settings.keysTitle': 'API Keys',
+ 'settings.keysCreate': 'New Key',
+ 'settings.keysDescription':
+ 'API keys allow headless clients (scripts, voice assistants, IoT devices) to authenticate without browser-based OAuth.',
+ 'settings.keysNoAuthWarning':
+ 'Authorization is currently disabled. Any keys created here are tied to the local anonymous user and grant full access to this Navi instance.',
+ 'settings.keysLoading': 'Loading API keys',
+ 'settings.keysEmpty': 'No API keys yet. Create one to get started.',
+ 'settings.keyCreateTitle': 'Create API Key',
+ 'settings.keyNamePlaceholder': 'e.g. Smart Watch, Automation Script',
+ 'settings.keyCreatedTitle': 'API Key Created',
+ 'settings.keyCopyNow': 'Copy this token now — it will',
+ 'settings.keyCopyNowStrong': 'never be shown again',
+
+ // settings — notifications
+ 'settings.notificationsTitle': 'Notifications',
+ 'settings.notificationsBridgeText':
+ 'The app keeps its connection open in the background and raises a native notification when a reply finishes while Navi is minimized. Notifications stop if you close the app itself.',
+ 'settings.notificationsWebText':
+ 'Push notifications let Navi ping you when a reply is ready — even when the tab is closed. You get one notification per finished reply, but only when no Navi window is open on that session.',
+ 'settings.notificationsUnsupported':
+ 'This browser does not support web push (needs HTTPS, a service worker and the Push API). Try Chrome or a recent Firefox.',
+ 'settings.notificationsRowTitle': 'Push notifications',
+ 'settings.notificationsOnBackground':
+ 'Enabled — background mode holds the connection open when minimized.',
+ 'settings.notificationsOn': 'Enabled — this device is subscribed.',
+ 'settings.notificationsBlockedApp': 'Blocked in the system app settings.',
+ 'settings.notificationsBlockedBrowser': 'Blocked in browser site settings.',
+ 'settings.notificationsOff': 'Disabled.',
+
+ // settings — MCP servers
+ 'settings.mcpTitle': 'MCP servers',
+ 'settings.mcpDescriptionAdmin':
+ 'Everything connected to your profiles. A server that offers a personal key slot runs under your own key — leave yours unset and it keeps using the shared key configured on this Navi.',
+ 'settings.mcpDescription':
+ 'Everything connected to your profiles. A server that offers a personal key slot needs',
+ 'settings.mcpDescriptionStrong': 'your own key',
+ 'settings.mcpDescriptionTail':
+ '— until you save one it is not connected for you at all. Servers without a slot are shared and always available.',
+ 'settings.mcpLoading': 'Loading MCP servers',
+ 'settings.mcpEmpty': 'No MCP servers are connected to any profile on this Navi.',
+ 'settings.mcpKeySet': 'Personal key set {date} — used instead of the shared key ({location}).',
+ 'settings.mcpKeyAbsentAdmin':
+ 'No personal key — the shared key from the config is used ({location}).',
+ 'settings.mcpKeyAbsent': 'No personal key — this server is not connected for you.',
+ 'settings.mcpNoSlot': 'No personal key slot — runs on the shared config.',
+ 'settings.mcpKeyPlaceholder': '••• saved — enter a new key to replace',
+ 'settings.mcpKeyPlaceholderNew': 'Paste your key',
+ 'settings.mcpReset': 'Reset to default',
+
+ // settings — Synapse deliveries
+ 'settings.synapseTargetsTitle': 'Synapse deliveries',
+ 'settings.synapseTargetAdd': 'Add Target',
+ 'settings.synapseTargetsDescription':
+ "Deliveries from the GNEXUS Synapse notification hub arrive at this Navi's webhook and are verified against a per-user target secret. Create the target in the Synapse admin panel, then register it here to let Navi accept its deliveries.",
+ 'settings.synapseTargetsLoading': 'Loading targets',
+ 'settings.synapseTargetsEmpty':
+ 'No Synapse targets registered. Add one to start receiving deliveries.',
+ 'settings.synapseTargetColRef': 'Token ref',
+ 'settings.synapseTargetAddTitle': 'Add Synapse Target',
+ 'settings.synapseTargetRefPlaceholder': 'e.g. navi-main (as in the Synapse admin panel)',
+ 'settings.synapseTargetSecret': 'Delivery secret',
+ 'settings.synapseTargetSecretPlaceholder': 's2s secret of the Synapse target',
+ 'settings.synapseTargetHintBefore':
+ 'In the Synapse admin panel create a target pointing at',
+ 'settings.synapseTargetHintAfter':
+ 'of this Navi and paste its secret here. Navi uses it to verify that deliveries really came from Synapse.',
+
+ // settings — Synapse reactions
+ 'settings.reactionsTitle': 'Synapse reactions',
+ 'settings.reactionsDescription':
+ 'When a GNEXUS Synapse event is delivered, Navi can react to it on its own: a dispatcher picks a profile by your reaction instructions, the work runs in a hidden service session, and you can browse that session later via the sidebar "Service sessions" toggle.',
+ 'settings.reactionsNotConfigured':
+ 'Synapse integration is not configured on this navi: options that send events to Synapse are disabled. App-only delivery keeps working.',
+ 'settings.reactionsLoading': 'Loading reaction settings',
+ 'settings.reactionsEnable': 'Enable reactions',
+ 'settings.reactionsOn': 'Delivered events start a background reaction run.',
+ 'settings.reactionsOff': 'Events are accepted and logged, but Navi does nothing.',
+ 'settings.reactionsNotify': 'Notify me about finished reactions',
+ 'settings.reactionsOnlyFailures': 'Only failures',
+ 'settings.reactionsPushAlways': 'Push on every finished run.',
+ 'settings.reactionsPushFailures': 'Push only when a run ended with errors.',
+ 'settings.reactionsPushNever': 'No completion push.',
+ 'settings.reactionsTarget': 'Where notifications go',
+ 'settings.reactionsTargetNote':
+ "Applies to Navi's notify tool and to reaction-completion pushes.",
+ 'settings.reactionsTargetApp': 'App only',
+ 'settings.reactionsTargetAppSynapse': 'App + Synapse',
+ 'settings.reactionsTargetSynapse': 'Synapse only',
+ 'settings.reactionsInstructions': 'Reaction instructions',
+ 'settings.reactionsInstructionsNote':
+ 'Guidance the dispatcher uses to pick a profile and to shape the task. Both you and Navi can edit it; every change is recorded in the history below.',
+ 'settings.reactionsInstructionsPlaceholder':
+ 'e.g. "Todo events from gntodo go to the developer profile; junk mail goes nowhere."',
+ 'settings.reactionsInstructionsSave': 'Save instructions',
+ 'settings.reactionsHistory': 'Edit history',
+
+ // settings — admin tools (admins only)
+ 'settings.adminToolsTitle': 'Tools',
+ 'settings.adminToolsReloading': 'Reloading…',
+ 'settings.adminToolsReload': 'Reload tools',
+ 'settings.adminToolsDescriptionBefore': 'Re-reads the tools in',
+ 'settings.adminToolsDescriptionAfter':
+ ', the context providers, and reconnects every MCP server — no restart, no downtime. New tools are callable from the next message of every session. Admin only: it swaps the toolset all profiles resolve against.',
+ 'settings.adminToolsLoaded': { one: '{n} tool loaded', other: '{n} tools loaded' },
+ 'settings.adminToolsRegistry': '({n} in the registry)',
+ 'settings.adminToolsMissing':
+ 'names {names} — no such tool exists, so it is dropped from every profile.',
+ 'settings.adminToolsMcpFailed': 'MCP reload failed: {error}',
+ 'settings.adminToolsMcpTools': { one: '{n} MCP tool', other: '{n} MCP tools' },
+ 'settings.adminToolsProviders': { one: '{n} context provider', other: '{n} context providers' },
}
diff --git a/webclient/src/i18n/messages/ru.js b/webclient/src/i18n/messages/ru.js
index 6416c98..177f34c 100644
--- a/webclient/src/i18n/messages/ru.js
+++ b/webclient/src/i18n/messages/ru.js
@@ -8,6 +8,30 @@
// общая лексика, повторяется везде, где смысл тот же
'common.error': 'Что-то пошло не так',
+ 'common.menu': 'Меню',
+ 'common.save': 'Сохранить',
+ 'common.cancel': 'Отмена',
+ 'common.create': 'Создать',
+ 'common.add': 'Добавить',
+ 'common.done': 'Готово',
+ 'common.never': 'Никогда',
+ 'common.always': 'Всегда',
+ 'common.you': 'Вы',
+ 'common.revoke': 'Отозвать',
+ 'common.copyToClipboard': 'Скопировать в буфер обмена',
+
+ // колонки таблиц
+ 'col.name': 'Название',
+ 'col.key': 'Ключ',
+ 'col.created': 'Создан',
+ 'col.lastUsed': 'Последнее использование',
+
+ // диалоги подтверждения
+ 'confirm.revokeKey': 'Отозвать API-ключ «{name}»? Это нельзя отменить.',
+ 'confirm.revokeTarget': 'Отозвать цель Synapse «{target}»? Это нельзя отменить.',
+ 'confirm.mcpResetAdmin': 'Сбросить ключ для «{server}» на общий по умолчанию?',
+ 'confirm.mcpReset':
+ 'Удалить ваш ключ для «{server}»? Сервер перестанет быть подключённым для вас.',
// сам аккаунт
'profile.languageAuto': 'Авто (из аккаунта)',
@@ -19,12 +43,221 @@
// всплывающие сообщения — заголовок, иногда строка текста под ним
'toast.languageSaved': 'Язык сохранён',
+ 'toast.keyCreateFailed': 'Не удалось создать ключ',
+ 'toast.keyRevoked': 'API-ключ отозван',
+ 'toast.keyRevokeFailed': 'Не удалось отозвать ключ',
+ 'toast.notificationsDenied': 'Разрешение не выдано',
+ 'toast.notificationsAllowApp': 'Разрешите уведомления в системных настройках приложения.',
+ 'toast.notificationsAllowBrowser': 'Разрешите уведомления в настройках сайта в браузере.',
+ 'toast.notificationsUnavailable': 'Push недоступен',
+ 'toast.notificationsUnavailableText': 'Сервер сейчас не принимает подписки на push.',
+ 'toast.notificationsFailed': 'Ошибка push',
+ 'toast.mcpKeySaved': 'Ключ сохранён для {server}',
+ 'toast.mcpKeySaveFailed': 'Не удалось сохранить ключ',
+ 'toast.mcpKeyReset': 'Ключ сброшен для {server}',
+ 'toast.mcpKeyResetFailed': 'Не удалось сбросить ключ',
+ 'toast.synapseTargetAdded': 'Цель добавлена',
+ 'toast.synapseTargetAddFailed': 'Не удалось добавить цель',
+ 'toast.synapseTargetRevoked': 'Цель отозвана',
+ 'toast.synapseTargetRevokeFailed': 'Не удалось отозвать цель',
+ 'toast.reactionsLoadFailed': 'Не удалось загрузить настройки реакций',
+ 'toast.saveFailed': 'Не удалось сохранить',
+ 'toast.instructionsSaved': 'Инструкции сохранены',
+ 'toast.instructionsFailed': 'Не удалось сохранить инструкции',
+ 'toast.toolsReloaded': {
+ one: 'Перезагружен {n} инструмент',
+ few: 'Перезагружено {n} инструмента',
+ many: 'Перезагружено {n} инструментов',
+ },
+ 'toast.toolsReloadErrors': 'Перезагрузка завершилась ошибками — смотрите панель',
+ 'toast.toolsReloadFailed': 'Не удалось перезагрузить инструменты',
- // настройки — строка языка на вкладке «Аккаунт», первая область, ушедшая с
- // жёстко прописанного английского
+ // настройки — оболочка, дальше по блоку на вкладку
+ 'settings.title': 'Настройки',
+ 'settings.sectionsLabel': 'Разделы настроек',
+ 'settings.tabApp': 'Приложение',
+ 'settings.tabAccount': 'Аккаунт',
+ 'settings.tabNotifications': 'Уведомления',
+ 'settings.tabSynapse': 'Synapse',
+ 'settings.tabMcp': 'MCP',
+
'settings.languageToolbar': 'Язык интерфейса',
'settings.languageDescription':
'По умолчанию Navi следует языку вашего аккаунта gnexus-auth; выбор здесь перекрывает его только для Navi. «Авто» возвращает язык аккаунта.',
'settings.languageDescriptionOpen':
'Вход в этот Navi отключён, поэтому язык сохраняется только в этом браузере.',
+
+ // настройки — «Приложение»
+ 'settings.appTitle': 'Приложение',
+ 'settings.appDescription':
+ 'Navi — веб-приложение, но оно может жить на устройстве как обычное: либо установленное в этот браузер, либо как Android-приложение, которое оборачивает тот же интерфейс. Это не исключающие друг друга варианты: на телефоне стоит держать Android-приложение, а везде ещё — установленное веб-приложение.',
+ 'settings.appInstallTitle': 'Установка в этот браузер',
+ 'settings.appInstallText':
+ 'Установка помещает Navi в отдельное окно без адресной строки, добавляет значок в док или на домашний экран и кэширует интерфейс, поэтому приложение открывается мгновенно и показывает сессии даже без сети. Она же включает уведомления о готовых ответах — в том числе когда вкладка закрыта.',
+ 'settings.appAndroidAlert':
+ 'Вы читаете это внутри Android-приложения, и оно уже установлено. Чтобы обновить его, скачайте APK ниже и установите поверх старого.',
+ 'settings.appInstalledAlert':
+ 'Установлено — Navi работает в отдельном окне, а не во вкладке браузера.',
+ 'settings.appInstallAction': 'Установить',
+ 'settings.appInstallHint': 'Браузер попросит подтвердить.',
+ 'settings.appBrowserSafari': 'Safari:',
+ 'settings.appIosTap': 'нажмите',
+ 'settings.appIosShare': '«Поделиться», затем',
+ 'settings.appIosAddHome': 'На экран «Домой»',
+ 'settings.appIosNote':
+ 'Уведомления там требуют iOS 16.4 или новее и работают только в установленном приложении, а не во вкладке Safari.',
+ 'settings.appBrowserChrome': 'Chrome или Edge:',
+ 'settings.appChromeClick':
+ 'нажмите значок установки в правом конце адресной строки или откройте',
+ 'settings.appChromeMenu': 'меню и выберите',
+ 'settings.appChromeInstall': 'Установить Navi',
+ 'settings.appBrowserAndroid': 'Android:',
+ 'settings.appAndroidOpen': 'откройте',
+ 'settings.appAndroidMenu': 'меню и выберите',
+ 'settings.appAndroidInstall': 'Установить приложение',
+ 'settings.appAndroidAlso': '(иногда «На главный экран»)',
+ 'settings.appHttpsBefore':
+ 'Браузеры предлагают это только по HTTPS или на localhost — по обычному адресу',
+ 'settings.appHttpsAfter':
+ 'такая возможность не появляется, как и офлайн-режим с уведомлениями.',
+ 'settings.appAndroidTitle': 'Android-приложение',
+ 'settings.appAndroidText':
+ 'Тот же Navi в нативной оболочке. То, чего не даст вкладка браузера: уведомления при заблокированном телефоне — фоновая служба держит соединение открытым — плюс нативная камера и выбор фото, а ссылки открываются в обычном браузере. Сессии, профили и настройки живут на сервере, поэтому приложение и веб-клиент не расходятся.',
+ 'settings.appApkMissing': 'APK не опубликован на этом сервере — соберите его командой',
+ 'settings.appVersion': 'Версия',
+ 'settings.appSize': 'Размер',
+ 'settings.appBuilt': 'Сборка',
+ 'settings.appDownloadApk': 'Скачать APK',
+ 'settings.appAndroidStep1':
+ 'Откройте скачанный файл на телефоне. Android спросит, разрешить ли установку приложений из этого источника, — разрешите.',
+ 'settings.appAndroidStep2': 'При первом запуске приложение спросит адрес Navi. Введите',
+ 'settings.appAndroidStep2Tail':
+ ', затем войдите через браузер, когда он откроется; приложение вернётся само.',
+ 'settings.appAndroidStep3':
+ 'Более новый APK устанавливается поверх старого и сохраняет настройки. Удалять прежнюю версию пришлось бы только при смене ключа подписи.',
+
+ // настройки — API-ключи
+ 'settings.keysTitle': 'API-ключи',
+ 'settings.keysCreate': 'Новый ключ',
+ 'settings.keysDescription':
+ 'API-ключи позволяют безголовым клиентам (скриптам, голосовым помощникам, устройствам IoT) аутентифицироваться без OAuth в браузере.',
+ 'settings.keysNoAuthWarning':
+ 'Авторизация сейчас отключена. Любые созданные здесь ключи привязаны к локальному анонимному пользователю и дают полный доступ к этому экземпляру Navi.',
+ 'settings.keysLoading': 'Загрузка API-ключей',
+ 'settings.keysEmpty': 'API-ключей пока нет. Создайте первый, чтобы начать.',
+ 'settings.keyCreateTitle': 'Создание API-ключа',
+ 'settings.keyNamePlaceholder': 'например, Умные часы, Скрипт автоматизации',
+ 'settings.keyCreatedTitle': 'API-ключ создан',
+ 'settings.keyCopyNow': 'Скопируйте этот токен сейчас — он',
+ 'settings.keyCopyNowStrong': 'больше не будет показан',
+
+ // настройки — уведомления
+ 'settings.notificationsTitle': 'Уведомления',
+ 'settings.notificationsBridgeText':
+ 'Приложение держит соединение открытым в фоне и показывает системное уведомление, когда ответ готов, а Navi свёрнут. Если закрыть само приложение, уведомления прекратятся.',
+ 'settings.notificationsWebText':
+ 'Push-уведомления позволяют Navi сообщить вам о готовом ответе — даже когда вкладка закрыта. На каждый готовый ответ приходит одно уведомление, но только если ни одно окно Navi не открыто на этой сессии.',
+ 'settings.notificationsUnsupported':
+ 'Этот браузер не поддерживает web push (нужны HTTPS, service worker и Push API). Попробуйте Chrome или свежий Firefox.',
+ 'settings.notificationsRowTitle': 'Push-уведомления',
+ 'settings.notificationsOnBackground':
+ 'Включены — фоновый режим держит соединение, пока приложение свёрнуто.',
+ 'settings.notificationsOn': 'Включены — это устройство подписано.',
+ 'settings.notificationsBlockedApp': 'Заблокированы в системных настройках приложения.',
+ 'settings.notificationsBlockedBrowser': 'Заблокированы в настройках сайта в браузере.',
+ 'settings.notificationsOff': 'Выключены.',
+
+ // настройки — MCP-серверы
+ 'settings.mcpTitle': 'MCP-серверы',
+ 'settings.mcpDescriptionAdmin':
+ 'Всё, что подключено к вашим профилям. Сервер со слотом личного ключа работает под вашим ключом — не задавайте свой, и он продолжит использовать общий ключ, настроенный на этом Navi.',
+ 'settings.mcpDescription':
+ 'Всё, что подключено к вашим профилям. Серверу со слотом личного ключа нужен',
+ 'settings.mcpDescriptionStrong': 'ваш собственный ключ',
+ 'settings.mcpDescriptionTail':
+ '— пока вы его не сохраните, для вас он не подключён вовсе. Серверы без слота общие и доступны всегда.',
+ 'settings.mcpLoading': 'Загрузка MCP-серверов',
+ 'settings.mcpEmpty': 'К этому Navi не подключено ни одного MCP-сервера ни в одном профиле.',
+ 'settings.mcpKeySet':
+ 'Личный ключ сохранён {date} — используется вместо общего ({location}).',
+ 'settings.mcpKeyAbsentAdmin':
+ 'Личного ключа нет — используется общий ключ из конфигурации ({location}).',
+ 'settings.mcpKeyAbsent': 'Личного ключа нет — этот сервер для вас не подключён.',
+ 'settings.mcpNoSlot': 'Слота личного ключа нет — работает на общей конфигурации.',
+ 'settings.mcpKeyPlaceholder': '••• сохранён — введите новый ключ, чтобы заменить',
+ 'settings.mcpKeyPlaceholderNew': 'Вставьте ваш ключ',
+ 'settings.mcpReset': 'Сбросить на общий',
+
+ // настройки — доставки Synapse
+ 'settings.synapseTargetsTitle': 'Доставки Synapse',
+ 'settings.synapseTargetAdd': 'Добавить цель',
+ 'settings.synapseTargetsDescription':
+ 'Доставки из хаба уведомлений GNEXUS Synapse приходят на вебхук этого Navi и проверяются по личному секрету цели. Создайте цель в админ-панели Synapse, затем зарегистрируйте её здесь, чтобы Navi принимал её доставки.',
+ 'settings.synapseTargetsLoading': 'Загрузка целей',
+ 'settings.synapseTargetsEmpty':
+ 'Ни одной цели Synapse не зарегистрировано. Добавьте её, чтобы начать принимать доставки.',
+ 'settings.synapseTargetColRef': 'Идентификатор цели',
+ 'settings.synapseTargetAddTitle': 'Добавление цели Synapse',
+ 'settings.synapseTargetRefPlaceholder': 'например, navi-main (как в админ-панели Synapse)',
+ 'settings.synapseTargetSecret': 'Секрет доставки',
+ 'settings.synapseTargetSecretPlaceholder': 's2s-секрет цели Synapse',
+ 'settings.synapseTargetHintBefore': 'В админ-панели Synapse создайте цель, указывающую на',
+ 'settings.synapseTargetHintAfter':
+ 'этого Navi, и вставьте её секрет здесь. Navi использует его, чтобы убедиться, что доставки действительно пришли из Synapse.',
+
+ // настройки — реакции Synapse
+ 'settings.reactionsTitle': 'Реакции Synapse',
+ 'settings.reactionsDescription':
+ 'Когда приходит событие GNEXUS Synapse, Navi может отреагировать сам: диспетчер выбирает профиль по вашим инструкциям, работа идёт в скрытой служебной сессии, а посмотреть её потом можно через переключатель «Служебные сессии» в боковой панели.',
+ 'settings.reactionsNotConfigured':
+ 'Интеграция с Synapse не настроена на этом Navi: варианты, отправляющие события в Synapse, отключены. Доставка только в приложение продолжает работать.',
+ 'settings.reactionsLoading': 'Загрузка настроек реакций',
+ 'settings.reactionsEnable': 'Включить реакции',
+ 'settings.reactionsOn': 'Пришедшие события запускают фоновый прогон реакции.',
+ 'settings.reactionsOff': 'События принимаются и пишутся в журнал, но Navi ничего не делает.',
+ 'settings.reactionsNotify': 'Сообщать о завершённых реакциях',
+ 'settings.reactionsOnlyFailures': 'Только сбои',
+ 'settings.reactionsPushAlways': 'Push после каждого завершённого прогона.',
+ 'settings.reactionsPushFailures': 'Push только когда прогон закончился ошибками.',
+ 'settings.reactionsPushNever': 'Не отправлять push о завершении.',
+ 'settings.reactionsTarget': 'Куда идут уведомления',
+ 'settings.reactionsTargetNote':
+ 'Относится к инструменту notify в Navi и к push о завершении реакций.',
+ 'settings.reactionsTargetApp': 'Только в приложение',
+ 'settings.reactionsTargetAppSynapse': 'Приложение + Synapse',
+ 'settings.reactionsTargetSynapse': 'Только Synapse',
+ 'settings.reactionsInstructions': 'Инструкции для реакций',
+ 'settings.reactionsInstructionsNote':
+ 'Указания, по которым диспетчер выбирает профиль и строит задачу. Их можете править и вы, и Navi; каждое изменение попадает в историю ниже.',
+ 'settings.reactionsInstructionsPlaceholder':
+ 'например, «События todo из gntodo идут в профиль developer; спам-письма — никуда».',
+ 'settings.reactionsInstructionsSave': 'Сохранить инструкции',
+ 'settings.reactionsHistory': 'История правок',
+
+ // настройки — инструменты (только для админа)
+ 'settings.adminToolsTitle': 'Инструменты',
+ 'settings.adminToolsReloading': 'Перезагрузка…',
+ 'settings.adminToolsReload': 'Перезагрузить инструменты',
+ 'settings.adminToolsDescriptionBefore': 'Перечитывает инструменты из',
+ 'settings.adminToolsDescriptionAfter':
+ ', провайдеры контекста и переподключает все MCP-серверы — без перезапуска и простоя. Новые инструменты можно вызывать со следующего сообщения в любой сессии. Только для админа: подменяет набор инструментов, с которым работают все профили.',
+ 'settings.adminToolsLoaded': {
+ one: 'загружено {n} инструмент',
+ few: 'загружено {n} инструмента',
+ many: 'загружено {n} инструментов',
+ },
+ 'settings.adminToolsRegistry': '({n} в реестре)',
+ 'settings.adminToolsMissing':
+ 'называет {names} — такого инструмента нет, он исключён из всех профилей.',
+ 'settings.adminToolsMcpFailed': 'Переподключение MCP не удалось: {error}',
+ 'settings.adminToolsMcpTools': {
+ one: '{n} MCP-инструмент',
+ few: '{n} MCP-инструмента',
+ many: '{n} MCP-инструментов',
+ },
+ 'settings.adminToolsProviders': {
+ one: '{n} провайдер контекста',
+ few: '{n} провайдера контекста',
+ many: '{n} провайдеров контекста',
+ },
}
diff --git a/webclient/src/i18n/messages/uk.js b/webclient/src/i18n/messages/uk.js
index e94e021..8229f22 100644
--- a/webclient/src/i18n/messages/uk.js
+++ b/webclient/src/i18n/messages/uk.js
@@ -8,6 +8,30 @@
// спільна лексика, повторюється всюди, де сенс той самий
'common.error': 'Щось пішло не так',
+ 'common.menu': 'Меню',
+ 'common.save': 'Зберегти',
+ 'common.cancel': 'Скасувати',
+ 'common.create': 'Створити',
+ 'common.add': 'Додати',
+ 'common.done': 'Готово',
+ 'common.never': 'Ніколи',
+ 'common.always': 'Завжди',
+ 'common.you': 'Ви',
+ 'common.revoke': 'Відкликати',
+ 'common.copyToClipboard': 'Копіювати в буфер обміну',
+
+ // колонки таблиць
+ 'col.name': 'Назва',
+ 'col.key': 'Ключ',
+ 'col.created': 'Створено',
+ 'col.lastUsed': 'Останнє використання',
+
+ // діалоги підтвердження
+ 'confirm.revokeKey': 'Відкликати API-ключ «{name}»? Це не можна скасувати.',
+ 'confirm.revokeTarget': 'Відкликати ціль Synapse «{target}»? Це не можна скасувати.',
+ 'confirm.mcpResetAdmin': 'Скинути ключ для «{server}» на спільний за замовчуванням?',
+ 'confirm.mcpReset':
+ 'Видалити ваш ключ для «{server}»? Сервер перестане бути підключеним для вас.',
// сам акаунт
'profile.languageAuto': 'Авто (з акаунта)',
@@ -19,12 +43,221 @@
// сповіщення-тости — заголовок, іноді рядок тексту під ним
'toast.languageSaved': 'Мову збережено',
+ 'toast.keyCreateFailed': 'Не вдалося створити ключ',
+ 'toast.keyRevoked': 'API-ключ відкликано',
+ 'toast.keyRevokeFailed': 'Не вдалося відкликати ключ',
+ 'toast.notificationsDenied': 'Дозвіл не надано',
+ 'toast.notificationsAllowApp': 'Дозвольте сповіщення в системних налаштуваннях застосунку.',
+ 'toast.notificationsAllowBrowser': 'Дозвольте сповіщення в налаштуваннях сайту в браузері.',
+ 'toast.notificationsUnavailable': 'Push недоступний',
+ 'toast.notificationsUnavailableText': 'Сервер зараз не приймає підписки на push.',
+ 'toast.notificationsFailed': 'Помилка push',
+ 'toast.mcpKeySaved': 'Ключ збережено для {server}',
+ 'toast.mcpKeySaveFailed': 'Не вдалося зберегти ключ',
+ 'toast.mcpKeyReset': 'Ключ скинуто для {server}',
+ 'toast.mcpKeyResetFailed': 'Не вдалося скинути ключ',
+ 'toast.synapseTargetAdded': 'Ціль додано',
+ 'toast.synapseTargetAddFailed': 'Не вдалося додати ціль',
+ 'toast.synapseTargetRevoked': 'Ціль відкликано',
+ 'toast.synapseTargetRevokeFailed': 'Не вдалося відкликати ціль',
+ 'toast.reactionsLoadFailed': 'Не вдалося завантажити налаштування реакцій',
+ 'toast.saveFailed': 'Не вдалося зберегти',
+ 'toast.instructionsSaved': 'Інструкції збережено',
+ 'toast.instructionsFailed': 'Не вдалося зберегти інструкції',
+ 'toast.toolsReloaded': {
+ one: 'Перезавантажено {n} інструмент',
+ few: 'Перезавантажено {n} інструменти',
+ many: 'Перезавантажено {n} інструментів',
+ },
+ 'toast.toolsReloadErrors': 'Перезавантаження завершилося помилками — дивіться панель',
+ 'toast.toolsReloadFailed': 'Не вдалося перезавантажити інструменти',
- // налаштування — рядок мови на вкладці «Акаунт», перша область, що пішла з
- // жорстко прописаної англійської
+ // налаштування — оболонка, далі по блоку на вкладку
+ 'settings.title': 'Налаштування',
+ 'settings.sectionsLabel': 'Розділи налаштувань',
+ 'settings.tabApp': 'Застосунок',
+ 'settings.tabAccount': 'Акаунт',
+ 'settings.tabNotifications': 'Сповіщення',
+ 'settings.tabSynapse': 'Synapse',
+ 'settings.tabMcp': 'MCP',
+
'settings.languageToolbar': 'Мова інтерфейсу',
'settings.languageDescription':
'Стандартно Navi наслідує мову вашого акаунта gnexus-auth; вибір тут перекриває її лише для Navi. «Авто» повертає мову акаунта.',
'settings.languageDescriptionOpen':
'Вхід у цей Navi вимкнено, тому мова зберігається лише в цьому браузері.',
+
+ // налаштування — «Застосунок»
+ 'settings.appTitle': 'Застосунок',
+ 'settings.appDescription':
+ 'Navi — це вебзастосунок, але він може жити на пристрої як звичайний: або встановлений у цей браузер, або як Android-застосунок, що обгортає той самий інтерфейс. Це не взаємовиключні варіанти: на телефоні варто мати Android-застосунок, а всюди інде — встановлений вебзастосунок.',
+ 'settings.appInstallTitle': 'Встановлення в цей браузер',
+ 'settings.appInstallText':
+ 'Встановлення розміщує Navi в окремому вікні без адресного рядка, додає значок у док або на початковий екран і кешує інтерфейс, тож застосунок відкривається миттєво й показує сесії навіть без мережі. Воно ж вмикає сповіщення про готові відповіді — зокрема коли вкладку закрито.',
+ 'settings.appAndroidAlert':
+ 'Ви читаєте це всередині Android-застосунку, і він уже встановлений. Щоб оновити його, завантажте APK нижче й установіть поверх старого.',
+ 'settings.appInstalledAlert':
+ 'Встановлено — Navi працює в окремому вікні, а не у вкладці браузера.',
+ 'settings.appInstallAction': 'Встановити',
+ 'settings.appInstallHint': 'Браузер попросить підтвердити.',
+ 'settings.appBrowserSafari': 'Safari:',
+ 'settings.appIosTap': 'натисніть',
+ 'settings.appIosShare': '«Поділитися», потім',
+ 'settings.appIosAddHome': 'На початковий екран',
+ 'settings.appIosNote':
+ 'Сповіщення там потребують iOS 16.4 або новішої і працюють лише у встановленому застосунку, а не у вкладці Safari.',
+ 'settings.appBrowserChrome': 'Chrome або Edge:',
+ 'settings.appChromeClick':
+ 'натисніть значок встановлення в правому кінці адресного рядка або відкрийте',
+ 'settings.appChromeMenu': 'меню і виберіть',
+ 'settings.appChromeInstall': 'Установити Navi',
+ 'settings.appBrowserAndroid': 'Android:',
+ 'settings.appAndroidOpen': 'відкрийте',
+ 'settings.appAndroidMenu': 'меню і виберіть',
+ 'settings.appAndroidInstall': 'Установити застосунок',
+ 'settings.appAndroidAlso': '(іноді «На головний екран»)',
+ 'settings.appHttpsBefore':
+ 'Браузери пропонують це лише через HTTPS або на localhost — за звичайною адресою',
+ 'settings.appHttpsAfter':
+ 'такої можливості не з’явиться, як і офлайн-режиму зі сповіщеннями.',
+ 'settings.appAndroidTitle': 'Android-застосунок',
+ 'settings.appAndroidText':
+ 'Той самий Navi у нативній оболонці. Те, чого не дасть вкладка браузера: сповіщення при заблокованому телефоні — фонова служба тримає з’єднання відкритим — плюс нативна камера й вибір фото, а посилання відкриваються у звичайному браузері. Сесії, профілі та налаштування живуть на сервері, тож застосунок і вебклієнт не розходяться.',
+ 'settings.appApkMissing': 'APK не опубліковано на цьому сервері — зберіть його командою',
+ 'settings.appVersion': 'Версія',
+ 'settings.appSize': 'Розмір',
+ 'settings.appBuilt': 'Збірка',
+ 'settings.appDownloadApk': 'Завантажити APK',
+ 'settings.appAndroidStep1':
+ 'Відкрийте завантажений файл на телефоні. Android запитає, чи дозволити встановлення застосунків із цього джерела, — дозвольте.',
+ 'settings.appAndroidStep2': 'При першому запуску застосунок запитає адресу Navi. Введіть',
+ 'settings.appAndroidStep2Tail':
+ ', потім увійдіть через браузер, коли він відкриється; застосунок повернеться сам.',
+ 'settings.appAndroidStep3':
+ 'Новіший APK встановлюється поверх старого й зберігає налаштування. Видаляти попередню версію довелося б лише в разі зміни ключа підпису.',
+
+ // налаштування — API-ключі
+ 'settings.keysTitle': 'API-ключі',
+ 'settings.keysCreate': 'Новий ключ',
+ 'settings.keysDescription':
+ 'API-ключі дозволяють безголовим клієнтам (скриптам, голосовим помічникам, пристроям IoT) автентифікуватися без OAuth у браузері.',
+ 'settings.keysNoAuthWarning':
+ 'Авторизацію зараз вимкнено. Будь-які створені тут ключі прив’язані до локального анонімного користувача й дають повний доступ до цього примірника Navi.',
+ 'settings.keysLoading': 'Завантаження API-ключів',
+ 'settings.keysEmpty': 'API-ключів ще немає. Створіть перший, щоб почати.',
+ 'settings.keyCreateTitle': 'Створення API-ключа',
+ 'settings.keyNamePlaceholder': 'наприклад, Розумний годинник, Скрипт автоматизації',
+ 'settings.keyCreatedTitle': 'API-ключ створено',
+ 'settings.keyCopyNow': 'Скопіюйте цей токен зараз — він',
+ 'settings.keyCopyNowStrong': 'більше не буде показаний',
+
+ // налаштування — сповіщення
+ 'settings.notificationsTitle': 'Сповіщення',
+ 'settings.notificationsBridgeText':
+ 'Застосунок тримає з’єднання відкритим у фоні й показує системне сповіщення, коли відповідь готова, а Navi згорнуто. Якщо закрити сам застосунок, сповіщення припиняться.',
+ 'settings.notificationsWebText':
+ 'Push-сповіщення дають Navi змогу повідомити вас про готову відповідь — навіть коли вкладку закрито. На кожну готову відповідь надходить одне сповіщення, але лише якщо жодне вікно Navi не відкрите на цій сесії.',
+ 'settings.notificationsUnsupported':
+ 'Цей браузер не підтримує web push (потрібні HTTPS, service worker і Push API). Спробуйте Chrome або свіжий Firefox.',
+ 'settings.notificationsRowTitle': 'Push-сповіщення',
+ 'settings.notificationsOnBackground':
+ 'Увімкнено — фоновий режим тримає з’єднання, поки застосунок згорнуто.',
+ 'settings.notificationsOn': 'Увімкнено — цей пристрій підписано.',
+ 'settings.notificationsBlockedApp': 'Заблоковано в системних налаштуваннях застосунку.',
+ 'settings.notificationsBlockedBrowser': 'Заблоковано в налаштуваннях сайту в браузері.',
+ 'settings.notificationsOff': 'Вимкнено.',
+
+ // налаштування — MCP-сервери
+ 'settings.mcpTitle': 'MCP-сервери',
+ 'settings.mcpDescriptionAdmin':
+ 'Усе, що підключено до ваших профілів. Сервер зі слотом особистого ключа працює під вашим ключем — не задавайте свій, і він далі використовуватиме спільний ключ, налаштований на цьому Navi.',
+ 'settings.mcpDescription':
+ 'Усе, що підключено до ваших профілів. Серверу зі слотом особистого ключа потрібен',
+ 'settings.mcpDescriptionStrong': 'ваш власний ключ',
+ 'settings.mcpDescriptionTail':
+ '— доки ви його не збережете, для вас він не підключений узагалі. Сервери без слота спільні й доступні завжди.',
+ 'settings.mcpLoading': 'Завантаження MCP-серверів',
+ 'settings.mcpEmpty': 'До цього Navi не підключено жодного MCP-сервера в жодному профілі.',
+ 'settings.mcpKeySet':
+ 'Особистий ключ збережено {date} — використовується замість спільного ({location}).',
+ 'settings.mcpKeyAbsentAdmin':
+ 'Особистого ключа немає — використовується спільний ключ із конфігурації ({location}).',
+ 'settings.mcpKeyAbsent': 'Особистого ключа немає — цей сервер для вас не підключено.',
+ 'settings.mcpNoSlot': 'Слота особистого ключа немає — працює на спільній конфігурації.',
+ 'settings.mcpKeyPlaceholder': '••• збережено — введіть новий ключ, щоб замінити',
+ 'settings.mcpKeyPlaceholderNew': 'Вставте ваш ключ',
+ 'settings.mcpReset': 'Скинути на спільний',
+
+ // налаштування — доставки Synapse
+ 'settings.synapseTargetsTitle': 'Доставки Synapse',
+ 'settings.synapseTargetAdd': 'Додати ціль',
+ 'settings.synapseTargetsDescription':
+ 'Доставки з хаба сповіщень GNEXUS Synapse надходять на вебхук цього Navi й перевіряються за особистим секретом цілі. Створіть ціль в адмін-панелі Synapse, потім зареєструйте її тут, щоб Navi приймав її доставки.',
+ 'settings.synapseTargetsLoading': 'Завантаження цілей',
+ 'settings.synapseTargetsEmpty':
+ 'Жодної цілі Synapse не зареєстровано. Додайте її, щоб почати приймати доставки.',
+ 'settings.synapseTargetColRef': 'Ідентифікатор цілі',
+ 'settings.synapseTargetAddTitle': 'Додавання цілі Synapse',
+ 'settings.synapseTargetRefPlaceholder': 'наприклад, navi-main (як в адмін-панелі Synapse)',
+ 'settings.synapseTargetSecret': 'Секрет доставки',
+ 'settings.synapseTargetSecretPlaceholder': 's2s-секрет цілі Synapse',
+ 'settings.synapseTargetHintBefore': 'В адмін-панелі Synapse створіть ціль, що вказує на',
+ 'settings.synapseTargetHintAfter':
+ 'цього Navi, і вставте її секрет тут. Navi використовує його, щоб переконатися, що доставки справді надійшли із Synapse.',
+
+ // налаштування — реакції Synapse
+ 'settings.reactionsTitle': 'Реакції Synapse',
+ 'settings.reactionsDescription':
+ 'Коли надходить подія GNEXUS Synapse, Navi може відреагувати сам: диспетчер обирає профіль за вашими інструкціями, робота йде у прихованій службовій сесії, а переглянути її потім можна через перемикач «Службові сесії» в бічній панелі.',
+ 'settings.reactionsNotConfigured':
+ 'Інтеграцію із Synapse не налаштовано на цьому Navi: варіанти, що надсилають події в Synapse, вимкнено. Доставка лише в застосунок працює далі.',
+ 'settings.reactionsLoading': 'Завантаження налаштувань реакцій',
+ 'settings.reactionsEnable': 'Увімкнути реакції',
+ 'settings.reactionsOn': 'Події, що надійшли, запускають фоновий прогін реакції.',
+ 'settings.reactionsOff': 'Події приймаються й записуються в журнал, але Navi нічого не робить.',
+ 'settings.reactionsNotify': 'Повідомляти про завершені реакції',
+ 'settings.reactionsOnlyFailures': 'Лише збої',
+ 'settings.reactionsPushAlways': 'Push після кожного завершеного прогону.',
+ 'settings.reactionsPushFailures': 'Push лише коли прогін закінчився помилками.',
+ 'settings.reactionsPushNever': 'Не надсилати push про завершення.',
+ 'settings.reactionsTarget': 'Куди йдуть сповіщення',
+ 'settings.reactionsTargetNote':
+ 'Стосується інструмента notify у Navi та push про завершення реакцій.',
+ 'settings.reactionsTargetApp': 'Лише в застосунок',
+ 'settings.reactionsTargetAppSynapse': 'Застосунок + Synapse',
+ 'settings.reactionsTargetSynapse': 'Лише Synapse',
+ 'settings.reactionsInstructions': 'Інструкції для реакцій',
+ 'settings.reactionsInstructionsNote':
+ 'Вказівки, за якими диспетчер обирає профіль і будує задачу. Їх можете правити і ви, і Navi; кожна зміна потрапляє в історію нижче.',
+ 'settings.reactionsInstructionsPlaceholder':
+ 'наприклад, «Події todo з gntodo йдуть у профіль developer; спам-листи — нікуди».',
+ 'settings.reactionsInstructionsSave': 'Зберегти інструкції',
+ 'settings.reactionsHistory': 'Історія правок',
+
+ // налаштування — інструменти (лише для адміна)
+ 'settings.adminToolsTitle': 'Інструменти',
+ 'settings.adminToolsReloading': 'Перезавантаження…',
+ 'settings.adminToolsReload': 'Перезавантажити інструменти',
+ 'settings.adminToolsDescriptionBefore': 'Перечитує інструменти з',
+ 'settings.adminToolsDescriptionAfter':
+ ', провайдери контексту й перепідключає всі MCP-сервери — без перезапуску й простою. Нові інструменти можна викликати з наступного повідомлення в будь-якій сесії. Лише для адміна: підміняє набір інструментів, з яким працюють усі профілі.',
+ 'settings.adminToolsLoaded': {
+ one: 'завантажено {n} інструмент',
+ few: 'завантажено {n} інструменти',
+ many: 'завантажено {n} інструментів',
+ },
+ 'settings.adminToolsRegistry': '({n} у реєстрі)',
+ 'settings.adminToolsMissing':
+ 'називає {names} — такого інструменту немає, його виключено з усіх профілів.',
+ 'settings.adminToolsMcpFailed': 'Перепідключення MCP не вдалося: {error}',
+ 'settings.adminToolsMcpTools': {
+ one: '{n} MCP-інструмент',
+ few: '{n} MCP-інструменти',
+ many: '{n} MCP-інструментів',
+ },
+ 'settings.adminToolsProviders': {
+ one: '{n} провайдер контексту',
+ few: '{n} провайдери контексту',
+ many: '{n} провайдерів контексту',
+ },
}
diff --git a/webclient/tests/unit/components/settings/settingsView.test.js b/webclient/tests/unit/components/settings/settingsView.test.js
index c308acb..1ac2c4a 100644
--- a/webclient/tests/unit/components/settings/settingsView.test.js
+++ b/webclient/tests/unit/components/settings/settingsView.test.js
@@ -6,8 +6,8 @@
import SettingsView from '@/components/settings/SettingsView.vue'
// Stub every panel — the view only orchestrates tabs and mounts one at a time.
-vi.mock('@/components/settings/ApiKeysPanel.vue', () => ({
- default: { name: 'ApiKeysPanel', template: '' },
+vi.mock('@/components/settings/AccountPanel.vue', () => ({
+ default: { name: 'AccountPanel', template: '' },
}))
vi.mock('@/components/settings/NotificationSettingsPanel.vue', () => ({
default: { name: 'NotificationSettingsPanel', template: '' },