diff --git a/apps/desktop/src/i18n/catalog.ts b/apps/desktop/src/i18n/catalog.ts index f35c957076db2..4ca731d25212b 100644 --- a/apps/desktop/src/i18n/catalog.ts +++ b/apps/desktop/src/i18n/catalog.ts @@ -2,6 +2,7 @@ import { ar } from './ar' import { en } from './en' import { ja } from './ja' import type { Locale, Translations } from './types' +import { ru } from './ru' import { zh } from './zh' import { zhHant } from './zh-hant' @@ -10,5 +11,6 @@ export const TRANSLATIONS: Record = { zh, 'zh-hant': zhHant, ja, - ar + ar, + ru } diff --git a/apps/desktop/src/i18n/languages.ts b/apps/desktop/src/i18n/languages.ts index e4a1fb1e02eb4..3bf57e39b78dd 100644 --- a/apps/desktop/src/i18n/languages.ts +++ b/apps/desktop/src/i18n/languages.ts @@ -34,6 +34,12 @@ export const LOCALE_OPTIONS = [ name: 'العربية', englishName: 'Arabic', configValue: 'ar' + }, + { + id: 'ru', + name: 'Русский', + englishName: 'Russian', + configValue: 'ru' } ] as const satisfies readonly { configValue: string; englishName: string; id: Locale; name: string }[] @@ -79,7 +85,12 @@ const LOCALE_ALIASES: Record = { 'ar-eg': 'ar', ar_eg: 'ar', arabic: 'ar', - العربية: 'ar' + العربية: 'ar', + ru: 'ru', + 'ru-ru': 'ru', + ru_ru: 'ru', + russian: 'ru', + русский: 'ru' } export function isLocale(value: unknown): value is Locale { diff --git a/apps/desktop/src/i18n/ru.ts b/apps/desktop/src/i18n/ru.ts new file mode 100644 index 0000000000000..0c23b363aeaaf --- /dev/null +++ b/apps/desktop/src/i18n/ru.ts @@ -0,0 +1,3093 @@ +import { FIELD_DESCRIPTIONS, FIELD_LABELS } from '@/app/settings/constants' + +import type { Translations } from './types' + +const plural = (n: number, one: string, few: string, many: string): string => { + const mod10 = n % 10 + const mod100 = n % 100 + if (mod10 === 1 && mod100 !== 11) return one + if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return few + return many +} + +export const ru: Translations = { + common: { + apply: 'Применить', + back: 'Назад', + save: 'Сохранить', + saving: 'Сохранение…', + cancel: 'Отмена', + change: 'Изменить', + choose: 'Выбрать', + clear: 'Очистить', + close: 'Закрыть', + collapse: 'Свернуть', + confirm: 'Подтвердить', + connect: 'Подключиться', + connecting: 'Подключение', + continue: 'Продолжить', + copied: 'Скопировано', + copy: 'Копировать', + copyFailed: 'Не удалось скопировать', + delete: 'Удалить', + docs: 'Документация', + done: 'Готово', + error: 'Ошибка', + expand: 'Развернуть', + failed: 'Не удалось', + formatJson: 'Форматировать JSON', + free: 'Бесплатно', + loading: 'Загрузка…', + notSet: 'Не задано', + refresh: 'Обновить', + remove: 'Убрать', + replace: 'Заменить', + retry: 'Повторить', + run: 'Запустить', + send: 'Отправить', + set: 'Задать', + skip: 'Пропустить', + update: 'Обновить', + tryHint: term => `Попробуйте «${term}»`, + on: 'Вкл.', + off: 'Выкл.' + }, + + fileMenu: { + revealFinder: 'Показать в Finder', + revealExplorer: 'Показать в проводнике', + revealFileManager: 'Открыть папку расположения', + revealInSidebar: 'Показать в боковой панели', + copyPath: 'Копировать путь', + copyRelativePath: 'Копировать относительный путь', + rename: 'Переименовать…', + delete: 'Удалить', + renameTitle: 'Переименование', + renameLabel: 'Новое имя', + deleteTitle: name => `Удалить ${name}?`, + deleteBody: 'Файл будет перемещён в корзину, откуда его можно восстановить.', + pathCopied: 'Путь скопирован' + }, + + boot: { + ready: 'Hermes Desktop готов', + desktopBootFailedWithMessage: message => `Не удалось запустить рабочий стол: ${message}`, + steps: { + connectingGateway: 'Подключение к шлюзу рабочего стола', + loadingSettings: 'Загрузка настроек Hermes', + loadingSessions: 'Загрузка недавних сессий', + startingDesktopConnection: 'Запуск подключения к рабочему столу', + startingHermesDesktop: 'Запуск Hermes Desktop…' + }, + errors: { + backgroundExited: 'Фоновый процесс Hermes завершился.', + backgroundExitedDuringStartup: 'Фоновый процесс Hermes завершился во время запуска.', + backendStopped: 'Бэкенд остановлен', + desktopBootFailed: 'Не удалось запустить рабочий стол', + gatewayConnectionLost: 'Соединение со шлюзом потеряно', + gatewaySignInRequired: 'Требуется вход в шлюз', + ipcBridgeUnavailable: 'IPC-мост рабочего стола недоступен.' + }, + failure: { + title: 'Hermes не удалось запустить', + description: + 'Фоновый шлюз не запустился. Попробуйте шаги восстановления ниже; ваши диалоги и настройки не будут удалены.', + remoteTitle: 'Требуется повторный вход в удалённый шлюз', + remoteDescription: + 'Сеанс удалённого шлюза истёк. Войдите снова, чтобы восстановить соединение. Ваши диалоги и настройки не будут удалены.', + retry: 'Повторить', + repairInstall: 'Исправить установку', + useLocalGateway: 'Использовать локальный шлюз', + gatewaySettings: 'Настройки шлюза', + back: 'Назад', + openLogs: 'Открыть журнал', + repairHint: 'Исправление перезапустит установщик; на новой машине это может занять несколько минут.', + remoteSignInHint: signInLabel => + `Сначала выйдите из сохранённого сеанса удалённого браузера, затем откройте ${signInLabel}. Также можно переключиться на локальный шлюз, который поставляется вместе с приложением.`, + signOutAndSignIn: 'Выйти и войти снова', + remoteFailureHint: 'Проверьте URL шлюза и учётные данные в «Настройках шлюза» или переключитесь на локальный шлюз.', + hideRecentLogs: 'Скрыть недавние записи журнала', + showRecentLogs: 'Показать недавние записи журнала', + signedInTitle: 'Вход выполнен', + signedInMessage: 'Переподключение к удалённому шлюзу…', + signInIncompleteTitle: 'Вход не завершён', + signInIncompleteMessage: 'Окно входа было закрыто до завершения аутентификации.', + signInFailed: 'Не удалось войти', + signInToRemoteGateway: 'Войти в удалённый шлюз', + signInWithProvider: provider => `Войти через ${provider}`, + identityProvider: 'Ваш поставщик идентификации' + } + }, + + notifications: { + region: 'Уведомления', + hide: 'Скрыть', + show: 'Показать', + more: count => `ещё ${count} ${plural(count, 'уведомление', 'уведомления', 'уведомлений')}`, + clearAll: 'Очистить все', + dismiss: 'Закрыть уведомление', + details: 'Подробности', + copyDetail: 'Копировать подробности', + copyDetailFailed: 'Не удалось скопировать подробности уведомления', + backendOutOfDateTitle: 'Устаревшая версия бэкенда', + backendOutOfDateMessage: + 'Ваш бэкенд Hermes старше текущей сборки рабочего стола и может работать некорректно. Обновите для совместимости.', + installMethodUnsupportedTitle: 'Неподдерживаемый способ установки', + updateHermes: 'Обновить Hermes', + updateReadyTitle: 'Доступно обновление', + updateReadyMessage: count => `Доступно ${count} ${plural(count, 'новое изменение', 'новых изменения', 'новых изменений')}.`, + seeWhatsNew: 'Что нового', + errors: { + elevenLabsNeedsKey: 'Для ElevenLabs STT требуется ELEVENLABS_API_KEY.', + elevenLabsRejectedKey: 'ElevenLabs отклонил этот API-ключ (401).', + diskFull: 'Диск заполнен — освободите место и попробуйте снова.', + gatewayAuthFailed: 'Ошибка аутентификации шлюза — проверьте API_SERVER_KEY.', + methodNotAllowed: + 'Бэкенд рабочего стола отклонил запрос (405 Method Not Allowed). Попробуйте перезапустить Hermes Desktop.', + microphonePermission: 'Доступ к микрофону запрещён.', + openaiRejectedApiKey: 'OpenAI отклонил этот API-ключ.', + openaiRejectedApiKeyWithStatus: status => `OpenAI отклонил этот API-ключ (${status} invalid_api_key).`, + openaiTtsNeedsKey: 'Для OpenAI TTS требуется VOICE_TOOLS_OPENAI_KEY или OPENAI_API_KEY.' + }, + voice: { + configureSpeechToText: 'Настройте распознавание речи, чтобы использовать голосовой режим.', + couldNotStartSession: 'Не удалось начать голосовой сеанс', + microphoneAccessDenied: 'Доступ к микрофону запрещён.', + microphoneConstraintsUnsupported: 'Текущие параметры микрофона не поддерживаются этим устройством.', + microphoneFailed: 'Ошибка микрофона', + microphoneInUse: 'Микрофон используется другим приложением.', + microphonePermissionDenied: 'Доступ к микрофону запрещён.', + microphoneStartFailed: 'Не удалось начать запись с микрофона.', + microphoneUnsupported: 'Запись с микрофона не поддерживается в текущей среде.', + noMicrophone: 'Микрофон не найден.', + noSpeechDetected: 'Речь не обнаружена', + playbackFailed: 'Не удалось воспроизвести аудио', + recordingFailed: 'Не удалось записать аудио', + sayStopToEnd: phrase => `Скажите «${phrase}», чтобы завершить голосовой диалог.`, + transcriptionFailed: 'Не удалось распознать речь', + transcriptionUnavailable: 'Распознавание речи временно недоступно.', + tryRecordingAgain: 'Попробуйте записать ещё раз.', + unavailable: 'Голосовой режим недоступен' + }, + native: { + approvalTitle: 'Требуется подтверждение', + approveAction: 'Разрешить', + rejectAction: 'Отклонить', + inputTitle: 'Требуется ввод', + inputBody: 'Hermes ожидает ваш ответ.', + turnDoneTitle: 'Hermes завершил', + turnDoneBody: '', + turnErrorTitle: 'Ошибка хода', + backgroundDoneTitle: 'Фоновая задача завершена', + backgroundFailedTitle: 'Фоновая задача не выполнена', + creditsTitle: 'Кредиты' + } + }, + + remoteDisplayBanner: { + message: reason => + `Включён программный рендеринг — обнаружен удалённый дисплей (${reason}). GPU-ускорение отключено для предотвращения мерцания.` + }, + + billingBlock: { + titleNous: 'Кредиты Nous исчерпаны', + titleProvider: provider => `Кредиты исчерпаны — ${provider}`, + fallbackMessage: 'Кредиты вашего аккаунта исчерпаны. Пополните баланс, чтобы продолжить.', + openBilling: 'Открыть биллинг', + addCredits: 'Пополнить кредиты', + dismiss: 'Закрыть' + }, + + titlebar: { + hideSidebar: 'Скрыть боковую панель', + showSidebar: 'Показать боковую панель', + search: 'Поиск', + searchTitle: 'Поиск сессий, представлений и действий', + swapSidebarSides: 'Поменять местами боковые панели', + hideRightSidebar: 'Скрыть правую панель', + showRightSidebar: 'Показать правую панель', + muteHaptics: 'Отключить тактильную отдачу', + unmuteHaptics: 'Включить тактильную отдачу', + openSettings: 'Открыть настройки', + openStarmap: 'Открыть граф памяти', + enterHud: 'Режим HUD', + exitHud: 'Выйти из HUD', + layoutEditor: 'Редактор компоновки', + layoutEditorTitle: 'Редактор компоновки — ⌘-клик сбрасывает компоновку' + }, + + keybinds: { + title: 'Горячие клавиши', + subtitle: open => `Нажмите на сочетание, чтобы переназначить · ${open} снова открывает эту панель.`, + search: 'Поиск сочетаний…', + rebind: 'Переназначить', + reset: 'Сбросить по умолчанию', + resetAll: 'Сбросить все', + pressKey: 'Нажмите клавишу…', + set: 'задать', + conflictWith: label => `Также назначено на «${label}»`, + categories: { + composer: 'Композер', + profiles: 'профили: мульти-агенты', + session: 'Сессия', + navigation: 'Навигация', + view: 'Вид' + }, + actions: { + 'keybinds.openPanel': 'Открыть горячие клавиши', + 'nav.commandPalette': 'Открыть палитру команд', + 'nav.commandCenter': 'Открыть центр управления', + 'nav.settings': 'Открыть настройки', + 'nav.profiles': 'Открыть профили', + 'nav.skills': 'Открыть скиллы', + 'nav.messaging': 'Открыть сообщения', + 'nav.artifacts': 'Открыть артефакты', + 'nav.cron': 'Открыть запланированные задачи', + 'nav.agents': 'Открыть агентов', + 'session.new': 'Новая сессия', + 'session.newTab': 'Новая вкладка сессии', + 'session.newWindow': 'Новое окно', + 'session.next': 'Следующая сессия', + 'session.prev': 'Предыдущая сессия', + 'session.slot.1': 'Переключиться на недавнюю сессию 1', + 'session.slot.2': 'Переключиться на недавнюю сессию 2', + 'session.slot.3': 'Переключиться на недавнюю сессию 3', + 'session.slot.4': 'Переключиться на недавнюю сессию 4', + 'session.slot.5': 'Переключиться на недавнюю сессию 5', + 'session.slot.6': 'Переключиться на недавнюю сессию 6', + 'session.slot.7': 'Переключиться на недавнюю сессию 7', + 'session.slot.8': 'Переключиться на недавнюю сессию 8', + 'session.slot.9': 'Переключиться на недавнюю сессию 9', + 'session.focusSearch': 'Поиск по сессиям', + 'session.togglePin': 'Закрепить / открепить текущую сессию', + 'workspace.newWorktree': 'Новое рабочее дерево', + 'workspace.openFolder': 'Открыть папку как проект', + 'composer.focus': 'Фокус на композер', + 'composer.modelPicker': 'Открыть выбор модели', + 'composer.voice': 'Начать / остановить голосовой диалог', + 'view.toggleSidebar': 'Переключить боковую панель сессий', + 'view.toggleRightSidebar': 'Переключить файловый браузер', + 'view.toggleReview': 'Переключить панель ревью', + 'view.toggleStatusbar': 'Переключить строку состояния', + 'view.showFiles': 'Показать файловый браузер', + 'view.toggleHud': 'Переключить режим HUD', + 'hud.snapToPointer': 'Переместить HUD к курсору (глобально, когда HUD открыт)', + 'view.showTerminal': 'Переключить терминал', + 'view.newTerminal': 'Новый терминал', + 'view.nextTerminal': 'Следующий терминал', + 'view.prevTerminal': 'Предыдущий терминал', + 'view.closeTerminal': 'Закрыть терминал', + 'view.terminalSelection': 'Отправить выделение из терминала в композер', + 'view.terminalCopy': 'Копировать выделение из терминала', + 'view.terminalPaste': 'Вставить в терминал', + 'view.closeTab': 'Закрыть вкладку', + 'view.reopenTab': 'Открыть заново закрытую вкладку', + 'view.flipPanes': 'Поменять местами боковые панели', + 'view.findInPage': 'Найти на странице', + 'view.findNext': 'Найти следующее совпадение', + 'view.findPrevious': 'Найти предыдущее совпадение', + 'appearance.toggleMode': 'Переключить светлую / тёмную тему', + 'profile.default': 'Переключиться на профиль по умолчанию', + 'profile.switch.1': 'Переключиться на профиль 1', + 'profile.switch.2': 'Переключиться на профиль 2', + 'profile.switch.3': 'Переключиться на профиль 3', + 'profile.switch.4': 'Переключиться на профиль 4', + 'profile.switch.5': 'Переключиться на профиль 5', + 'profile.switch.6': 'Переключиться на профиль 6', + 'profile.switch.7': 'Переключиться на профиль 7', + 'profile.switch.8': 'Переключиться на профиль 8', + 'profile.switch.9': 'Переключиться на профиль 9', + 'profile.switch.10': 'Переключиться на профиль 10', + 'profile.switch.11': 'Переключиться на профиль 11', + 'profile.switch.12': 'Переключиться на профиль 12', + 'profile.switch.13': 'Переключиться на профиль 13', + 'profile.switch.14': 'Переключиться на профиль 14', + 'profile.switch.15': 'Переключиться на профиль 15', + 'profile.switch.16': 'Переключиться на профиль 16', + 'profile.switch.17': 'Переключиться на профиль 17', + 'profile.switch.18': 'Переключиться на профиль 18', + 'profile.next': 'Следующий профиль', + 'profile.prev': 'Предыдущий профиль', + 'profile.toggleAll': 'Переключить режим всех профилей', + 'profile.create': 'Создать профиль', + 'composer.send': 'Отправить сообщение', + 'composer.newline': 'Вставить новую строку', + 'composer.steer': 'Скорректировать текущий ход', + 'composer.queue': 'Поставить сообщение в очередь', + 'composer.sendQueued': 'Отправить следующий ход из очереди', + 'composer.mention': 'Ссылки на файлы, папки, URL', + 'composer.slash': 'Палитра slash-команд', + 'composer.help': 'Быстрая справка', + 'composer.history': 'Переключение окна / истории', + 'composer.cancel': 'Закрыть окно · отменить выполнение' + } + }, + + findInPage: { + next: 'Следующее совпадение', + previous: 'Предыдущее совпадение' + }, + + language: { + label: 'Язык', + description: 'Выберите язык интерфейса приложения.', + saving: 'Сохранение языка…', + saveError: 'Не удалось сменить язык', + switchTo: 'Сменить язык', + searchPlaceholder: 'Поиск языков…', + noResults: 'Языки не найдены' + }, + + settings: { + closeSettings: 'Закрыть настройки', + exportConfig: 'Экспортировать конфигурацию', + importConfig: 'Импортировать конфигурацию', + resetToDefaults: 'Сбросить к значениям по умолчанию', + resetConfirm: 'Сбросить все настройки к значениям Hermes по умолчанию?', + exportFailed: 'Не удалось экспортировать', + resetFailed: 'Не удалось сбросить', + nav: { + providers: 'Провайдеры', + providerAccounts: 'Аккаунты', + providerApiKeys: 'API-ключи', + providerCustomEndpoints: "Свои endpoint'ы", + gateway: 'Шлюз', + apiKeys: 'Инструменты и ключи', + keybinds: 'Горячие клавиши', + keysTools: 'Инструменты', + keysSettings: 'Настройки', + mcp: 'MCP', + archivedChats: 'Архив чатов', + about: 'О программе', + billing: 'Оплата', + notifications: 'Уведомления', + plugins: 'Плагины' + }, + plugins: { + title: 'Плагины приложения', + blurb: 'Входят в комплект или добавлены в папку desktop-plugins. Отключение выгружает плагин на ходу.', + count: n => `${n} установлено`, + openFolder: 'Открыть папку плагинов', + rescan: 'Пересканировать', + reveal: 'Показать в файловом менеджере', + enable: 'Включить', + disable: 'Отключить', + failed: 'ошибка', + empty: 'Плагины приложения пока не установлены.', + kinds: { bundled: 'встроенный', disk: 'на диске', runtime: 'среда выполнения' }, + agent: { + title: 'Плагины агента', + blurb: + 'Работают в бэкенде Hermes — инструменты, скиллы, MCP-серверы, хуки и slash-команды. Переносимые из них — пакеты Agent Plugins (скиллы + MCP-бандлы, которые работают и в других агентах). Переключатели применяются к новым сессиям.', + empty: 'Плагины агента пока не установлены.', + loadFailed: 'Не удалось загрузить плагины агента', + portable: 'переносимый', + search: 'Поиск плагинов…', + noMatches: 'Плагинов по вашему запросу нет.', + toggleFailed: (name: string) => `Не удалось переключить ${name}`, + updateBackendToManage: 'Обновите бэкенд Hermes, чтобы управлять этим плагином из приложения.', + sources: { bundled: 'встроенный', user: 'пользователь', git: 'git', project: 'проект', entrypoint: 'pip' } + } + }, + notifications: { + title: 'Уведомления', + intro: 'Системные уведомления (не всплывающие внутри приложения). Для каждого устройства отдельно.', + enableAll: 'Включить уведомления', + enableAllDesc: '«Выкл.» отключает все уведомления ниже.', + focusedHint: 'Оповещения о завершении срабатывают только когда Hermes в фоне.', + kinds: { + approval: { + label: 'Требуется подтверждение', + description: 'Команда ожидает вашего одобрения или отказа.' + }, + input: { + label: 'Требуется ввод', + description: 'Hermes задал вопрос или ему нужен пароль либо секрет.' + }, + turnDone: { + label: 'Ответ готов', + description: 'Ход завершился, пока Hermes был в фоне.' + }, + turnError: { + label: 'Ошибка хода', + description: 'Ошибки фоновых ходов.' + }, + backgroundDone: { + label: 'Фоновая задача завершена', + description: 'Фоновая команда терминала завершилась.' + }, + credits: { + label: 'Оповещения о кредитах', + description: 'Доступ к кредитам приостановлен или восстановлен.' + }, + plugin: { + label: 'Уведомления плагинов', + description: 'Плагин приложения отправил уведомление, пока Hermes был в фоне.' + } + }, + test: 'Отправить тестовое уведомление', + testTitle: 'Hermes', + testBody: 'Уведомления работают.', + testSent: 'Тест отправлен. Если ничего не появилось, проверьте разрешения уведомлений в ОС и режим «Не беспокоить».', + testUnsupported: 'Эта система не поддерживает нативные уведомления.', + completionSoundTitle: 'Звук завершения', + completionSoundDesc: 'Звучит, когда ход агента завершается. Выберите вариант и прослушайте его здесь.', + completionSoundPreview: 'Предпросмотр' + }, + sections: { + model: 'Модель', + chat: 'Чат', + appearance: 'Внешний вид', + workspace: 'Рабочая область', + safety: 'Безопасность', + memory: 'Память и контекст', + voice: 'Голос', + advanced: 'Дополнительно' + }, + searchPlaceholder: { + about: 'О Hermes Desktop', + config: 'Поиск настроек...', + gateway: 'Подключение к gateway...', + keys: 'Поиск API-ключей...', + mcp: 'Поиск MCP-серверов...', + sessions: 'Поиск архивных сессий...' + }, + modeOptions: { + light: { label: 'Светлая', description: 'Яркие поверхности интерфейса' }, + dark: { label: 'Тёмная', description: 'Рабочее место без блеска' }, + system: { label: 'Система', description: 'Следовать оформлению ОС' } + }, + appearance: { + title: 'Внешний вид', + intro: 'Только для десктопа. Режим — это яркость; тема — палитра и оформление чата.', + colorMode: 'Цветовой режим', + colorModeDesc: 'Выберите фиксированный режим или позвольте Hermes следовать системной настройке.', + toolViewTitle: 'Отображение вызовов инструментов', + toolViewDesc: '«Продуктовый» скрывает исходные данные инструментов; «Технический» показывает полный вход/выход.', + uiScaleTitle: 'Масштаб интерфейса', + uiScaleDesc: (percent: number) => + `Масштабирует текст и элементы во всём приложении. Также работают Cmd/Ctrl с +, - и 0. Сейчас: ${percent}%.`, + terminalFontTitle: 'Шрифт терминала', + terminalFontDesc: + 'Выберите установленный шрифт для терминалов приложения. Nerd Fonts отображают Powerlevel10k и иконки оболочки; оставьте пустым, чтобы использовать встроенный JetBrains Mono.', + terminalFontPlaceholder: 'MesloLGS NF или CSS-стек шрифтов', + terminalFontPreview: 'Предпросмотр глифов', + terminalFontReset: 'По умолчанию', + translucencyTitle: 'Прозрачность окна', + translucencyDesc: 'Видеть рабочий стол через всё окно. Только macOS и Windows.', + backdropTitle: 'Фон чата', + backdropDesc: 'Едва заметное изображение статуи за диалогом.', + reactionsTitle: 'Реакции на сообщения', + reactionsDesc: 'Эмодзи-реакции в стиле iMessage — реагируйте на сообщения, а Hermes может реагировать на ваши.', + embedsTitle: 'Встроенные превью', + embedsDesc: + 'Богатые превью загружаются со сторонних сайтов (YouTube, X, …). «Спрашивать» показывает заглушку, пока вы не разрешите каждый; «Всегда» загружает автоматически; «Выкл.» оставляет обычные ссылки.', + embedsAsk: 'Спрашивать', + embedsAlways: 'Всегда', + embedsOff: 'Выкл.', + embedsReset: (count: number) => + `Сбросить ${count} ${plural(count, 'разрешённый сервис', 'разрешённых сервиса', 'разрешённых сервисов')}`, + product: 'Продуктовый', + productDesc: 'Понятная человеку активность инструментов с краткими сводками.', + technical: 'Технический', + technicalDesc: 'Показывать исходные аргументы/результаты инструментов и низкоуровневые детали.', + themeTitle: 'Тема', + themeDesc: 'Только палитры приложения. Выбранный режим применяется поверх.', + themeProfileNote: profile => `Сохранено для профиля ${profile} — у каждого профиля своя тема.`, + installTitle: 'Установить из VS Code', + installDesc: + 'Вставьте id расширения из Marketplace (например, dracula-theme.theme-dracula), чтобы преобразовать его цветовую тему в палитру приложения.', + installPlaceholder: 'publisher.extension', + installButton: 'Установить', + installing: 'Установка…', + installError: 'Не удалось установить эту тему.', + installed: name => `Тема «${name}» установлена.`, + removeTheme: 'Удалить тему', + importedBadge: 'Импортировано', + pet: { + title: 'Питомец', + intro: + 'Заведите анимированного питомца petdex, который парит над приложением и реагирует на работу Hermes — бегает во время работы инструментов, радуется успеху и грустит при ошибках.', + restartHint: + 'Питомцам нужен быстрый перезапуск — приложение запущено до появления этой функции. Закройте и снова откройте Hermes, затем вернитесь сюда.', + on: 'Вкл.', + off: 'Выкл.', + scaleTitle: 'Размер', + scaleDesc: 'Измените размер парящего питомца. Применяется везде сразу.', + roamTitle: 'Прогулки', + roamDesc: 'Разрешить питомцу самостоятельно гулять по окну в простое.', + chooseTitle: 'Выберите питомца', + chooseDesc: 'При выборе питомец установится (если нужно) и станет активным.', + searchPlaceholder: 'Поиск питомцев…', + unreachable: 'Не удалось связаться с галереей petdex. Проверьте соединение и откройте эту страницу снова.', + noMatch: query => `Нет питомцев по запросу «${query}».`, + installedTag: 'установлен', + generatedTag: 'Сгенерирован', + countCapped: (cap, total) => `Показано ${cap} из ${total} — уточните запрос.`, + count: n => `${n} ${plural(n, 'питомец', 'питомца', 'питомцев')}.`, + uninstall: name => `Удалить ${name}`, + delete: name => `Удалить ${name}`, + deleteTitle: name => `Удалить ${name}?`, + deleteBody: 'Питомец будет удалён навсегда — переустановить его не получится.', + deleteConfirm: 'Удалить', + rename: name => `Переименовать ${name}`, + renameTitle: 'Переименовать питомца', + renamePlaceholder: 'Придумайте имя питомцу', + renameSave: 'Сохранить', + exportPet: name => `Экспортировать ${name}`, + adoptFailed: slug => `Не удалось приютить ${slug}`, + uninstallFailed: slug => `Не удалось удалить ${slug}`, + renameFailed: slug => `Не удалось переименовать ${slug}`, + exportFailed: slug => `Не удалось экспортировать ${slug}`, + noneAvailable: 'Сейчас нет питомцев, которых можно включить.', + turnOnFailed: 'Не удалось включить питомца.', + turnOffFailed: 'Не удалось выключить питомца.' + } + }, + fieldLabels: FIELD_LABELS, + fieldDescriptions: FIELD_DESCRIPTIONS, + about: { + heading: 'Hermes Desktop', + version: value => `Версия ${value}`, + versionUnavailable: 'Версия недоступна', + updates: 'Обновления', + checkNow: 'Проверить сейчас', + checking: 'Проверка…', + seeWhatsNew: 'Что нового', + updateNow: 'Обновить сейчас', + releaseNotes: 'Примечания к выпуску', + onLatest: 'У вас последняя версия.', + installing: 'Обновление уже устанавливается.', + cantUpdate: 'Эта сборка не может обновить себя из приложения.', + cantReach: 'Не удалось связаться с сервером обновлений.', + tapCheck: 'Нажмите «Проверить сейчас», чтобы поискать обновления.', + updateReady: count => `Новое обновление готово (включено ${count} ${plural(count, 'изменение', 'изменения', 'изменений')}).`, + lastChecked: age => `Последняя проверка ${age}`, + justNowSuffix: ' · только что', + automaticUpdates: 'Автоматические обновления', + automaticUpdatesDesc: + 'Hermes проверяет обновления автоматически в фоне и сообщает, когда обновление готово.', + branchCommit: (branch, commit) => `Ветка ${branch} · Коммит ${commit}`, + never: 'никогда', + justNow: 'только что', + minAgo: count => `${count} мин назад`, + hoursAgo: count => `${count} ч назад`, + daysAgo: count => `${count} дн. назад` + }, + config: { + none: 'Нет', + noneParen: '(нет)', + builtinOnly: 'Только встроенные', + notSet: 'Не задано', + commaSeparated: 'значения через запятую', + searchPlaceholder: 'Поиск…', + noResults: 'Ничего не найдено', + systemDefault: 'Системная по умолчанию', + loading: 'Загрузка конфигурации Hermes...', + emptyTitle: 'Нечего настраивать', + emptyDesc: 'В этом разделе нет настраиваемых параметров.', + failedLoad: 'Не удалось загрузить настройки', + autosaveFailed: 'Не удалось автосохранить', + imported: 'Конфигурация импортирована', + invalidJson: 'Некорректный JSON конфигурации', + keepAwakeTitle: 'Не давать компьютеру засыпать', + keepAwakeDesc: 'Не давать компьютеру засыпать, чтобы длинные и ночные запуски продолжались. Экран при этом может гаснуть.', + attachmentSizeTitle: 'Макс. размер загрузки предпросмотра / изображения', + attachmentSizeDesc: + 'Насколько большой локальный файл приложение загрузит для предпросмотра и прикрепления изображений, в МБ. По умолчанию 16. Для удалённых файлов, кроме изображений, действует отдельный лимит 256 МБ. Слишком большое значение загружает весь файл в память и может подвесить приложение.', + attachmentSizeUnit: 'МБ', + attachmentSizeLabel: 'Макс. размер загрузки предпросмотра / изображения в мегабайтах' + }, + quickEntry: { + enabledTitle: 'Быстрый ввод', + enabledDesc: + 'Вызывайте небольшой композер откуда угодно глобальным сочетанием клавиш и отправляйте промпт, не открывая Hermes.', + shortcutTitle: 'Сочетание для быстрого ввода', + shortcutDesc: 'Нужен хотя бы один модификатор, например CommandOrControl+Shift+Space.', + active: 'Сочетание активно.', + takenBy: 'Это сочетание уже занято другим приложением — выберите другое.', + invalidShortcut: 'Недопустимое сочетание. Добавьте хотя бы одну клавишу-модификатор.' + }, + credentials: { + pasteKey: 'Вставьте ключ', + pasteLabelKey: label => `Вставьте ключ ${label}`, + optional: 'Необязательно', + enterValueFirst: 'Сначала введите значение.', + couldNotSave: 'Не удалось сохранить учётные данные.', + remove: 'Убрать', + getKey: 'Получить ключ', + saving: 'Сохранение' + }, + envActions: { + actions: 'Действия', + manageInKeys: 'Управлять в API-ключах', + docs: 'Документация', + hideValue: 'Скрыть значение', + revealValue: 'Показать значение', + replace: 'Заменить', + set: 'Задать', + clear: 'Очистить' + }, + gateway: { + loading: 'Загрузка настроек gateway...', + unavailableTitle: 'Настройки gateway недоступны', + unavailableDesc: 'IPC-мост приложения не предоставляет настройки gateway.', + title: 'Подключение к gateway', + envOverride: 'переопределено env', + intro: + 'По умолчанию — локально. Используйте удалённый режим, если приложение должно управлять бэкендом Hermes в другом месте. Переопределения по профилям — ниже.', + appliesTo: 'Применяется к', + allProfiles: 'Все профили', + defaultConnection: 'Подключение по умолчанию для всех профилей без собственной настройки.', + profileConnection: profile => + `Подключение используется только когда активен профиль «${profile}». Выберите «Использовать gateway по умолчанию», чтобы убрать переопределение.`, + envOverrideTitle: 'Этой сессией приложения управляют переменные окружения.', + envOverrideDesc: + 'Снимите HERMES_DESKTOP_REMOTE_URL и HERMES_DESKTOP_REMOTE_TOKEN, чтобы использовать сохранённую настройку ниже.', + modeTitle: 'Режим подключения', + localTitle: 'Локальный gateway', + localDesc: 'Запустить приватный бэкенд Hermes на localhost. Это вариант по умолчанию, работает без интернета.', + inheritTitle: 'Использовать gateway по умолчанию', + inheritDesc: 'Убрать переопределение для этого профиля и использовать подключение по умолчанию.', + remoteTitle: 'Удалённый gateway', + remoteDesc: 'Подключить это приложение к удалённому бэкенду Hermes.', + remoteAuthHint: 'Хостинговые gateway используют OAuth либо имя пользователя и пароль; self-hosted могут использовать токен сессии.', + cloudTitle: 'Hermes Cloud', + cloudDesc: 'Войдите один раз в Hermes Cloud и выберите агента из своего аккаунта — URL вставлять не нужно.', + cloudSignInTitle: 'Hermes Cloud', + cloudSignIn: 'Войти в Hermes Cloud', + cloudSignedIn: 'Вход в Hermes Cloud выполнен', + cloudNeedsSignIn: 'Войдите в Hermes Cloud, чтобы найти агентов в вашем аккаунте.', + cloudSignedInDesc: 'Вы вошли. Выберите агента ниже; сессия обновляется автоматически.', + cloudAgentsTitle: 'Ваши агенты', + cloudOrgPickerTitle: 'Выберите организацию', + cloudOrgSelect: 'Выбрать', + cloudOrgChange: 'Сменить организацию', + cloudOrgRole: role => `Роль: ${role}`, + cloudLoadingAgents: 'Загрузка ваших агентов…', + cloudNoAgents: { + before: 'В этом аккаунте нет агентов. Создайте его в ', + linkText: 'портале Nous', + after: ', затем обновите.' + }, + cloudRefresh: 'Обновить', + cloudConnect: 'Подключиться', + cloudConnecting: 'Подключение…', + cloudDiscoverFailed: 'Не удалось загрузить ваших агентов Hermes Cloud', + cloudConnectFailed: 'Не удалось подключиться к этому агенту', + cloudSignInFailed: 'Не удалось войти в Hermes Cloud', + cloudSignedOutTitle: 'Выход из Hermes Cloud выполнен', + cloudSignedOutMessage: 'Сессия Hermes Cloud очищена.', + cloudConnectedTitle: 'Подключено', + cloudConnectedPill: 'Подключено', + cloudConnectedTo: name => `Подключено к ${name}.`, + cloudAgentProvisioning: 'Подготовка…', + cloudStatusLabel: status => `Статус: ${status}`, + remoteUrlTitle: 'Удалённый URL', + remoteUrlDesc: 'Базовый URL удалённого бэкенда дашборда. Префиксы пути поддерживаются, например /hermes.', + probing: 'Проверяем, как аутентифицируется этот gateway…', + probeError: 'Пока не удалось связаться с этим gateway. Проверьте URL — метод аутентификации появится, когда он ответит.', + signedIn: 'Вход выполнен', + signIn: 'Войти', + signOut: 'Выйти', + signInWith: provider => `Войти через ${provider}`, + authTitle: 'Аутентификация', + authSignedInPassword: + 'Этот gateway использует имя пользователя и пароль. Вы вошли; сессия обновляется автоматически.', + authSignedInOauth: 'Этот gateway использует OAuth. Вы вошли; сессия обновляется автоматически.', + authNeedsPassword: 'Этот gateway использует имя пользователя и пароль. Войдите, чтобы авторизовать это приложение.', + authNeedsOauth: provider => `Этот gateway использует OAuth. Войдите через ${provider}, чтобы авторизовать это приложение.`, + tokenTitle: 'Токен сессии', + tokenDesc: 'Токен сессии дашборда для доступа по REST и WebSocket. Оставьте пустым, чтобы сохранить текущий token.', + existingToken: value => `Существующий token ${value}`, + savedToken: 'сохранён', + pasteSessionToken: 'Вставьте токен сессии', + plainTextConfirmTitle: 'Сохранить token gateway обычным текстом?', + plainTextConfirmDesc: + 'На этой машине не найдено службы keyring, поэтому token сохранится незашифрованным в файле настроек подключения приложения — его сможет прочитать любой процесс этого пользователя. Установите или включите GNOME Keyring либо KWallet для шифрованного хранения.', + plainTextConfirmAction: 'Сохранить как обычный текст', + plainTextStoredTitle: 'Token хранится обычным текстом', + plainTextStoredDesc: + 'Безопасное хранилище недоступно, поэтому сохранённый token хранится незашифрованным в файле настроек подключения приложения на этой машине. Установите или включите GNOME Keyring либо KWallet, чтобы его зашифровать.', + testRemote: 'Проверить удалённый', + saveForRestart: 'Сохранить до следующего перезапуска', + saveAndReconnect: 'Сохранить и переподключиться', + diagnostics: 'Диагностика', + diagnosticsDesc: 'Показать desktop.log в файловом менеджере — полезно, когда gateway не запускается.', + openLogs: 'Открыть журнал', + incompleteTitle: 'Удалённый gateway настроен не полностью', + incompleteSignIn: 'Введите удалённый URL и войдите, прежде чем переключаться на удалённый режим.', + incompleteToken: 'Введите удалённый URL и токен сессии, прежде чем переключаться на удалённый режим.', + incompleteSignInTest: 'Введите удалённый URL и войдите, прежде чем выполнять проверку.', + incompleteTokenTest: 'Введите удалённый URL и токен сессии, прежде чем выполнять проверку.', + enterUrlFirst: 'Сначала введите удалённый URL.', + restartingTitle: 'Подключение к gateway перезапускается', + savedTitle: 'Настройки gateway сохранены', + restartingMessage: 'Hermes Desktop переподключится с сохранёнными настройками — приложение останется открытым.', + savedMessage: 'Сохранено до следующего перезапуска.', + connectedTo: (baseUrl, version) => `Подключено к ${baseUrl}${version ? ` · Hermes ${version}` : ''}`, + reachableTitle: 'Удалённый gateway доступен', + signedOutTitle: 'Выход выполнен', + signedOutMessage: 'Сессия удалённого gateway очищена.', + failedLoad: 'Не удалось загрузить настройки gateway', + signInFailed: 'Не удалось войти', + signOutFailed: 'Не удалось выйти', + testFailed: 'Проверка удалённого gateway не удалась', + applyFailed: 'Не удалось применить настройки gateway', + saveFailed: 'Не удалось сохранить настройки gateway', + sshTitle: 'Подключиться по SSH', + sshDesc: + 'Hermes запускается на удалённой машине по SSH и туннелируется в это приложение — ничего запускать и открывать наружу не нужно. Требуется рабочий доступ по SSH-ключу к хосту.', + sshTrustHint: 'Первый предъявленный ключ хоста запоминается и закрепляется; последующие изменения блокируют подключение.', + sshHostTitle: 'Хост', + sshHostDesc: 'user@host или алиас Host из ~/.ssh/config.', + sshHostPick: 'Выберите хост…', + sshHostPickTitle: 'Хост', + sshHostPickDesc: 'Алиас Host из ~/.ssh/config или «Свой вариант», чтобы ввести вручную.', + sshHostCustom: 'Свой вариант (ввести вручную)…', + sshUserTitle: 'Пользователь', + sshUserDesc: 'Пусто = ~/.ssh/config или текущий пользователь.', + sshUserPlaceholder: 'из ~/.ssh/config', + sshPortTitle: 'Порт', + sshPortDesc: 'Пусто = 22 или порт из ~/.ssh/config.', + sshKeyTitle: 'Файл ключа', + sshKeyDesc: 'Путь к приватному ключу. Пусто = ssh-agent или ~/.ssh/config.', + sshHermesPathTitle: 'Путь к Hermes (необязательно)', + sshHermesPathDesc: 'Полный путь к удалённому исполняемому файлу hermes. Пусто = автоопределение.', + sshHermesPathPlaceholder: 'автоопределение', + sshRemoteProfileTitle: 'Удалённый профиль (необязательно)', + sshRemoteProfileDesc: 'Имя профиля на удалённом хосте. Пусто = имя профиля из приложения.', + sshTestConnection: 'Проверить SSH', + sshConnect: 'Подключиться', + sshButtonsHint: '«Сохранить» применится при следующем запуске. «Подключиться» переподключает сейчас.', + sshReachable: (host, platform) => `Доступен: ${host} (${platform}) — Hermes найден`, + sshIncompleteHost: 'Введите SSH-хост перед подключением.', + sshErrUnreachable: 'Не удалось связаться с этим хостом по SSH. Проверьте хост, порт и сеть.', + sshErrAuth: + 'Не удалось пройти аутентификацию SSH. Загрузите ключ в ssh-agent (ssh-add) или укажите IdentityFile в ~/.ssh/config — Hermes запускает ssh неинтерактивно.', + sshErrHostKey: + 'Ключ хоста ИЗМЕНИЛСЯ с момента последнего подключения. Убедитесь, что это ожидаемо, затем выполните ssh-keygen -R и переподключитесь.', + sshErrNotInstalled: + 'Hermes не установлен на удалённом хосте. Установите его там (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | sh) или укажите путь к Hermes.', + sshErrPlatform: + 'Неподдерживаемая удалённая платформа. Режим SSH в Hermes Desktop поддерживает удалённые хосты Linux, macOS и Windows.', + sshErrTimeout: 'Истекло время SSH-подключения. Возможно, хост недоступен или спит.', + sshErrUpdateRequired: 'Обновите Hermes на удалённом хосте перед подключением через Desktop SSH.', + sshErrUnknown: 'Не удалось подключиться по SSH.' + }, + keys: { + loading: 'Загрузка API-ключей и учётных данных...', + failedLoad: 'Не удалось загрузить API-ключи', + empty: 'В этой категории пока ничего не настроено.' + }, + mcp: { + loading: 'Загрузка MCP-серверов...', + failedLoad: 'Не удалось загрузить конфигурацию MCP', + nameRequiredTitle: 'Нужно имя', + nameRequiredMessage: 'Задайте этому MCP-серверу ключ конфигурации.', + objectRequired: 'Конфигурация сервера должна быть JSON-объектом', + invalidJson: 'Некорректный JSON для MCP', + saveFailed: 'Не удалось сохранить', + removeFailed: 'Не удалось удалить', + gatewayUnavailableTitle: 'Gateway недоступен', + gatewayUnavailableMessage: 'Переподключите gateway перед перезагрузкой MCP.', + reloadedTitle: 'Инструменты MCP перезагружены', + reloadedMessage: 'Новые схемы инструментов применяются к новым ходам.', + reloadFailed: 'Не удалось перезагрузить MCP', + savedTitle: 'MCP-сервер сохранён', + savedMessage: name => `${name} применится после перезагрузки MCP.`, + newServer: 'Новый сервер', + reload: 'Перезагрузить MCP', + reloading: 'Перезагрузка...', + emptyTitle: 'Нет MCP-серверов', + emptyDesc: 'Добавьте stdio- или HTTP-сервер, чтобы получить MCP-инструменты.', + disabled: 'отключено', + editServer: 'Изменить сервер', + name: 'Имя', + serverJson: 'JSON сервера', + remove: 'Убрать', + saveServer: 'Сохранить сервер', + test: 'Проверить подключение', + testing: 'Проверка...', + testOk: count => `Подключено — доступно ${count} ${plural(count, 'инструмент', 'инструмента', 'инструментов')}`, + testFailed: 'Не удалось подключиться', + enableServer: name => `Включить ${name}`, + disableServer: name => `Отключить ${name}`, + serverEnabled: name => `${name} включён — применится к новым сессиям.`, + serverDisabled: name => `${name} отключён — применится к новым сессиям.`, + toggleFailed: (name, enabled) => `Не удалось ${enabled ? 'включить' : 'выключить'} ${name}`, + tabServers: 'Серверы', + tabCatalog: 'Каталог', + catalogLoading: 'Загрузка каталога MCP...', + catalogLoadFailed: 'Не удалось загрузить каталог MCP', + catalogEmpty: 'В каталоге нет записей.', + catalogInstalled: 'Установлено', + catalogEnabled: 'Включено', + catalogNeedsInstall: 'Нужна сборка', + catalogInstall: 'Установить', + catalogInstalling: 'Установка...', + catalogInstallStarted: name => `Установка ${name}... применится к новым сессиям по завершении.`, + catalogInstallFailed: name => `Не удалось установить ${name}`, + catalogEnvPrompt: name => `${name} требует учётных данных`, + catalogEnvRequired: 'Заполните обязательные значения перед установкой.', + capabilitySummary: (tools, prompts, resources) => + `Включено: ${[ + `${tools} ${plural(tools, 'инструмент', 'инструмента', 'инструментов')}`, + ...(prompts ? [`${prompts} ${plural(prompts, 'промпт', 'промпта', 'промптов')}`] : []), + ...(resources ? [`${resources} ${plural(resources, 'ресурс', 'ресурса', 'ресурсов')}`] : []) + ].join(', ')}`, + statusConnecting: 'Подключение…', + statusNeedsAuth: 'Требуется аутентификация', + statusError: 'Ошибка', + statusOff: 'Выкл.', + allServers: 'Все серверы', + authenticatedTitle: 'Аутентификация выполнена', + authenticatedMessage: (server, count) => `${server}: ${count} инструментов`, + waitingForBrowser: 'Ожидание браузера…', + authenticate: 'Аутентифицировать', + unsavedConnect: 'Не сохранено — сохраните mcp.json, чтобы подключиться.', + enableTool: tool => `Включить ${tool}`, + disableTool: tool => `Отключить ${tool}`, + noOutput: 'Вывода пока нет.' + }, + model: { + loading: 'Загрузка конфигурации моделей...', + appliesDesc: 'Применяется к новым сессиям. Используйте выбор модели в композере, чтобы сменить модель в текущем чате.', + provider: 'Провайдер', + model: 'Модель', + applying: 'Применение...', + defaultsLabel: 'По умолчанию', + reasoning: 'Рассуждения', + reasoningOff: 'Выкл.', + defaultsFailed: 'Не удалось сохранить модели по умолчанию', + auxiliaryTitle: 'Вспомогательные модели', + resetAllToMain: 'Сбросить все на основную', + auxiliaryDesc: 'Вспомогательные задачи по умолчанию выполняются на основной модели. Назначьте задаче отдельную модель, чтобы переопределить.', + setToMain: 'Задать основную', + change: 'Изменить', + autoUseMain: 'авто · использовать основную модель', + providerDefault: '(по умолчанию у провайдера)', + fallbackAdd: 'Добавить резервную', + fallbackEmpty: 'Резервных моделей нет — используется модель по умолчанию, пока она работает.', + notInCatalog: 'нет в списке моделей этого провайдера — вызовы могут уйти на резервную.', + tasks: { + vision: { label: 'Зрение', hint: 'Анализ изображений' }, + web_extract: { label: 'Извлечение из веба', hint: 'Сводка страниц' }, + compression: { label: 'Сжатие', hint: 'Сжатие контекста' }, + skills_hub: { label: 'Хаб скиллов', hint: 'Поиск скиллов' }, + approval: { label: 'Одобрение', hint: 'Умное автоодобрение' }, + mcp: { label: 'MCP', hint: 'Маршрутизация MCP-инструментов' }, + title_generation: { label: 'Генерация заголовков', hint: 'Заголовки сессий' }, + curator: { label: 'Куратор', hint: 'Проверка использования скиллов' } + } + }, + providers: { + connectAccount: 'Подключить аккаунт', + haveApiKey: 'Есть API-ключ?', + intro: + 'Войдите с подпиской — API-ключ копировать не нужно. Hermes выполнит вход через браузер прямо здесь, в приложении.', + connected: 'Подключено', + collapse: 'Свернуть', + connectAnother: 'Подключить другого провайдера', + otherProviders: 'Другие провайдеры', + disconnect: 'Отключить', + disconnectInTerminal: 'Отключить (выполнит команду удаления в терминале)', + removeConfirm: provider => `Удалить ${provider}?`, + removeExternalGeneric: provider => `${provider} управляется своим CLI — удалите его там.`, + removeKeyManaged: provider => `${provider} настроен через API-ключ. Удалите его в разделе API-ключей.`, + removeTerminalConfirm: (provider, command) => + `Отключить ${provider}? В терминале будет выполнено «${command}», чтобы удалить учётные данные.`, + removeTerminalRunning: provider => `Выполняем отключение ${provider} в терминале…`, + removedTitle: 'Аккаунт удалён', + removedMessage: provider => `${provider} удалён.`, + failedRemove: provider => `Не удалось удалить ${provider}`, + noProviderKeys: 'Нет доступных API-ключей провайдеров.', + searchKeys: 'Поиск провайдеров…', + noKeysMatch: 'Провайдеров по вашему запросу нет.', + localEndpoint: { + title: 'Локальный / свой endpoint', + description: 'Направьте Hermes на любой OpenAI-совместимый endpoint (Zyphra, vLLM, llama.cpp, Ollama и т. д.).' + }, + loading: 'Загрузка провайдеров...' + }, + sessions: { + loading: 'Загрузка архивных сессий…', + archivedTitle: 'Архивные сессии', + archivedIntro: + 'Архивные чаты скрыты из боковой панели, но сохраняют все сообщения. Ctrl/⌘-клик по чату в панели архивирует его.', + emptyArchivedTitle: 'Архив пуст', + emptyArchivedDesc: 'Архивируйте чат, чтобы он появился здесь.', + unarchive: 'Разархивировать', + deletePermanently: 'Удалить навсегда', + messages: count => `${count} ${plural(count, 'сообщение', 'сообщения', 'сообщений')}`, + restored: 'Восстановлено', + deleteConfirm: title => `Удалить «${title}» навсегда? Это действие нельзя отменить.`, + autoArchiveTitle: 'Автоархивация неактивных чатов', + autoArchiveDesc: + 'Автоматически архивировать чаты, к которым вы давно не возвращались. Закреплённые чаты никогда не архивируются, и ничего не удаляется — архивные чаты просто попадают сюда.', + autoArchiveDaysLabel: 'Архивировать после', + autoArchiveDaysUnit: 'дней без активности', + autoArchiveFailed: 'Не удалось обновить автоархивацию', + defaultDirTitle: 'Директория проекта по умолчанию', + defaultDirDesc: + 'Новые сессии начинаются в этой папке, если вы не выберете другую. Оставьте пустым, чтобы использовать домашнюю директорию.', + defaultDirUpdated: 'Директория проекта по умолчанию обновлена — начните новый чат (Ctrl/⌘+N), чтобы это применилось', + defaultsTo: label => `По умолчанию — ${label}.`, + change: 'Изменить', + choose: 'Выбрать', + clear: 'Очистить', + notSet: 'Не задано', + failedLoad: 'Не удалось загрузить архивные сессии', + unarchiveFailed: 'Не удалось разархивировать', + deleteFailed: 'Не удалось удалить', + updateDirFailed: 'Не удалось обновить директорию по умолчанию', + clearDirFailed: 'Не удалось очистить директорию по умолчанию' + }, + toolsets: { + loadingConfig: 'Загрузка конфигурации', + savedTitle: 'Учётные данные сохранены', + savedMessage: key => `${key} обновлён.`, + removedTitle: 'Учётные данные удалены', + removedMessage: key => `${key} удалён.`, + failedSave: key => `Не удалось сохранить ${key}`, + failedRemove: key => `Не удалось удалить ${key}`, + failedReveal: key => `Не удалось показать ${key}`, + removeConfirm: key => `Удалить ${key} из .env?`, + set: 'Задать', + notSet: 'Не задано', + selectedTitle: 'Провайдер выбран', + selectedMessage: provider => `${provider} теперь активен.`, + failedSelect: provider => `Не удалось выбрать ${provider}`, + failedLoad: 'Не удалось загрузить конфигурацию инструментов', + noProviderOptions: 'У этого набора инструментов нет вариантов провайдера — включите его, и он будет работать с вашей текущей конфигурацией.', + noProviders: 'Сейчас для этого набора инструментов нет доступных провайдеров.', + ready: 'Готово к работе', + needsSignIn: 'Нужен вход', + needsSetup: 'Требуется настройка', + activeBackend: 'Активный', + activeBackendHint: 'Это ваш активный бэкенд', + useBackend: 'Использовать этот бэкенд', + nousIncluded: 'Входит в подписку Nous — войдите в Nous Portal, чтобы активировать.', + nousAuthNeededTitle: 'Войдите в Nous Portal', + nousAuthNeededMessage: provider => `${provider} сохранён, но не активируется, пока вы не войдёте в Nous Portal.`, + nousAuthSignIn: 'Войти', + nousAuthDoneTitle: 'Nous Portal подключён', + nousAuthDoneMessage: 'Бэкенды из вашей подписки теперь активны.', + nousAuthFailed: 'Вход в Nous Portal не завершён', + noApiKeyRequired: 'API-ключ не требуется.', + postSetupHint: step => + `Этому бэкенду нужна разовая установка (${step}). Выполняется на этой машине — может занять несколько минут.`, + postSetupInstalledHint: 'Установлено. Запускайте настройку заново только если что-то сломалось.', + postSetupRun: 'Запустить настройку', + postSetupRerun: 'Запустить настройку заново', + postSetupInstalled: 'Установлено', + postSetupRunning: 'Установка…', + postSetupStarting: 'Запуск…', + postSetupCompleteTitle: 'Настройка завершена', + postSetupCompleteMessage: step => `${step} установлено.`, + postSetupErrorTitle: 'Настройка завершена с ошибками', + postSetupErrorMessage: step => `Проверьте лог ${step}.`, + postSetupFailed: step => `Не удалось выполнить настройку ${step}`, + webSearchActive: backend => `Поиск: ${backend}`, + webExtractActive: backend => `Извлечение: ${backend}`, + webCapabilityUnset: 'не задано', + webUseForSearch: 'Использовать для поиска', + webUseForExtract: 'Использовать для извлечения', + webUsedForSearch: 'Бэкенд поиска', + webUsedForExtract: 'Бэкенд извлечения', + webCapabilitySelectedMessage: (provider, capability) => `${provider} теперь отвечает за web ${capability}.`, + failedSelectCapability: provider => `Не удалось задать ${provider}`, + loadingModels: 'Загрузка каталога моделей...', + modelSectionTitle: 'Модель', + modelCount: count => `${count} ${plural(count, 'модель', 'модели', 'моделей')}`, + modelInUse: 'Используется', + modelDefault: 'по умолчанию', + modelInactiveHint: 'Сначала выберите этот бэкенд, чтобы сменить его модель.', + modelSelectedTitle: 'Модель выбрана', + modelSelectedMessage: model => `${model} применяется к новым сессиям.`, + failedSelectModel: model => `Не удалось выбрать ${model}`, + terminalBackend: { + sectionTitle: 'Бэкенд выполнения', + loading: 'Проверяем бэкенды выполнения…', + failedLoad: 'Не удалось загрузить бэкенды терминала', + ready: 'Готово к работе', + needsSetup: 'Нужна настройка', + unavailable: 'Недоступен', + inUse: 'Используется', + selectedTitle: 'Бэкенд выбран', + selectedMessage: backend => `Команды терминала теперь выполняются через ${backend}. Применяется к новым сессиям.`, + failedSelect: backend => `Не удалось выбрать ${backend}`, + needsSetupHint: 'Этот бэкенд можно выбрать сейчас — команды будут падать, пока настройка не завершена.' + } + } + }, + + skills: { + tabSkills: 'Навыки', + tabToolsets: 'Инструменты', + tabMcp: 'MCP', + tabHub: 'Обзор хаба', + all: 'Все', + searchSkills: 'Поиск скиллов...', + searchToolsets: 'Поиск инструментов...', + refresh: 'Обновить скиллы', + refreshing: 'Обновление скиллов', + loading: 'Загрузка возможностей...', + noSkillsTitle: 'Скиллы не найдены', + noSkillsDesc: 'Попробуйте более общий запрос или другую категорию.', + noToolsetsTitle: 'Наборы инструментов не найдены', + noToolsetsDesc: 'Попробуйте более общий поисковый запрос.', + noDescription: 'Нет описания.', + configured: 'Настроено', + needsKeys: 'Нужны ключи', + visionModelHint: + 'Зрение использует настройку вспомогательных моделей — модель с поддержкой изображений выбирается там, а не здесь по провайдеру.', + visionModelLink: 'Выбрать модель зрения в Настройках → Модели', + toolsetsEnabled: (enabled, total) => `Включено наборов: ${enabled}/${total}`, + configureToolset: label => `Настроить ${label}`, + toggleToolset: (label, enabled) => `${enabled ? 'Включить' : 'Выключить'} набор инструментов ${label}`, + skillsLoadFailed: 'Не удалось загрузить скиллы', + toolsetsRefreshFailed: 'Не удалось обновить наборы инструментов', + skillEnabled: 'Скилл включён', + skillDisabled: 'Скилл отключён', + toolsetEnabled: 'Набор инструментов включён', + toolsetDisabled: 'Набор инструментов отключён', + appliesToNewSessions: name => `${name} применяется к новым сессиям.`, + failedToUpdate: name => `Не удалось обновить ${name}`, + sortMostUsed: 'Чаще всего', + sortAlpha: 'А–Я', + sortMostUsedDesc: '↓ Чаще всего', + sortLeastUsedAsc: '↑ Реже всего', + enableAll: 'Включить все', + disableAll: 'Отключить все', + disableUnused: 'Отключить неиспользуемые', + bulkUpdated: count => `Обновлено ${count} ${plural(count, 'элемент', 'элемента', 'элементов')} для новых сессий.`, + bulkNoChange: 'Менять нечего.', + usageCount: count => `использован ${count}×`, + provenance: { + agent: 'Изучено', + bundled: 'Встроенный', + hub: 'Хаб' + }, + emptyNoneFound: noun => `${noun} не найдены`, + emptyNothingMatches: query => `Ничего не найдено по запросу «${query}».`, + emptyNoneAvailable: noun => `Пока нет доступных ${noun}.`, + changesApplyNewSessions: 'Изменения применяются к новым сессиям.', + skillUpdated: 'Скилл обновлён', + edit: 'Редактировать', + archive: 'В архив', + skillArchivedTitle: 'Скилл заархивирован', + skillArchivedMessage: 'Можно восстановить через hermes curator restore.', + hub: { + searchPlaceholder: 'Поиск в хабе скиллов', + search: 'Поиск', + searching: 'Поиск...', + connectingHubs: 'Подключение к хабам скиллов...', + connectedHubs: 'Подключённые хабы:', + featured: 'Рекомендуемые скиллы', + landingHint: + 'Ищите в хабе устанавливаемые скиллы из официального индекса, GitHub и сообщества.', + noResults: 'В хабе не найдено подходящих скиллов.', + resultCount: (count, ms) => + `${count} ${plural(count, 'результат', 'результата', 'результатов')}${ms !== null ? ` за ${ms} мс` : ''}`, + timedOut: sources => `Истекло время ожидания: ${sources}`, + installed: 'Установлено', + install: 'Установить', + installing: 'Установка...', + uninstall: 'Удалить', + uninstalling: 'Удаление...', + updateAll: 'Обновить установленные', + updating: 'Обновление...', + preview: 'Предпросмотр', + scan: 'Проверить', + scanning: 'Проверка...', + close: 'Закрыть', + files: 'Файлы', + noReadme: 'У этого скилла нет предпросмотра SKILL.md.', + trust: { + builtin: 'встроенный', + trusted: 'доверенный', + community: 'сообщество' + }, + verdictSafe: 'Безопасно', + verdictCaution: 'Осторожно', + verdictDangerous: 'Опасно', + policyAllow: 'Установка разрешена', + policyAsk: 'Проверьте перед установкой', + policyBlock: 'Установка запрещена политикой', + findings: count => `${count} ${plural(count, 'находка', 'находки', 'находок')}`, + noFindings: 'Проблем безопасности не найдено.', + installStarted: name => `Установка ${name}...`, + uninstallStarted: name => `Удаление ${name}...`, + updateStarted: 'Обновление установленных скиллов...', + actionFailed: 'Не удалось выполнить действие со скиллом', + actionLog: 'Журнал действий', + loadFailed: 'Не удалось загрузить хаб скиллов', + previewFailed: 'Не удалось показать предпросмотр скилла', + scanFailed: 'Не удалось выполнить проверку безопасности', + searchFailed: 'Не удалось выполнить поиск в хабе' + } + }, + + starmap: { + title: 'Граф памяти', + subtitle: (nodes, clusters) => `${nodes} скиллов в ${clusters} категориях`, + close: 'Закрыть граф памяти', + refresh: 'Обновить', + memory: 'Память', + filterAll: 'Все', + filterUsed: 'Использовано', + filterLearned: 'Изучено', + viewGraph: 'Граф', + loadFailed: 'Не удалось загрузить граф памяти', + loading: 'Загрузка…', + emptyTitle: 'Пока ничего не изучено', + emptyDesc: 'По мере того как Hermes создаёт скиллы и воспоминания для вашей работы, они появляются здесь.', + share: 'Поделиться картой', + shareHint: + 'Скопируйте код, чтобы поделиться этой картой, или вставьте чужой, чтобы загрузить. Код содержит только компоновку, без текста вашей памяти и скиллов.', + shareTitle: 'Импорт / экспорт карты', + sharePlaceholder: 'Вставьте код карты…', + copy: 'Копировать код карты', + copied: 'Скопировано!', + importMap: 'Импорт карты', + importBtn: 'Загрузить', + importEmpty: 'Вставьте код карты, чтобы загрузить её.', + importSuccess: nodes => `Загружена карта: ${nodes} ${plural(nodes, 'узел', 'узла', 'узлов')}.`, + importedBadge: 'импортированная карта', + resetToMine: 'Вернуться к моей карте' + }, + agents: { + close: 'Закрыть агентов', + title: 'Дерево запусков', + subtitle: 'Активность субагентов текущего хода.', + emptyTitle: 'Нет активных субагентов', + emptyDesc: 'Когда ход делегирует работу, дочерние агенты передают сюда свой прогресс.', + running: 'Выполняется', + failed: 'Не удалось', + done: 'Готово', + streaming: 'Передача', + files: 'Файлы', + moreFiles: count => `+${count} файлов`, + delegation: index => `Делегирование ${index}`, + workers: count => `${count} воркеров`, + workersActive: count => `${count} активных`, + agentsCount: count => `${count} ${plural(count, 'агент', 'агента', 'агентов')}`, + activeCount: count => `${count} активных`, + failedCount: count => `${count} с ошибкой`, + toolsCount: count => `${count} инструментов`, + filesCount: count => `${count} файлов`, + updatedAgo: age => `обновлено ${age}`, + ageNow: 'сейчас', + ageSeconds: seconds => `${seconds} с назад`, + ageMinutes: minutes => `${minutes} мин назад`, + ageHours: hours => `${hours} ч назад`, + ageDays: days => `${days} д назад`, + durationSeconds: seconds => `${seconds} с`, + durationMinutes: (minutes, seconds) => `${minutes} мин ${seconds} с`, + tokens: value => `${value} ток.` + }, + + commandCenter: { + close: 'Закрыть центр управления', + paletteTitle: 'Палитра команд', + back: 'Назад', + searchPlaceholder: 'Поиск сессий, представлений и действий', + goTo: 'Перейти к', + goToSession: 'Перейти к сессии', + branches: 'Ветки', + projects: 'Проекты', + openFolder: 'Открыть папку как проект…', + openFolderAt: path => `Открыть папку как проект — ${path}`, + newSessionInProject: project => `Новая сессия в ${project}`, + commands: 'Команды', + startInBranch: branch => `Новый диалог в ${branch}`, + commandCenter: 'Центр управления', + appearance: 'Внешний вид', + settings: 'Настройки', + changeTheme: 'Сменить тему', + changeColorMode: 'Сменить цветовой режим…', + pets: { + title: 'Питомцы', + placeholder: 'Поиск питомцев…', + loading: 'Загрузка галереи petdex…', + error: 'Не удалось связаться с галереей petdex.', + staleBackend: 'Перезапустите Hermes, чтобы использовать питомцев — бэкенд старше этой функции.', + empty: 'Подходящих питомцев нет.', + turnOff: 'Выключить', + turnOn: 'Включить', + installed: 'Установлено', + generatedTag: 'Сгенерирован', + adoptFailed: 'Не удалось приютить этого питомца.', + toggleFailed: enabled => `Не удалось ${enabled ? 'включить' : 'выключить'} питомца.`, + noneAvailable: 'Питомцев нет — выберите одного ниже для установки.' + }, + generatePet: { + title: 'Сгенерировать питомца', + placeholder: 'Опишите питомца для генерации…', + promptHint: 'Введите описание и нажмите Enter, чтобы создать четыре варианта.', + readyHint: 'Нажмите Enter, чтобы создать четыре варианта по вашему описанию.', + generate: 'Сгенерировать', + generating: 'Генерация…', + retry: 'Повторить', + hatch: 'Вылупить', + spawning: 'Запуск…', + hatching: 'Вылупляем питомца…', + hatchingSub: 'Оживляем…', + hatched: 'Вылупился!', + hatchRow: (_state, done, total) => `Набросок кадра ${done} из ${total}…`, + hatchComposing: 'Собираем воедино…', + hatchSaving: 'Почти готово…', + namePlaceholder: 'Придумайте имя питомцу', + staleBackend: 'Обновите Hermes, чтобы генерировать питомцев.', + backgroundHint: 'Это окно можно закрыть — Hermes сообщит, когда всё будет готово.', + slowProviderHint: 'Это может занять несколько минут', + remix: 'Ремикс', + remixConfirmTitle: 'Сделать ремикс этого варианта?', + remixConfirmBody: + 'Будет создан новый набор вариантов на основе этого. Это может занять несколько минут.', + genericError: 'Не удалось сгенерировать — попробуйте снова или выберите подсказку.', + referenceImageTooLarge: 'Референсное изображение слишком большое. Используйте файл меньше 16 МБ.', + referenceImageInvalid: 'Не удалось прочитать это референсное изображение. Попробуйте PNG, JPG, WebP или GIF.', + adopt: 'Приютить', + startOver: 'Начать заново' + }, + installTheme: { + title: 'Установить тему…', + pageTitle: 'Установить тему', + placeholder: 'Поиск в VS Code Marketplace...', + loading: 'Поиск в Marketplace...', + error: 'Не удалось связаться с Marketplace.', + empty: 'Подходящих тем нет.', + install: 'Установить', + installing: 'Установка...', + installed: 'Установлено', + installs: count => `${count} установок` + }, + settingsFields: 'Поля настроек', + mcpServers: 'MCP-серверы', + archivedChats: 'Архив чатов', + sections: { maintenance: 'Обслуживание', sessions: 'Сессии', system: 'Система', usage: 'Использование' }, + sectionDescriptions: { + maintenance: 'Диагностика, резервные копии, куратор и данные памяти', + sessions: 'Поиск и управление сессиями', + system: 'Статус, журналы и системные действия', + usage: 'Токены, стоимость и активность скиллов за период' + }, + nav: { + newChat: { title: 'Новая сессия', detail: 'Начать новую сессию' }, + settings: { title: 'Настройки', detail: 'Настроить Hermes Desktop' }, + skills: { title: 'Возможности', detail: 'Скиллы, инструменты и MCP-серверы' }, + messaging: { title: 'Мессенджеры', detail: 'Настройте Telegram, Slack, Discord и другое' }, + artifacts: { title: 'Артефакты', detail: 'Просмотр созданных результатов' } + }, + sectionEntries: { + sessions: { title: 'Панель сессий', detail: 'Поиск, закрепление и управление сессиями' }, + system: { title: 'Системная панель', detail: 'Статус gateway, журналы, перезапуск/обновление' }, + usage: { title: 'Панель использования', detail: 'Токены, стоимость и активность скиллов' } + }, + providerNavigate: 'Навигация', + providerSessions: 'Сессии', + refresh: 'Обновить', + refreshing: 'Обновление...', + noResults: 'Совпадений не найдено.', + pinSession: 'Закрепить сессию', + unpinSession: 'Открепить сессию', + exportSession: 'Экспорт сессии', + deleteSession: 'Удалить сессию', + noSessions: 'Пока нет сессий.', + gatewayRunning: 'Gateway обмена сообщениями работает', + gatewayStopped: 'Gateway обмена сообщениями остановлен', + hermesActiveSessions: (version, count) => `Hermes ${version} · Активных сессий: ${count}`, + restartGateway: 'Перезапустить gateway', + gatewayRestartFailed: 'Не удалось перезапустить gateway.', + updateHermes: 'Обновить Hermes', + actionRunning: 'выполняется', + actionDone: 'готово', + actionFailed: 'ошибка', + actionStartedWaiting: 'Действие запущено, ожидание статуса...', + loadingStatus: 'Загрузка статуса...', + recentLogs: 'Последние журналы', + noLogs: 'Журналы пока не загружены.', + days: count => `${count} д`, + statSessions: 'Сессии', + statApiCalls: 'вызовы API', + statTokens: 'Токены вход/выход', + statCost: 'Прибл. стоимость', + actualCost: cost => `фактически ${cost}`, + loadingUsage: 'Загрузка статистики...', + noUsage: period => `Нет активности за последние ${period} дней.`, + retry: 'Повторить', + dailyTokens: 'Токены за день', + input: 'ввод', + output: 'вывод', + noDailyActivity: 'Нет активности за день.', + topModels: 'Топ моделей', + noModelUsage: 'Пока нет данных об использовании моделей.', + topSkills: 'Топ скиллов', + noSkillActivity: 'Пока нет активности скиллов.', + actions: count => `${count} действий`, + logFile: 'Файл журнала', + logLevel: 'Уровень', + logSearchPlaceholder: 'Фильтр строк журнала...', + maintenance: { + runOps: 'Диагностика', + doctor: 'Запустить doctor', + doctorDesc: 'Проверить установку, конфигурацию и провайдеров', + securityAudit: 'Аудит безопасности', + securityAuditDesc: 'Проверить конфигурацию и скиллы на рискованные настройки', + backup: 'Создать резервную копию', + backupDesc: 'Архивировать конфигурацию, память, скиллы и сессии', + debugShare: 'Отладочный отчёт', + debugShareDesc: 'Загрузить обезличенный отчёт и журналы, получить ссылки (удаляются через 6 ч)', + debugShareRunning: 'Загрузка отладочного отчёта...', + debugShareLinks: 'Ссылки для обмена', + debugShareFailed: 'Не удалось отправить отладочный отчёт', + copyLink: 'Копировать ссылку', + linkCopied: 'Ссылка скопирована', + curator: 'Куратор скиллов', + curatorDesc: 'Фоновая проверка, которая архивирует устаревшие скиллы, созданные агентом', + curatorPaused: 'Приостановлено', + curatorActive: 'Активный', + curatorDisabled: 'Отключено', + curatorLastRun: when => `Последний запуск ${when}`, + curatorNeverRan: 'Ни разу не запускался', + pause: 'Пауза', + resume: 'Возобновить', + runNow: 'Запустить сейчас', + memoryData: 'Данные памяти', + memoryDataDesc: 'Встроенные файлы памяти, подключаемые к каждой сессии', + memoryProvider: name => `Активный провайдер: ${name}`, + builtinMemory: 'встроенная', + memoryFile: 'Память агента (MEMORY.md)', + userFile: 'Профиль пользователя (USER.md)', + bytes: size => size, + empty: 'пусто', + resetMemory: 'Сбросить память', + resetUser: 'Сбросить профиль', + resetAll: 'Сбросить оба', + resetConfirm: target => `Удалить ${target}? Это действие нельзя отменить.`, + resetDone: files => `Удалено: ${files}.`, + resetFailed: 'Не удалось сбросить память', + actionStarted: name => `${name} запущено — читаем журнал...`, + actionFailed: name => `Не удалось запустить ${name}`, + running: 'Выполняется...', + viewLog: 'Журнал действий' + } + }, + + messaging: { + search: 'Поиск по сообщениям...', + loading: 'Загрузка платформ обмена сообщениями...', + loadFailed: 'Не удалось загрузить платформы обмена сообщениями', + states: { + connected: 'Подключено', + connecting: 'Подключение', + disabled: 'Отключено', + fatal: 'Ошибка', + gateway_stopped: 'Gateway обмена сообщениями остановлен', + not_configured: 'Нужна настройка', + pending_restart: 'Нужен перезапуск', + retrying: 'Повторная попытка', + startup_failed: 'Не удалось запустить' + }, + unknown: 'Неизвестно', + hintPendingRestart: 'Перезапустите gateway из строки состояния, чтобы применить это изменение.', + hintGatewayStopped: 'Запустите gateway из строки состояния, чтобы подключиться.', + credentialsSet: 'Учётные данные заданы', + needsSetup: 'Нужна настройка', + gatewayStopped: 'Gateway обмена сообщениями остановлен', + getCredentials: 'Получить учётные данные', + openSetupGuide: 'Открыть руководство по настройке', + required: 'Обязательно', + recommended: 'Рекомендуется', + advanced: count => `Дополнительно (${count})`, + noTokenNeeded: 'Этой платформе здесь не нужен token. Используйте руководство выше, затем включите её ниже.', + enabled: 'Включено', + disabled: 'Отключено', + unsavedChanges: 'Несохранённые изменения', + saving: 'Сохранение...', + saveChanges: 'Сохранить изменения', + saved: 'Сохранено', + replaceValue: 'Заменить текущее значение', + openDocs: 'Открыть документацию', + clearField: key => `Очистить ${key}`, + enableAria: name => `Включить ${name}`, + disableAria: name => `Отключить ${name}`, + platformEnabled: name => `${name} включён`, + platformDisabled: name => `${name} отключён`, + restartToApply: 'Изменение вступит в силу после перезапуска gateway.', + setupSaved: name => `Настройка ${name} сохранена`, + restartToReconnect: 'Новые учётные данные вступят в силу после перезапуска gateway.', + keyCleared: key => `${key} очищен`, + setupUpdated: name => `Настройка ${name} обновлена.`, + failedUpdate: name => `Не удалось обновить ${name}`, + failedSave: name => `Не удалось сохранить ${name}`, + failedClear: key => `Не удалось очистить ${key}`, + pendingRequests: count => `Запросы в ожидании (${count})`, + pendingAria: count => `${count} ${plural(count, 'запрос', 'запроса', 'запросов')} на сопряжение в ожидании`, + approvedUsers: count => `Одобренные пользователи (${count})`, + approve: 'Разрешить', + approving: 'Одобрение...', + revoke: 'Отозвать', + revoking: 'Отзыв доступа...', + revokeAria: name => `Отозвать доступ у ${name}`, + revokeTitle: 'Отозвать доступ', + revokeDesc: (name: string) => `${name} потеряет доступ и больше не будет распознаваться при следующем сообщении.`, + approvedUser: name => `${name} одобрен`, + approvedHint: 'Они распознаются автоматически при следующем сообщении.', + revokedUser: name => `Доступ у ${name} отозван`, + failedApprove: name => `Не удалось одобрить ${name}`, + failedRevoke: name => `Не удалось отозвать доступ у ${name}`, + pairingLockedOut: 'Слишком много неудачных одобрений — платформа заблокирована. Попробуйте позже.', + waitingSince: minutes => (minutes < 1 ? 'только что' : `${minutes} мин назад`), + fieldCopy: { + TELEGRAM_BOT_TOKEN: { + label: 'Bot token', + help: 'Создайте бота через @BotFather и вставьте выданный token.', + placeholder: 'Вставьте bot token Telegram' + }, + TELEGRAM_ALLOWED_USERS: { + label: 'Разрешённые ID пользователей Telegram', + help: 'Рекомендуется. Числовые ID через запятую из @userinfobot. Без этого писать боту может любой.' + }, + TELEGRAM_PROXY: { label: 'URL proxy', help: 'Нужно только в сетях, где Telegram заблокирован.' }, + DISCORD_BOT_TOKEN: { + label: 'Bot token', + help: 'Создайте приложение в Discord Developer Portal, добавьте бота и вставьте его token.' + }, + DISCORD_ALLOWED_USERS: { + label: 'Разрешённые ID пользователей Discord', + help: 'Рекомендуется. ID пользователей Discord через запятую.' + }, + DISCORD_REPLY_TO_MODE: { label: 'Стиль ответов', help: 'first, all или off.' }, + DISCORD_ALLOW_ALL_USERS: { + label: 'Разрешить всех пользователей Discord', + help: 'Только для разработки. Если включено, писать боту может любой без allowlist.' + }, + DISCORD_HOME_CHANNEL: { + label: 'ID основного канала', + help: 'Канал, куда бот отправляет сообщения по своей инициативе (вывод cron, напоминания).' + }, + DISCORD_HOME_CHANNEL_NAME: { + label: 'Имя основного канала', + help: 'Отображаемое имя основного канала в логах и статусе.' + }, + BLUEBUBBLES_ALLOW_ALL_USERS: { + label: 'Разрешить всех пользователей iMessage', + help: 'Если включено, allowlist BlueBubbles игнорируется.' + }, + MATTERMOST_ALLOW_ALL_USERS: { label: 'Разрешить всех пользователей Mattermost' }, + MATTERMOST_HOME_CHANNEL: { label: 'Основной канал' }, + QQ_ALLOW_ALL_USERS: { label: 'Разрешить всех пользователей QQ' }, + QQBOT_HOME_CHANNEL: { label: 'Основной канал QQ', help: 'Канал или группа по умолчанию для доставки cron.' }, + QQBOT_HOME_CHANNEL_NAME: { label: 'Имя основного канала QQ' }, + SLACK_BOT_TOKEN: { + label: 'Bot token Slack', + help: 'Используйте bot token из раздела OAuth & Permissions после установки вашего приложения Slack.', + placeholder: 'Вставьте bot token Slack' + }, + SLACK_APP_TOKEN: { + label: 'App token Slack', + help: 'Используйте app-level token, необходимый для Socket Mode.', + placeholder: 'Вставьте app token Slack' + }, + SLACK_ALLOWED_USERS: { label: 'Разрешённые ID пользователей Slack', help: 'Рекомендуется. ID пользователей Slack через запятую.' }, + MATTERMOST_URL: { label: 'URL сервера', placeholder: 'https://mattermost.example.com' }, + MATTERMOST_TOKEN: { label: 'Bot token' }, + MATTERMOST_ALLOWED_USERS: { + label: 'Разрешённые ID пользователей', + help: 'Рекомендуется. ID пользователей Mattermost через запятую.' + }, + MATRIX_HOMESERVER: { label: 'URL homeserver', placeholder: 'https://matrix.org' }, + MATRIX_ACCESS_TOKEN: { label: 'Access token' }, + MATRIX_USER_ID: { label: 'ID пользователя-бота', placeholder: '@hermes:example.org' }, + MATRIX_ALLOWED_USERS: { + label: 'Разрешённые ID пользователей Matrix', + help: 'Рекомендуется. ID пользователей через запятую в формате @user:server.' + }, + SIGNAL_HTTP_URL: { + label: 'URL моста Signal', + placeholder: 'http://127.0.0.1:8080', + help: 'URL работающего REST-моста signal-cli.' + }, + SIGNAL_ACCOUNT: { label: 'Номер телефона', help: 'Номер, зарегистрированный в вашем мосте signal-cli.' }, + SIGNAL_ALLOWED_USERS: { label: 'Разрешённые пользователи Signal', help: 'Рекомендуется. Идентификаторы Signal через запятую.' }, + WHATSAPP_ENABLED: { + label: 'Включить мост WhatsApp', + help: 'Задаётся автоматически переключателем ниже. Не меняйте без необходимости.' + }, + WHATSAPP_MODE: { label: 'Режим моста' }, + WHATSAPP_ALLOWED_USERS: { + label: 'Разрешённые пользователи WhatsApp', + help: 'Рекомендуется. Номера телефонов или WhatsApp ID через запятую.' + } + }, + platformIntro: {} + }, + + webhooks: { + search: 'Поиск webhooks...', + loading: 'Загрузка webhooks...', + loadFailed: 'Не удалось загрузить webhooks', + subscriptions: (count: number) => `Подписки (${count})`, + hint: 'Изменения подписок применяются на ходу, когда приёмник запущен. Отключённые подписки отклоняют входящие события.', + empty: 'Подписок на webhooks пока нет.', + disabledTitle: 'Приём webhooks отключён', + disabledBody: + 'Webhooks — отдельная платформа gateway. Включите их здесь, чтобы принимать входящие HTTP-события; чат-каналы нужны только когда подписка доставляет сообщения в Telegram, Discord, Slack или другой канал.', + enable: 'Включить webhooks', + enabling: 'Включение...', + enabled: (name: string) => `Включено: «${name}»`, + disabled: (name: string) => `Отключено: «${name}»`, + enableRow: 'Включить', + disableRow: 'Отключить', + delete: 'Удалить', + deleting: 'Удаление...', + deleted: 'Webhook удалён', + deleteTitle: 'Удалить webhook', + deleteDescPrefix: 'Будет навсегда удалён ', + deleteDescSuffix: '. Это действие нельзя отменить.', + deleteFailed: (name: string) => `Не удалось удалить «${name}»`, + toggleFailed: (name, enabled) => `Не удалось ${enabled ? 'включить' : 'выключить'} «${name}»`, + newSubscription: 'Новая подписка', + restarting: 'Gateway перезапускается...', + restartNeeded: 'Webhooks включены, но gateway всё ещё нужно перезапустить, чтобы приёмник заработал.', + restartGateway: 'Перезапустить gateway', + restartingGateway: 'Перезапуск...', + restartFailed: (detail: string) => `Не удалось перезапустить gateway${detail}`, + enabledRestarting: 'Webhooks включены; gateway перезапускается...', + all: '(все)', + deliverOnly: 'только доставка', + createdTitle: 'Подписка создана', + createdSecretHint: 'Скопируйте секрет сейчас — он показывается только один раз.', + webhookUrl: 'URL webhook', + secretOnce: 'Секрет (показывается один раз)', + done: 'Готово', + fieldName: 'Имя', + fieldNamePlaceholder: 'например, github-push', + fieldDescription: 'Описание', + fieldDescriptionPlaceholder: 'Что делает этот webhook (необязательно)', + fieldEvents: 'События', + fieldEventsPlaceholder: 'через запятую, оставьте пустым для всех', + fieldSkills: 'Навыки', + fieldSkillsPlaceholder: 'названия скиллов через запятую (необязательно)', + fieldDeliver: 'Доставить в', + fieldDeliverOnly: 'Доставлять только payload', + fieldPrompt: 'Запрос', + fieldPromptPlaceholder: 'Инструкции для агента при срабатывании этого webhook (необязательно)', + nameRequired: 'Нужно имя', + create: 'Создать', + creating: 'Создание...', + created: 'Создан', + createFailed: (detail: string) => `Не удалось создать: ${detail}`, + copy: 'Копировать', + deliverOptions: { + log: 'Журнал', + telegram: 'Telegram', + discord: 'Discord', + slack: 'Slack', + email: 'Email', + github_comment: 'Комментарий GitHub' + } + }, + + profiles: { + close: 'Закрыть профили', + nameHint: 'Строчные латинские буквы, цифры, дефисы и подчёркивания. Должно начинаться с буквы или цифры.', + title: 'профили: мульти-агенты', + count: count => `${count} ${plural(count, 'профиль', 'профиля', 'профилей')}`, + search: 'Поиск профилей...', + loading: 'Загрузка профилей...', + newProfile: 'Новый профиль', + importProfile: 'Импортировать профиль…', + exportProfile: 'Экспортировать профиль…', + imported: 'Профиль импортирован', + exported: 'Профиль экспортирован', + failedImport: 'Не удалось импортировать профиль', + failedExport: 'Не удалось экспортировать профиль', + allProfiles: 'Все профили', + showAllProfiles: 'Показать все профили', + switchToProfile: name => `Переключиться на ${name}`, + manageProfiles: 'Управление профилями…', + actions: 'Действия', + color: 'Цвет…', + colorFor: 'Цвет', + setColor: color => `Задать цвет ${color}`, + autoColor: 'Авто', + noProfiles: 'Профилей пока нет.', + selectPrompt: 'Выберите профиль, чтобы увидеть детали.', + refresh: 'Обновить профили', + refreshing: 'Обновление профилей', + default: 'по умолчанию', + skills: count => `${count} ${plural(count, 'скилл', 'скилла', 'скиллов')}`, + env: 'env', + defaultBadge: 'По умолчанию', + rename: 'Переименование', + renameMenu: 'Переименовать…', + editSoul: 'Редактировать SOUL.md…', + copySetup: 'Скопировать команду настройки', + copying: 'Копирование...', + modelLabel: 'Модель', + skillsLabel: 'Навыки', + notSet: 'Не задано', + soulDesc: 'Системный промпт и описание персоны, встроенные в этот профиль.', + soulOptional: 'необязательно', + soulPlaceholder: mode => `Системный промпт / персона для этого профиля.\nОставьте пустым, чтобы использовать вариант по умолчанию (${mode}).`, + soulPlaceholderCloned: 'клонированный', + soulPlaceholderEmpty: 'пусто', + unsavedChanges: 'Несохранённые изменения', + loadingSoul: 'Загрузка SOUL.md...', + emptySoul: 'SOUL.md пуст — начните описывать персону...', + saving: 'Сохранение...', + saveSoul: 'Сохранить SOUL.md', + deleteTitle: 'Удалить профиль?', + deleteDescPrefix: 'Будет удалён профиль ', + deleteDescMid: ' вместе с его каталогом ', + deleteDescSuffix: '. Это действие нельзя отменить.', + deleting: 'Удаление...', + createDesc: 'Профили — независимые окружения Hermes: отдельные конфигурация, скиллы и SOUL.md.', + nameLabel: 'Имя', + cloneFrom: 'Клонировать из', + cloneFromNone: 'Нет (пусто)', + cloneFromDesc: 'Копирует конфигурацию, скиллы и SOUL.md из выбранного исходного профиля.', + cloneFromDefault: 'Клонировать из профиля по умолчанию', + cloneFromDefaultDesc: 'Скопировать конфигурацию, скиллы и SOUL.md из вашего профиля по умолчанию.', + invalidName: hint => `Недопустимое имя. ${hint}`, + nameRequired: 'Имя обязательно.', + creating: 'Создание...', + createAction: 'Создать профиль', + renameTitle: 'Переименовать профиль', + renameDescPrefix: 'Переименование обновит каталог профиля и все вспомогательные скрипты в ', + renameDescSuffix: '.', + newNameLabel: 'Новое имя', + renaming: 'Переименование...', + created: 'Профиль создан', + renamed: 'Профиль переименован', + deleted: 'Профиль удалён', + setupCopied: 'Команда настройки скопирована', + soulSaved: 'SOUL.md сохранён', + failedLoad: 'Не удалось загрузить профили', + failedDelete: 'Не удалось удалить профиль', + failedCopy: 'Не удалось скопировать команду настройки', + failedLoadSoul: 'Не удалось загрузить SOUL.md', + failedSaveSoul: 'Не удалось сохранить SOUL.md', + failedCreate: 'Не удалось создать профиль', + failedRename: 'Не удалось переименовать профиль' + }, + + cron: { + close: 'Закрыть cron', + title: 'Запланированные задачи', + count: count => `${count} ${plural(count, 'задача', 'задачи', 'задач')}`, + modelImpact: { + title: 'Запланированные задачи требуют проверки', + message: count => + `${count} ${plural(count, 'запланированная задача будет пропущена', 'запланированные задачи будут пропущены', 'запланированных задач будут пропущены')}, пока вы не проверите их настройки модели.`, + detailMore: (names, remaining) => `${names} и ещё ${remaining}`, + review: 'Проверить запланированные задачи', + saveFailed: 'Hermes не сохранил это изменение модели.' + }, + search: 'Поиск cron-задач...', + loading: 'Загрузка cron-задач...', + states: { + enabled: 'включено', + scheduled: 'запланировано', + running: 'выполняется', + paused: 'приостановлено', + disabled: 'отключено', + error: 'ошибка', + completed: 'завершено' + }, + deliveryLabels: { + local: 'Этот компьютер', + telegram: 'Telegram', + discord: 'Discord', + slack: 'Slack', + email: 'Email' + }, + scheduleLabels: { + daily: 'Ежедневно', + weekdays: 'По будням', + weekly: 'Еженедельно', + monthly: 'Ежемесячно', + hourly: 'Каждый час', + 'every-15-minutes': 'Каждые 15 минут', + custom: 'Пользовательский' + }, + scheduleHints: { + daily: 'Каждый день в 9:00', + weekdays: 'С понедельника по пятницу в 9:00', + weekly: 'Каждый понедельник в 9:00', + monthly: 'Первого числа каждого месяца в 9:00', + hourly: 'В начале каждого часа', + 'every-15-minutes': 'Каждые 15 минут', + custom: 'Cron-синтаксис или обычный текст' + }, + days: { + '0': 'Воскресенье', + '1': 'Понедельник', + '2': 'Вторник', + '3': 'Среда', + '4': 'Четверг', + '5': 'Пятница', + '6': 'Суббота', + '7': 'Воскресенье' + }, + dayFallback: value => `день ${value}`, + everyDayAt: time => `Каждый день в ${time}`, + weekdaysAt: time => `По будням в ${time}`, + everyDayOfWeekAt: (day, time) => `Каждую неделю: ${day} в ${time}`, + monthlyOnDayAt: (dayOfMonth, time) => `Ежемесячно ${dayOfMonth}-го числа в ${time}`, + topOfHour: 'В начале каждого часа', + everyHourAt: minute => `Каждый час в :${minute}`, + newCron: 'Новый cron', + emptyDescNew: + 'Запланируйте промпт по cron-выражению. Hermes выполнит его и доставит результаты туда, куда вы укажете.', + emptyDescSearch: 'Попробуйте более общий поисковый запрос.', + emptyTitleNew: 'Пока нет запланированных задач', + emptyTitleSearch: 'Совпадений нет', + last: 'Последний:', + next: 'Следующий:', + noRuns: 'Запусков пока не было', + manage: 'Управление', + showRuns: 'Показать запуски', + hideRuns: 'Скрыть запуски', + runHistory: 'История запусков', + actionsTitle: 'Действия с cron-задачей', + resume: 'Возобновить cron', + pause: 'Приостановить cron', + resumeTitle: 'Возобновить', + pauseTitle: 'Пауза', + triggerNow: 'Запустить сейчас', + edit: 'Изменить cron', + deleteTitle: 'Удалить cron-задачу?', + deleteDescPrefix: 'Это навсегда удалит ', + deleteDescSuffix: '. Задача сразу перестанет срабатывать.', + deleting: 'Удаление...', + resumed: 'Cron возобновлён', + paused: 'Cron приостановлен', + triggered: 'Cron запущен', + deleted: 'Cron удалён', + created: 'Cron создан', + updated: 'Cron обновлён', + failedLoad: 'Не удалось загрузить cron-задачи', + failedUpdate: 'Не удалось обновить cron-задачу', + failedTrigger: 'Не удалось запустить cron-задачу', + failedDelete: 'Не удалось удалить cron-задачу', + failedSave: 'Не удалось сохранить cron-задачу', + editTitle: 'Изменить cron-задачу', + createTitle: 'Новая cron-задача', + editDesc: 'Измените расписание, промпт или получателя. Изменения вступят в силу при следующем запуске.', + createDesc: 'Запланируйте автоматический запуск промпта. Используйте cron-синтаксис или обычную фразу вроде «каждые 15 минут».', + nameLabel: 'Имя', + namePlaceholder: 'Утренняя сводка', + promptLabel: 'Запрос', + promptPlaceholder: 'Подведи итоги непрочитанных тредов в Slack и пришли мне топ-5 на почту...', + frequencyLabel: 'Частота', + deliverLabel: 'Доставить в', + deliverNeedsHomeChannel: 'сначала задайте основной канал', + modelLabel: 'Модель', + modelDefault: 'По умолчанию (глобальная модель)', + customScheduleLabel: 'Своё расписание', + customPlaceholder: '0 9 * * * или по будням в 9:00', + customHint: 'Cron-выражение или фразы вроде «каждый час» или «по будням в 9:00».', + optional: 'Необязательно', + promptRequired: 'Промпт обязателен.', + promptScheduleRequired: 'Промпт и расписание обязательны.', + scheduleRequired: 'Расписание обязательно.', + scriptOnlyEditHint: 'Задача только со скриптом (без AI-промпта). ID задачи:', + saveChanges: 'Сохранить изменения', + createAction: 'Создать cron', + tabs: { + jobs: 'Задачи', + blueprints: 'Шаблоны' + }, + blueprints: { + tab: 'Шаблоны', + startFrom: 'Начать с', + custom: 'Пользовательский', + subtitle: 'Готовые автоматизации', + dialogDesc: 'Заполните детали и запланируйте задачу.', + scheduleIt: 'Запланировать', + scheduling: 'Планирование...', + scheduled: 'Шаблон запланирован', + loading: 'Загрузка шаблонов...', + failedLoad: 'Не удалось загрузить шаблоны', + emptyTitle: 'Шаблоны недоступны', + emptyDesc: 'На этом бэкенде нет доступных шаблонов автоматизации.' + } + }, + + artifacts: { + search: 'Поиск артефактов...', + refresh: 'Обновить артефакты', + refreshing: 'Обновление артефактов', + indexing: 'Индексация артефактов недавних сессий', + tabAll: 'Все', + tabImages: 'Изображения', + tabFiles: 'Файлы', + tabLinks: 'Ссылки', + noArtifactsTitle: 'Артефакты не найдены', + noArtifactsDesc: 'Сгенерированные изображения и файлы будут появляться здесь по мере работы сессий.', + failedLoad: 'Не удалось загрузить артефакты', + openFailed: 'Не удалось открыть', + itemsImage: 'изображения', + itemsLink: 'ссылки', + itemsFile: 'файлы', + itemsGeneric: 'элементы', + zero: '0', + rangeOf: (start, end, total) => `${start}–${end} из ${total}`, + goToPage: (itemLabel, page) => `Перейти на страницу ${page} — ${itemLabel}`, + colTitleLink: 'Заголовок ссылки', + colTitleFile: 'Имя', + colTitleDefault: 'Заголовок / имя', + colLocationLink: 'URL', + colLocationFile: 'Путь', + colLocationDefault: 'Расположение', + colSession: 'Сессия', + kindImage: 'изображение', + kindFile: 'файл', + kindLink: 'ссылка', + chat: 'Чат', + copyUrl: 'Копировать URL', + copyPath: 'Копировать путь' + }, + + artifactCard: { + kind: { code: 'Код', html: 'Интерактивная страница', svg: 'Графика' }, + generating: lines => `Генерация… ${lines} строк`, + versionBadge: count => `${count} версий`, + open: 'Открыть' + }, + + artifactPreview: { + versionOf: (current, total) => `v${current} из ${total}`, + olderVersion: 'Более старая версия', + newerVersion: 'Более новая версия', + latest: 'Последнее', + copyContent: 'Копировать содержимое', + download: 'Скачать', + openInBrowser: 'Открыть в браузере', + openInBrowserFailed: 'Не удалось открыть в браузере', + missingTitle: 'Артефакт недоступен', + missingBody: 'Этого артефакта больше нет в локальном реестре.' + }, + + sidebar: { + nav: { + 'new-session': 'Новая сессия', + skills: 'Возможности', + messaging: 'Мессенджеры', + artifacts: 'Артефакты', + cron: 'Запланированные задачи' + }, + searchAria: 'Поиск по сессиям', + searchPlaceholder: 'Поиск сессий…', + clearSearch: 'Очистить поиск', + noMatch: query => `Нет сессий по запросу «${query}».`, + results: 'Результаты', + pinned: 'Закреплённые', + sessions: 'Сессии', + cronJobs: 'Cron-задачи', + groupAriaGrouped: 'Показать сессии одним списком', + groupAriaUngrouped: 'Группировать сессии по рабочей области', + showProjects: 'Показать проекты', + showSessions: 'Показать сессии', + groupTitleGrouped: 'Разгруппировать сессии', + groupTitleUngrouped: 'Группировать по рабочей области', + allPinned: 'Здесь всё закреплено. Открепите чат, чтобы он появился в недавних.', + shiftClickHint: 'Shift-клик по чату закрепляет его', + noWorkspace: 'Без рабочей области', + projectEmpty: 'Нет сессий', + noSessions: 'Нет сессий', + noFilterMatches: 'Нет сессий, подходящих под эти фильтры', + projects: { + sectionLabel: 'Проекты', + home: 'Главная', + newButton: 'Новый проект', + createTitle: 'Новый проект', + createDesc: 'Назовите рабочую область и добавьте одну или несколько папок.', + renameTitle: 'Переименовать проект', + addFolderTitle: 'Добавить папку', + namePlaceholder: 'например, Skunkworks', + foldersLabel: 'Папки', + ideaLabel: 'Идея', + ideaPlaceholder: 'О чём этот проект? (сохраняется в IDEA.md)', + ideaGenerate: 'Сгенерировать идею', + ideaGenerating: 'Генерация…', + ideaShuffle: 'Перемешать шаблоны', + noFolders: 'Папки пока не добавлены.', + addFolder: 'Добавить папку', + primaryBadge: 'основная', + removeFolder: 'Убрать', + create: 'Создать', + menu: 'Действия', + menuRename: 'Переименование', + menuAppearance: 'Внешний вид', + noColor: 'Без цвета', + menuAddFolder: 'Добавить папку', + menuSetActive: 'Сделать активным', + menuDelete: 'Удалить', + moveToProject: 'Переместить в проект', + movedTo: name => `Перемещено в ${name}`, + moveFailed: 'Не удалось переместить сессию', + moveNoFolder: 'У этого проекта нет папки, куда можно переместить', + moveNoProjects: 'Других проектов нет', + reveal: 'Показать в папке', + copyPath: 'Копировать путь', + removeFromSidebar: 'Скрыть из боковой панели', + createFailed: 'Не удалось создать проект', + staleBackend: + 'Обновите бэкенд Hermes, чтобы создавать проекты — ваш бэкенд старше этого приложения (Настройки → Обновления → Бэкенд).', + deleteConfirm: 'Сохранённый проект будет удалён из Hermes. Файлы, git-репозитории и рабочие деревья останутся нетронутыми.', + startWork: 'Новое рабочее дерево', + newWorktreeTitle: 'Новое рабочее дерево', + newWorktreeDesc: 'Назовите ветку для этого рабочего дерева.', + branchPlaceholder: 'например, my-feature', + branchOff: () => ({ after: '', before: 'ответвить от ' }), + baseBranchPlaceholder: 'Поиск веток…', + baseBranchNone: 'Ветки не найдены', + startWorkFailed: 'Не удалось создать рабочее дерево', + worktreeProjectLabel: 'Проект', + worktreeProjectPlaceholder: 'Поиск проектов…', + worktreeProjectNone: 'Нет проектов с папкой', + convertBranch: 'Преобразовать ветку…', + convertBranchTitle: 'Преобразовать ветку', + convertBranchDesc: 'Откройте выгруженные ветки или создайте рабочее дерево для свободной ветки.', + convertBranchPlaceholder: 'Поиск веток…', + convertBranchInstead: 'Преобразовать существующую ветку', + branchOpenExisting: 'открыть', + branchSwitchHome: 'переключить основную', + branchCreateWorktree: 'новое рабочее дерево', + branchTrackRemote: 'отслеживать удалённую', + branchesLoading: 'Загрузка веток…', + noBranches: 'Ветки не найдены', + removeWorktree: 'Удалить рабочее дерево', + removeWorktreeFailed: 'Не удалось удалить рабочее дерево (незакоммиченные изменения?)', + removeWorktreeConfirm: + 'Удалить его из git (каталог рабочего дерева будет удалён, ветка останется) или просто скрыть дорожку из боковой панели, оставив рабочее дерево на диске.', + removeWorktreeDirty: + 'В этом рабочем дереве есть незакоммиченные изменения. Удалить принудительно (изменения будут потеряны) или просто скрыть дорожку и оставить его на диске.', + forceRemove: 'Принудительно удалить', + enter: label => `Открыть ${label}`, + reorder: label => `Изменить порядок ${label}`, + toggle: (label, open) => `${open ? 'Показать' : 'Скрыть'} сессии ${label}`, + back: 'Все проекты' + }, + newSessionIn: label => `Новая сессия в ${label}`, + showMoreIn: (count, label) => `Показать ещё ${count} в ${label}`, + loading: 'Загрузка…', + loadMore: 'Загрузить ещё', + loadCount: step => `Загрузить ещё ${step}`, + row: { + pin: 'Закрепить', + unpin: 'Открепить', + copyId: 'Копировать ID', + export: 'Экспорт', + branchFrom: 'Ветка', + rename: 'Переименование', + archive: 'В архив', + newWindow: 'Новое окно', + hideTabBar: 'Скрыть панель вкладок', + openInNewTab: 'Открыть в новой вкладке', + openInSplit: 'Открыть в разделённом виде', + copyIdFailed: 'Не удалось скопировать ID сессии', + sessionActions: 'Действия с сессией', + sessionRunning: 'Сессия выполняется', + needsInput: 'Требуется ваш ввод', + waitingForAnswer: 'Ожидает вашего ответа', + finishedUnread: 'Завершено — не прочитано', + backgroundRunning: 'Фоновая задача выполняется', + draftSession: 'Черновик — ничего не отправлено', + handoffOrigin: platform => `Передано из ${platform}`, + ownedByProfile: profile => `Профиль: ${profile}`, + renamed: 'Переименован', + renameFailed: 'Не удалось переименовать', + renameTitle: 'Переименовать сессию', + renameDesc: 'Оставьте пустым, чтобы очистить.', + untitledPlaceholder: 'Сессия без названия', + untitledChat: id => `Чат ${id}`, + messageCount: count => `${count} ${plural(count, 'сообщение', 'сообщения', 'сообщений')}`, + todoProgress: 'Задач выполнено', + ageNow: 'сейчас', + ageDay: 'д', + ageHour: 'ч', + ageMin: 'м' + }, + dateDivider: { + today: 'Ранее сегодня', + yesterday: 'Вчера', + thisWeek: 'Ранее на этой неделе', + lastWeek: 'На прошлой неделе', + thisMonth: 'Ранее в этом месяце' + }, + statusDivider: { + working: 'В работе', + done: 'Готово' + } + }, + + composer: { + message: 'Сообщение', + wakingProfile: profile => `Пробуждаем ${profile}…`, + placeholderStarting: 'Запуск Hermes...', + placeholderReconnecting: 'Переподключение к Hermes…', + placeholderFollowUp: 'Отправить уточнение', + newSessionPlaceholders: [ + 'Что будем создавать?', + 'Поставьте задачу Hermes', + 'О чём вы думаете?', + 'Опишите, что вам нужно', + 'За что возьмёмся?', + 'Спросите о чём угодно', + 'Начните с цели' + ], + followUpPlaceholders: [ + 'Отправьте уточнение', + 'Добавьте контекст', + 'Уточните запрос', + 'Что дальше?', + 'Продолжаем', + 'Развейте мысль', + 'Уточните или продолжите' + ], + startVoice: 'Начать голосовой диалог', + openDirective: 'Открыть', + queueMessage: 'Поставить сообщение в очередь', + steer: 'Скорректировать текущий запуск', + stop: 'Остановить', + send: 'Отправить', + speaking: 'Говорит', + transcribing: 'Расшифровка', + thinking: 'Думаю', + muted: 'Микрофон отключён', + listening: 'Слушаю', + muteMic: 'Отключить микрофон', + unmuteMic: 'Включить микрофон', + stopListening: 'Остановить прослушивание и отправить', + stopShort: 'Остановить', + endConversation: 'Завершить голосовой диалог', + endShort: 'Завершить', + stopDictation: 'Остановить диктовку', + transcribingDictation: 'Расшифровка диктовки', + voiceDictation: 'Голосовая диктовка', + speakReplies: 'Читать ответы вслух', + stopSpeakingReplies: 'Перестать читать ответы вслух', + wakeWordListening: phrase => `Ключевая фраза: «${phrase}» — слушаю`, + wakeWordOff: phrase => `Ключевая фраза: «${phrase}» — выключена`, + wakeWordPausedVoice: phrase => `Ключевая фраза: «${phrase}» — приостановлена во время голосового чата`, + lookupLoading: 'Поиск…', + lookupNoMatches: 'Совпадений нет.', + lookupTry: 'Попробуйте', + lookupOr: 'или', + commonCommands: 'Частые команды', + hotkeys: 'Горячие клавиши', + helpFooter: 'открывает полную панель · backspace закрывает', + commandDescs: { + '/help': 'полный список команд и горячих клавиш', + '/clear': 'начать новую сессию', + '/resume': 'возобновить прошлую сессию', + '/details': 'управлять уровнем детализации переписки', + '/copy': 'копировать выделение или последнее сообщение ассистента', + '/quit': 'выйти из hermes' + }, + hotkeyDescs: { + 'composer.mention': 'ссылки на файлы, папки, URL, git', + 'composer.slash': 'палитра slash-команд', + 'composer.help': 'эта быстрая справка (delete — закрыть)', + 'composer.sendNewline': 'отправить · Shift+Enter — новая строка', + 'composer.sendQueued': 'отправить следующий ход из очереди', + 'keybinds.openPanel': 'все горячие клавиши', + 'composer.cancel': 'закрыть окно · отменить выполнение', + 'composer.history': 'переключение окна / истории' + }, + attachUrlTitle: 'Прикрепить URL', + attachUrlDesc: 'Hermes загрузит страницу и добавит её как контекст для этого хода.', + urlPlaceholder: 'https://example.com/post', + urlHintPre: 'Укажите полный URL, например ', + attach: 'Прикрепить', + queued: count => `В очереди: ${count}`, + queuedPaused: count => `В очереди: ${count} — приостановлено`, + attachmentOnly: 'Ход только с вложением', + emptyTurn: 'Пустой ход', + attachments: count => `${count} ${plural(count, 'вложение', 'вложения', 'вложений')}`, + editingInComposer: 'Редактирование в композере', + editingQueuedInComposer: 'Редактирование хода из очереди в композере', + queueEdit: 'Редактировать', + queueSendNext: 'Следующий', + queueSteer: 'Скорректировать — перенаправить текущий ход', + queueSend: 'Отправить', + queueDelete: 'Удалить', + queueResume: 'Возобновить', + queueResumeTip: 'Приостановлено кнопкой «Стоп» — возобновите отправку ходов из очереди', + queueStuckTitle: 'Сообщение из очереди не отправлено', + queueStuckBody: 'Ход из очереди не удалось отправить. Он всё ещё в очереди — попробуйте отправить снова.', + previewUnavailable: 'Предпросмотр недоступен', + previewLabel: label => `Предпросмотр ${label}`, + couldNotPreview: label => `Не удалось показать предпросмотр ${label}`, + removeAttachment: label => `Убрать ${label}`, + dictating: 'Диктовка', + preparingAudio: 'Подготовка звука', + speakingResponse: 'Озвучивание ответа', + readingAloud: 'Чтение вслух', + themeSuggestions: 'Предложения тем для десктопа', + noMatchingThemes: 'Подходящих тем нет.', + themeTryPre: 'Попробуйте ', + themeTryPost: '.', + attachLabel: 'Прикрепить', + files: 'Файлы…', + folder: 'Папка…', + images: 'Изображения…', + pasteImage: 'Вставить изображение', + url: 'URL…', + promptSnippets: 'Шаблоны промптов…', + tipPre: 'Подсказка: введите ', + tipPost: ' для ссылок на файлы прямо в тексте.', + snippetsTitle: 'Шаблоны промптов', + snippetsDesc: 'Выберите готовый промпт, чтобы вставить его в композер.', + dropFiles: 'Перетащите файлы, чтобы прикрепить', + dropSession: 'Перетащите, чтобы связать этот чат', + mcpSuggestions: { + label: server => `Добавить ${server}`, + tip: keyword => `Предложено, потому что вы упомянули «${keyword}» — нажмите, чтобы подключить`, + connecting: server => `Подключение ${server}…`, + cancelTip: 'Нажмите, чтобы отменить', + added: server => `${server} добавлен`, + addedTip: 'Подключено — его инструменты готовы в этом чате', + connectFailed: server => `Не удалось подключить ${server}` + }, + skillSuggestions: { + label: skill => `Использовать скилл: ${skill}`, + tip: skill => `Вы упомянули «${skill}» — нажмите, чтобы начать с этого скилла`, + done: skill => `Добавлено /${skill}`, + doneTip: 'Скилл загрузится при отправке' + }, + repairSuggestions: { + label: server => `Переподключить ${server}`, + tip: server => `Вызов ${server} завершился ошибкой подключения`, + working: server => `Переподключение ${server}…`, + workingTip: 'Нажмите, чтобы отменить', + done: server => `${server} переподключён`, + doneTip: 'Новые учётные данные активны в этом чате', + failed: server => `Не удалось переподключить ${server}` + }, + cronSuggestions: { + label: 'Запланировать', + tip: phrase => `«${phrase}» звучит как регулярная задача — запустите её по расписанию`, + prefix: 'Настроить это как запланированную задачу:', + done: 'Отмечено для планирования', + doneTip: 'Отправьте — и агент создаст задачу' + }, + snippets: { + codeReview: { + label: 'Ревью кода', + description: 'Проверить текущие изменения на регрессии, пропущенные пограничные случаи и отсутствующие тесты.', + text: 'Проверьте это на ошибки, регрессии и отсутствующие тесты.' + }, + implementationPlan: { + label: 'План реализации', + description: 'Наметить подход до правок кода, чтобы диff остался компактным.', + text: 'Составьте краткий план реализации до изменения кода.' + }, + explainThis: { + label: 'Объяснить это', + description: 'Разобрать, как работает выбранный код, со ссылками на ключевые файлы.', + text: 'Объясните, как это работает, и укажите ключевые файлы.' + } + } + }, + + statusStack: { + agents: 'Агенты', + background: count => `${count} в фоне`, + goalActive: 'Цель активна', + goalDone: 'Цель достигнута', + goalPaused: 'Цель приостановлена', + goalWaiting: 'Цель ожидает', + subagents: count => `${count} ${plural(count, 'субагент', 'субагента', 'субагентов')}`, + todos: (done, total) => `Задачи ${done}/${total}`, + running: 'Выполняется', + stop: 'Остановить', + dismiss: 'Закрыть', + exit: code => `код выхода ${code}`, + coding: { + title: 'Рабочее дерево', + noBranch: 'Нет ветки', + detached: 'detached', + clean: 'Чисто', + changed: count => `${count} изменено`, + ahead: count => `${count} впереди`, + behind: count => `${count} позади`, + review: 'Обзор', + close: 'Закрыть', + openChanges: 'Открыть изменения', + openFile: 'Открыть файл', + stage: 'В индекс', + unstage: 'Убрать из индекса', + stageAll: 'Всё в индекс', + viewAsTree: 'Показать деревом', + viewAsList: 'Показать списком', + revert: 'Откатить', + revertAll: 'Откатить всё', + revertConfirm: 'Отменить изменения в этом файле и вернуть его к состоянию коммита? Это действие нельзя отменить.', + revertAllConfirm: 'Отменить все изменения и вернуть файлы к состоянию коммита? Это действие нельзя отменить.', + staged: 'В индексе', + noChanges: 'Нет изменений', + notRepo: 'Это не git-репозиторий', + noDiff: 'Нет изменений для показа', + scopeUncommitted: 'Незакоммиченные', + scopeBranch: 'Ветка', + scopeLastTurn: 'Последний ход', + commit: 'Коммит', + commitAndPush: 'Коммит и push', + commitPlaceholder: 'Сообщение (⌘↵ — закоммитить)', + generateCommitMessage: 'Сгенерировать сообщение коммита', + stopGenerating: 'Остановить генерацию', + createPr: 'Создать PR', + openPr: 'Открыть PR', + ghMissing: 'Установите GitHub CLI (gh) и войдите, чтобы открывать PR', + agentShip: 'Попросить Hermes открыть PR', + agentShipPrompt: + 'Проверь текущие изменения, закоммить их с понятным сообщением в стиле conventional commit, отправь ветку и открой pull request.', + newBranch: 'Новая ветка', + branchOffFrom: base => `Новая ветка от ${base}`, + switchTo: branch => `Переключиться на ${branch}`, + switchFailed: branch => `Не удалось переключиться на ${branch}`, + worktrees: 'Рабочие деревья' + } + }, + + updates: { + stages: { + idle: 'Готовимся…', + prepare: 'Готовимся…', + fetch: 'Загрузка…', + pull: 'Почти готово…', + pydeps: 'Завершаем…', + update: 'Обновление Hermes…', + rebuild: 'Пересобираем приложение…', + restart: 'Перезапускаем Hermes…', + done: 'Обновление завершено', + manual: 'Обновление из терминала', + guiSkew: 'Обновите приложение', + error: 'Обновление приостановлено' + }, + checking: 'Ищем обновления…', + checkFailedTitle: 'Не удалось проверить обновления', + tryAgain: 'Попробовать снова', + notAvailableTitle: 'Обновление недоступно', + unsupportedMessage: 'Эта версия Hermes не может обновить себя из приложения.', + connectionRetry: 'Проверьте соединение и попробуйте снова.', + latestBody: 'У вас последняя версия.', + latestBodyBackend: 'На бэкенде последняя версия.', + allSetTitle: 'Всё готово', + availableTitle: 'Доступно новое обновление', + availableBody: 'Новая версия Hermes готова к установке.', + availableTitleBackend: 'Доступно обновление бэкенда', + availableBodyBackend: 'Готова к установке более новая версия подключённого бэкенда Hermes.', + availableBodyNoChangelog: 'Более новая версия готова. Примечания к выпуску недоступны для этого типа установки.', + updateNow: 'Обновить сейчас', + maybeLater: 'Может быть, позже', + moreChanges: count => `+ ещё ${count} ${plural(count, 'изменение', 'изменения', 'изменений')}.`, + manualTitle: 'Обновление из терминала', + manualBody: 'Вы установили Hermes из командной строки, поэтому обновления выполняются там же. Вставьте это в терминал:', + manualPickedUp: 'Hermes подхватит новую версию при следующем запуске.', + guiSkewTitle: 'Обновите приложение', + guiSkewBody: + 'Бэкенд обновлён, но пакет этого приложения не изменился. Обновите или переустановите Hermes Desktop (ваш AppImage / .deb / .rpm), чтобы версии совпали.', + copy: 'Копировать', + copied: 'Скопировано', + done: 'Готово', + applyingBody: + 'Обновление Hermes продолжится в отдельном окне, и Hermes откроется автоматически по завершении. Пожалуйста, не открывайте Hermes сами, пока идёт обновление.', + applyingBodyBackend: + 'Удалённый бэкенд применяет обновление и будет перезапущен. Hermes переподключится автоматически.', + applyingClose: 'Это окно закроется на время обновления, затем Hermes откроется сам.', + errorTitle: 'Обновление не завершилось', + errorBody: 'Не волнуйтесь — ничего не потеряно. Можно попробовать снова.', + notNow: 'Не сейчас', + applyStatus: { + preparing: 'Обновляем бэкенд…', + pulling: 'Бэкенд обновляется…', + restarting: 'Бэкенд перезапускается, чтобы загрузить обновление…', + notAvailable: 'Обновление недоступно для этого бэкенда.', + failed: 'Не удалось обновить бэкенд.', + noReturn: 'Бэкенд не вернулся в сеть. Обновление могло не завершиться — проверьте хост бэкенда.' + } + }, + + install: { + stageStates: { + pending: 'В ожидании', + running: 'Установка', + succeeded: 'Готово', + skipped: 'Пропущено', + failed: 'Не удалось' + }, + oneTimeTitle: 'Hermes нужна разовая установка', + unsupportedDesc: platform => + `Автоматическая установка при первом запуске пока недоступна на ${platform}. Откройте терминал, выполните команду ниже и перезапустите приложение. При следующих запусках этот шаг будет пропущен.`, + installCommand: 'Команда установки', + copyCommand: 'Копировать команду', + viewDocs: 'Открыть документацию по установке', + installTo: 'Будет установлено в', + retryAfterRun: 'Я выполнил — повторить', + setupChoiceTitle: 'Настройка Hermes Desktop', + setupChoiceDesc: + 'Подключите это приложение к уже работающему Hermes gateway или установите Hermes локально на этом компьютере.', + connectExistingTitle: 'Подключиться к существующему Hermes', + connectExistingShort: 'Подключить существующий', + connectExistingDesc: 'Использовать удалённый бэкенд с токеном сессии или входом через браузер. Локальная установка не начнётся.', + installLocalTitle: 'Установить Hermes локально', + installLocalDesc: 'Скачать Hermes, создать окружение Python и запустить бэкенд на этом компьютере.', + localStartUnavailable: 'Не удалось начать локальную установку. Перезапустите Hermes Desktop и попробуйте снова.', + remoteSetupTitle: 'Подключиться к существующему Hermes', + remoteSetupDesc: 'Введите URL вашего gateway. Hermes Desktop определит, нужен ли токен или вход через браузер.', + remoteUrlTitle: 'URL gateway', + remoteUrlDesc: 'Укажите базовый URL Hermes gateway, включая https:// для удалённого доступа.', + remoteUrlPlaceholder: 'https://gateway.example.com/hermes', + probing: 'Определение аутентификации gateway...', + probeError: 'Не удалось связаться с этим Hermes gateway.', + identityProvider: 'Ваш поставщик идентификации', + authTitle: 'Аутентификация', + authNeedsOauth: provider => `Войдите через ${provider}, прежде чем проверять этот gateway.`, + authSignedIn: 'Вход через браузер завершён.', + connected: 'Подключено', + signIn: 'Войти', + signInWith: provider => `Войти через ${provider}`, + enterUrlFirst: 'Сначала введите URL gateway.', + signInIncomplete: 'Окно входа закрылось до завершения аутентификации.', + tokenTitle: 'Токен сессии', + tokenDesc: 'Вставьте токен сессии из файла .env удалённого gateway.', + pasteSessionToken: 'Вставьте токен сессии', + incompleteSignInTest: 'Войдите, прежде чем проверять этот gateway с OAuth.', + incompleteTokenTest: 'Введите токен сессии, прежде чем проверять этот gateway.', + testConnection: 'Проверить подключение', + testSucceeded: (baseUrl, version) => `Подключено к ${baseUrl}${version ? ` (${version})` : ''}.`, + applyRemote: 'Применить и переподключиться', + backToSetup: 'Назад', + failedTitle: 'Установка не удалась', + settingUpTitle: 'Настройка Hermes Agent', + finishingTitle: 'Завершаем', + failedDesc: + 'Один из шагов установки завершился ошибкой. В Windows это бывает, если запущен другой Hermes CLI или экземпляр десктопа. Остановите все запущенные экземпляры Hermes и повторите попытку. Подробности ниже или в логе десктопа.', + activeDesc: + 'Это разовая настройка. Установщик Hermes загружает зависимости и настраивает ваш компьютер. При следующих запусках этот шаг будет пропущен.', + progress: (completed, total) => `Выполнено шагов: ${completed} из ${total}`, + currentStage: stage => ` — сейчас: ${stage}`, + fetchingManifest: 'Получение манифеста установщика...', + error: 'Ошибка', + hideOutput: 'Скрыть вывод установщика', + showOutput: 'Показать вывод установщика', + lines: count => `${count} ${plural(count, 'строка', 'строки', 'строк')}`, + noOutput: 'Вывода пока нет.', + cancelling: 'Отмена...', + cancelInstall: 'Отменить установку', + transcriptSaved: 'Полный журнал сохранён в', + copiedOutput: 'Скопировано!', + copyOutput: 'Копировать вывод', + reloadRetry: 'Перезагрузить и повторить' + }, + + onboarding: { + headerTitle: 'Давайте настроим Hermes Agent', + headerDesc: 'Подключите провайдера моделей, чтобы начать общение. Большинство вариантов — в один клик.', + preparingInstall: 'Hermes завершает установку. При первом запуске это обычно занимает меньше минуты.', + starting: 'Запуск Hermes…', + lookingUpProviders: 'Поиск провайдеров...', + collapse: 'Свернуть', + otherProviders: 'Другие провайдеры', + haveApiKey: 'У меня есть API-ключ', + chooseLater: 'Я выберу провайдера позже', + recommended: 'Рекомендуется', + connected: 'Подключено', + featuredPitch: 'Одна подписка, 300+ передовых моделей — рекомендуемый способ работы с Hermes', + fireworksPitch: 'Прямой API моделей — передовые модели на Fireworks', + openRouterPitch: 'Один ключ, сотни моделей — надёжный выбор по умолчанию', + apiKeyOptions: { + fireworks: { + short: 'прямой API моделей', + description: 'Прямой доступ к моделям, размещённым в Fireworks AI.' + }, + openrouter: { + short: 'один ключ, много моделей', + description: 'Сотни моделей по одному ключу. Хороший выбор по умолчанию для новых установок.' + }, + openai: { short: 'модели класса GPT', description: 'Прямой доступ к моделям OpenAI.' }, + gemini: { short: 'модели Gemini', description: 'Прямой доступ к моделям Google Gemini.' }, + xai: { short: 'модели Grok', description: 'Прямой доступ к моделям xAI Grok.' }, + local: { + short: 'self-hosted', + description: 'Направьте Hermes на локальный или self-hosted OpenAI-совместимый endpoint (vLLM, llama.cpp, Ollama и т. д.).' + } + }, + backToSignIn: 'Назад к входу', + getKey: 'Получить ключ', + replaceCurrent: 'Заменить текущее значение', + pasteApiKey: 'Вставьте API-ключ', + localApiKeyPlaceholder: 'API-ключ (необязательно — только если ваш endpoint его требует)', + couldNotSave: 'Не удалось сохранить учётные данные.', + connecting: 'Подключение', + update: 'Обновить', + flowSubtitles: { + pkce: 'Открывает браузер для входа, затем работа продолжится здесь', + device_code: 'Открывает страницу подтверждения в браузере — Hermes подключится автоматически', + external: 'Войдите один раз в терминале, затем возвращайтесь в чат' + }, + startingSignIn: provider => `Начинаем вход для ${provider}...`, + verifyingCode: provider => `Проверяем ваш код через ${provider}...`, + connectedProvider: provider => `${provider} подключён`, + connectedPicking: provider => `${provider} подключён. Подбираем модель по умолчанию...`, + signInFailed: 'Не удалось войти. Попробуйте снова.', + pickDifferentProvider: 'Выбрать другого провайдера', + signInWith: provider => `Войти через ${provider}`, + openedBrowser: provider => `Мы открыли ${provider} в вашем браузере.`, + authorizeThere: 'Разрешите доступ Hermes там.', + copyAuthCode: 'Скопируйте код авторизации и вставьте его ниже.', + pasteAuthCode: 'Вставьте код авторизации', + reopenAuthPage: 'Открыть страницу авторизации заново', + autoBrowser: provider => + `Мы открыли ${provider} в вашем браузере. Разрешите доступ Hermes — и подключение произойдёт автоматически, ничего копировать не нужно.`, + reopenSignInPage: 'Открыть страницу входа заново', + waitingAuthorize: 'Ожидаем вашего подтверждения...', + externalPending: provider => + `${provider} выполняет вход через свой CLI. Запустите эту команду в терминале, затем вернитесь и нажмите «Я вошёл»:`, + signedIn: 'Я вошёл', + deviceCodeOpened: provider => `Мы открыли ${provider} в вашем браузере. Введите там этот код:`, + reopenVerification: 'Снова открыть страницу подтверждения', + copy: 'Копировать', + defaultModel: 'Модель по умолчанию', + freeTier: 'Бесплатный тариф', + pro: 'Pro', + free: 'Бесплатно', + price: (input, output) => `${input} вход / ${output} выход за Mtok`, + change: 'Изменить', + startChatting: 'Начать', + docs: provider => `документация ${provider}` + }, + + modelPicker: { + title: 'Сменить модель', + current: 'текущая:', + unknown: '(неизвестно)', + search: 'Фильтр провайдеров и моделей...', + noModels: 'Модели не найдены.', + addProvider: 'Добавить провайдера', + loadFailed: 'Не удалось загрузить модели', + noAuthenticatedProviders: 'Нет авторизованных провайдеров.', + pro: 'Pro', + proNeedsSubscription: 'Для Pro-моделей нужна платная подписка Nous.', + free: 'Бесплатно', + freeTier: 'Бесплатный тариф', + priceTitle: 'Цена входа / выхода за миллион токенов', + wasPrice: 'было' + }, + + modelVisibility: { + title: 'Модели', + search: 'Поиск моделей', + noAuthenticatedProviders: 'Нет авторизованных провайдеров.', + addProvider: 'Добавить провайдера…' + }, + + shell: { + windowControls: 'Управление окном', + paneControls: 'Управление панелями', + appControls: 'Управление приложением', + modelMenu: { + search: 'Поиск моделей', + noModels: 'Модели не найдены', + editModels: 'Изменить модели…', + refreshModels: 'Обновить модели', + fast: 'Быстро' + }, + modelOptions: { + noOptions: 'Для этой модели нет параметров', + options: 'Параметры', + thinking: 'Думаю', + fast: 'Быстро', + effort: 'Усилия', + minimal: 'Минимальные', + low: 'Низкие', + medium: 'Средние', + high: 'Высокие', + xhigh: 'Очень высокие', + max: 'Максимум', + ultra: 'Ультра', + updateFailed: 'Не удалось обновить параметр модели', + fastFailed: 'Не удалось обновить быстрый режим' + }, + gatewayMenu: { + gateway: 'Шлюз', + connected: 'Подключено', + connecting: 'Подключение', + offline: 'Не в сети', + inferenceReady: 'Инференс готов', + inferenceNotReady: 'Инференс не готов', + checkingInference: 'Проверка инференса', + disconnected: 'Отключено', + openSystem: 'Открыть системную панель', + connection: label => `Подключение: ${label}`, + recentActivity: 'Недавняя активность', + viewAllLogs: 'Смотреть все логи →', + messagingPlatforms: 'Платформы обмена сообщениями' + }, + approvalMode: { + title: 'Режим одобрения', + ariaLabel: mode => `Режим одобрения: ${mode}`, + manual: 'Вручную', + manualDescription: 'Спрашивать перед действиями, требующими одобрения', + smart: 'Умный', + smartDescription: 'Автоматически оценивать действия и спрашивать при необходимости', + off: 'Выкл.', + offDescription: 'Выполнять без запросов на одобрение' + }, + statusbar: { + unknown: 'неизвестно', + restart: 'перезапуск', + update: 'обновить', + updateInProgress: 'Идёт обновление', + commitsBehind: (count, branch) => `Отстаёт от ${branch} на ${count} ${plural(count, 'коммит', 'коммита', 'коммитов')}`, + desktopVersion: version => `Hermes Desktop v${version}`, + backendVersion: version => `Бэкенд v${version}`, + clientLabel: version => `клиент v${version}`, + connectionSsh: host => `SSH: ${host}`, + connectionRemote: host => `Удалённо: ${host}`, + connectionCloud: host => `Cloud: ${host}`, + connectionCloudTooltip: host => `Hermes Cloud · ${host}`, + connectionSshTooltip: host => `SSH · ${host}`, + connectionRemoteTooltip: host => `Удалённо · ${host}`, + backendLabel: version => `бэкенд v${version}`, + commit: sha => `коммит ${sha}`, + branch: branch => `ветка ${branch}`, + closeCommandCenter: 'Закрыть центр управления', + openCommandCenter: 'Открыть центр управления', + showTerminal: 'Показать терминал', + hideTerminal: 'Скрыть терминал', + gateway: 'Шлюз', + gatewayReady: 'готов', + gatewayNeedsSetup: 'нужна настройка', + gatewayChecking: 'проверка', + gatewayConnecting: 'подключение', + gatewayOffline: 'не в сети', + gatewayRestarting: 'перезапуск…', + gatewayTitle: 'Шлюз', + customizeTitle: 'Показывать в строке состояния', + hideStatusbar: 'Скрыть строку состояния', + resetStatusbar: 'Сбросить к значениям по умолчанию', + toggleApprovalMode: 'Одобрения', + toggleBackendVersion: 'Версия бэкенда', + toggleCommandCenter: 'Центр управления', + toggleContextUsage: 'Индикатор контекста', + toggleRunningTimer: 'Таймер хода', + toggleSessionTimer: 'Таймер сессии', + toggleTerminal: 'Терминал', + toggleVersion: 'Версия и обновления', + toggleWorkspace: 'Рабочая область', + agents: 'Агенты', + closeAgents: 'Закрыть агентов', + openAgents: 'Открыть агентов', + subagents: count => `${count} ${plural(count, 'субагент', 'субагента', 'субагентов')}`, + failed: count => `${count} с ошибкой`, + running: count => `${count} выполняется`, + cron: 'Cron', + openCron: 'Открыть cron-задачи', + webhooks: 'Webhooks', + openWebhooks: 'Открыть webhooks', + starmap: 'Граф памяти', + openStarmap: 'Открыть граф памяти', + turnRunning: 'Выполняется', + contextUsage: 'Использование контекста', + contextUsagePanel: { + categories: { + conversation: 'Диалог', + mcp: 'MCP', + memory: 'Память', + rules: 'Правила', + skills: 'Навыки', + subagent_definitions: 'Описания субагентов', + system_prompt: 'Системный промпт', + tool_definitions: 'Описания инструментов' + }, + empty: 'Пока нет данных о контексте', + loading: 'Загрузка разбивки…', + percentFull: percent => `${percent}% заполнено`, + title: 'Использование контекста', + tokenSummary: (used, max) => `${used} / ${max} токенов` + }, + session: 'Сессия', + yoloOn: 'YOLO включён — опасные команды одобряются автоматически. Shift+клик переключает глобально.', + yoloOff: 'YOLO выключен. Shift+клик переключает глобально.', + modelNone: 'нет', + noModel: 'нет модели', + switchModel: 'Сменить модель', + openModelPicker: 'Открыть выбор модели', + modelPinned: 'закреплено вами; новые чаты используют эту модель вместо той, что задана в настройках', + modelTitle: (provider, model) => `Модель · ${provider}: ${model}`, + providerModelTitle: (provider, model) => `${provider} · ${model}` + } + }, + + rightSidebar: { + aria: 'Правая боковая панель', + panelsAria: 'Панели правой боковой панели', + files: 'Файловая система', + terminal: 'Терминал', + noFolderSelected: 'Папка не выбрана', + changeCwdTitle: 'Сменить рабочую папку', + remotePickerTitle: 'Выбор удалённой папки', + remotePickerDescription: 'Обзор папок на подключённом бэкенде.', + remotePickerSelect: 'Выбрать папку', + folderTip: cwd => cwd, + openFolder: 'Открыть папку', + refreshTree: 'Обновить дерево', + collapseAll: 'Свернуть все папки', + previewUnavailable: 'Предпросмотр недоступен', + couldNotPreview: path => `Не удалось показать предпросмотр ${path}`, + noProjectTitle: 'Нет проекта', + noProjectBody: 'Откройте проект, чтобы просматривать файлы и изменения.', + noProjectOpen: 'Проект не открыт', + noDiffs: 'Нет различий', + unreadableTitle: 'Нечитаемо', + unreadableBody: error => `Не удалось прочитать эту папку (${error}).`, + emptyTitle: 'Пусто', + emptyBody: 'Эта папка пуста.', + treeErrorTitle: 'Ошибка дерева', + treeErrorBody: 'При отображении этой папки произошла ошибка файлового дерева.', + tryAgain: 'Попробовать снова', + loadingTree: 'Загрузка файлового дерева', + loadingFiles: 'Загрузка файлов', + terminalHide: 'Скрыть терминал', + terminalsAria: 'Терминалы', + terminalNew: 'Новый терминал', + terminalCloseOthers: 'Закрыть другие', + terminalCloseAll: 'Закрыть все', + addToChat: 'Добавить в чат' + }, + + preview: { + tab: 'Предпросмотр', + closePane: 'Закрыть панель предпросмотра', + loading: 'Загрузка предпросмотра', + unavailable: 'Предпросмотр недоступен', + opening: 'Открытие…', + hide: 'Скрыть', + openPreview: 'Открыть предпросмотр', + openInBrowser: 'Открыть в браузере', + linkHint: '⌘/Ctrl+клик для панели предпросмотра', + sourceLineTitle: 'Клик для выделения · Shift+клик для расширения · перетащите в композер', + source: 'ИСХОДНИК', + renderedPreview: 'ПРЕДПРОСМОТР', + diff: 'РАЗЛИЧИЯ', + unknownSize: 'размер неизвестен', + binaryTitle: 'Похоже на двоичный файл', + binaryBody: label => `Предпросмотр ${label} может показать нечитаемый текст.`, + largeTitle: 'Файл большой', + largeBody: (label, size) => `${label} — ${size}. Hermes покажет только первые 512 КБ.`, + previewAnyway: 'Всё равно показать', + truncated: 'Показаны первые 512 КБ.', + noInlineTitle: 'Нет встроенного предпросмотра', + noInlineBody: mimeType => `${mimeType || 'Этот тип файла'} всё равно можно прикрепить как контекст.`, + edit: 'Редактировать', + editing: 'Редактирование', + unsavedChanges: 'Несохранённые изменения', + saveFailed: message => `Не удалось сохранить: ${message}`, + diskChangedTitle: 'Файл изменён на диске', + diskChangedBody: + 'Этот файл изменился с момента открытия. Перезаписать вашей версией или отменить правки и перезагрузить?', + overwrite: 'Перезаписать', + discardReload: 'Отменить и перезагрузить', + console: { + deselect: 'Снять выделение', + select: 'Выделить запись', + copyFailed: 'Не удалось скопировать вывод консоли', + copyEntry: 'Копировать эту запись', + sendEntry: 'Отправить эту запись в чат', + messages: count => `${count} сообщений консоли`, + resize: 'Изменить размер консоли предпросмотра', + title: 'Консоль предпросмотра', + selected: count => `${count} выбрано`, + sendToChat: 'Отправить в чат', + copySelected: 'Копировать выбранное в буфер', + copyAll: 'Копировать всё в буфер', + copy: 'Копировать', + clear: 'Очистить', + empty: 'Нет сообщений консоли.', + promptHeader: 'Консоль предпросмотра:', + sentTitle: 'Отправлено в чат', + sentMessage: count => `${count} ${plural(count, 'запись', 'записи', 'записей')} журнала добавлено в композер` + }, + web: { + appFailedToBoot: 'Не удалось запустить приложение предпросмотра', + serverNotFound: 'Сервер не найден', + failedToLoad: 'Не удалось загрузить предпросмотр', + tryAgain: 'Попробовать снова', + restarting: 'Hermes перезапускается…', + askRestart: 'Попросить Hermes перезапустить сервер', + lookingRestart: taskId => `Hermes ищет сервер предпросмотра для перезапуска (${taskId})`, + restartingTitle: 'Перезапуск сервера предпросмотра', + restartingMessage: 'Hermes работает в фоне. Следите за консолью предпросмотра.', + startRestartFailed: message => `Не удалось начать перезапуск сервера: ${message}`, + restartFailed: 'Не удалось перезапустить сервер', + hideConsole: 'Скрыть консоль предпросмотра', + showConsole: 'Показать консоль предпросмотра', + hideDevTools: 'Скрыть DevTools предпросмотра', + openDevTools: 'Открыть DevTools предпросмотра', + finishedRestarting: message => `Hermes завершил перезапуск сервера предпросмотра${message ? `: ${message}` : ''}`, + failedRestarting: message => `Не удалось перезапустить сервер: ${message}`, + unknownError: 'неизвестная ошибка', + restartedTitle: 'Сервер предпросмотра перезапущен', + reloadingNow: 'Перезагрузка предпросмотра.', + restartFailedTitle: 'Не удалось перезапустить предпросмотр', + restartFailedMessage: 'Hermes не смог перезапустить сервер.', + stillWorking: + 'Hermes продолжает работу, но результат перезапуска ещё не получен. Команда сервера может выполняться в приоритетном режиме.', + workspaceReloading: 'Рабочая область изменена, перезагрузка предпросмотра', + fileChanged: url => `Файл изменён, перезагрузка предпросмотра: ${url}`, + filesChanged: (count, url) => `Изменений файлов: ${count}, перезагружаем предпросмотр: ${url}`, + watchFailed: message => `Не удалось отслеживать файл предпросмотра: ${message}`, + moduleMimeDescription: + 'Модульные скрипты обслуживаются с неверным MIME-типом. Обычно это означает, что статический сервер отдаёт Vite/React-приложение вместо dev-сервера проекта.', + loadFailedConsole: (code, message) => `Ошибка загрузки${code ? ` (${code})` : ''}: ${message}`, + unreachableDescription: 'Страница предпросмотра недоступна.', + openTarget: url => `Открыть ${url}`, + fallbackTitle: 'Предпросмотр' + } + }, + + zones: { + showHeader: 'Показать заголовок', + hideHeader: 'Скрыть заголовок', + minimize: 'Свернуть', + restore: 'Восстановить', + closeRunningTitle: 'Закрыть активную вкладку?', + closeRunningBody: + 'Этот чат ещё работает (или ожидает ваш ввод). Закрытие вкладки скроет его — сессия сохранит прогресс и может быть открыта из боковой панели.', + closeRunningConfirm: 'Закрыть вкладку', + reload: 'Перезагрузить', + closeOthers: 'Закрыть другие', + closeToRight: 'Закрыть справа', + closeAll: 'Закрыть все', + newSessionTab: 'Новая вкладка сессии', + pluginDisabled: pluginId => `Плагин «${pluginId}» отключён`, + pluginDisabledBody: 'Включите его в Настройки → Плагины, чтобы вернуть панель.', + missingPane: paneId => `отсутствует панель: ${paneId}`, + editTitle: 'Макеты', + editHint: 'Выберите макет или перетащите панели между зонами.', + reset: 'Сбросить', + templates: 'Шаблоны', + custom: 'Пользовательский', + newGridLayout: 'Новый сеточный макет', + saveCurrentAs: 'Сохранить текущее расположение как шаблон', + nameLayoutPlaceholder: 'Название макета…', + deletePreset: name => `Удалить ${name}`, + zoneEditorTitle: 'Редактор зон', + editorHintPre: 'клик для разделения · ', + editorHintPost: ' меняет направление линии · перетащите через зоны для объединения · перетащите общие границы для изменения размера', + templateColumns: 'Столбцы', + templateRows: 'Строки', + templateGrid: 'Сетка', + templatePriority: 'Приоритет', + zoneTag: index => `зона ${index}`, + mergeZones: count => `Объединить ${count} зон`, + customZoneName: count => `Пользовательская ${count}-зонная`, + layoutNamePlaceholder: fallback => `Название макета (${fallback})`, + saveApply: 'Сохранить и применить', + notExpressible: 'это расположение имеет блокировку (вертушка) — пока не выражается через вложенные разделения', + zoneCount: count => `${count} зон`, + tabCount: count => `${count} вкладок` + }, + + assistant: { + thread: { + loadingSession: 'Загрузка сессии', + showEarlier: 'Показать более ранние сообщения', + loadingResponse: 'Hermes загружает ответ', + resumeWhenBackgroundDone: count => + count === 1 + ? 'Возобновится после завершения фоновой задачи' + : `Возобновится после завершения ${count} фоновых задач`, + thinking: 'Думаю', + thought: 'Мысль', + thoughtBriefly: 'Краткая мысль', + thoughtFor: duration => `Думал ${duration}`, + today: time => `Сегодня, ${time}`, + yesterday: time => `Вчера, ${time}`, + copy: 'Копировать', + refresh: 'Обновить', + moreActions: 'Ещё действия', + branchNewChat: 'Ответвить в новый чат', + react: 'Реакция', + dismissError: 'Закрыть ошибку', + filesChanged: count => (count === 1 ? 'Изменён 1 файл' : `Изменено файлов: ${count}`), + reviewChanges: 'Обзор', + readAloudFailed: 'Не удалось озвучить', + preparingAudio: 'Подготовка аудио…', + stopReading: 'Остановить чтение', + readAloud: 'Озвучить', + editMessage: 'Редактировать сообщение', + expandMessage: 'Развернуть сообщение', + scrollToBottom: 'Прокрутить вниз', + stop: 'Остановить', + restorePrevious: 'Восстановить предыдущую точку', + restoreCheckpoint: 'Восстановить точку', + restoreFromHere: 'Восстановить точку — перезапустить с этого промпта', + restoreTitle: 'Восстановить до этой точки?', + restoreBody: + 'Всё после этого промпта будет удалено из диалога, и промпт будет выполнен заново.', + restoreConfirm: 'Восстановить и перезапустить', + restoreNext: 'Восстановить следующую точку', + goForward: 'Вперёд', + sendEdited: 'Отправить отредактированное сообщение', + attachingFile: 'Прикрепление…' + }, + approval: { + gatewayDisconnected: 'Шлюз Hermes не подключён', + sendFailed: 'Не удалось отправить ответ подтверждения', + run: 'Запустить', + command: 'Команда', + moreOptions: 'Ещё варианты подтверждения', + allowSession: 'Разрешить для этой сессии', + alwaysAllowMenu: 'Всегда разрешать…', + jumpToApproval: 'Требуется подтверждение', + reject: 'Отклонить', + alwaysTitle: 'Всегда разрешать эту команду?', + alwaysDescription: pattern => + `Это добавляет шаблон «${pattern}» в постоянный список разрешений (~/.hermes/config.yaml). Hermes больше не будет спрашивать для подобных команд — ни в этой сессии, ни в будущих.`, + alwaysAllow: 'Всегда разрешать' + }, + clarify: { + notReady: 'Запрос уточнения ещё не готов', + gatewayDisconnected: 'Шлюз Hermes не подключён', + sendFailed: 'Не удалось отправить ответ уточнения', + loadingQuestion: 'Загрузка вопроса…', + other: 'Другое (введите свой ответ)', + placeholder: 'Введите ответ…', + skip: 'Пропустить', + skipped: 'Пропущено', + continueLabel: 'Продолжить', + lateAnswer: (question, choice) => `Re: «${question}» — мой ответ: ${choice}`, + lateAnswerTip: 'Оформить этот ответ как следующее сообщение', + lateAnswerHint: 'Этот запрос больше не ожидает. Выберите вариант, чтобы оформить его как следующее сообщение.' + }, + mcpSetup: { + installTitle: server => `Добавить MCP-сервер ${server}?`, + enableTitle: server => `Включить MCP-сервер ${server}?`, + authorizeTitle: server => `Авторизовать MCP-сервер ${server}?`, + installAction: 'Установить', + enableAction: 'Включить', + authorizeAction: 'Авторизовать', + decline: 'Не сейчас', + declined: 'Отклонено', + installed: server => `${server} установлен`, + enabled: server => `${server} включён`, + authorized: server => `${server} авторизован`, + failed: server => `Не удалось настроить ${server}`, + unanswered: 'Нет ответа', + toolCount: count => (count === 1 ? '1 инструмент' : `${count} инструментов`), + notInCatalog: server => `«${server}» нет в каталоге MCP`, + catalogSource: 'Из одобренного Nous каталога', + envRequired: 'Сначала заполните необходимые учётные данные', + sendFailed: 'Не удалось отправить ответ настройки MCP', + reloadFailed: 'Сервер сохранён, но перезагрузка инструментов MCP не удалась — они загрузятся в следующей сессии', + gatewayDisconnected: 'Шлюз Hermes не подключён' + }, + tool: { + copyCode: 'Копировать код', + renderingImage: 'Рендеринг изображения', + copyOutput: 'Копировать вывод', + copyCommand: 'Копировать команду', + copyContent: 'Копировать содержимое', + copyUrl: 'Копировать URL', + copyResults: 'Копировать результаты', + copyQuery: 'Копировать запрос', + copyFile: 'Копировать файл', + copyPath: 'Копировать путь', + outputAlt: 'Вывод инструмента', + rawResponse: 'Исходный ответ', + copyActivity: 'Копировать активность', + recoveredOne: 'Восстановлено после 1 неудачного шага', + recoveredMany: count => `Восстановлено после ${count} неудачных шагов`, + failedOne: '1 шаг не выполнен', + failedMany: count => `${count} шагов с ошибкой`, + statusRunning: 'Выполняется', + statusError: 'Ошибка', + statusRecovered: 'Восстановлено', + statusDone: 'Готово', + memoryWriteNoted: 'Запись в память отмечена', + actions: { + read: 'Чтение', + reading: 'Читается', + opened: 'Открыто', + opening: 'Открывается', + failedToOpen: 'Не удалось открыть', + searched: 'Поиск выполнен', + searching: 'Поиск', + ran: 'Выполнено', + running: 'Выполняется', + ranCode: 'Код выполнен', + runningCode: 'Скриптинг' + }, + prefixes: { + browser: 'Браузер', + web: 'Веб' + }, + titleTemplates: { + actionCommand: (action, command) => `${action} ${command}`, + actionQuoted: (action, value) => `${action} «${value}»`, + actionTarget: (action, target) => `${action} ${target}`, + prefixedDone: (prefix, action) => `${prefix} ${action}`, + runningPrefixedTool: (prefix, action) => `Выполняется ${prefix.toLowerCase()} ${action.toLowerCase()}`, + runningTool: action => `Выполняется ${action.toLowerCase()}` + }, + titles: { + browser_click: { done: 'Клик по элементу страницы', pending: 'Клик по элементу страницы', pendingAction: 'Клик' }, + browser_fill: { done: 'Заполнено поле формы', pending: 'Заполнение поля формы', pendingAction: 'Заполнение' }, + browser_navigate: { done: 'Страница открыта', pending: 'Открытие страницы', pendingAction: 'Открывается' }, + browser_snapshot: { + done: 'Снимок страницы получен', + pending: 'Получение снимка страницы', + pendingAction: 'Снимок' + }, + browser_take_screenshot: { + done: 'Скриншот сделан', + pending: 'Создание скриншота', + pendingAction: 'Снимок' + }, + browser_type: { done: 'Ввод на странице', pending: 'Ввод на странице', pendingAction: 'Ввод' }, + clarify: { done: 'Задан вопрос', pending: 'Задание вопроса', pendingAction: 'Вопрос' }, + cronjob: { done: 'Cron-задача', pending: 'Планирование cron-задачи', pendingAction: 'Планирование' }, + edit_file: { done: 'Файл отредактирован', pending: 'Редактирование файла', pendingAction: 'Редактирование' }, + execute_code: { done: 'Код выполнен', pending: 'Скриптинг', pendingAction: 'Скриптинг' }, + image_generate: { done: 'Изображение создано', pending: 'Создание изображения', pendingAction: 'Создание' }, + list_files: { done: 'Список файлов получен', pending: 'Получение списка файлов', pendingAction: 'Список' }, + memory: { done: 'Сохранено в память', pending: 'Сохранение в память', pendingAction: 'Сохранение' }, + patch: { done: 'Файл пропатчен', pending: 'Патчинг файла', pendingAction: 'Патчинг' }, + read_file: { done: 'Файл прочитан', pending: 'Чтение файла', pendingAction: 'Читается' }, + search_files: { done: 'Поиск по файлам', pending: 'Поиск по файлам', pendingAction: 'Поиск' }, + session_search_recall: { + done: 'Поиск по истории сессий', + pending: 'Поиск по истории сессий', + pendingAction: 'Поиск' + }, + terminal: { done: 'Команда выполнена', pending: 'Выполнение команды', pendingAction: 'Выполняется' }, + todo: { done: 'Задачи обновлены', pending: 'Обновление задач', pendingAction: 'Обновление' }, + vision_analyze: { done: 'Изображение проанализировано', pending: 'Анализ изображения', pendingAction: 'Анализ' }, + web_extract: { done: 'Веб-страница прочитана', pending: 'Чтение веб-страницы', pendingAction: 'Читается' }, + web_search: { done: 'Поиск в интернете', pending: 'Поиск в интернете', pendingAction: 'Поиск' }, + write_file: { done: 'Файл отредактирован', pending: 'Редактирование файла', pendingAction: 'Редактирование' } + } + } + }, + + prompts: { + gatewayDisconnected: 'Шлюз Hermes не подключён', + sudoSendFailed: 'Не удалось отправить пароль sudo', + secretSendFailed: 'Не удалось отправить секрет', + sudoTitle: 'Пароль администратора', + sudoDesc: 'Hermes нужен ваш пароль sudo для выполнения привилегированной команды. Он отправляется только вашему локальному агенту.', + sudoPlaceholder: 'пароль sudo', + secretTitle: 'Требуется секрет', + secretDesc: 'Hermes нужны учётные данные для продолжения.', + secretPlaceholder: 'значение секрета' + }, + + desktop: { + audioReadFailed: 'Не удалось прочитать записанное аудио', + sessionUnavailable: 'Сессия недоступна', + createSessionFailed: 'Не удалось создать новую сессию', + promptFailed: 'Не удалось отправить промпт', + providerCredentialRequired: 'Добавьте учётные данные провайдера перед отправкой первого сообщения.', + emptySlashCommand: 'пустая слеш-команда', + desktopCommands: 'Команды рабочего стола', + skillCommandsAvailable: count => `Доступно ${count} команд навыков.`, + warningLine: message => `предупреждение: ${message}`, + yoloArmed: 'YOLO активирован для этого чата', + yoloOff: 'YOLO выключен', + yoloSystem: active => `YOLO ${active ? 'включён' : 'выключен'} для этой сессии`, + yoloTitle: 'YOLO', + yoloToggleFailed: 'Не удалось переключить YOLO', + profileStatus: current => + `Профиль: ${current}. Используйте /profile <имя> или выбор «Новой сессии», чтобы начать чат в другом профиле.`, + unknownProfile: 'Неизвестный профиль', + noProfileNamed: (target, available) => `Нет профиля «${target}». Доступные: ${available}`, + newChatsProfile: name => `Новые чаты будут использовать профиль ${name}.`, + setProfileFailed: 'Не удалось задать профиль', + sttDisabled: 'Распознавание речи отключено в настройках.', + stopFailed: 'Не удалось остановить', + regenerateFailed: 'Не удалось сгенерировать заново', + editFailed: 'Не удалось отредактировать', + resumeFailed: 'Не удалось возобновить', + resumeStrandedTitle: 'Не удалось загрузить эту сессию', + resumeStrandedBody: + 'Не удалось подключиться к этой сессии, автоматические попытки исчерпаны. Убедитесь, что gateway запущен, и попробуйте снова.', + resumeRetry: 'Повторить', + nothingToBranch: 'Нечего ветвить', + branchNeedsChat: 'Начните или возобновите чат, прежде чем ветвиться.', + sessionBusy: 'Сессия занята', + branchStopCurrent: 'Остановите текущий ход, прежде чем ветвить этот чат.', + branchNoText: 'В этом сообщении нет текста для ветвления.', + branchTitle: n => `Черновик: ветка #${n}`, + branchFailed: 'Не удалось создать ветку', + deleteFailed: 'Не удалось удалить', + archived: 'В архиве', + archiveFailed: 'Не удалось архивировать', + cwdChangeFailed: 'Не удалось изменить рабочую директорию', + cwdStagedTitle: 'Рабочая директория подготовлена', + cwdStagedMessage: 'Перезапустите бэкенд десктопа, чтобы применить смену рабочей директории к этой активной сессии.', + modelSwitchFailed: 'Не удалось сменить модель', + sessionExported: 'Сессия экспортирована', + sessionExportFailed: 'Не удалось экспортировать сессию', + imageSaved: 'Изображение сохранено', + downloadStarted: 'Загрузка начата', + restartToUseSaveImage: 'Перезапустите Hermes Desktop, чтобы использовать сохранение изображения.', + restartToSaveImages: 'Перезапустите Hermes Desktop, чтобы сохранять изображения', + imageDownloadFailed: 'Не удалось скачать изображение', + openImage: 'Открыть изображение', + downloadImage: 'Скачать изображение', + savingImage: 'Сохранение изображения', + imagePreviewFailed: 'Не удалось показать изображение', + imageAttach: 'Прикрепление изображения', + imageWriteFailed: 'Не удалось записать изображение на диск.', + imageAttachFailed: 'Не удалось прикрепить изображение', + attachImages: 'Прикрепить изображения', + clipboard: 'Буфер обмена', + noClipboardImage: 'В буфере обмена нет изображения', + clipboardPasteFailed: 'Не удалось вставить из буфера обмена', + dropFiles: 'Перетащите файлы', + handoff: { + pickPlatform: 'Выберите получателя', + success: platform => `Передано в ${platform}. Можно вернуться сюда в любой момент.`, + systemNote: platform => `↻ Передано в ${platform} — можно вернуться сюда в любой момент.`, + failed: error => `Не удалось передать: ${error}`, + timedOut: 'Истекло время ожидания gateway. Запущен ли `hermes gateway`?' + } + }, + + errors: { + genericFailure: 'Что-то пошло не так', + boundaryTitle: 'Что-то сломалось в интерфейсе', + boundaryDesc: 'В интерфейсе произошла непредвиденная ошибка. Ваши чаты и настройки в порядке.', + reloadWindow: 'Перезагрузить окно', + openLogs: 'Открыть журнал' + }, + + ui: { + search: { + clear: 'Очистить поиск' + }, + pagination: { + label: 'пагинация', + previous: 'Назад', + previousAria: 'Перейти на предыдущую страницу', + next: 'Следующий', + nextAria: 'Перейти на следующую страницу' + }, + sidebar: { + title: 'Боковая панель', + description: 'Показывает мобильную боковую панель.', + toggle: open => `${open ? 'Показать' : 'Скрыть'} боковую панель` + } + } +} diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index 4772ef6561bbb..4aab48cf1260e 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -5,7 +5,7 @@ // partial locales should use `defineLocale()` so missing desktop-only strings // fall back to English while new keys remain type-checked. -export type Locale = 'en' | 'zh' | 'zh-hant' | 'ja' | 'ar' +export type Locale = 'en' | 'zh' | 'zh-hant' | 'ja' | 'ar' | 'ru' export type ToolTitleKey = | 'browser_click'