@goodandready/dsh-russian-lang 0.3.13 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -392,18 +392,21 @@ return results.sort((a, b) => b.score - a.score).map((r) => r.item)
392
392
  }
393
393
  const RU = {"common":{"back":"Назад","cancel":"Отмена","close":"Закрыть","collapse":"Свернуть","copied":"Скопировано","copy":"Копировать","delete":"Удалить","edit":"Изменить","expand":"Развернуть","loading":"Загрузка…","more":"Ещё","next":"Дальше","ok":"ОК","retry":"Повторить","save":"Сохранить","search":"Поиск","submit":"Отправить"},"dsh-cron":{"sidebar.label":"Задачи по расписанию"},"dsh-usage-stats":{"footer.todayLabel":"Сегодня"},"pluginMarket":{"trigger":"Магазин плагинов"}}
394
394
  const ZH_RU = {}
395
- RU['russian-lang'] = {"cardTitle":"Русская локализация","cardSub":"Язык интерфейса, типографика, раскладка","badgeRu":"🟢 RU активен","badgeEn":"⚪ EN активен","badgeCoverage":"🟢 100% (7,902 ключа)","badgeSmartUx":"⚡ Smart UX активен","secLanguage":"🌐 Язык интерфейса","secLanguageDesc":"Нативное переключение языка интерфейса DSH на русский без перезагрузки страницы.","enabled":"Русский язык включён","enabledDesc":"Переключает язык интерфейса DeepSeek Harness на русский.","quickSwitchNote":"Быстрый переключатель RU ⇄ EN доступен в шапке сессии рядом с кнопками диалога.","secTypography":"✍️ Умная типографика и ввод (Smart UX)","secTypographyDesc":"Автоматическое улучшение текстов по нормам русской типографики во время диалога и набора.","typography":"Типографика вывода","typographyDesc":"Исправляет типографику ответов модели: кавычки-«ёлочки», тире («—») вместо дефисов, неразрывные пробелы после предлогов.","yo":"Буква «ё»","yoDesc":"Восстанавливать «ё» в частых словах (ещё, чёрный, идёт и др.), написанных через «е». Неоднозначные слова (все/всё) не трогаются.","liveInput":"Живая типографика инпута","liveInputDesc":"Форматировать текст сообщения перед отправкой: кавычки «», тире — и неразрывные пробелы. Внимание: изменяет отправляемый агенту текст (команды и флаги с латиницей защищены).","slashAliases":"Русские алиасы команд","slashAliasesDesc":"Поддержка русских команд: /цель -> /goal, /сжать -> /compact, /план -> /plan, /справка -> /help, /память -> /memory.","altLHintText":"Мгновенная конвертация раскладки текущего поля (ghbdtn ⇄ привет, /vjltkm ⇄ /model). В углу поля ввода также отображается метка раскладки.","secAgentPrompt":"🤖 Системный промпт агента","secAgentPromptDesc":"Официальная секция расширения DSH systemPrompt для ведения диалога на русском языке.","agentPrompt":"Русский промпт агента","agentPromptDesc":"Добавляет в системный промпт инструкцию отвечать по-русски в выбранном стиле.","agentPromptPreset":"Стиль ответов агента","presetExpert":"Технический эксперт (строгая терминология, чистый код)","presetWriter":"Технический писатель (Markdown, таблицы, ГОСТ)","presetConcise":"Лаконичный режим (кратко, без лишней воды)","presetReviewer":"Код-ревьюер (поиск багов, безопасность, аудит)","presetArchitect":"Системный архитектор (контракты, масштабируемость)","presetTutor":"Наставник (понятные объяснения, пошаговый разбор)","secUpdater":"🔄 Обновление языкового пакета","secUpdaterDesc":"Проверка наличия новых релизов в реестре npm и обновление в один клик.","updaterCurrent":"Текущая версия: v{version}","updaterLatest":"Доступна новая версия: v{version}","updaterUpToDate":"Установлена актуальная версия","updaterChecking":"Проверка…","updaterCheckBtn":"Проверить обновления","updaterBtn":"Обновить до v{version} в 1 клик","updaterUpdating":"Установка обновления…","updaterSuccess":"✅ Плагин успешно обновлён! Перезапустите DSH для применения.","updaterFailed":"❌ Не удалось проверить/обновить плагин. Проверьте сеть или логи сервера.","badgeUpdateAvailable":"Доступно обновление","badgeUpToDate":"Актуальная версия","secTranslator":"🌐 Перевод сообщений ассистента","secTranslatorDesc":"Настройка перевода ответов модели в чате на русский язык (локально в Docker или онлайн).","translateEngine":"Движок перевода","engineOff":"Выключен (по умолчанию, без сетевых вызовов)","engineLocal":"Локальный LibreTranslate (приватно, ~600 МБ RAM)","engineGoogle":"Google Translate (онлайн)","googleWarn":"⚠️ Внимание: при онлайн-переводе текст сообщений ассистента передаётся на публичные серверы Google для перевода на лету. Не используйте для конфиденциальных данных.","localStatusRunning":"🟢 Контейнер LibreTranslate активен","localStatusStopped":"⚪ Контейнер LibreTranslate не запущен","localStartBtn":"Запустить LibreTranslate в Docker (~600 МБ RAM)","localStarting":"Запуск контейнера…","localInfo":"LibreTranslate запускается изолированно в Docker на сервере, потребляет ~500–700 МБ RAM и переводит на 100% локально.","secOverrides":"✍️ Пользовательские переопределения (Custom Overrides)","secOverridesDesc":"Возможность заменить любую формулировку интерфейса DSH на собственную. Переопределения сохраняются в настройках и имеют наивысший приоритет.","overrideKeyPlaceholder":"Ключ (например, common.back или settings.title)","overrideValuePlaceholder":"Ваш русский перевод","overrideAddBtn":"Добавить","overrideEmpty":"Переопределений пока нет.","overrideDelete":"Удалить","overrideTip":"💡 Совет: зажмите Alt и кликните на любой переведённый элемент интерфейса, чтобы открыть всплывающий инспектор перевода и сразу изменить формулировку.","secSupport":"📊 Покрытие экосистемы и поддержка","secSupportDesc":"Словари синхронизированы с DSH v0.1.6-alpha.1. 100.0% UI-покрытие ядра и всех установленных плагинов (7,902 ключа) без черновых машинных переводов.","statNamespaces":"Пространств имён","statCoreKeys":"Ключей ядра","statPluginKeys":"Ключей плагинов","overridesCount":"Своих переопределений","statusLoading":"Настройки загружаются…","statusUnavailable":"Настройки недоступны на этом хосте","translateTurn":"Перевести на русский","reportIssue":"Сообщить о неточности перевода","exportMdHint":"Экспорт диалога в Markdown доступен по кнопке [ 📥 MD ] в шапке сессии.","translateTurnHint":"Перевод ответов ассистента на русский доступен по кнопке [ RU ↗ ] на блоках сообщений."}
395
+ RU['russian-lang'] = {"cardTitle":"Русская локализация","cardSub":"Язык интерфейса, типографика, раскладка","badgeRu":"🟢 RU активен","badgeEn":"⚪ EN активен","badgeCoverage":"🟢 100% (7,902 ключа)","badgeSmartUx":"⚡ Smart UX активен","secLanguage":"🌐 Язык интерфейса","secLanguageDesc":"Нативное переключение языка интерфейса DSH на русский без перезагрузки страницы.","enabled":"Русский язык включён","enabledDesc":"Переключает язык интерфейса DeepSeek Harness на русский.","quickSwitchNote":"Быстрый переключатель RU ⇄ EN доступен в шапке сессии рядом с кнопками диалога.","secTypography":"✍️ Умная типографика и ввод (Smart UX)","secTypographyDesc":"Нормы русской типографики во время диалога и набора.","typography":"Типографика вывода","typographyDesc":"Кавычки «», тире — вместо дефисов, неразрывные пробелы.","yo":"Буква «ё»","yoDesc":"Восстановление «ё» в частых словах (ещё, идёт и др.).","liveInput":"Живая типографика инпута","liveInputDesc":"Кавычки «», тире — и неразрывные пробелы перед отправкой.","slashAliases":"Русские алиасы команд","slashAliasesDesc":"Команды: /цель, /сжать, /план, /справка, /память.","altLHintText":"Конвертация раскладки текущего поля (ghbdtn ⇄ привет).","secAgentPrompt":"🤖 Системный промпт агента","secAgentPromptDesc":"Официальная секция расширения DSH systemPrompt для ведения диалога на русском языке.","agentPrompt":"Русский промпт агента","agentPromptDesc":"Добавляет в системный промпт инструкцию отвечать по-русски в выбранном стиле.","agentPromptPreset":"Стиль ответов агента","presetExpert":"Технический эксперт (строгая терминология, чистый код)","presetWriter":"Технический писатель (Markdown, таблицы, ГОСТ)","presetConcise":"Лаконичный режим (кратко, без лишней воды)","presetReviewer":"Код-ревьюер (поиск багов, безопасность, аудит)","presetArchitect":"Системный архитектор (контракты, масштабируемость)","presetTutor":"Наставник (понятные объяснения, пошаговый разбор)","secUpdater":"🔄 Обновление языкового пакета","secUpdaterDesc":"Проверка наличия новых релизов в реестре npm и обновление в один клик.","updaterCurrent":"Текущая версия: v{version}","updaterLatest":"Доступна новая версия: v{version}","updaterUpToDate":"Установлена актуальная версия","updaterChecking":"Проверка…","updaterCheckBtn":"Проверить обновления","updaterBtn":"Обновить до v{version} в 1 клик","updaterUpdating":"Установка обновления…","updaterSuccess":"✅ Плагин успешно обновлён! Перезапустите DSH для применения.","updaterFailed":"❌ Не удалось проверить/обновить плагин. Проверьте сеть или логи сервера.","badgeUpdateAvailable":"Доступно обновление","badgeUpToDate":"Актуальная версия","secTranslator":"🌐 Перевод сообщений ассистента","secTranslatorDesc":"Перевод ответов модели в чате на русский язык.","translateEngine":"Движок перевода","engineOff":"Выключен (по умолчанию, без сетевых вызовов)","engineLocal":"Локальный LibreTranslate (приватно, ~600 МБ RAM)","engineGoogle":"Google Translate (онлайн)","googleWarn":"⚠️ Текст сообщений передаётся на публичные серверы Google.","localStatusRunning":"🟢 Контейнер LibreTranslate активен","localStatusStopped":"⚪ Контейнер LibreTranslate не запущен","localStartBtn":"Запустить LibreTranslate в Docker (~600 МБ RAM)","localStarting":"Запуск контейнера…","localInfo":"LibreTranslate в Docker: изолированно, ~600 МБ RAM.","secOverrides":"✍️ Пользовательские переопределения (Custom Overrides)","secOverridesDesc":"Замена любой формулировки интерфейса DSH на собственную.","overrideKeyPlaceholder":"Ключ (например, common.back или settings.title)","overrideValuePlaceholder":"Ваш русский перевод","overrideAddBtn":"Добавить","overrideEmpty":"Переопределений пока нет.","overrideDelete":"Удалить","overrideTip":"💡 Зажмите Alt и кликните на элемент UI для быстрой правки.","secSupport":"📊 Покрытие экосистемы и поддержка","secSupportDesc":"100% UI-покрытие ядра и плагинов DSH v0.1.6-alpha.1.","statNamespaces":"Пространств имён","statCoreKeys":"Ключей ядра","statPluginKeys":"Ключей плагинов","overridesCount":"Своих переопределений","statusLoading":"Настройки загружаются…","statusUnavailable":"Настройки недоступны на этом хосте","translateTurn":"Перевести на русский","reportIssue":"Сообщить о неточности перевода","exportMdHint":"Экспорт диалога в Markdown доступен по кнопке [ 📥 MD ] в шапке сессии.","translateTurnHint":"Перевод ответов ассистента на русский доступен по кнопке [ RU ↗ ] на блоках сообщений."}
396
396
  const SETTINGS_NS_NAME = 'russian-lang'
397
+ const FORM_NS = 'dsh-russian-lang'
398
+ const LEGACY_NS = 'russian-lang'
397
399
  const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
398
400
  function apply(ctx) {const runtime = ctx.locale
399
- const resolveScope = (c) => {try {if (c && c.configForms) return c.configForms.get(SETTINGS_NS_NAME)
400
- if (c && c.settingsScope) return c.settingsScope.bind({ namespace: SETTINGS_NS_NAME })
401
+ const resolveScope = (c) => {try {if (c && c.configForms) return c.configForms.get(FORM_NS)
402
+ if (c && c.settingsScope) return c.settingsScope.bind({ namespace: LEGACY_NS })
401
403
  } catch (err) { void err }
402
404
  return null
403
405
  }
404
406
  const rawScope = resolveScope(ctx)
405
407
  const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
406
408
  }
409
+ const legacyFlagMode = !(ctx && ctx.configForms)
407
410
  for (const ns of Object.keys(RU)) {ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', RU[ns]) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
408
411
  }
409
412
  const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
@@ -413,7 +416,7 @@ if (typeof dict !== 'object' || !dict) continue
413
416
  if (!RU[ns]) {RU[ns] = dict
414
417
  ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
415
418
  } else {Object.assign(RU[ns], dict)
416
- }}}
419
+ try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) {}}}}
417
420
  const CACHE_NAME = 'dsh-ru-cache-v1'
418
421
  const loadLocalDict = (key) => {try {const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
419
422
  return raw ? JSON.parse(raw) : null
@@ -540,7 +543,8 @@ for (const p of RUNTIME_PROP_NAMES) {if (origProps[p] !== undefined) runtime[p]
540
543
  else delete runtime[p]
541
544
  }
542
545
  if (typeof removeLang === 'function') { try { removeLang() } catch (_) { } }}}, 'dsh-russian-lang: runtime-lifecycle')
543
- const syncFlag = () => {try {const wantRu = runtime.getLocale().active === 'ru'
546
+ const syncFlag = () => {if (!legacyFlagMode) return
547
+ try {const wantRu = runtime.getLocale().active === 'ru'
544
548
  const value = scope.getSnapshot().value || {}
545
549
  if (!!value.enabled !== wantRu) {const r = scope.set('enabled', wantRu)
546
550
  if (r && typeof r.catch === 'function') {r.catch((err) => {console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
@@ -554,7 +558,7 @@ runtime.setLocale('ru')
554
558
  let booted = false
555
559
  const tryBoot = () => {if (booted) return
556
560
  try {const value = scope.getSnapshot().value
557
- if (value && value.enabled === true) { booted = true; activate() }} catch (err) { }}
561
+ if (legacyFlagMode && value && value.enabled === true) { booted = true; activate() }} catch (err) { }}
558
562
  ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
559
563
  tryBoot()
560
564
  const unsubscribeLang = runtime.subscribe(syncLang)
@@ -608,7 +612,7 @@ const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
608
612
  const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
609
613
  const ZH_EXACT = new Map()
610
614
  const ZH_PATTERNS = []
611
- const CORE_ZH_PRESETS = {'请求批准': 'Запрашивать подтверждение','完全放开': 'Полный доступ','只读': 'Только чтение','只能读,任何写入都需要审批。': 'Только чтение, любая запись требует подтверждения.','工作区内可写;工作区外的操作请求人工审批。': 'Запись в рабочей области разрешена; операции вне рабочей области требуют подтверждения.','全放行,不弹审批。': 'Полный доступ, запросы на подтверждение не выводятся.','确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.','手动测试': 'Ручное тестирование','事件': 'Событие','模拟 (看匹配)': 'Симуляция (проверка)','执行 (真实触发)': 'Выполнение (реальный триггер)','通知渠道测试': 'Тест каналов уведомлений','渠道': 'Канал','Slack 风格单行摘要': 'Сводка в стиле Slack','发送测试通知': 'Отправить тестовое уведомление','飞书通知': 'Уведомления Feishu','扫码连接飞书': 'Подключить Feishu по QR-коду','将创建名为 [DSH 通知机器人] 的飞书应用': 'Будет создано приложение Feishu [DSH 通知机器人]','卡片截断长度': 'Длина обрезки карточки','复制 YAML': 'Копировать YAML','去抖': 'Дебаунс','已断开': 'Отключено','已连接': 'Подключено','已保存': 'Сохранено','保存失败': 'Не удалось сохранить','编辑': 'Редактировать','取消编辑': 'Отмена','启用': 'Включить','停用': 'Отключить','重试': 'Повторить','刷新': 'Обновить','网络请求失败': 'Сетевой запрос не удался','如未立即生效请重启': 'Если изменения не применились, перезапустите DSH','通知机器人': 'Бот уведомлений','飞书扫码授权二维码': 'QR-код авторизации Feishu','在浏览器中打开飞书授权链接': 'Открыть ссылку авторизации Feishu в браузере','重新扫码会覆盖现有应用凭据与本': 'Повторное сканирование перезапишет учётные данные','扫码者本人接收通知卡片': 'Получатель карточки уведомлений — авторизованный пользователь','tool (可选)': 'Инструмент (опц.)','runningSubagents (可选)': 'Подагенты (опц.)','durationMs (可选)': 'Длительность мс (опц.)','usage 输入 (可选)': 'Входные токены (опц.)','usage 输出 (可选)': 'Выходные токены (опц.)','profile (写入哪个 profile 的 cordis.patch.yml)': 'Профиль (куда записать cordis.patch.yml)','URL (留空用 DSH_HOOKS_WEBHOOK_URL)': 'URL (по умолчанию DSH_HOOKS_WEBHOOK_URL)','当前已配置的 Hook 规则列表': 'Список текущих настроенных правил хуков','测试通道': 'Тест канала'
615
+ const CORE_ZH_PRESETS = {'请求批准': 'Запрашивать подтверждение','完全放开': 'Полный доступ','只读': 'Только чтение','只能读,任何写入都需要审批。': 'Только чтение, любая запись требует подтверждения.','工作区内可写;工作区外的操作请求人工审批。': 'Запись в рабочей области разрешена; операции вне рабочей области требуют подтверждения.','全放行,不弹审批。': 'Полный доступ, запросы на подтверждение не выводятся.','确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.','手动测试': 'Ручное тестирование','事件': 'Событие','模拟 (看匹配)': 'Симуляция (проверка)','执行 (真实触发)': 'Выполнение (реальный триггер)','通知渠道测试': 'Тест каналов уведомлений','渠道': 'Канал','Slack 风格单行摘要': 'Сводка в стиле Slack','发送测试通知': 'Отправить тестовое уведомление','飞书通知': 'Уведомления Feishu','扫码连接飞书': 'Подключить Feishu по QR-коду','将创建名为 [DSH 通知机器人] 的飞书应用': 'Будет создано приложение Feishu [DSH 通知机器人]','卡片截断长度': 'Длина обрезки карточки','复制 YAML': 'Копировать YAML','去抖': 'Дебаунс','已断开': 'Отключено','已连接': 'Подключено','已保存': 'Сохранено','保存失败': 'Не удалось сохранить','编辑': 'Редактировать','取消编辑': 'Отмена','启用': 'Включить','停用': 'Отключить','重试': 'Повторить','刷新': 'Обновить','网络请求失败': 'Сетевой запрос не удался','如未立即生效请重启': 'Если изменения не применились, перезапустите DSH','通知机器人': 'Бот уведомлений','飞书扫码授权二维码': 'QR-код авторизации Feishu','在浏览器中打开飞书授权链接': 'Открыть ссылку авторизации Feishu в браузере','重新扫码会覆盖现有应用凭据与本': 'Повторное сканирование перезапишет учётные данные','扫码者本人接收通知卡片': 'Получатель карточки уведомлений — авторизованный пользователь','tool (可选)': 'Инструмент (опц.)','runningSubagents (可选)': 'Подагенты (опц.)','durationMs (可选)': 'Длительность мс (опц.)','usage 输入 (可选)': 'Входные токены (опц.)','usage 输出 (可选)': 'Выходные токены (опц.)','profile (写入哪个 profile 的 cordis.patch.yml)': 'Профиль (куда записать cordis.patch.yml)','URL (留空用 DSH_HOOKS_WEBHOOK_URL)': 'URL (по умолчанию DSH_HOOKS_WEBHOOK_URL)','当前已配置的 Hook 规则列表': 'Список текущих настроенных правил хуков','测试通道': 'Тест канала','专家提示词服务尚未就绪,请稍后重试。': 'Сервис промптов экспертов ещё не готов, повторите попытку позже.','专家提示词服务尚未就绪,请稍后': 'Сервис промптов экспертов ещё не готов, пожалуйста, ','专家团服务不可用,请重新加载插件。': 'Сервис команд экспертов недоступен, перезагрузите плагин.','搜索团队或工作目标': 'Поиск команды или цели работы','专家': 'Эксперт','专家团': 'Команда экспертов'
612
616
  }
613
617
  for (const [k, v] of Object.entries(CORE_ZH_PRESETS)) ZH_EXACT.set(k, v)
614
618
  const zhSortedExact = []
@@ -652,7 +656,7 @@ if (changed) return replaced
652
656
  return null
653
657
  }
654
658
  const DOM_EN_ATTRS = {'Streaming preview': 'Предпросмотр стриминга','Visualization streaming preview': 'Предпросмотр визуализации','Security Auditor Shield': 'Защитный щит аудитора','Shield: Safe': 'Щит: Безопасно','Shield: Alert': 'Щит: Тревога','e.g. Implement report export and cover with unit tests': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами','e.g. Implement report export and cover with unit tests...': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами…','e.g. Implement user profile settings card with theme tokens': 'Например: Реализовать карточку настроек профиля с токенами темы','Detailed functional specs, acceptance criteria, constraints...': 'Детальная функциональная спецификация, критерии приёмки, ограничения…','Specific instructions, questions, or requirements for this specialist...': 'Конкретные инструкции, вопросы или требования для данного специалиста…',}
655
- const DOM_EN_TEXT = {'Scheduled tasks': 'Задачи по расписанию','Today': 'Сегодня','Plugin Market': 'Магазин плагинов','GitHub ops': 'Операции с GitHub','Auto mode': 'Автоматический режим','Full access': 'Полный доступ','Read only': 'Только чтение','Side card': 'Боковая панель','panelName': 'Палитра команд','Hooks': 'Хуки','Manage what the side card shows and how it behaves': 'Настройка содержимого и поведения боковой панели','Inject the sidebar-open tool for the model': 'Предоставить модели инструмент sidebar-open','Position compatibility mode': 'Режим совместимости расположения','Auto-detect': 'Автоопределение','Sidebar content': 'Содержимое боковой панели','Changes': 'Изменения','Tasks': 'Задачи','Time Machine': 'Машина времени','Live Canvas': 'Живой холст','Side Chat (beta)': 'Боковой чат (бета)','Terminal': 'Терминал','Feature settings': 'Настройки функции','Low': 'Низкий','Medium': 'Средний','High': 'Высокий','Effort': 'Рассуждения','Search engine (ModSearch)': 'Поисковая система (ModSearch)','Search engine provider configuration.': 'Настройка провайдера поисковой системы.','X search only': 'Только поиск в X','Security Auditor Shield': 'Защитный щит аудитора','Shield: Safe': 'Щит: Безопасно','Shield: Alert': 'Щит: Тревога','Lens': 'Линза','Gallery': 'Галерея','SSH: Local': 'SSH: Локально','SSH: Remote': 'SSH: Удалённо','7/7 rot': '7/7 рот.','Smoke chat': 'Тестовый чат','Quick Launch Goal': 'Быстрый запуск цели','Define the objective for the agent in autonomous mode:': 'Сформулируйте задачу для автономной работы агента:','Fix Bug': 'Исправление бага','Refactor (YAGNI)': 'Рефакторинг (YAGNI)','Tests & Coverage': 'Тесты и покрытие','Code Review': 'Ревью кода','New Feature': 'Новая функция','Security Audit': 'Аудит безопасности','Docs & Contract': 'Документация и контракт','Upgrade Deps': 'Обновление зависимостей','Dead Code': 'Мёртвый код','Performance': 'Производительность','Start Goal': 'Запустить цель','Launch Multi-Agent Orchestrator': 'Запуск мультиагентного оркестратора','Full DAG Pipeline': 'Полный DAG-пайплайн','Direct Specialist Subagent': 'Прямой субагент-специалист','Objective Title': 'Название задачи','Scope & Requirements': 'Объём и требования','Topology Scenario': 'Топологический сценарий','Auto (Infer based on prompt complexity)': 'Авто (определить по сложности задачи)','Hotfix (1 Stage: Triage & Minimal Fix)': 'Хотфикс (1 этап: анализ и точечный фикс)','Simple (2 Stages: Spec + Exec)': 'Простой (2 этапа: ТЗ + реализация)','Medium (4 Stages: Spec -> Design -> Code -> QA)': 'Средний (4 этапа: ТЗ -> Дизайн -> Код -> Тестирование)','Complex (6 Stages: Full Engineering Lifecycle)': 'Сложный (6 этапов: полный инженерный цикл)','Enterprise (7 Stages: R&D Spike -> Fullstack -> Gate)': 'Enterprise (7 этапов: R&D исследование -> Фулстек -> Гейт приёмки)','Target Specialist Role': 'Роль целевого специалиста','UI/UX Interface Designer': 'UI/UX дизайнер интерфейсов','System Architect (DESIGN.md / ADR)': 'Системный архитектор (DESIGN.md / ADR)','Technical Spec Analyst': 'Аналитик технических спецификаций','Senior Frontend Developer': 'Ведущий frontend-разработчик','Senior Backend Developer': 'Ведущий backend-разработчик','QA Automation Engineer': 'Инженер автоматизации тестирования','Refactoring & Complexity Specialist': 'Специалист по рефакторингу и сложности','Hotfix & Diagnostic Engineer': 'Инженер хотфиксов и диагностики','Documentation Specialist': 'Технический писатель / Документация','Spike & R&D Researcher': 'Исследователь R&D и прототипирования','DevOps & Tooling Specialist': 'DevOps и инфраструктурный специалист','Instructions for Subagent': 'Инструкции для субагента','Start Pipeline': 'Запустить пайплайн','Delegate Subagent': 'Делегировать субагенту','Dispatching...': 'Отправка…','Discounted rate active': 'Действует сниженный тариф','off-peak': 'непиковый','peak': 'пиковый','Context cache saved:': 'Сэкономлено на кэше:','1M tokens, $': '1 млн токенов, $','Input (cache hit)': 'Ввод (попадание в кэш)','Input (cache miss)': 'Ввод (промах кэша)','Output': 'Вывод','SESSION TOKENS': 'ТОКЕНЫ СЕССИИ','SPEND BY MODEL': 'РАСХОД ПО МОДЕЛЯМ','Session total:': 'Всего за сессию:','Copy Summary': 'Скопировать сводку','Context Lens': 'Линза контекста','Saved tokens': 'Сэкономлено токенов','% Saved': '% экономии','ops': 'операций','Budget': 'Бюджет','Active focus': 'Активный фокус','No focus paths set': 'Пути фокусировки не заданы','Status': 'Статус','Graph & CI': 'Граф и CI','Events & PRs': 'События и PR','Branch': 'Ветка','Sync': 'Синхронизация','Up to date with remote (@{upstream})': 'Синхронизировано с удалённым репозиторием (@{upstream})','Up to date with remote': 'Синхронизировано с удалённым репозиторием','Clean': 'Чисто','No modified or untracked files in the working directory.': 'В рабочем каталоге нет изменённых или неотслеживаемых файлов.','RECENT COMMITS': 'ПОСЛЕДНИЕ КОММИТЫ','Active jobs': 'Активные задачи','No active jobs': 'Нет активных задач','Register in Models': 'Зарегистрировать в моделях','Base URL': 'Базовый URL','API key env / credential name': 'Имя переменной окружения / ключа API','Default model': 'Модель по умолчанию','Enabled': 'Включено','Save': 'Сохранить','Saved': 'Сохранено','Refresh': 'Обновить','Loading settings…': 'Загрузка настроек…','Preferred engine': 'Предпочитаемый движок','API key': 'API-ключ','stored, leave empty to keep it': 'сохранён, оставьте пустым для сохранения','Built-in official endpoint, leave blank to use it': 'Встроенная официальная конечная точка, оставьте пустым','Discard': 'Сбросить','Model synchronization': 'Синхронизация моделей','All API-key providers': 'Все провайдеры API-ключей','Preview only (dry-run)': 'Только предпросмотр (dry-run)','Confirm stale removal': 'Подтверждать удаление устаревших','Refresh status': 'Обновить статус','Refresh all': 'Обновить всё','Discover': 'Обнаружить','Check availability': 'Проверить доступность','Check credentials': 'Проверить учётные данные','Choose models': 'Выбрать модели','Manual model selection': 'Выбор моделей вручную','Mode': 'Режим','Hybrid (auto-rewrite + tools)': 'Гибридный (авто-переписывание + инструменты)','Describe strategy': 'Стратегия описания','Auto (use vision LLM)': 'Авто (использовать Vision LLM)','Escalation': 'Эскалация','Simple only (one pass)': 'Только простая (один проход)','Routing': 'Маршрутизация','Channel order': 'Порядок каналов','Issue Reporter': 'Репортёр проблем','GitHub sign-in is not configured for this installation.': 'Вход через GitHub не настроен для этой установки.','0 plugins': '0 плагинов','Catalog': 'Каталог','Report Editor': 'Редактор отчёта','My Reports': 'Мои отчёты','Authorization': 'Авторизация','Search installed plugins…': 'Поиск установленных плагинов…','Search installed plugins...': 'Поиск установленных плагинов...','Refresh inventory': 'Обновить список','Loading inventory & status…': 'Загрузка списка и статуса…','Loading inventory & status...': 'Загрузка списка и статуса...'
659
+ const DOM_EN_TEXT = {'Scheduled tasks': 'Задачи по расписанию','Files': 'Файлы','Back': 'Назад','Hosts': 'Хосты','Containers': 'Контейнеры','Tunnels': 'Туннели','Cluster': 'Кластер','Remote Workspace': 'Удалённое рабочее пространство','Native SSH connections to remote development environments with terminal, sftp, and tunneling.': 'SSH-соединения с удалёнными средами разработки: терминал, SFTP и туннели.','Import ~/.ssh/config': 'Импорт ~/.ssh/config','Terminal font': 'Шрифт терминала','Save Profile': 'Сохранить профиль','Edit Host Profile': 'Редактирование профиля хоста','Add New Host Profile': 'Добавление профиля хоста','Profile Name': 'Название профиля','Host / IP Address': 'Хост / IP-адрес','Port': 'Порт','Username': 'Имя пользователя','Jump hosts': 'Jump-хосты','Proxy command': 'Команда прокси','Environment': 'Окружение','Location': 'Расположение','Tags': 'Теги','Description': 'Описание','Authentication Method': 'Метод аутентификации','SSH Private Key': 'Приватный SSH-ключ','SSH Agent': 'SSH-агент','Password': 'Пароль','Private Key Path': 'Путь к приватному ключу','Key Passphrase (optional)': 'Парольная фраза ключа (опционально)','Remote Workspace Directory': 'Каталог удалённой рабочей области','Browse Remote...': 'Обзор удалённых…','Local Mirror Directory': 'Каталог локального зеркала','Search names, fields or keywords': 'Поиск по имени, сфере или ключевым словам','Search names, f[专家] or keywords': 'Поиск по имени, сфере или ключевым словам','No enabled experts yet. Type a keyword to find one.': 'Нет активных экспертов. Введите ключевое слово для поиска.','Search teams or goals': 'Поиск команды или цели работы','Team service unavailable. Reload the plugin.': 'Сервис команд экспертов недоступен, перезагрузите плагин.','Image Studio & Vault': 'Студия изображений и хранилище','Agent Browser': 'Браузер агента','Request approval': 'Запрашивать подтверждение','Today': 'Сегодня','Plugin Market': 'Магазин плагинов','GitHub ops': 'Операции с GitHub','Auto mode': 'Автоматический режим','Full access': 'Полный доступ','Read only': 'Только чтение','Side card': 'Боковая панель','panelName': 'Палитра команд','Hooks': 'Хуки','Manage what the side card shows and how it behaves': 'Настройка содержимого и поведения боковой панели','Inject the sidebar-open tool for the model': 'Предоставить модели инструмент sidebar-open','Position compatibility mode': 'Режим совместимости расположения','Auto-detect': 'Автоопределение','Sidebar content': 'Содержимое боковой панели','Changes': 'Изменения','Tasks': 'Задачи','Time Machine': 'Машина времени','Live Canvas': 'Живой холст','Side Chat (beta)': 'Боковой чат (бета)','Terminal': 'Терминал','Feature settings': 'Настройки функции','Low': 'Низкий','Medium': 'Средний','High': 'Высокий','Effort': 'Рассуждения','Search engine (ModSearch)': 'Поисковая система (ModSearch)','Search engine provider configuration.': 'Настройка провайдера поисковой системы.','X search only': 'Только поиск в X','Security Auditor Shield': 'Защитный щит аудитора','Shield: Safe': 'Щит: Безопасно','Shield: Alert': 'Щит: Тревога','Lens': 'Линза','Gallery': 'Галерея','SSH: Local': 'SSH: Локально','SSH: Remote': 'SSH: Удалённо','7/7 rot': '7/7 рот.','Smoke chat': 'Тестовый чат','Quick Launch Goal': 'Быстрый запуск цели','Define the objective for the agent in autonomous mode:': 'Сформулируйте задачу для автономной работы агента:','Fix Bug': 'Исправление бага','Refactor (YAGNI)': 'Рефакторинг (YAGNI)','Tests & Coverage': 'Тесты и покрытие','Code Review': 'Ревью кода','New Feature': 'Новая функция','Security Audit': 'Аудит безопасности','Docs & Contract': 'Документация и контракт','Upgrade Deps': 'Обновление зависимостей','Dead Code': 'Мёртвый код','Performance': 'Производительность','Start Goal': 'Запустить цель','Launch Multi-Agent Orchestrator': 'Запуск мультиагентного оркестратора','Full DAG Pipeline': 'Полный DAG-пайплайн','Direct Specialist Subagent': 'Прямой субагент-специалист','Objective Title': 'Название задачи','Scope & Requirements': 'Объём и требования','Topology Scenario': 'Топологический сценарий','Auto (Infer based on prompt complexity)': 'Авто (определить по сложности задачи)','Hotfix (1 Stage: Triage & Minimal Fix)': 'Хотфикс (1 этап: анализ и точечный фикс)','Simple (2 Stages: Spec + Exec)': 'Простой (2 этапа: ТЗ + реализация)','Medium (4 Stages: Spec -> Design -> Code -> QA)': 'Средний (4 этапа: ТЗ -> Дизайн -> Код -> Тестирование)','Complex (6 Stages: Full Engineering Lifecycle)': 'Сложный (6 этапов: полный инженерный цикл)','Enterprise (7 Stages: R&D Spike -> Fullstack -> Gate)': 'Enterprise (7 этапов: R&D исследование -> Фулстек -> Гейт приёмки)','Target Specialist Role': 'Роль целевого специалиста','UI/UX Interface Designer': 'UI/UX дизайнер интерфейсов','System Architect (DESIGN.md / ADR)': 'Системный архитектор (DESIGN.md / ADR)','Technical Spec Analyst': 'Аналитик технических спецификаций','Senior Frontend Developer': 'Ведущий frontend-разработчик','Senior Backend Developer': 'Ведущий backend-разработчик','QA Automation Engineer': 'Инженер автоматизации тестирования','Refactoring & Complexity Specialist': 'Специалист по рефакторингу и сложности','Hotfix & Diagnostic Engineer': 'Инженер хотфиксов и диагностики','Documentation Specialist': 'Технический писатель / Документация','Spike & R&D Researcher': 'Исследователь R&D и прототипирования','DevOps & Tooling Specialist': 'DevOps и инфраструктурный специалист','Instructions for Subagent': 'Инструкции для субагента','Start Pipeline': 'Запустить пайплайн','Delegate Subagent': 'Делегировать субагенту','Dispatching...': 'Отправка…','Discounted rate active': 'Действует сниженный тариф','off-peak': 'непиковый','peak': 'пиковый','Context cache saved:': 'Сэкономлено на кэше:','1M tokens, $': '1 млн токенов, $','Input (cache hit)': 'Ввод (попадание в кэш)','Input (cache miss)': 'Ввод (промах кэша)','Output': 'Вывод','SESSION TOKENS': 'ТОКЕНЫ СЕССИИ','SPEND BY MODEL': 'РАСХОД ПО МОДЕЛЯМ','Session total:': 'Всего за сессию:','Copy Summary': 'Скопировать сводку','Context Lens': 'Линза контекста','Saved tokens': 'Сэкономлено токенов','% Saved': '% экономии','ops': 'операций','Budget': 'Бюджет','Active focus': 'Активный фокус','No focus paths set': 'Пути фокусировки не заданы','Status': 'Статус','Graph & CI': 'Граф и CI','Events & PRs': 'События и PR','Branch': 'Ветка','Sync': 'Синхронизация','Up to date with remote (@{upstream})': 'Синхронизировано с удалённым репозиторием (@{upstream})','Up to date with remote': 'Синхронизировано с удалённым репозиторием','Clean': 'Чисто','No modified or untracked files in the working directory.': 'В рабочем каталоге нет изменённых или неотслеживаемых файлов.','RECENT COMMITS': 'ПОСЛЕДНИЕ КОММИТЫ','Active jobs': 'Активные задачи','No active jobs': 'Нет активных задач','Register in Models': 'Зарегистрировать в моделях','Base URL': 'Базовый URL','API key env / credential name': 'Имя переменной окружения / ключа API','Default model': 'Модель по умолчанию','Enabled': 'Включено','Save': 'Сохранить','Saved': 'Сохранено','Refresh': 'Обновить','Loading settings…': 'Загрузка настроек…','Preferred engine': 'Предпочитаемый движок','API key': 'API-ключ','stored, leave empty to keep it': 'сохранён, оставьте пустым для сохранения','Built-in official endpoint, leave blank to use it': 'Встроенная официальная конечная точка, оставьте пустым','Discard': 'Сбросить','Model synchronization': 'Синхронизация моделей','All API-key providers': 'Все провайдеры API-ключей','Preview only (dry-run)': 'Только предпросмотр (dry-run)','Confirm stale removal': 'Подтверждать удаление устаревших','Refresh status': 'Обновить статус','Refresh all': 'Обновить всё','Discover': 'Обнаружить','Check availability': 'Проверить доступность','Check credentials': 'Проверить учётные данные','Choose models': 'Выбрать модели','Manual model selection': 'Выбор моделей вручную','Mode': 'Режим','Hybrid (auto-rewrite + tools)': 'Гибридный (авто-переписывание + инструменты)','Describe strategy': 'Стратегия описания','Auto (use vision LLM)': 'Авто (использовать Vision LLM)','Escalation': 'Эскалация','Simple only (one pass)': 'Только простая (один проход)','Routing': 'Маршрутизация','Channel order': 'Порядок каналов','Issue Reporter': 'Репортёр проблем','GitHub sign-in is not configured for this installation.': 'Вход через GitHub не настроен для этой установки.','0 plugins': '0 плагинов','Catalog': 'Каталог','Report Editor': 'Редактор отчёта','My Reports': 'Мои отчёты','Authorization': 'Авторизация','Search installed plugins…': 'Поиск установленных плагинов…','Search installed plugins...': 'Поиск установленных плагинов...','Refresh inventory': 'Обновить список','Loading inventory & status…': 'Загрузка списка и статуса…','Loading inventory & status...': 'Загрузка списка и статуса...'
656
660
  }
657
661
  const ZH_WALKER = (root) => {try {const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
658
662
  const hits = []
@@ -685,32 +689,40 @@ else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr
685
689
  let zhObserver = null
686
690
  let zhDebounceTimer = null
687
691
  let zhWalking = false
688
- const queueZhWalk = (target) => {if (zhDebounceTimer) return
692
+ const queueZhWalk = () => {if (zhDebounceTimer) return
689
693
  zhDebounceTimer = setTimeout(() => {zhDebounceTimer = null
690
694
  if (zhWalking) return
691
695
  zhWalking = true
692
- try {ZH_WALKER(target || document.body)
696
+ try {ZH_WALKER(document.body)
693
697
  } finally {zhWalking = false
694
- }}, 120)
698
+ }}, 80)
695
699
  }
700
+ const onInteraction = () => queueZhWalk()
696
701
  const syncZhDom = () => {try {if (typeof document === 'undefined') return
697
702
  const ru = runtime.getLocale().active === 'ru'
698
703
  if (!ru) {if (zhObserver) { zhObserver.disconnect(); zhObserver = null }
699
704
  if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
705
+ document.removeEventListener('pointerup', onInteraction)
706
+ document.removeEventListener('click', onInteraction)
700
707
  return
701
708
  }
702
709
  if (!zhObserver) {zhObserver = new MutationObserver((records) => {for (const record of records) {if (record.type === 'childList') {for (const node of record.addedNodes) {if (node.nodeType === 1) {if (node.closest && node.closest('.chat-message, .markdown-body, pre, code, [data-stream]')) continue
703
- queueZhWalk(node)
710
+ queueZhWalk()
704
711
  return
705
712
  }}}}})
706
713
  zhObserver.observe(document.body, { childList: true, subtree: true })
714
+ document.addEventListener('pointerup', onInteraction, { passive: true })
715
+ document.addEventListener('click', onInteraction, { passive: true })
707
716
  }
708
- queueZhWalk(document.body)
717
+ queueZhWalk()
709
718
  } catch (err) { }}
710
719
  const unsubscribeZh = runtime.subscribe(syncZhDom)
711
720
  ctx.effect(() => {return () => {unsubscribeZh()
712
721
  if (zhObserver) zhObserver.disconnect()
713
- if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }}}, 'dsh-russian-lang: zh-dom')
722
+ if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
723
+ if (typeof document !== 'undefined') {document.removeEventListener('pointerup', onInteraction)
724
+ document.removeEventListener('click', onInteraction)
725
+ }}}, 'dsh-russian-lang: zh-dom')
714
726
  syncZhDom()
715
727
  const TYPO_YO_PAIRS = [["еще","ещё"],["ее","её"],["черный","чёрный"],["черная","чёрная"],["черные","чёрные"],["зеленый","зелёный"],["желтый","жёлтый"],["легкий","лёгкий"],["тяжелый","тяжёлый"],["надежный","надёжный"],["дешевый","дешёвый"],["идет","идёт"],["дает","даёт"],["ведет","ведёт"],["несет","несёт"],["живет","живёт"],["привел","привёл"],["шел","шёл"],["нее","неё"],["мое","моё"],["серьезно","серьёзно"],["придется","придётся"],["насчет","насчёт"],["твое","твоё"],["свое","своё"],["пойдем","пойдём"],["пришел","пришёл"],["нашел","нашёл"],["идем","идём"],["вперед","вперёд"],["пошел","пошёл"],["ребенка","ребёнка"],["ребенок","ребёнок"],["счет","счёт"],["своем","своём"],["придет","придёт"],["ушел","ушёл"],["ждет","ждёт"],["мертв","мёртв"],["твоем","твоём"],["вернется","вернётся"],["днем","днём"],["найдем","найдём"],["пойдет","пойдёт"],["начнем","начнём"],["вернемся","вернёмся"],["остается","остаётся"],["принес","принёс"],["идешь","идёшь"],["трех","трёх"],["ребенком","ребёнком"],["самолет","самолёт"],["определенно","определённо"],["пойдешь","пойдёшь"],["умрет","умрёт"],["прием","приём"],["прошел","прошёл"],["убьет","убьёт"],["тетя","тётя"],["провел","провёл"],["произойдет","произойдёт"],["семьей","семьёй"],["пойдемте","пойдёмте"],["пройдет","пройдёт"],["возьмем","возьмём"],["вернешься","вернёшься"],["найдешь","найдёшь"],["живешь","живёшь"],["отчет","отчёт"],["займет","займёт"],["ждем","ждём"],["сошел","сошёл"],["вдвоем","вдвоём"],["найдет","найдёт"],["вел","вёл"],["живем","живём"],["зашел","зашёл"],["начнется","начнётся"],["путем","путём"],["подойдет","подойдёт"],["режиссер","режиссёр"],["уйдет","уйдёт"],["придешь","придёшь"],["идемте","идёмте"],["ведешь","ведёшь"],["пес","пёс"],["умрешь","умрёшь"],["ждешь","ждёшь"],["найдете","найдёте"],["найдется","найдётся"],["клево","клёво"],["поймешь","поймёшь"],["разберемся","разберёмся"],["времен","времён"],["введен","введён"],["включен","включён"],["включенных","включённых"],["возьмется","возьмётся"],["завершен","завершён"],["завершенного","завершённого"],["задает","задаёт"],["задается","задаётся"],["заменен","заменён"],["звезд","звёзд"],["звезды","звёзды"],["изменен","изменён"],["истек","истёк"],["незавершенная","незавершённая"],["неподтвержденные","неподтверждённые"],["несохраненные","несохранённые"],["обновлен","обновлён"],["обновленные","обновлённые"],["определен","определён"],["отклонен","отклонён"],["отменен","отменён"],["очередность","очерёдность"],["поврежден","повреждён"],["повторен","повторён"],["подключен","подключён"],["подтвержденные","подтверждённые"],["приемки","приёмки"],["раздает","раздаёт"],["разрешенная","разрешённая"],["разрешенные","разрешённые"],["создаем","создаём"],["сойдется","сойдётся"],["сохранен","сохранён"],["сохраненный","сохранённый"],["счету","счёту"],["тяжелые","тяжёлые"],["темная","тёмная"],["уберет","уберёт"],["удален","удалён"],["учетные","учётные"],["учетных","учётных"],["емкости","ёмкости"],["емкость","ёмкость"]]
716
728
  const typoYo = makeTypoYo(TYPO_YO_PAIRS)
@@ -1114,7 +1126,7 @@ setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
1114
1126
  }}
1115
1127
  const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
1116
1128
  if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
1117
- ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.13' })
1129
+ ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.15' })
1118
1130
  : ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
1119
1131
  try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
1120
1132
  copyReportBtn.textContent = '✓ Скопировано'
@@ -1401,7 +1413,7 @@ const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope
1401
1413
  const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
1402
1414
  const [typo, setTypoState] = React.useState(() =>
1403
1415
  (snap.value && snap.value.typography) || {})
1404
- const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.13',latestVersion: undefined,updateAvailable: false
1416
+ const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.15',latestVersion: undefined,updateAvailable: false
1405
1417
  })
1406
1418
  const [upLoading, setUpLoading] = React.useState(false)
1407
1419
  const [upMsg, setUpMsg] = React.useState(null)
@@ -1494,7 +1506,7 @@ scope.set('agentPromptPreset', val)
1494
1506
  } catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }}},React.createElement('option', { value: 'technical_expert' }, t('presetExpert')),React.createElement('option', { value: 'tech_writer' }, t('presetWriter')),React.createElement('option', { value: 'concise' }, t('presetConcise')),React.createElement('option', { value: 'code_reviewer' }, t('presetReviewer')),React.createElement('option', { value: 'architect' }, t('presetArchitect')),React.createElement('option', { value: 'tutor' }, t('presetTutor')))),presetInfo ? React.createElement('div', { className: 'rl-preview-box' },React.createElement('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),presetInfo.text
1495
1507
  ) : null
1496
1508
  ) : null
1497
- ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secUpdater')),React.createElement('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))),React.createElement('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },React.createElement('div', null,React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.13')),upStatus.updateAvailable
1509
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secUpdater')),React.createElement('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))),React.createElement('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },React.createElement('div', null,React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.15')),upStatus.updateAvailable
1498
1510
  ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
1499
1511
  : React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
1500
1512
  ? React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
@@ -1532,7 +1544,7 @@ if (!input) return
1532
1544
  try {const parsed = JSON.parse(input)
1533
1545
  if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
1534
1546
  scope.set('overrides', merged)
1535
- }} catch (err) { alert('Ошибка разбора JSON: ' + (err && err.message || err)) }}}, '📥 Импорт JSON'))),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secSupport')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')),React.createElement('div', { className: 'rl-section-desc' }, t('secSupportDesc')),React.createElement('div', { className: 'rl-grid-3' },React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '113'),React.createElement('div', { className: 'rl-stat-label' }, t('statNamespaces'))),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '1 225'),React.createElement('div', { className: 'rl-stat-label' }, t('statCoreKeys'))),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '5 924'),React.createElement('div', { className: 'rl-stat-label' }, t('statPluginKeys')))),React.createElement('div', { className: 'rl-actions-row' },React.createElement('a', {href: makeIssueUrl({}, '0.3.13'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1547
+ }} catch (err) { alert('Ошибка разбора JSON: ' + (err && err.message || err)) }}}, '📥 Импорт JSON'))),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secSupport')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')),React.createElement('div', { className: 'rl-section-desc' }, t('secSupportDesc')),React.createElement('div', { className: 'rl-grid-3' },React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '113'),React.createElement('div', { className: 'rl-stat-label' }, t('statNamespaces'))),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '1 225'),React.createElement('div', { className: 'rl-stat-label' }, t('statCoreKeys'))),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '5 924'),React.createElement('div', { className: 'rl-stat-label' }, t('statPluginKeys')))),React.createElement('div', { className: 'rl-actions-row' },React.createElement('a', {href: makeIssueUrl({}, '0.3.15'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1536
1548
  }, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
1537
1549
  }
1538
1550
  const RL_CSS = ['.rl-modal-mask{position:fixed;top:0;left:0;width:100vw;height:100vh;background:var(--dsw-alias-bg-mask);display:flex;align-items:center;justify-content:center;z-index:99999;backdrop-filter:blur(2px)}.rl-modal-box{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;width:calc(100vw - 40px);max-width:500px;box-shadow:var(--dsw-alias-shadow-l3);display:flex;flex-direction:column;gap:12px;padding:18px}.rl-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;overflow:hidden;transition:border-color .15s ease}.rl-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 18px}.rl-head:hover{background:var(--dsw-alias-bg-layer-2)}.rl-head-main{flex:1;display:flex;flex-direction:column;gap:4px}.rl-title{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.rl-sub{color:var(--dsw-alias-label-secondary);font-size:13px}.rl-chev{margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary);display:inline-flex;transition:transform .16s}.rl-chev-open{transform:rotate(180deg)}.rl-body{border-top:1px solid var(--dsw-alias-border-l2);padding:18px}.rl-page{display:flex;flex-direction:column;gap:16px;max-width:960px}.rl-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}.rl-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px}.rl-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}.rl-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:10px}.rl-grid-3{display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:10px}.rl-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}.rl-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}.rl-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}.rl-badge-dim{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}.rl-item-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}.rl-item-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.rl-item-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px;cursor:pointer}.rl-item-desc{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}.rl-stat-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}.rl-stat-val{font-size:16px;font-weight:700;color:var(--dsw-alias-label-primary)}.rl-stat-label{font-size:11px;color:var(--dsw-alias-label-secondary);letter-spacing:0.3px}.rl-hotkey-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4;display:flex;align-items:flex-start;gap:10px}.rl-hotkey-tag{font-size:11px;font-weight:700;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);white-space:nowrap}.rl-preview-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}.rl-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease;text-decoration:none}.rl-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-secondary)}.rl-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}.rl-btn-primary:hover:not(:disabled){opacity:0.9}.rl-select{height:32px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;outline:none;width:100%;max-width:380px}.rl-select:focus{border-color:var(--dsw-alias-state-brand-primary)}.rl-check{width:16px;height:16px;accent-color:var(--dsw-alias-label-primary);cursor:pointer;flex-shrink:0}.rl-hint-text{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}.rl-actions-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding-top:4px}.rl-lang-chip{appearance:none;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:700;padding:2px 7px;border-radius:6px;display:inline-flex;align-items:center;transition:all .15s;margin:0 4px}.rl-lang-chip:hover{border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-label-primary)}.rl-lang-chip-active{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.rl-action-btn{appearance:none;background:0 0;border:1px solid transparent;border-radius:4px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;padding:2px 5px;display:inline-flex;align-items:center;transition:all .15s}.rl-action-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3)}.rl-action-btn-active{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}.rl-turn-translation{margin:8px 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:8px;font-size:13px;line-height:1.5}.rl-trans-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px}.rl-trans-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:0.5px}.rl-trans-tools{display:flex;gap:4px}.rl-trans-btn{appearance:none;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;padding:2px 7px;border-radius:4px;font-size:11px}.rl-trans-btn:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.rl-trans-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;user-select:text}.rl-export-md-btn{appearance:none;border:1px solid var(--dsw-alias-border-l2);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:transparent;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:500;display:inline-flex;white-space:nowrap;margin-left:6px;transition:all .15s ease}.rl-export-md-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}.rl-inspector-hover{position:absolute;pointer-events:none;border:2px solid var(--dsw-alias-state-brand-primary);border-radius:4px;background:color-mix(in srgb,var(--dsw-alias-state-brand-primary) 12%,transparent);box-shadow:0 0 10px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 30%,transparent);z-index:99998;transition:all .06s ease;display:none}.rl-inspector-badge{position:absolute;bottom:calc(100% + 4px);left:0;background:var(--dsw-alias-state-brand-primary);color:var(--dsw-alias-label-inverse);font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;white-space:nowrap;box-shadow:var(--dsw-alias-shadow-l2);pointer-events:none}.rl-inspector-pill{position:fixed;bottom:18px;right:18px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:20px;padding:6px 12px;box-shadow:var(--dsw-alias-shadow-l3);display:none;align-items:center;gap:8px;font-size:12px;z-index:99990;user-select:none}.rl-inspector-pill-active{border-color:var(--dsw-alias-state-brand-primary);box-shadow:0 0 12px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 35%,transparent)}'
@@ -33,6 +33,11 @@ window.__ModuleLoader__.load({
33
33
  RU['russian-lang'] = /*__CARD_RU__*/{}
34
34
 
35
35
  const SETTINGS_NS_NAME = 'russian-lang'
36
+ // Namespace формы настроек: на 0.1.7+ это id записи загрузчика
37
+ // ('dsh-russian-lang' из cordis.patch.yml), а не имя, выбранное плагином
38
+ // ('russian-lang'), как было на 0.1.6 и ниже. Это разные вещи.
39
+ const FORM_NS = 'dsh-russian-lang'
40
+ const LEGACY_NS = 'russian-lang'
36
41
  // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>'.
37
42
  const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
38
43
 
@@ -40,8 +45,8 @@ window.__ModuleLoader__.load({
40
45
  const runtime = ctx.locale
41
46
  const resolveScope = (c) => {
42
47
  try {
43
- if (c && c.configForms) return c.configForms.get(SETTINGS_NS_NAME)
44
- if (c && c.settingsScope) return c.settingsScope.bind({ namespace: SETTINGS_NS_NAME })
48
+ if (c && c.configForms) return c.configForms.get(FORM_NS)
49
+ if (c && c.settingsScope) return c.settingsScope.bind({ namespace: LEGACY_NS })
45
50
  } catch (err) { void err }
46
51
  return null
47
52
  }
@@ -51,6 +56,10 @@ window.__ModuleLoader__.load({
51
56
  subscribe: () => () => {},
52
57
  set: () => Promise.resolve()
53
58
  }
59
+ // На 0.1.7+ выбор языка хранит сам DSH (locale.preference): собственный
60
+ // флаг там не читаем и не пишем, виджет работает просто с локалью.
61
+ // На 0.1.6 и ниже поведение прежнее — флаг живёт в настройках плагина.
62
+ const legacyFlagMode = !(ctx && ctx.configForms)
54
63
 
55
64
  // 1. Словари ядра DSH: каждый namespace — свой эффект, снимается вместе с плагином
56
65
  for (const ns of Object.keys(RU)) {
@@ -75,6 +84,7 @@ window.__ModuleLoader__.load({
75
84
  }, 'dsh-russian-lang: ' + ns)
76
85
  } else {
77
86
  Object.assign(RU[ns], dict)
87
+ try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) {}
78
88
  }
79
89
  }
80
90
  }
@@ -304,6 +314,7 @@ window.__ModuleLoader__.load({
304
314
  // scope.set возвращает Promise<void> в v0.1.2-alpha.2, ошибка приходит
305
315
  // через rejection и try/catch её не ловит — обрабатываем оба канала.
306
316
  const syncFlag = () => {
317
+ if (!legacyFlagMode) return
307
318
  try {
308
319
  const wantRu = runtime.getLocale().active === 'ru'
309
320
  const value = scope.getSnapshot().value || {}
@@ -337,7 +348,7 @@ window.__ModuleLoader__.load({
337
348
  if (booted) return
338
349
  try {
339
350
  const value = scope.getSnapshot().value
340
- if (value && value.enabled === true) { booted = true; activate() }
351
+ if (legacyFlagMode && value && value.enabled === true) { booted = true; activate() }
341
352
  } catch (err) { /* ignore */ }
342
353
  }
343
354
  ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
@@ -482,7 +493,13 @@ window.__ModuleLoader__.load({
482
493
  'profile (写入哪个 profile 的 cordis.patch.yml)': 'Профиль (куда записать cordis.patch.yml)',
483
494
  'URL (留空用 DSH_HOOKS_WEBHOOK_URL)': 'URL (по умолчанию DSH_HOOKS_WEBHOOK_URL)',
484
495
  '当前已配置的 Hook 规则列表': 'Список текущих настроенных правил хуков',
485
- '测试通道': 'Тест канала'
496
+ '测试通道': 'Тест канала',
497
+ '专家提示词服务尚未就绪,请稍后重试。': 'Сервис промптов экспертов ещё не готов, повторите попытку позже.',
498
+ '专家提示词服务尚未就绪,请稍后': 'Сервис промптов экспертов ещё не готов, пожалуйста, ',
499
+ '专家团服务不可用,请重新加载插件。': 'Сервис команд экспертов недоступен, перезагрузите плагин.',
500
+ '搜索团队或工作目标': 'Поиск команды или цели работы',
501
+ '专家': 'Эксперт',
502
+ '专家团': 'Команда экспертов'
486
503
  }
487
504
  for (const [k, v] of Object.entries(CORE_ZH_PRESETS)) ZH_EXACT.set(k, v)
488
505
 
@@ -558,6 +575,46 @@ window.__ModuleLoader__.load({
558
575
  }
559
576
  const DOM_EN_TEXT = {
560
577
  'Scheduled tasks': 'Задачи по расписанию',
578
+ 'Files': 'Файлы',
579
+ 'Back': 'Назад',
580
+ 'Hosts': 'Хосты',
581
+ 'Containers': 'Контейнеры',
582
+ 'Tunnels': 'Туннели',
583
+ 'Cluster': 'Кластер',
584
+ 'Remote Workspace': 'Удалённое рабочее пространство',
585
+ 'Native SSH connections to remote development environments with terminal, sftp, and tunneling.': 'SSH-соединения с удалёнными средами разработки: терминал, SFTP и туннели.',
586
+ 'Import ~/.ssh/config': 'Импорт ~/.ssh/config',
587
+ 'Terminal font': 'Шрифт терминала',
588
+ 'Save Profile': 'Сохранить профиль',
589
+ 'Edit Host Profile': 'Редактирование профиля хоста',
590
+ 'Add New Host Profile': 'Добавление профиля хоста',
591
+ 'Profile Name': 'Название профиля',
592
+ 'Host / IP Address': 'Хост / IP-адрес',
593
+ 'Port': 'Порт',
594
+ 'Username': 'Имя пользователя',
595
+ 'Jump hosts': 'Jump-хосты',
596
+ 'Proxy command': 'Команда прокси',
597
+ 'Environment': 'Окружение',
598
+ 'Location': 'Расположение',
599
+ 'Tags': 'Теги',
600
+ 'Description': 'Описание',
601
+ 'Authentication Method': 'Метод аутентификации',
602
+ 'SSH Private Key': 'Приватный SSH-ключ',
603
+ 'SSH Agent': 'SSH-агент',
604
+ 'Password': 'Пароль',
605
+ 'Private Key Path': 'Путь к приватному ключу',
606
+ 'Key Passphrase (optional)': 'Парольная фраза ключа (опционально)',
607
+ 'Remote Workspace Directory': 'Каталог удалённой рабочей области',
608
+ 'Browse Remote...': 'Обзор удалённых…',
609
+ 'Local Mirror Directory': 'Каталог локального зеркала',
610
+ 'Search names, fields or keywords': 'Поиск по имени, сфере или ключевым словам',
611
+ 'Search names, f[专家] or keywords': 'Поиск по имени, сфере или ключевым словам',
612
+ 'No enabled experts yet. Type a keyword to find one.': 'Нет активных экспертов. Введите ключевое слово для поиска.',
613
+ 'Search teams or goals': 'Поиск команды или цели работы',
614
+ 'Team service unavailable. Reload the plugin.': 'Сервис команд экспертов недоступен, перезагрузите плагин.',
615
+ 'Image Studio & Vault': 'Студия изображений и хранилище',
616
+ 'Agent Browser': 'Браузер агента',
617
+ 'Request approval': 'Запрашивать подтверждение',
561
618
  'Today': 'Сегодня',
562
619
  'Plugin Market': 'Магазин плагинов',
563
620
  'GitHub ops': 'Операции с GitHub',
@@ -783,20 +840,22 @@ window.__ModuleLoader__.load({
783
840
  let zhObserver = null
784
841
  let zhDebounceTimer = null
785
842
  let zhWalking = false
786
- const queueZhWalk = (target) => {
843
+ const queueZhWalk = () => {
787
844
  if (zhDebounceTimer) return
788
845
  zhDebounceTimer = setTimeout(() => {
789
846
  zhDebounceTimer = null
790
847
  if (zhWalking) return
791
848
  zhWalking = true
792
849
  try {
793
- ZH_WALKER(target || document.body)
850
+ ZH_WALKER(document.body)
794
851
  } finally {
795
852
  zhWalking = false
796
853
  }
797
- }, 120)
854
+ }, 80)
798
855
  }
799
856
 
857
+ const onInteraction = () => queueZhWalk()
858
+
800
859
  const syncZhDom = () => {
801
860
  try {
802
861
  if (typeof document === 'undefined') return
@@ -804,6 +863,8 @@ window.__ModuleLoader__.load({
804
863
  if (!ru) {
805
864
  if (zhObserver) { zhObserver.disconnect(); zhObserver = null }
806
865
  if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
866
+ document.removeEventListener('pointerup', onInteraction)
867
+ document.removeEventListener('click', onInteraction)
807
868
  return
808
869
  }
809
870
  if (!zhObserver) {
@@ -813,7 +874,7 @@ window.__ModuleLoader__.load({
813
874
  for (const node of record.addedNodes) {
814
875
  if (node.nodeType === 1) {
815
876
  if (node.closest && node.closest('.chat-message, .markdown-body, pre, code, [data-stream]')) continue
816
- queueZhWalk(node)
877
+ queueZhWalk()
817
878
  return
818
879
  }
819
880
  }
@@ -821,8 +882,10 @@ window.__ModuleLoader__.load({
821
882
  }
822
883
  })
823
884
  zhObserver.observe(document.body, { childList: true, subtree: true })
885
+ document.addEventListener('pointerup', onInteraction, { passive: true })
886
+ document.addEventListener('click', onInteraction, { passive: true })
824
887
  }
825
- queueZhWalk(document.body)
888
+ queueZhWalk()
826
889
  } catch (err) { /* ignore */ }
827
890
  }
828
891
  const unsubscribeZh = runtime.subscribe(syncZhDom)
@@ -831,6 +894,10 @@ window.__ModuleLoader__.load({
831
894
  unsubscribeZh()
832
895
  if (zhObserver) zhObserver.disconnect()
833
896
  if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
897
+ if (typeof document !== 'undefined') {
898
+ document.removeEventListener('pointerup', onInteraction)
899
+ document.removeEventListener('click', onInteraction)
900
+ }
834
901
  }
835
902
  }, 'dsh-russian-lang: zh-dom')
836
903
  syncZhDom()
package/lib/index.js CHANGED
@@ -39,48 +39,158 @@ const sendJsonWithEtag = (request, response, obj, maxAge = 300) => {
39
39
 
40
40
  export const name = '@goodandready/dsh-russian-lang'
41
41
 
42
- const RussianLangSettingsSchema = z.object({
43
- enabled: z.boolean().required(false),
44
- overrides: z.dict(z.string()).required(false),
45
- typography: z.object({
42
+ // Легаси-namespace настроек (DSH <= 0.1.6). Namespace форм на 0.1.7+ —
43
+ // id записи загрузчика ('dsh-russian-lang' из cordis.patch.yml), схема
44
+ // попадает туда через экспортированный Config ниже.
45
+ const NS = 'russian-lang'
46
+
47
+ // `.volatile()` есть только в новом schemastery; на старом его вызов
48
+ // бросает TypeError на уровне модуля — поэтому только feature-detect.
49
+ const vol = (s) => (typeof s?.volatile === 'function' ? s.volatile() : s)
50
+
51
+ export const Config = z.object({
52
+ enabled: vol(z.boolean().required(false)),
53
+ overrides: vol(z.dict(z.string()).required(false)),
54
+ typography: vol(z.object({
46
55
  enabled: z.boolean().required(false),
47
56
  yo: z.boolean().required(false),
48
57
  liveInput: z.boolean().required(false)
49
- }).required(false),
50
- agentPrompt: z.boolean().required(false),
51
- agentPromptPreset: z.string().required(false),
52
- slashAliases: z.boolean().required(false),
53
- quickSwitch: z.boolean().required(false),
54
- translateEngine: z.string().required(false),
55
- localApiUrl: z.string().required(false)
58
+ }).required(false)),
59
+ agentPrompt: vol(z.boolean().required(false)),
60
+ agentPromptPreset: vol(z.string().required(false)),
61
+ slashAliases: vol(z.boolean().required(false)),
62
+ quickSwitch: vol(z.boolean().required(false)),
63
+ translateEngine: vol(z.string().required(false)),
64
+ localApiUrl: vol(z.string().required(false))
56
65
  })
57
66
 
58
- export function apply(ctx) {
59
- let globalSettingsCtx = null
67
+ // Историческое имя схемы; сама схема — Config выше.
68
+ const RussianLangSettingsSchema = Config
69
+
70
+ // Plain-схема для легаси-ветки register(): старый провайдер ждёт schemastery-схему,
71
+ // поэтому сериализуем, рекурсивно снимаем meta.volatile и рехидрируем обратно
72
+ // в схему — иначе volatile-поля остались бы рефами и сломали бы старое хранилище.
73
+ // Любая ошибка — возвращаем исходную схему, наружу не бросаем.
74
+ function plainSchema(schema) {
75
+ try {
76
+ const json = (schema && typeof schema.toJSON === 'function') ? schema.toJSON() : schema
77
+ const clone = JSON.parse(JSON.stringify(json))
78
+ const seen = new Set()
79
+ const strip = (node) => {
80
+ if (!node || typeof node !== 'object' || seen.has(node)) return
81
+ seen.add(node)
82
+ if (node.meta && typeof node.meta === 'object') {
83
+ try { delete node.meta.volatile } catch (_) { /* bestEffort */ void _ }
84
+ }
85
+ for (const key of Object.keys(node)) strip(node[key])
86
+ }
87
+ strip(clone)
88
+ return new z(clone)
89
+ } catch (_) {
90
+ return schema
91
+ }
92
+ }
93
+
94
+ export function apply(ctx, config) {
95
+ // Живой провайдер настроек; на 0.1.7+ у него есть configure(),
96
+ // на старых — register()/get(). Пусто до первого inject.
97
+ let settingsProvider = null
98
+
99
+ // Единый снимок значений: на 0.1.7+ разворачиваем volatile-рефы второго
100
+ // аргумента (ссылки стабильны — загрузчик обновляет их на месте),
101
+ // на старой ветке читаем документ провайдера. Никогда не бросает.
102
+ const readSettings = () => {
103
+ try {
104
+ const unwrap = (src) => {
105
+ if (!src || typeof src !== 'object') return {}
106
+ const out = {}
107
+ for (const key of Object.keys(src)) {
108
+ const v = src[key]
109
+ out[key] = (v && typeof v.get === 'function') ? v.get() : v
110
+ }
111
+ return out
112
+ }
113
+ if (settingsProvider && typeof settingsProvider.configure === 'function') {
114
+ return unwrap(config)
115
+ }
116
+ if (settingsProvider && typeof settingsProvider.get === 'function') {
117
+ return settingsProvider.get(NS) || {}
118
+ }
119
+ return unwrap(config)
120
+ } catch (_) {
121
+ return {}
122
+ }
123
+ }
124
+
60
125
  ctx.inject(['settings'], (settingsCtx) => {
61
- globalSettingsCtx = settingsCtx
62
- settingsCtx.settings.register('russian-lang', RussianLangSettingsSchema)
126
+ let provider = null
127
+ try {
128
+ provider = settingsCtx?.settings
129
+ ?? (typeof settingsCtx?.get === 'function' ? settingsCtx.get('settings') : null)
130
+ ?? null
131
+ } catch (_) { provider = null }
132
+ settingsProvider = provider
133
+ if (!provider) return
134
+ try {
135
+ if (typeof provider.configure === 'function') {
136
+ // 0.1.7+: схема уже у загрузчика через экспортированный Config;
137
+ // отключаем автогенерацию страницы (карточка у нас кастомная).
138
+ if (typeof settingsCtx?.effect === 'function') {
139
+ settingsCtx.effect(() => provider.configure({ auto: false }, ctx.fiber))
140
+ } else {
141
+ provider.configure({ auto: false }, ctx.fiber)
142
+ }
143
+ } else if (typeof provider.register === 'function') {
144
+ // Легаси: plain-схема без volatile-мета.
145
+ try {
146
+ provider.register(NS, plainSchema(RussianLangSettingsSchema))
147
+ } catch (_) {
148
+ provider.register(NS, RussianLangSettingsSchema)
149
+ }
150
+ }
151
+ // Иначе тихая деградация: readSettings вернёт {} / развернёт рефы.
152
+ } catch (_) { /* never throw outwards */ void _ }
63
153
  })
64
154
 
65
155
  // #68: русский системный промпт агента (опционально). Регистрируем секцию
66
156
  // промпта, которая задёт стиль ответов по-русски, когда включён флаг
67
157
  // russian-lang.agentPrompt. Секция — официальная точка расширения ядра.
68
- ctx.inject(['settings', 'systemPrompt'], (settingsCtx, prompt) => {
69
- const NS = 'russian-lang'
158
+ // Значения — через readSettings() (volatile-рефы на 0.1.7+, документ
159
+ // провайдера на легаси). Пересинхронизация: loader/volatile-update
160
+ // (свой ctx, 0.1.7) и settings/updated (старые ядра).
161
+ ctx.inject(['systemPrompt'], (promptCtx) => {
162
+ let prompt = promptCtx?.systemPrompt
163
+ try {
164
+ if (!prompt && typeof promptCtx?.get === 'function') prompt = promptCtx.get('systemPrompt')
165
+ } catch (_) { /* bestEffort */ void _ }
166
+ if (!prompt || typeof prompt.section !== 'function') return
70
167
  const RU_SECTION = 'dsh-russian-lang-agent-prompt'
71
168
  const DEFAULT_RU_TEXT = 'Отвечай пользователю на русском языке. Если пользователь пишет на другом языке, отвечай на его языке.'
169
+ let unregisterSection = null
72
170
  let currentText = ''
73
171
  const sync = () => {
74
- const value = settingsCtx.settings.get(NS)
75
- const want = !!(value && value.agentPrompt)
76
- const presetKey = value && value.agentPromptPreset
77
- const preset = SYSTEM_PROMPT_PRESETS[presetKey]
78
- const text = want ? (preset ? preset.text : DEFAULT_RU_TEXT) : ''
79
- if (text === currentText) return
80
- currentText = text
81
- prompt.section({ name: RU_SECTION, order: 1000, text })
172
+ try {
173
+ const value = readSettings()
174
+ const want = !!(value && value.agentPrompt)
175
+ const presetKey = value && value.agentPromptPreset
176
+ const preset = SYSTEM_PROMPT_PRESETS[presetKey]
177
+ const text = want ? (preset ? preset.text : DEFAULT_RU_TEXT) : ''
178
+ if (text === currentText) return
179
+ // section() не идемпотентен: повторная регистрация того же имени
180
+ // бросает, поэтому сначала снимаем прежнюю секцию через disposer.
181
+ if (typeof unregisterSection === 'function') {
182
+ try { unregisterSection() } catch (_) { /* bestEffort */ void _ }
183
+ }
184
+ unregisterSection = null
185
+ currentText = text
186
+ // Выключение флага снимает секцию, а не пушит пустой текст.
187
+ if (!text) return
188
+ const dispose = prompt.section({ name: RU_SECTION, order: 1000, text })
189
+ unregisterSection = typeof dispose === 'function' ? dispose : null
190
+ } catch (_) { /* never throw outwards */ void _ }
82
191
  }
83
- ctx.on('settings/updated', (ns) => { if (ns === NS) sync() })
192
+ try { ctx.on('loader/volatile-update', sync) } catch (_) { /* bestEffort */ void _ }
193
+ try { ctx.on('settings/updated', (ns) => { if (ns === NS) sync() }) } catch (_) { /* bestEffort */ void _ }
84
194
  sync()
85
195
  })
86
196
 
@@ -95,7 +205,7 @@ export function apply(ctx) {
95
205
  manifestUrl: new URL('../package.json', import.meta.url),
96
206
  })
97
207
 
98
- const unregTranslator = registerTranslator(wctx, globalSettingsCtx)
208
+ const unregTranslator = registerTranslator(wctx, readSettings)
99
209
 
100
210
  let ws = null
101
211
  try { ws = wctx?.webServer } catch (_) { ws = null }
@@ -1 +1 @@
1
- {"@goodandready/dsh-remote-workspace":{"checkUpdate":"Проверить обновления","checking":"Проверка...","currentVersion":"Текущая версия","latestVersion":"npm Последняя","restartRequired":"Обновление успешно установлено. Перезапустите DSH для применения.","upToDate":"Актуальная версия","updateAvailable":"Доступно обновление","updateFailed":"Сбой обновления","updateNow":"Обновить сейчас","updaterTitle":"Обновление плагина","updating":"Обновление..."},"dsh-remote-workspace":{"actionFailed":"Запрос не выполнен.","authPromptCode":"Одноразовый код","authPromptSubmit":"Продолжить","authPromptTitle":"Код авторизации","btnDownload":"Скачать","btnUpload":"Загрузить","checkUpdate":"Проверить обновления","checking":"Проверка…","clusterDeleteConfirm":"Удалить этот узел кластера?","clusterHealth":"Кластер: {status}","clusterNoNodes":"Узлы кластера не настроены.","clusterNodeAdd":"Добавить узел","clusterNodeName":"Имя узла","clusterNodeRole":"Роль","clusterTitle":"Узлы кластера","currentVersion":"Установленная версия","diagAuth":"Аутентификация","diagLatency":"Задержка","diagOs":"Операционная система","diagPorts":"Открытые порты","diagUptime":"Время работы","dockerCompose":"Стеки Compose","dockerComposeDown":"Остановить","dockerComposeRestart":"Перезапустить","dockerComposeUp":"Запустить","dockerNoStacks":"Стеков Compose не найдено.","emptyClusterNodes":"В этом кластере нет узлов.","emptyContainers":"Нет работающих контейнеров.","emptyFiles":"Каталог пуст.","emptyHosts":"Хосты не настроены.","emptyTunnels":"Нет активных туннелей.","fAuthCode":"Код подтверждения","fKey":"Закрытый ключ","fKeyUpload":"Загрузить файл ключа","fPortForward":"Проброшенные порты","fTunnelLocal":"Локальная привязка","fTunnelRemote":"Удалённый адрес","importEmpty":"В ~/.ssh/config не найдено хостов","importFound":"Найдено хостов в ~/.ssh/config: {count}","importSelected":"Импортировать выбранные","importSsh":"Импорт из ~/.ssh/config","importSuccess":"Импортировано хостов: {count}","latestVersion":"Последняя версия в npm","noFiles":"Файлов не найдено.","noHostsConfigured":"Удалённые хосты не настроены.","pinned":"Закреплён","restartRequired":"Обновление установлено. Перезапустите DSH для применения.","setActiveBtn":"Сделать текущим","skipDuplicate":"уже импортирован","skipMatch":"Блоки Match не импортируются","skipMissing":"включённый файл не найден","skipWildcard":"шаблон не является отдельным хостом","statusEmpty":"Нет активного хоста","statusError":"Статус недоступен","statusLoading":"Проверка соединения","statusReady":"Хост готов","tabCluster":"Кластер","tabFiles":"Файлы","testGroup":"Группа тестов","transferCancel":"Отмена","transferFailed":"Сбой передачи: {name}","transferProgress":"{name}: {percent}%","upToDate":"Уже последняя версия","updateAvailable":"Доступно обновление","updateFailed":"Не удалось обновить","updateNow":"Обновить","updaterTitle":"Обновление плагина","updating":"Обновление…","versionUnknown":"Версия неизвестна","wsBack":"Назад","wsCluster":"Кластер","wsDocker":"Контейнеры","wsFiles":"Файлы","wsHosts":"Хосты","wsTerminal":"Терминал","wsTunnels":"Туннели"}}
1
+ {"@goodandready/dsh-remote-workspace":{"actionFailed":"Запрос не выполнен.","actionsCol":"Действия","activeBadge":"Активен","addBtn":"+ Добавить хост","alertLabel":"Оповещения ({count})","authPromptCode":"Одноразовый код","authPromptSubmit":"Продолжить","authPromptTitle":"Код авторизации","autoSyncDesc":"Автоматически отправлять изменённые файлы в удалённое рабочее пространство при локальном сохранении.","autoSyncTitle":"Автосинхронизация при сохранении","badgeKey":"🔑 Вход по ключу","badgeOffline":"Хост не выбран","badgeOnline":"Активный хост: {name} ({latency} мс)","badgePassword":"🔒 Вход по паролю","badgeTunnels":"Туннелей: {count}","btnBrowse":"📁 Выбрать на сервере…","btnCancel":"Отмена","btnDelete":"Удалить","btnDownload":"Скачать","btnImportSsh":"Импорт из ~/.ssh/config","btnLogs":"Логи","btnRestart":"Перезапустить","btnSave":"Сохранить профиль","btnSaveFile":"Сохранить изменения","btnSelectThis":"Выбрать эту папку","btnStop":"Остановить","btnTest":"⚡ Проверить связь (Ping)","btnTransfer":"Передать на хост…","btnUpload":"Загрузить","checkUpdate":"Проверить обновления","checking":"Проверка…","clusterCommand":"Команда","clusterCount":"Настроено серверов: {count}","clusterFail":"ошибка","clusterOk":"успешно","clusterRun":"Выполнить","clusterRunning":"Выполняется...","clusterTitle":"Запуск на группе","containerImage":"Образ","containerName":"Контейнер","containerPorts":"Порты","containerStatus":"Статус","containersTitle":"Удалённый Docker / Podman Engine","currentVersion":"Установленная версия","delBtn":"Удалить","delConfirm":"Удалить профиль хоста «{name}»?","diagDesc":"Проверка доступности и измерение задержки активного сервера.","diagProbeBtn":"⚡ Быстрая диагностика","diagTitle":"📊 Диагностика активного подключения","diagnoseDisk":"Использование диска","diagnoseOom":"Проверка OOM Killer","diagnosePorts":"Открытые порты","diagnoseTitle":"Экспресс-диагностика","editBtn":"Изменить","envHideVal":"Скрыть","envKey":"Ключ","envSaveVar":"Сохранить","envShowVal":"Показать","envTitle":"Удалённые переменные окружения (.env)","envVal":"Значение","explorerTitle":"Удалённый файловый менеджер","fAgentPath":"Сокет агента","fAgentPathHint":"Оставьте пустым для использования SSH_AUTH_SOCK. В Windows используйте pageant.","fAuthAgent":"SSH Agent","fAuthAgentShort":"агент","fAuthKey":"SSH-ключ","fAuthKeyShort":"ключ","fAuthPass":"Пароль","fAuthPassShort":"пароль","fAuthType":"Способ авторизации","fDescription":"Описание","fEnvironment":"Окружение","fHost":"Хост / IP-адрес","fJumpHost":"Jump-хост (бастион)","fJumpHostNone":"Прямое подключение (без jump-хоста)","fJumpHosts":"Jump-хосты","fJumpHostsHint":"ID профилей по порядку через запятую. Первый хост — входной бастион.","fKeyPath":"Путь к ключу","fKeyPathHint":"Путь к ~/.ssh/id_rsa или /home/user/.ssh/id_ed25519","fLocalMirror":"Локальная папка зеркала","fLocalMirrorHint":"Локальная директория для зеркалирования файлов при быстрой трёхсторонней синхронизации","fLocation":"Локация","fName":"Название подключения","fPassphrase":"Кодовая фраза ключа (если есть)","fPassword":"Пароль учетной записи","fPort":"Порт","fProxyCommand":"Proxy-команда","fProxyCommandHint":"OpenSSH ProxyCommand. Заполнители: %h хост, %p порт, %r пользователь, %n имя профиля.","fRemotePath":"Удалённая папка проекта","fRemotePathHint":"(Не обязательно сейчас — можно выбрать позже при работе)","fTags":"Теги","fTerminalFont":"Шрифт терминала","fTerminalFontHint":"Семейство шрифтов для терминала. Оставьте пустым для моноширинного стека по умолчанию.","fUser":"Имя пользователя","filePreviewTitle":"Просмотр файла: {path}","fileSaved":"Файл успешно сохранён.","fileSize":"{size} КБ","formTitleEdit":"Редактирование профиля хоста","formTitleNew":"Добавление нового SSH хоста","groupEnvironment":"По окружению","groupFlat":"Сплошной список","groupOffline":"не в сети: {err}","groupOnline":"в сети, {latency} мс","groupTag":"По тегам","groupUngrouped":"Без группы","healthCpu":"ЦП: {val}%","healthDisk":"Диск: {val}%","healthMem":"ОЗУ: {val}% ({used}/{total} МБ)","healthUptime":"Аптайм: {val}","hostCol":"Хост / Пользователь","importAdded":"Импортировано серверов: {count}.","latestVersion":"Последняя на npm","nameCol":"Имя","navLabel":"Удаленные серверы","noContainers":"Активных контейнеров не найдено или демон Docker не запущен.","noHosts":"Удалённые хосты ещё не настроены. Нажмите «+ Добавить хост».","openLocalLink":"🌐 Открыть локальный порт","pathCol":"Удалённый путь","pinBtn":"Закрепить активный","pinned":"Активный хост","portCol":"Порт","profilesDesc":"Настройка удалённых хостов для выполнения команд, SFTP доступа и синхронизации.","profilesTitle":"🖥️ Профили SSH серверов","pullBtn":"⬇️ Скачать с сервера (Pull)","pulling":"Получение файлов…","pushBtn":"⬆️ Загрузить на сервер (Push)","pushing":"Отправка файлов…","restartRequired":"Обновление установлено. Перезапустите DSH для применения.","saving":"Сохранение…","setActiveBtn":"Сделать активным","skipDuplicate":"уже импортировано","skipMatch":"Блоки Match не импортируются","skipMissing":"включённый файл не найден","skipWildcard":"шаблон не является отдельным хостом","status":"Готово","statusEmpty":"Нет активного хоста","statusError":"Статус недоступен","statusLoading":"Проверка соединения","statusReady":"Хост готов","subtitle":"Управление подключениями по SSH, синхронизацией SFTP и туннелями","syncConflict":"Предупреждение о конфликте: изменено с обеих сторон файлов — {count}.","syncDesc":"Ручная синхронизация файлов с удалённым сервером и статус проброса портов.","syncErr":"Ошибка синхронизации: {err}","syncFailed":"Ошибка синхронизации: ","syncOk":"Синхронизация завершена! Обновлено файлов: {count}.","syncSuccess":"Синхронизация успешно выполнена","syncTitle":"🔄 Синхронизация зеркала и туннели","tLocalPort":"Локальный порт","tNoTunnels":"Нет активных туннелей проброса портов.","tOpenBtn":"+ Открыть туннель","tOpenErr":"Не удалось открыть туннель: {err}","tOpenOk":"Туннель открыт: 127.0.0.1:{localPort} -> Удалённый:{remotePort}","tOpening":"Открытие…","tRemotePort":"Удалённый порт","tStopBtn":"Остановить","tabCluster":"Кластер","tabContainers":"🐳 Контейнеры","tabEnv":"🗃️ Менеджер .env","tabExplorer":"📂 Проводник файлов","tabFiles":"Файлы","tabProfiles":"🖥️ Профили хостов","tabSync":"🔄 Станция синхронизации","tabTerminal":"⚡ Веб-терминал","tabTunnels":"🔗 Порт-туннели","tarSyncFast":"🚀 Быстрый поток архива (Tarball Stream)","termClear":"Очистить","termConnected":"Подключено к PTY-сессии {id} на {host}","termDisconnected":"Терминал отключён.","termPlaceholder":"Введите команду и нажмите Enter (напр. htop, ls, docker ps)…","termStart":"Подключить терминал","termStop":"Отключить","testFail":"❌ Ошибка подключения: {err}","testGroup":"Тестовая группа","testOk":"✅ Соединение установлено! Задержка: {latency} мс | ОС: {os}","testing":"Проверка соединения…","title":"Удалённое рабочее пространство","transferBtnExec":"Передать напрямую","transferCancel":"Отмена","transferDestHost":"Целевой сервер","transferDestPath":"Целевой путь к файлу","transferFailed":"Сбой передачи: {name}","transferProgress":"{name}: {percent}%","transferSuccess":"Успешно передано {bytes} байт на {host}:{path}","transferTitle":"Прямая передача сервер-сервер","tunnelTraffic":"Вход: {rx} | Выход: {tx}","tunnelUptime":"Работает: {sec} с","tunnelsColActions":"Действия","tunnelsColId":"ID туннеля","tunnelsColRoute":"Маршрут проброса","tunnelsDesc":"Проброс удалённых портов (базы данных, веб-серверы, API) на локальный компьютер через зашифрованные SSH-туннели.","tunnelsTitle":"🔀 Проброс портов SSH (туннели)","upToDate":"Актуальная версия","updateAvailable":"Доступно обновление","updateFailed":"Сбой обновления","updateNow":"Обновить сейчас","updaterTitle":"Обновление плагина","updating":"Обновление…","vaultProtectedBadge":"🛡️ Защищено .env Vault","versionUnknown":"Версия неизвестна","wsBack":"Назад","wsCluster":"Кластер","wsDocker":"Контейнеры","wsFiles":"Файлы","wsHosts":"Хосты","wsTerminal":"Терминал","wsTunnels":"Туннели"},"dsh-remote-workspace":{"actionFailed":"Запрос не выполнен.","actionsCol":"Действия","activeBadge":"Активен","addBtn":"+ Добавить хост","alertLabel":"Оповещения ({count})","authPromptCode":"Одноразовый код","authPromptSubmit":"Продолжить","authPromptTitle":"Код авторизации","autoSyncDesc":"Автоматически отправлять изменённые файлы в удалённое рабочее пространство при локальном сохранении.","autoSyncTitle":"Автосинхронизация при сохранении","badgeKey":"🔑 Вход по ключу","badgeOffline":"Хост не выбран","badgeOnline":"Активный хост: {name} ({latency} мс)","badgePassword":"🔒 Вход по паролю","badgeTunnels":"Туннелей: {count}","btnBrowse":"📁 Выбрать на сервере…","btnCancel":"Отмена","btnDelete":"Удалить","btnDownload":"Скачать","btnImportSsh":"Импорт из ~/.ssh/config","btnLogs":"Логи","btnRestart":"Перезапустить","btnSave":"Сохранить профиль","btnSaveFile":"Сохранить изменения","btnSelectThis":"Выбрать эту папку","btnStop":"Остановить","btnTest":"⚡ Проверить связь (Ping)","btnTransfer":"Передать на хост…","btnUpload":"Загрузить","checkUpdate":"Проверить обновления","checking":"Проверка…","clusterCommand":"Команда","clusterCount":"Настроено серверов: {count}","clusterDeleteConfirm":"Удалить этот узел кластера?","clusterFail":"ошибка","clusterHealth":"Кластер: {status}","clusterNoNodes":"Узлы кластера не настроены.","clusterNodeAdd":"Добавить узел","clusterNodeName":"Имя узла","clusterNodeRole":"Роль","clusterOk":"успешно","clusterRun":"Выполнить","clusterRunning":"Выполняется...","clusterTitle":"Запуск на группе","containerImage":"Образ","containerName":"Контейнер","containerPorts":"Порты","containerStatus":"Статус","containersTitle":"Удалённый Docker / Podman Engine","currentVersion":"Установленная версия","delBtn":"Удалить","delConfirm":"Удалить профиль хоста «{name}»?","diagAuth":"Аутентификация","diagDesc":"Проверка доступности и измерение задержки активного сервера.","diagLatency":"Задержка","diagOs":"Операционная система","diagPorts":"Открытые порты","diagProbeBtn":"⚡ Быстрая диагностика","diagTitle":"📊 Диагностика активного подключения","diagUptime":"Время работы","diagnoseDisk":"Использование диска","diagnoseOom":"Проверка OOM Killer","diagnosePorts":"Открытые порты","diagnoseTitle":"Экспресс-диагностика","dockerCompose":"Стеки Compose","dockerComposeDown":"Остановить","dockerComposeRestart":"Перезапустить","dockerComposeUp":"Запустить","dockerNoStacks":"Стеков Compose не найдено.","editBtn":"Изменить","emptyClusterNodes":"В этом кластере нет узлов.","emptyContainers":"Нет работающих контейнеров.","emptyFiles":"Каталог пуст.","emptyHosts":"Хосты не настроены.","emptyTunnels":"Нет активных туннелей.","envHideVal":"Скрыть","envKey":"Ключ","envSaveVar":"Сохранить","envShowVal":"Показать","envTitle":"Удалённые переменные окружения (.env)","envVal":"Значение","explorerTitle":"Удалённый файловый менеджер","fAgentPath":"Сокет агента","fAgentPathHint":"Оставьте пустым для использования SSH_AUTH_SOCK. В Windows используйте pageant.","fAuthAgent":"SSH Agent","fAuthAgentShort":"агент","fAuthCode":"Код подтверждения","fAuthKey":"SSH-ключ","fAuthKeyShort":"ключ","fAuthPass":"Пароль","fAuthPassShort":"пароль","fAuthType":"Способ авторизации","fDescription":"Описание","fEnvironment":"Окружение","fHost":"Хост / IP-адрес","fJumpHost":"Jump-хост (бастион)","fJumpHostNone":"Прямое подключение (без jump-хоста)","fJumpHosts":"Jump-хосты","fJumpHostsHint":"ID профилей по порядку через запятую. Первый хост — входной бастион.","fKey":"Закрытый ключ","fKeyPath":"Путь к ключу","fKeyPathHint":"Путь к ~/.ssh/id_rsa или /home/user/.ssh/id_ed25519","fKeyUpload":"Загрузить файл ключа","fLocalMirror":"Локальная папка зеркала","fLocalMirrorHint":"Локальная директория для зеркалирования файлов при быстрой трёхсторонней синхронизации","fLocation":"Локация","fName":"Название подключения","fPassphrase":"Кодовая фраза ключа (если есть)","fPassword":"Пароль учетной записи","fPort":"Порт","fPortForward":"Проброшенные порты","fProxyCommand":"Proxy-команда","fProxyCommandHint":"OpenSSH ProxyCommand. Заполнители: %h хост, %p порт, %r пользователь, %n имя профиля.","fRemotePath":"Удалённая папка проекта","fRemotePathHint":"(Не обязательно сейчас — можно выбрать позже при работе)","fTags":"Теги","fTerminalFont":"Шрифт терминала","fTerminalFontHint":"Семейство шрифтов для терминала. Оставьте пустым для моноширинного стека по умолчанию.","fTunnelLocal":"Локальная привязка","fTunnelRemote":"Удалённый адрес","fUser":"Имя пользователя","filePreviewTitle":"Просмотр файла: {path}","fileSaved":"Файл успешно сохранён.","fileSize":"{size} КБ","formTitleEdit":"Редактирование профиля хоста","formTitleNew":"Добавление нового SSH хоста","groupEnvironment":"По окружению","groupFlat":"Сплошной список","groupOffline":"не в сети: {err}","groupOnline":"в сети, {latency} мс","groupTag":"По тегам","groupUngrouped":"Без группы","healthCpu":"ЦП: {val}%","healthDisk":"Диск: {val}%","healthMem":"ОЗУ: {val}% ({used}/{total} МБ)","healthUptime":"Аптайм: {val}","hostCol":"Хост / Пользователь","importAdded":"Импортировано серверов: {count}.","importEmpty":"В ~/.ssh/config не найдено хостов","importFound":"Найдено хостов в ~/.ssh/config: {count}","importSelected":"Импортировать выбранные","importSsh":"Импорт из ~/.ssh/config","importSuccess":"Импортировано хостов: {count}","latestVersion":"Последняя на npm","nameCol":"Имя","navLabel":"Удаленные серверы","noContainers":"Активных контейнеров не найдено или демон Docker не запущен.","noFiles":"Файлов не найдено.","noHosts":"Удалённые хосты ещё не настроены. Нажмите «+ Добавить хост».","noHostsConfigured":"Удалённые хосты не настроены.","openLocalLink":"🌐 Открыть локальный порт","pathCol":"Удалённый путь","pinBtn":"Закрепить активный","pinned":"Активный хост","portCol":"Порт","profilesDesc":"Настройка удалённых хостов для выполнения команд, SFTP доступа и синхронизации.","profilesTitle":"🖥️ Профили SSH серверов","pullBtn":"⬇️ Скачать с сервера (Pull)","pulling":"Получение файлов…","pushBtn":"⬆️ Загрузить на сервер (Push)","pushing":"Отправка файлов…","restartRequired":"Обновление установлено. Перезапустите DSH для применения.","saving":"Сохранение…","setActiveBtn":"Сделать активным","skipDuplicate":"уже импортировано","skipMatch":"Блоки Match не импортируются","skipMissing":"включённый файл не найден","skipWildcard":"шаблон не является отдельным хостом","status":"Готово","statusEmpty":"Нет активного хоста","statusError":"Статус недоступен","statusLoading":"Проверка соединения","statusReady":"Хост готов","subtitle":"Управление подключениями по SSH, синхронизацией SFTP и туннелями","syncConflict":"Предупреждение о конфликте: изменено с обеих сторон файлов — {count}.","syncDesc":"Ручная синхронизация файлов с удалённым сервером и статус проброса портов.","syncErr":"Ошибка синхронизации: {err}","syncFailed":"Ошибка синхронизации: ","syncOk":"Синхронизация завершена! Обновлено файлов: {count}.","syncSuccess":"Синхронизация успешно выполнена","syncTitle":"🔄 Синхронизация зеркала и туннели","tLocalPort":"Локальный порт","tNoTunnels":"Нет активных туннелей проброса портов.","tOpenBtn":"+ Открыть туннель","tOpenErr":"Не удалось открыть туннель: {err}","tOpenOk":"Туннель открыт: 127.0.0.1:{localPort} -> Удалённый:{remotePort}","tOpening":"Открытие…","tRemotePort":"Удалённый порт","tStopBtn":"Остановить","tabCluster":"Кластер","tabContainers":"🐳 Контейнеры","tabEnv":"🗃️ Менеджер .env","tabExplorer":"📂 Проводник файлов","tabFiles":"Файлы","tabProfiles":"🖥️ Профили хостов","tabSync":"🔄 Станция синхронизации","tabTerminal":"⚡ Веб-терминал","tabTunnels":"🔗 Порт-туннели","tarSyncFast":"🚀 Быстрый поток архива (Tarball Stream)","termClear":"Очистить","termConnected":"Подключено к PTY-сессии {id} на {host}","termDisconnected":"Терминал отключён.","termPlaceholder":"Введите команду и нажмите Enter (напр. htop, ls, docker ps)…","termStart":"Подключить терминал","termStop":"Отключить","testFail":"❌ Ошибка подключения: {err}","testGroup":"Тестовая группа","testOk":"✅ Соединение установлено! Задержка: {latency} мс | ОС: {os}","testing":"Проверка соединения…","title":"Удалённое рабочее пространство","transferBtnExec":"Передать напрямую","transferCancel":"Отмена","transferDestHost":"Целевой сервер","transferDestPath":"Целевой путь к файлу","transferFailed":"Сбой передачи: {name}","transferProgress":"{name}: {percent}%","transferSuccess":"Успешно передано {bytes} байт на {host}:{path}","transferTitle":"Прямая передача сервер-сервер","tunnelTraffic":"Вход: {rx} | Выход: {tx}","tunnelUptime":"Работает: {sec} с","tunnelsColActions":"Действия","tunnelsColId":"ID туннеля","tunnelsColRoute":"Маршрут проброса","tunnelsDesc":"Проброс удалённых портов (базы данных, веб-серверы, API) на локальный компьютер через зашифрованные SSH-туннели.","tunnelsTitle":"🔀 Проброс портов SSH (туннели)","upToDate":"Актуальная версия","updateAvailable":"Доступно обновление","updateFailed":"Сбой обновления","updateNow":"Обновить сейчас","updaterTitle":"Обновление плагина","updating":"Обновление…","vaultProtectedBadge":"🛡️ Защищено .env Vault","versionUnknown":"Версия неизвестна","wsBack":"Назад","wsCluster":"Кластер","wsDocker":"Контейнеры","wsFiles":"Файлы","wsHosts":"Хосты","wsTerminal":"Терминал","wsTunnels":"Туннели"}}
@@ -1 +1 @@
1
- {"dsh-remote-workspace":{"actionsCol":"Действия","activeBadge":"Активен","addBtn":"+ Добавить хост","alertLabel":"Оповещения ({count})","autoSyncDesc":"Автоматически отправлять изменённые файлы в удалённое рабочее пространство при локальном сохранении.","autoSyncTitle":"Автосинхронизация при сохранении","badgeKey":"🔑 Вход по ключу","badgeOffline":"Хост не выбран","badgeOnline":"Активный хост: {name} ({latency} мс)","badgePassword":"🔒 Вход по паролю","badgeTunnels":"Туннелей: {count}","btnBrowse":"📁 Выбрать на сервере…","btnCancel":"Отмена","btnDelete":"Удалить","btnImportSsh":"Импорт из ~/.ssh/config","btnLogs":"Логи","btnRestart":"Перезапустить","btnSave":"Сохранить профиль","btnSaveFile":"Сохранить изменения","btnSelectThis":"Выбрать эту папку","btnStop":"Остановить","btnTest":"⚡ Проверить связь (Ping)","btnTransfer":"Передать на хост…","clusterCommand":"Команда","clusterCount":"Настроено серверов: {count}","clusterFail":"ошибка","clusterOk":"успешно","clusterRun":"Выполнить","clusterRunning":"Выполняется...","containerImage":"Образ","containerName":"Контейнер","containerPorts":"Порты","containerStatus":"Статус","containersTitle":"Удалённый Docker / Podman Engine","delBtn":"Удалить","delConfirm":"Удалить профиль хоста «{name}»?","diagDesc":"Проверка доступности и измерение задержки активного сервера.","diagProbeBtn":"⚡ Быстрая диагностика","diagTitle":"📊 Диагностика активного подключения","diagnoseDisk":"Использование диска","diagnoseOom":"Проверка OOM Killer","diagnosePorts":"Открытые порты","diagnoseTitle":"Экспресс-диагностика","editBtn":"Изменить","envHideVal":"Скрыть","envKey":"Ключ","envSaveVar":"Сохранить","envShowVal":"Показать","envTitle":"Удалённые переменные окружения (.env)","envVal":"Значение","explorerTitle":"Удалённый файловый менеджер","fAgentPath":"Сокет агента","fAgentPathHint":"Оставьте пустым для использования SSH_AUTH_SOCK. В Windows используйте pageant.","fAuthAgent":"SSH Agent","fAuthAgentShort":"агент","fAuthKey":"SSH-ключ","fAuthKeyShort":"ключ","fAuthPass":"Пароль","fAuthPassShort":"пароль","fAuthType":"Способ авторизации","fDescription":"Описание","fEnvironment":"Окружение","fHost":"Хост / IP-адрес","fJumpHost":"Jump-хост (бастион)","fJumpHostNone":"Прямое подключение (без jump-хоста)","fJumpHosts":"Jump-хосты","fJumpHostsHint":"ID профилей по порядку через запятую. Первый хост — входной бастион.","fKeyPath":"Путь к ключу","fKeyPathHint":"Путь к ~/.ssh/id_rsa или /home/user/.ssh/id_ed25519","fLocalMirror":"Локальная папка зеркала","fLocalMirrorHint":"Локальная директория для зеркалирования файлов при быстрой трёхсторонней синхронизации","fLocation":"Локация","fName":"Название подключения","fPassphrase":"Кодовая фраза ключа (если есть)","fPassword":"Пароль учетной записи","fPort":"Порт","fProxyCommand":"Proxy-команда","fProxyCommandHint":"OpenSSH ProxyCommand. Заполнители: %h хост, %p порт, %r пользователь, %n имя профиля.","fRemotePath":"Удалённая папка проекта","fRemotePathHint":"(Не обязательно сейчас — можно выбрать позже при работе)","fTags":"Теги","fTerminalFont":"Шрифт терминала","fTerminalFontHint":"Семейство шрифтов для терминала. Оставьте пустым для моноширинного стека по умолчанию.","fUser":"Имя пользователя","filePreviewTitle":"Просмотр файла: {path}","fileSaved":"Файл успешно сохранён.","fileSize":"{size} КБ","formTitleEdit":"Редактирование профиля хоста","formTitleNew":"Добавление нового SSH хоста","groupEnvironment":"По окружению","groupFlat":"Сплошной список","groupOffline":"не в сети: {err}","groupOnline":"в сети, {latency} мс","groupTag":"По тегам","groupUngrouped":"Без группы","healthCpu":"ЦП: {val}%","healthDisk":"Диск: {val}%","healthMem":"ОЗУ: {val}% ({used}/{total} МБ)","healthUptime":"Аптайм: {val}","hostCol":"Хост / Пользователь","importAdded":"Импортировано серверов: {count}.","nameCol":"Имя","navLabel":"Удаленные серверы","noContainers":"Активных контейнеров не найдено или демон Docker не запущен.","noHosts":"Удалённые хосты ещё не настроены. Нажмите «+ Добавить хост».","openLocalLink":"🌐 Открыть локальный порт","pathCol":"Удалённый путь","pinBtn":"Закрепить активный","pinned":"Активный хост","portCol":"Порт","profilesDesc":"Настройка удалённых хостов для выполнения команд, SFTP доступа и синхронизации.","profilesTitle":"🖥️ Профили SSH серверов","pullBtn":"⬇️ Скачать с сервера (Pull)","pulling":"Получение файлов…","pushBtn":"⬆️ Загрузить на сервер (Push)","pushing":"Отправка файлов…","saving":"Сохранение…","setActiveBtn":"Сделать активным","status":"Готово","subtitle":"Управление подключениями по SSH, синхронизацией SFTP и туннелями","syncConflict":"Предупреждение о конфликте: изменено с обеих сторон файлов — {count}.","syncDesc":"Ручная синхронизация файлов с удалённым сервером и статус проброса портов.","syncErr":"Ошибка синхронизации: {err}","syncFailed":"Ошибка синхронизации: ","syncOk":"Синхронизация завершена! Обновлено файлов: {count}.","syncSuccess":"Синхронизация успешно выполнена","syncTitle":"🔄 Синхронизация зеркала и туннели","tLocalPort":"Локальный порт","tNoTunnels":"Нет активных туннелей проброса портов.","tOpenBtn":"+ Открыть туннель","tOpenErr":"Не удалось открыть туннель: {err}","tOpenOk":"Туннель открыт: 127.0.0.1:{localPort} -> Удалённый:{remotePort}","tOpening":"Открытие…","tRemotePort":"Удалённый порт","tStopBtn":"Остановить","tabContainers":"🐳 Контейнеры","tabEnv":"🗃️ Менеджер .env","tabExplorer":"📂 Проводник файлов","tabProfiles":"🖥️ Профили хостов","tabSync":"🔄 Станция синхронизации","tabTerminal":"⚡ Веб-терминал","tabTunnels":"🔗 Порт-туннели","tarSyncFast":"🚀 Быстрый поток архива (Tarball Stream)","termClear":"Очистить","termConnected":"Подключено к PTY-сессии {id} на {host}","termDisconnected":"Терминал отключён.","termPlaceholder":"Введите команду и нажмите Enter (напр. htop, ls, docker ps)…","termStart":"Подключить терминал","termStop":"Отключить","testFail":"❌ Ошибка подключения: {err}","testOk":"✅ Соединение установлено! Задержка: {latency} мс | ОС: {os}","testing":"Проверка соединения…","title":"Удалённое рабочее пространство","transferBtnExec":"Передать напрямую","transferDestHost":"Целевой сервер","transferDestPath":"Целевой путь к файлу","transferSuccess":"Успешно передано {bytes} байт на {host}:{path}","transferTitle":"Прямая передача сервер-сервер","tunnelTraffic":"Вход: {rx} | Выход: {tx}","tunnelUptime":"Работает: {sec} с","tunnelsColActions":"Действия","tunnelsColId":"ID туннеля","tunnelsColRoute":"Маршрут проброса","tunnelsDesc":"Проброс удалённых портов (базы данных, веб-серверы, API) на локальный компьютер через зашифрованные SSH-туннели.","tunnelsTitle":"🔀 Проброс портов SSH (туннели)","vaultProtectedBadge":"🛡️ Защищено .env Vault"}}
1
+ {"@goodandready/dsh-remote-workspace":{"actionFailed":"Запрос не выполнен.","actionsCol":"Действия","activeBadge":"Активен","addBtn":"+ Добавить хост","alertLabel":"Оповещения ({count})","authPromptCode":"Одноразовый код","authPromptSubmit":"Продолжить","authPromptTitle":"Код авторизации","autoSyncDesc":"Автоматически отправлять изменённые файлы в удалённое рабочее пространство при локальном сохранении.","autoSyncTitle":"Автосинхронизация при сохранении","badgeKey":"🔑 Вход по ключу","badgeOffline":"Хост не выбран","badgeOnline":"Активный хост: {name} ({latency} мс)","badgePassword":"🔒 Вход по паролю","badgeTunnels":"Туннелей: {count}","btnBrowse":"📁 Выбрать на сервере…","btnCancel":"Отмена","btnDelete":"Удалить","btnDownload":"Скачать","btnImportSsh":"Импорт из ~/.ssh/config","btnLogs":"Логи","btnRestart":"Перезапустить","btnSave":"Сохранить профиль","btnSaveFile":"Сохранить изменения","btnSelectThis":"Выбрать эту папку","btnStop":"Остановить","btnTest":"⚡ Проверить связь (Ping)","btnTransfer":"Передать на хост…","btnUpload":"Загрузить","checkUpdate":"Проверить обновления","checking":"Проверка…","clusterCommand":"Команда","clusterCount":"Настроено серверов: {count}","clusterFail":"ошибка","clusterOk":"успешно","clusterRun":"Выполнить","clusterRunning":"Выполняется...","clusterTitle":"Запуск на группе","containerImage":"Образ","containerName":"Контейнер","containerPorts":"Порты","containerStatus":"Статус","containersTitle":"Удалённый Docker / Podman Engine","currentVersion":"Установленная версия","delBtn":"Удалить","delConfirm":"Удалить профиль хоста «{name}»?","diagDesc":"Проверка доступности и измерение задержки активного сервера.","diagProbeBtn":"⚡ Быстрая диагностика","diagTitle":"📊 Диагностика активного подключения","diagnoseDisk":"Использование диска","diagnoseOom":"Проверка OOM Killer","diagnosePorts":"Открытые порты","diagnoseTitle":"Экспресс-диагностика","editBtn":"Изменить","envHideVal":"Скрыть","envKey":"Ключ","envSaveVar":"Сохранить","envShowVal":"Показать","envTitle":"Удалённые переменные окружения (.env)","envVal":"Значение","explorerTitle":"Удалённый файловый менеджер","fAgentPath":"Сокет агента","fAgentPathHint":"Оставьте пустым для использования SSH_AUTH_SOCK. В Windows используйте pageant.","fAuthAgent":"SSH Agent","fAuthAgentShort":"агент","fAuthKey":"SSH-ключ","fAuthKeyShort":"ключ","fAuthPass":"Пароль","fAuthPassShort":"пароль","fAuthType":"Способ авторизации","fDescription":"Описание","fEnvironment":"Окружение","fHost":"Хост / IP-адрес","fJumpHost":"Jump-хост (бастион)","fJumpHostNone":"Прямое подключение (без jump-хоста)","fJumpHosts":"Jump-хосты","fJumpHostsHint":"ID профилей по порядку через запятую. Первый хост — входной бастион.","fKeyPath":"Путь к ключу","fKeyPathHint":"Путь к ~/.ssh/id_rsa или /home/user/.ssh/id_ed25519","fLocalMirror":"Локальная папка зеркала","fLocalMirrorHint":"Локальная директория для зеркалирования файлов при быстрой трёхсторонней синхронизации","fLocation":"Локация","fName":"Название подключения","fPassphrase":"Кодовая фраза ключа (если есть)","fPassword":"Пароль учетной записи","fPort":"Порт","fProxyCommand":"Proxy-команда","fProxyCommandHint":"OpenSSH ProxyCommand. Заполнители: %h хост, %p порт, %r пользователь, %n имя профиля.","fRemotePath":"Удалённая папка проекта","fRemotePathHint":"(Не обязательно сейчас — можно выбрать позже при работе)","fTags":"Теги","fTerminalFont":"Шрифт терминала","fTerminalFontHint":"Семейство шрифтов для терминала. Оставьте пустым для моноширинного стека по умолчанию.","fUser":"Имя пользователя","filePreviewTitle":"Просмотр файла: {path}","fileSaved":"Файл успешно сохранён.","fileSize":"{size} КБ","formTitleEdit":"Редактирование профиля хоста","formTitleNew":"Добавление нового SSH хоста","groupEnvironment":"По окружению","groupFlat":"Сплошной список","groupOffline":"не в сети: {err}","groupOnline":"в сети, {latency} мс","groupTag":"По тегам","groupUngrouped":"Без группы","healthCpu":"ЦП: {val}%","healthDisk":"Диск: {val}%","healthMem":"ОЗУ: {val}% ({used}/{total} МБ)","healthUptime":"Аптайм: {val}","hostCol":"Хост / Пользователь","importAdded":"Импортировано серверов: {count}.","latestVersion":"Последняя на npm","nameCol":"Имя","navLabel":"Удаленные серверы","noContainers":"Активных контейнеров не найдено или демон Docker не запущен.","noHosts":"Удалённые хосты ещё не настроены. Нажмите «+ Добавить хост».","openLocalLink":"🌐 Открыть локальный порт","pathCol":"Удалённый путь","pinBtn":"Закрепить активный","pinned":"Активный хост","portCol":"Порт","profilesDesc":"Настройка удалённых хостов для выполнения команд, SFTP доступа и синхронизации.","profilesTitle":"🖥️ Профили SSH серверов","pullBtn":"⬇️ Скачать с сервера (Pull)","pulling":"Получение файлов…","pushBtn":"⬆️ Загрузить на сервер (Push)","pushing":"Отправка файлов…","restartRequired":"Обновление установлено. Перезапустите DSH для применения.","saving":"Сохранение…","setActiveBtn":"Сделать активным","skipDuplicate":"уже импортировано","skipMatch":"Блоки Match не импортируются","skipMissing":"включённый файл не найден","skipWildcard":"шаблон не является отдельным хостом","status":"Готово","statusEmpty":"Нет активного хоста","statusError":"Статус недоступен","statusLoading":"Проверка соединения","statusReady":"Хост готов","subtitle":"Управление подключениями по SSH, синхронизацией SFTP и туннелями","syncConflict":"Предупреждение о конфликте: изменено с обеих сторон файлов — {count}.","syncDesc":"Ручная синхронизация файлов с удалённым сервером и статус проброса портов.","syncErr":"Ошибка синхронизации: {err}","syncFailed":"Ошибка синхронизации: ","syncOk":"Синхронизация завершена! Обновлено файлов: {count}.","syncSuccess":"Синхронизация успешно выполнена","syncTitle":"🔄 Синхронизация зеркала и туннели","tLocalPort":"Локальный порт","tNoTunnels":"Нет активных туннелей проброса портов.","tOpenBtn":"+ Открыть туннель","tOpenErr":"Не удалось открыть туннель: {err}","tOpenOk":"Туннель открыт: 127.0.0.1:{localPort} -> Удалённый:{remotePort}","tOpening":"Открытие…","tRemotePort":"Удалённый порт","tStopBtn":"Остановить","tabCluster":"Кластер","tabContainers":"🐳 Контейнеры","tabEnv":"🗃️ Менеджер .env","tabExplorer":"📂 Проводник файлов","tabFiles":"Файлы","tabProfiles":"🖥️ Профили хостов","tabSync":"🔄 Станция синхронизации","tabTerminal":"⚡ Веб-терминал","tabTunnels":"🔗 Порт-туннели","tarSyncFast":"🚀 Быстрый поток архива (Tarball Stream)","termClear":"Очистить","termConnected":"Подключено к PTY-сессии {id} на {host}","termDisconnected":"Терминал отключён.","termPlaceholder":"Введите команду и нажмите Enter (напр. htop, ls, docker ps)…","termStart":"Подключить терминал","termStop":"Отключить","testFail":"❌ Ошибка подключения: {err}","testGroup":"Тестовая группа","testOk":"✅ Соединение установлено! Задержка: {latency} мс | ОС: {os}","testing":"Проверка соединения…","title":"Удалённое рабочее пространство","transferBtnExec":"Передать напрямую","transferCancel":"Отмена","transferDestHost":"Целевой сервер","transferDestPath":"Целевой путь к файлу","transferFailed":"Сбой передачи: {name}","transferProgress":"{name}: {percent}%","transferSuccess":"Успешно передано {bytes} байт на {host}:{path}","transferTitle":"Прямая передача сервер-сервер","tunnelTraffic":"Вход: {rx} | Выход: {tx}","tunnelUptime":"Работает: {sec} с","tunnelsColActions":"Действия","tunnelsColId":"ID туннеля","tunnelsColRoute":"Маршрут проброса","tunnelsDesc":"Проброс удалённых портов (базы данных, веб-серверы, API) на локальный компьютер через зашифрованные SSH-туннели.","tunnelsTitle":"🔀 Проброс портов SSH (туннели)","upToDate":"Актуальная версия","updateAvailable":"Доступно обновление","updateFailed":"Сбой обновления","updateNow":"Обновить сейчас","updaterTitle":"Обновление плагина","updating":"Обновление…","vaultProtectedBadge":"🛡️ Защищено .env Vault","versionUnknown":"Версия неизвестна","wsBack":"Назад","wsCluster":"Кластер","wsDocker":"Контейнеры","wsFiles":"Файлы","wsHosts":"Хосты","wsTerminal":"Терминал","wsTunnels":"Туннели"},"dsh-remote-workspace":{"actionFailed":"Запрос не выполнен.","actionsCol":"Действия","activeBadge":"Активен","addBtn":"+ Добавить хост","alertLabel":"Оповещения ({count})","authPromptCode":"Одноразовый код","authPromptSubmit":"Продолжить","authPromptTitle":"Код авторизации","autoSyncDesc":"Автоматически отправлять изменённые файлы в удалённое рабочее пространство при локальном сохранении.","autoSyncTitle":"Автосинхронизация при сохранении","badgeKey":"🔑 Вход по ключу","badgeOffline":"Хост не выбран","badgeOnline":"Активный хост: {name} ({latency} мс)","badgePassword":"🔒 Вход по паролю","badgeTunnels":"Туннелей: {count}","btnBrowse":"📁 Выбрать на сервере…","btnCancel":"Отмена","btnDelete":"Удалить","btnDownload":"Скачать","btnImportSsh":"Импорт из ~/.ssh/config","btnLogs":"Логи","btnRestart":"Перезапустить","btnSave":"Сохранить профиль","btnSaveFile":"Сохранить изменения","btnSelectThis":"Выбрать эту папку","btnStop":"Остановить","btnTest":"⚡ Проверить связь (Ping)","btnTransfer":"Передать на хост…","btnUpload":"Загрузить","checkUpdate":"Проверить обновления","checking":"Проверка…","clusterCommand":"Команда","clusterCount":"Настроено серверов: {count}","clusterFail":"ошибка","clusterOk":"успешно","clusterRun":"Выполнить","clusterRunning":"Выполняется...","clusterTitle":"Запуск на группе","containerImage":"Образ","containerName":"Контейнер","containerPorts":"Порты","containerStatus":"Статус","containersTitle":"Удалённый Docker / Podman Engine","currentVersion":"Установленная версия","delBtn":"Удалить","delConfirm":"Удалить профиль хоста «{name}»?","diagDesc":"Проверка доступности и измерение задержки активного сервера.","diagProbeBtn":"⚡ Быстрая диагностика","diagTitle":"📊 Диагностика активного подключения","diagnoseDisk":"Использование диска","diagnoseOom":"Проверка OOM Killer","diagnosePorts":"Открытые порты","diagnoseTitle":"Экспресс-диагностика","editBtn":"Изменить","envHideVal":"Скрыть","envKey":"Ключ","envSaveVar":"Сохранить","envShowVal":"Показать","envTitle":"Удалённые переменные окружения (.env)","envVal":"Значение","explorerTitle":"Удалённый файловый менеджер","fAgentPath":"Сокет агента","fAgentPathHint":"Оставьте пустым для использования SSH_AUTH_SOCK. В Windows используйте pageant.","fAuthAgent":"SSH Agent","fAuthAgentShort":"агент","fAuthKey":"SSH-ключ","fAuthKeyShort":"ключ","fAuthPass":"Пароль","fAuthPassShort":"пароль","fAuthType":"Способ авторизации","fDescription":"Описание","fEnvironment":"Окружение","fHost":"Хост / IP-адрес","fJumpHost":"Jump-хост (бастион)","fJumpHostNone":"Прямое подключение (без jump-хоста)","fJumpHosts":"Jump-хосты","fJumpHostsHint":"ID профилей по порядку через запятую. Первый хост — входной бастион.","fKeyPath":"Путь к ключу","fKeyPathHint":"Путь к ~/.ssh/id_rsa или /home/user/.ssh/id_ed25519","fLocalMirror":"Локальная папка зеркала","fLocalMirrorHint":"Локальная директория для зеркалирования файлов при быстрой трёхсторонней синхронизации","fLocation":"Локация","fName":"Название подключения","fPassphrase":"Кодовая фраза ключа (если есть)","fPassword":"Пароль учетной записи","fPort":"Порт","fProxyCommand":"Proxy-команда","fProxyCommandHint":"OpenSSH ProxyCommand. Заполнители: %h хост, %p порт, %r пользователь, %n имя профиля.","fRemotePath":"Удалённая папка проекта","fRemotePathHint":"(Не обязательно сейчас — можно выбрать позже при работе)","fTags":"Теги","fTerminalFont":"Шрифт терминала","fTerminalFontHint":"Семейство шрифтов для терминала. Оставьте пустым для моноширинного стека по умолчанию.","fUser":"Имя пользователя","filePreviewTitle":"Просмотр файла: {path}","fileSaved":"Файл успешно сохранён.","fileSize":"{size} КБ","formTitleEdit":"Редактирование профиля хоста","formTitleNew":"Добавление нового SSH хоста","groupEnvironment":"По окружению","groupFlat":"Сплошной список","groupOffline":"не в сети: {err}","groupOnline":"в сети, {latency} мс","groupTag":"По тегам","groupUngrouped":"Без группы","healthCpu":"ЦП: {val}%","healthDisk":"Диск: {val}%","healthMem":"ОЗУ: {val}% ({used}/{total} МБ)","healthUptime":"Аптайм: {val}","hostCol":"Хост / Пользователь","importAdded":"Импортировано серверов: {count}.","latestVersion":"Последняя на npm","nameCol":"Имя","navLabel":"Удаленные серверы","noContainers":"Активных контейнеров не найдено или демон Docker не запущен.","noHosts":"Удалённые хосты ещё не настроены. Нажмите «+ Добавить хост».","openLocalLink":"🌐 Открыть локальный порт","pathCol":"Удалённый путь","pinBtn":"Закрепить активный","pinned":"Активный хост","portCol":"Порт","profilesDesc":"Настройка удалённых хостов для выполнения команд, SFTP доступа и синхронизации.","profilesTitle":"🖥️ Профили SSH серверов","pullBtn":"⬇️ Скачать с сервера (Pull)","pulling":"Получение файлов…","pushBtn":"⬆️ Загрузить на сервер (Push)","pushing":"Отправка файлов…","restartRequired":"Обновление установлено. Перезапустите DSH для применения.","saving":"Сохранение…","setActiveBtn":"Сделать активным","skipDuplicate":"уже импортировано","skipMatch":"Блоки Match не импортируются","skipMissing":"включённый файл не найден","skipWildcard":"шаблон не является отдельным хостом","status":"Готово","statusEmpty":"Нет активного хоста","statusError":"Статус недоступен","statusLoading":"Проверка соединения","statusReady":"Хост готов","subtitle":"Управление подключениями по SSH, синхронизацией SFTP и туннелями","syncConflict":"Предупреждение о конфликте: изменено с обеих сторон файлов — {count}.","syncDesc":"Ручная синхронизация файлов с удалённым сервером и статус проброса портов.","syncErr":"Ошибка синхронизации: {err}","syncFailed":"Ошибка синхронизации: ","syncOk":"Синхронизация завершена! Обновлено файлов: {count}.","syncSuccess":"Синхронизация успешно выполнена","syncTitle":"🔄 Синхронизация зеркала и туннели","tLocalPort":"Локальный порт","tNoTunnels":"Нет активных туннелей проброса портов.","tOpenBtn":"+ Открыть туннель","tOpenErr":"Не удалось открыть туннель: {err}","tOpenOk":"Туннель открыт: 127.0.0.1:{localPort} -> Удалённый:{remotePort}","tOpening":"Открытие…","tRemotePort":"Удалённый порт","tStopBtn":"Остановить","tabCluster":"Кластер","tabContainers":"🐳 Контейнеры","tabEnv":"🗃️ Менеджер .env","tabExplorer":"📂 Проводник файлов","tabFiles":"Файлы","tabProfiles":"🖥️ Профили хостов","tabSync":"🔄 Станция синхронизации","tabTerminal":"⚡ Веб-терминал","tabTunnels":"🔗 Порт-туннели","tarSyncFast":"🚀 Быстрый поток архива (Tarball Stream)","termClear":"Очистить","termConnected":"Подключено к PTY-сессии {id} на {host}","termDisconnected":"Терминал отключён.","termPlaceholder":"Введите команду и нажмите Enter (напр. htop, ls, docker ps)…","termStart":"Подключить терминал","termStop":"Отключить","testFail":"❌ Ошибка подключения: {err}","testGroup":"Тестовая группа","testOk":"✅ Соединение установлено! Задержка: {latency} мс | ОС: {os}","testing":"Проверка соединения…","title":"Удалённое рабочее пространство","transferBtnExec":"Передать напрямую","transferCancel":"Отмена","transferDestHost":"Целевой сервер","transferDestPath":"Целевой путь к файлу","transferFailed":"Сбой передачи: {name}","transferProgress":"{name}: {percent}%","transferSuccess":"Успешно передано {bytes} байт на {host}:{path}","transferTitle":"Прямая передача сервер-сервер","tunnelTraffic":"Вход: {rx} | Выход: {tx}","tunnelUptime":"Работает: {sec} с","tunnelsColActions":"Действия","tunnelsColId":"ID туннеля","tunnelsColRoute":"Маршрут проброса","tunnelsDesc":"Проброс удалённых портов (базы данных, веб-серверы, API) на локальный компьютер через зашифрованные SSH-туннели.","tunnelsTitle":"🔀 Проброс портов SSH (туннели)","upToDate":"Актуальная версия","updateAvailable":"Доступно обновление","updateFailed":"Сбой обновления","updateNow":"Обновить сейчас","updaterTitle":"Обновление плагина","updating":"Обновление…","vaultProtectedBadge":"🛡️ Защищено .env Vault","versionUnknown":"Версия неизвестна","wsBack":"Назад","wsCluster":"Кластер","wsDocker":"Контейнеры","wsFiles":"Файлы","wsHosts":"Хосты","wsTerminal":"Терминал","wsTunnels":"Туннели"}}
@@ -0,0 +1 @@
1
+ {"@michengai/dsh-agency-agents":{"btn.refresh":"Обновить","button.title":"Вызов эксперта","custom.avatar":"Аватар","custom.base":"Встроенный","custom.cancel":"Отмена","custom.conflict":"Параметры были изменены в другом окне. Ваш черновик сохранён в этой форме. Загрузите актуальное содержимое и сравните перед сохранением.","custom.continueAsNew":"Продолжить как нового эксперта","custom.copy":"Создать пользовательскую копию","custom.delete":"Удалить эксперта","custom.deleteHint":"Данные эксперта будут безвозвратно удалены. История сообщений сохранится.","custom.deleteTitle":"Удалить эксперта","custom.deleted":"Эксперт безвозвратно удалён.","custom.description":"Краткое описание","custom.descriptionPlaceholder":"В чём специализируется этот эксперт?","custom.discard":"Отменить изменения","custom.discardTitle":"Сбросить несохранённые изменения?","custom.division":"Категория","custom.edit":"Редактировать эксперта","custom.emptyHint":"Превратите свой опыт и методики в эксперта, которого можно вызывать по имени.","custom.emptyTitle":"Создайте первого эксперта","custom.enableHint":"После включения эксперт доступен через @. Перезагрузка не требуется.","custom.intro":"Определите обязанности и подход к работе, чтобы эксперт действовал по вашим правилам.","custom.invalid":"Проверьте обязательные поля и их длину.","custom.keep":"Продолжить редактирование","custom.keepDraft":"Оставить мой черновик","custom.latestContent":"Текущее сохранённое содержимое","custom.name":"Имя эксперта","custom.nameConflict":"Конфликт имён","custom.nameConflictHint":"Эксперты с конфликтующими именами не могут быть вызваны. Переименуйте или удалите эксперта.","custom.namePlaceholder":"Например: Консультант по аренде недвижимости","custom.new":"Новый эксперт","custom.prompt":"Промпт эксперта","custom.promptPlaceholder":"Опишите обязанности, методы анализа и формат ответа…","custom.required":"Обязательно","custom.reviewDeleted":"Этот эксперт был удалён. Оставьте черновик и сохраните как нового эксперта.","custom.reviewHint":"Продолжите редактирование после выбора, затем нажмите Сохранить.","custom.reviewLatest":"Загрузить актуальные данные","custom.reviewNew":"Каталог обновлён. Черновик нового эксперта сохранён без изменений.","custom.save":"Сохранить","custom.saveChanges":"Сохранить изменения","custom.saveEnable":"Сохранить и включить","custom.saved":"Сохранено. Активные эксперты доступны через @ и кнопку экспертов.","custom.saving":"Сохранение…","custom.source":"Пользовательский","custom.template":"Использовать шаблон","custom.templateText":"Вы — профильный эксперт-консультант.\n\nОбязанности\nОпределите вашу область и решаемые задачи.\n\nПодход\nСначала вникните в контекст и цели. Задавайте вопросы при нехватке данных.\n\nРезультат\nПредоставляйте выводы, факты и конкретные шаги. Не выдумывайте факты.","custom.unavailable":"Эксперт в черновике отключён, удалён или конфликтует по имени.","custom.useLatest":"Использовать актуальное содержимое","discovery.conflict":"Конфликт имён","discovery.empty":"Нет активных экспертов. Введите ключевое слово для поиска.","discovery.enableSelect":"Включить и выбрать","discovery.enabledInsertFailed":"Эксперт включён, но не удалось вставить ссылку. Попробуйте выбрать снова.","discovery.exampleFailed":"Эксперт выбран, но пример задачи не вставился. Введите задачу вручную.","discovery.noResults":"Нет подходящих экспертов. Попробуйте другие ключевые слова.","discovery.refine":"Показано 8 наиболее подходящих экспертов. Уточните запрос ключевыми словами.","discovery.search":"Поиск по имени, сфере или ключевым словам","discovery.working":"Обработка…","division.academic":"Академические","division.company":"Руководство компанией","division.design":"Дизайн","division.engineering":"Инженерия и разработка","division.finance":"Финансы","division.game-development":"Разработка игр","division.gis":"Геоинформационные системы","division.healthcare":"Здравоохранение","division.hr":"Управление персоналом","division.legal":"Юриспруденция","division.marketing":"Маркетинг","division.paid-media":"Платный трафик и реклама","division.product":"Продуктовый менеджмент","division.project-management":"Управление проектами","division.research":"Исследования","division.sales":"Продажи","division.security":"Информационная безопасность","division.spatial-computing":"Пространственные вычисления","division.specialized":"Специализированные","division.supply-chain":"Цепочки поставок","division.support":"Клиентская поддержка","division.testing":"Тестирование","error.conflict":"Параметры были изменены в другом окне.","error.conflict.refreshFailed":"Параметры были изменены в другом окне, но обновление не удалось.","error.conflict.refreshed":"Параметры были обновлены из актуального состояния.","error.insertFailed":"Не удалось вставить ссылку на эксперта. Попробуйте снова.","error.personaSourceUnavailable":"Сервис подсказок экспертов ещё не готов, повторите попытку позже.","error.promptCopy":"Не удалось скопировать промпт. Проверьте права на буфер обмена.","settings.copyPrompt":"Копировать","settings.copyPromptTitle":"Скопировать промпт","settings.copySuccess":"Скопировано","settings.disabled":"Отключено","settings.empty":"Нет подходящих экспертов. Попробуйте другое слово или переключитесь на {all}.","settings.empty.reset":"Сбросить фильтры","settings.enabled":"Включено","settings.feedback":"Обратная связь","settings.filter.allCategories":"Все категории","settings.filter.allSources":"Все источники","settings.filter.allStatuses":"Все статусы","settings.filter.category":"Категория","settings.filter.source":"Источник","settings.filter.status":"Статус","settings.loading":"Загрузка экспертов…","settings.nav":"Эксперты","settings.promptClose":"Закрыть","settings.promptTitle":"Промпт для {name}","settings.search":"Поиск","settings.search.clear":"Очистить поиск","settings.search.placeholder":"Поиск экспертов, обязанностей или сфер","settings.title":"Эксперты","settings.viewProject":"GitHub","settings.viewPrompt":"Просмотр","settings.viewPromptTitle":"Просмотреть промпт","summary.enabledPrefix":"Активно","summary.total.one":"эксперт","summary.total.other":"экспертов"},"agency":{"btn.refresh":"Обновить","button.title":"Вызов эксперта","custom.avatar":"Аватар","custom.base":"Встроенный","custom.cancel":"Отмена","custom.conflict":"Параметры были изменены в другом окне. Ваш черновик сохранён в этой форме. Загрузите актуальное содержимое и сравните перед сохранением.","custom.continueAsNew":"Продолжить как нового эксперта","custom.copy":"Создать пользовательскую копию","custom.delete":"Удалить эксперта","custom.deleteHint":"Данные эксперта будут безвозвратно удалены. История сообщений сохранится.","custom.deleteTitle":"Удалить эксперта","custom.deleted":"Эксперт безвозвратно удалён.","custom.description":"Краткое описание","custom.descriptionPlaceholder":"В чём специализируется этот эксперт?","custom.discard":"Отменить изменения","custom.discardTitle":"Сбросить несохранённые изменения?","custom.division":"Категория","custom.edit":"Редактировать эксперта","custom.emptyHint":"Превратите свой опыт и методики в эксперта, которого можно вызывать по имени.","custom.emptyTitle":"Создайте первого эксперта","custom.enableHint":"После включения эксперт доступен через @. Перезагрузка не требуется.","custom.intro":"Определите обязанности и подход к работе, чтобы эксперт действовал по вашим правилам.","custom.invalid":"Проверьте обязательные поля и их длину.","custom.keep":"Продолжить редактирование","custom.keepDraft":"Оставить мой черновик","custom.latestContent":"Текущее сохранённое содержимое","custom.name":"Имя эксперта","custom.nameConflict":"Конфликт имён","custom.nameConflictHint":"Эксперты с конфликтующими именами не могут быть вызваны. Переименуйте или удалите эксперта.","custom.namePlaceholder":"Например: Консультант по аренде недвижимости","custom.new":"Новый эксперт","custom.prompt":"Промпт эксперта","custom.promptPlaceholder":"Опишите обязанности, методы анализа и формат ответа…","custom.required":"Обязательно","custom.reviewDeleted":"Этот эксперт был удалён. Оставьте черновик и сохраните как нового эксперта.","custom.reviewHint":"Продолжите редактирование после выбора, затем нажмите Сохранить.","custom.reviewLatest":"Загрузить актуальные данные","custom.reviewNew":"Каталог обновлён. Черновик нового эксперта сохранён без изменений.","custom.save":"Сохранить","custom.saveChanges":"Сохранить изменения","custom.saveEnable":"Сохранить и включить","custom.saved":"Сохранено. Активные эксперты доступны через @ и кнопку экспертов.","custom.saving":"Сохранение…","custom.source":"Пользовательский","custom.template":"Использовать шаблон","custom.templateText":"Вы — профильный эксперт-консультант.\n\nОбязанности\nОпределите вашу область и решаемые задачи.\n\nПодход\nСначала вникните в контекст и цели. Задавайте вопросы при нехватке данных.\n\nРезультат\nПредоставляйте выводы, факты и конкретные шаги. Не выдумывайте факты.","custom.unavailable":"Эксперт в черновике отключён, удалён или конфликтует по имени.","custom.useLatest":"Использовать актуальное содержимое","discovery.conflict":"Конфликт имён","discovery.empty":"Нет активных экспертов. Введите ключевое слово для поиска.","discovery.enableSelect":"Включить и выбрать","discovery.enabledInsertFailed":"Эксперт включён, но не удалось вставить ссылку. Попробуйте выбрать снова.","discovery.exampleFailed":"Эксперт выбран, но пример задачи не вставился. Введите задачу вручную.","discovery.noResults":"Нет подходящих экспертов. Попробуйте другие ключевые слова.","discovery.refine":"Показано 8 наиболее подходящих экспертов. Уточните запрос ключевыми словами.","discovery.search":"Поиск по имени, сфере или ключевым словам","discovery.working":"Обработка…","division.academic":"Академические","division.company":"Руководство компанией","division.design":"Дизайн","division.engineering":"Инженерия и разработка","division.finance":"Финансы","division.game-development":"Разработка игр","division.gis":"Геоинформационные системы","division.healthcare":"Здравоохранение","division.hr":"Управление персоналом","division.legal":"Юриспруденция","division.marketing":"Маркетинг","division.paid-media":"Платный трафик и реклама","division.product":"Продуктовый менеджмент","division.project-management":"Управление проектами","division.research":"Исследования","division.sales":"Продажи","division.security":"Информационная безопасность","division.spatial-computing":"Пространственные вычисления","division.specialized":"Специализированные","division.supply-chain":"Цепочки поставок","division.support":"Клиентская поддержка","division.testing":"Тестирование","error.conflict":"Параметры были изменены в другом окне.","error.conflict.refreshFailed":"Параметры были изменены в другом окне, но обновление не удалось.","error.conflict.refreshed":"Параметры были обновлены из актуального состояния.","error.insertFailed":"Не удалось вставить ссылку на эксперта. Попробуйте снова.","error.personaSourceUnavailable":"Сервис подсказок экспертов ещё не готов, повторите попытку позже.","error.promptCopy":"Не удалось скопировать промпт. Проверьте права на буфер обмена.","settings.copyPrompt":"Копировать","settings.copyPromptTitle":"Скопировать промпт","settings.copySuccess":"Скопировано","settings.disabled":"Отключено","settings.empty":"Нет подходящих экспертов. Попробуйте другое слово или переключитесь на {all}.","settings.empty.reset":"Сбросить фильтры","settings.enabled":"Включено","settings.feedback":"Обратная связь","settings.filter.allCategories":"Все категории","settings.filter.allSources":"Все источники","settings.filter.allStatuses":"Все статусы","settings.filter.category":"Категория","settings.filter.source":"Источник","settings.filter.status":"Статус","settings.loading":"Загрузка экспертов…","settings.nav":"Эксперты","settings.promptClose":"Закрыть","settings.promptTitle":"Промпт для {name}","settings.search":"Поиск","settings.search.clear":"Очистить поиск","settings.search.placeholder":"Поиск экспертов, обязанностей или сфер","settings.title":"Эксперты","settings.viewProject":"GitHub","settings.viewPrompt":"Просмотр","settings.viewPromptTitle":"Просмотреть промпт","summary.enabledPrefix":"Активно","summary.total.one":"эксперт","summary.total.other":"экспертов"}}
package/lib/translator.js CHANGED
@@ -101,8 +101,30 @@ export function isTrustedTranslatorRequest(request) {
101
101
  }
102
102
 
103
103
 
104
- export async function getTranslatorStatus(settingsCtx) {
105
- const currentSettings = settingsCtx?.settings?.get('russian-lang') || {}
104
+ /**
105
+ * Нормализует источник настроек к plain-объекту: принимает либо готовый
106
+ * объект, либо функцию-ридер (как readSettings из lib/index.js), либо —
107
+ * для обратной совместимости — легаси settings-контекст с settings.get().
108
+ */
109
+ function resolveTranslatorSettings(source) {
110
+ try {
111
+ if (typeof source === 'function') return source() || {}
112
+ if (source && typeof source === 'object') {
113
+ if (source.settings && typeof source.settings.get === 'function') {
114
+ return source.settings.get('russian-lang') || {}
115
+ }
116
+ if (typeof source.translateEngine === 'string' || typeof source.localApiUrl === 'string'
117
+ || typeof source.agentPrompt !== 'undefined' || typeof source.enabled !== 'undefined') {
118
+ return source
119
+ }
120
+ }
121
+ } catch (_) { /* bestEffort */ void _ }
122
+ return {}
123
+ }
124
+
125
+
126
+ export async function getTranslatorStatus(settingsSource) {
127
+ const currentSettings = resolveTranslatorSettings(settingsSource)
106
128
  const engine = currentSettings.translateEngine || 'off'
107
129
  const localUrl = (currentSettings.localApiUrl || 'http://127.0.0.1:5000').replace(/\/+$/, '')
108
130
 
@@ -236,7 +258,7 @@ async function translateLibre(text, targetLang = 'ru', localUrl = 'http://127.0.
236
258
  return data.translatedText || text
237
259
  }
238
260
 
239
- export function registerTranslator(wctx, settingsCtx) {
261
+ export function registerTranslator(wctx, settingsSource) {
240
262
  let ws = null
241
263
  try { ws = wctx?.webServer } catch (_) { ws = null }
242
264
  if (!ws || typeof ws.register !== 'function') return () => {}
@@ -255,7 +277,7 @@ export function registerTranslator(wctx, settingsCtx) {
255
277
  response.end(JSON.stringify({ error: 'Method not allowed' }))
256
278
  return
257
279
  }
258
- const status = await getTranslatorStatus(settingsCtx)
280
+ const status = await getTranslatorStatus(settingsSource)
259
281
  response.writeHead(200, {
260
282
  'content-type': 'application/json; charset=utf-8',
261
283
  'cache-control': 'no-store'
@@ -336,7 +358,7 @@ export function registerTranslator(wctx, settingsCtx) {
336
358
  return
337
359
  }
338
360
 
339
- const settings = settingsCtx?.settings?.get('russian-lang') || {}
361
+ const settings = resolveTranslatorSettings(settingsSource)
340
362
  const engine = settings.translateEngine || 'off'
341
363
  const localUrl = settings.localApiUrl || 'http://127.0.0.1:5000'
342
364
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-russian-lang",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "Russian localization for DeepSeek Harness (DSH v0.1.7-rc.1+) web UI: adds Русский as native language option. 100.0% coverage across 11,295 keys (127 namespaces). Features full DSH v0.1.7-rc.1 core support (terminal, archive, command palette, Auto review, trajectory, PTC/Creator presets) and complete GoodAndReady plugin catalog (subscriptions, tts, image-gen, voice, key-limits, notify, commandcode, plugin-console).",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",