@goodandready/dsh-russian-lang 0.2.20 → 0.2.22
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/README.md +13 -3
- package/README.ru.md +13 -3
- package/lib/client.js +198 -74
- package/lib/index.js +10 -2
- package/lib/locales/plugins/07-better-sidebar.json +1 -1
- package/lib/locales/plugins/25-hooks.json +1 -1
- package/lib/locales/plugins/32-vision-bridge.json +1 -1
- package/lib/locales/plugins/34-key-rotation.json +1 -1
- package/lib/locales/plugins/71-memory-meter.json +1 -0
- package/lib/locales/plugins/72-server-monitor.json +1 -0
- package/lib/locales/plugins/73-agentrouter.json +1 -0
- package/lib/translator.js +303 -0
- package/package.json +1 -2
- package/README.zh.md +0 -102
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h3>Полная русская локализация, умная типографика и исправление раскладки клавиатуры для DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.2.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.2.22-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
10
|
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
11
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
+
> [!NOTE]
|
|
44
|
+
> **Языковая политика проекта**: `dsh-russian-lang` — специализированный плагин русской локализации интерфейса DSH. Каноническая документация ведётся исключительно на русском языке (`README.md`, `README.ru.md`). Общее требование трёхъязычной документации к нему не применяется.
|
|
45
|
+
|
|
43
46
|
## ⚡ Обзор
|
|
44
47
|
|
|
45
48
|
**`dsh-russian-lang`** — официальный, всеобъемлющий пакет русской локализации для веб-интерфейса [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).
|
|
@@ -217,5 +220,12 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
|
217
220
|
- Мгновенная конвертация набранного транслита в кириллицу и обратно прямо в поле ввода (`privet` ⇄ `привет`).
|
|
218
221
|
- *(Deprecated since v0.2.15: экспериментальный UI-перехватчик удалён для обеспечения беспрепятственного нативного ввода Lexical; программная функция `phoneticTranslit` сохранена в `lib/pure.js`).*
|
|
219
222
|
|
|
220
|
-
7. **Инлайн-перевод реплик ассистента (`RU ↗`)
|
|
221
|
-
- Кнопка `RU ↗` в панели действий ответа ассистента
|
|
223
|
+
7. **Инлайн-перевод реплик ассистента (`RU ↗`) и приватность**:
|
|
224
|
+
- Кнопка `RU ↗` в панели действий ответа ассистента позволяет переводить ответы модели на русский язык с сохранением блоков кода (`📋 Копировать`).
|
|
225
|
+
- 🔒 **Конфиденциальность и выбор движка**:
|
|
226
|
+
- **По умолчанию (Выключен)**: Никакие данные сообщений никуда не отправляются. Клик по кнопке информирует о необходимости выбрать движок.
|
|
227
|
+
- **Локальный LibreTranslate** *(Рекомендуется)*: Работает на 100% автономно на вашем сервере в Docker-контейнере (`~500–700 МБ RAM`). Запускается в 1 клик прямо из карточки настроек DSH или вручную:
|
|
228
|
+
```bash
|
|
229
|
+
docker run -d --name libretranslate --restart unless-stopped -p 127.0.0.1:5000:5000 libretranslate/libretranslate --load-only en,ru,zh
|
|
230
|
+
```
|
|
231
|
+
- **Google Translate (Онлайн)**: Для пользователей, которым удобен онлайн-перевод без выделения ресурсов RAM. В настройках отображается явное предупреждение о передаче текста на публичные серверы Google.
|
package/README.ru.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h3>Полная русская локализация, умная типографика и исправление раскладки клавиатуры для DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.2.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.2.22-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
10
|
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
11
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
+
> [!NOTE]
|
|
44
|
+
> **Языковая политика проекта**: `dsh-russian-lang` — специализированный плагин русской локализации интерфейса DSH. Каноническая документация ведётся исключительно на русском языке (`README.md`, `README.ru.md`). Общее требование трёхъязычной документации к нему не применяется.
|
|
45
|
+
|
|
43
46
|
## ⚡ Обзор
|
|
44
47
|
|
|
45
48
|
**`dsh-russian-lang`** — официальный, всеобъемлющий пакет русской локализации для веб-интерфейса [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).
|
|
@@ -217,5 +220,12 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
|
217
220
|
- Мгновенная конвертация набранного транслита в кириллицу и обратно прямо в поле ввода (`privet` ⇄ `привет`).
|
|
218
221
|
- *(Deprecated since v0.2.14: экспериментальный UI-перехватчик удалён для обеспечения беспрепятственного нативного ввода Lexical; программная функция `phoneticTranslit` сохранена в `lib/pure.js`).*
|
|
219
222
|
|
|
220
|
-
7. **Инлайн-перевод реплик ассистента (`RU ↗`)
|
|
221
|
-
- Кнопка `RU ↗` в панели действий ответа ассистента
|
|
223
|
+
7. **Инлайн-перевод реплик ассистента (`RU ↗`) и приватность**:
|
|
224
|
+
- Кнопка `RU ↗` в панели действий ответа ассистента позволяет переводить ответы модели на русский язык с сохранением блоков кода (`📋 Копировать`).
|
|
225
|
+
- 🔒 **Конфиденциальность и выбор движка**:
|
|
226
|
+
- **По умолчанию (Выключен)**: Никакие данные сообщений никуда не отправляются. Клик по кнопке информирует о необходимости выбрать движок.
|
|
227
|
+
- **Локальный LibreTranslate** *(Рекомендуется)*: Работает на 100% автономно на вашем сервере в Docker-контейнере (`~500–700 МБ RAM`). Запускается в 1 клик прямо из карточки настроек DSH или вручную:
|
|
228
|
+
```bash
|
|
229
|
+
docker run -d --name libretranslate --restart unless-stopped -p 127.0.0.1:5000:5000 libretranslate/libretranslate --load-only en,ru,zh
|
|
230
|
+
```
|
|
231
|
+
- **Google Translate (Онлайн)**: Для пользователей, которым удобен онлайн-перевод без выделения ресурсов RAM. В настройках отображается явное предупреждение о передаче текста на публичные серверы Google.
|
package/lib/client.js
CHANGED
|
@@ -522,7 +522,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
522
522
|
/** zh-строка -> ru-строка для DOM-перевода панелей вне locale-ядра. */
|
|
523
523
|
const ZH_RU = {}
|
|
524
524
|
|
|
525
|
-
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": "Лаконичный режим (кратко, без лишней воды)", "secUpdater": "🔄 Обновление языкового пакета", "secUpdaterDesc": "Проверка наличия новых релизов в реестре npm и обновление в один клик.", "updaterCurrent": "Текущая версия: v{version}", "updaterLatest": "Доступна новая версия: v{version}", "updaterUpToDate": "Установлена актуальная версия", "updaterChecking": "Проверка…", "updaterCheckBtn": "Проверить обновления", "updaterBtn": "Обновить до v{version} в 1 клик", "updaterUpdating": "Установка обновления…", "updaterSuccess": "✅ Плагин успешно обновлён! Перезапустите DSH для применения.", "updaterFailed": "❌ Не удалось проверить/обновить плагин. Проверьте сеть или логи сервера.", "badgeUpdateAvailable": "Доступно обновление", "badgeUpToDate": "Актуальная версия", "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 ↗ ] на блоках сообщений."}
|
|
525
|
+
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": "Лаконичный режим (кратко, без лишней воды)", "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% локально.", "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 ↗ ] на блоках сообщений."}
|
|
526
526
|
|
|
527
527
|
const SETTINGS_NS_NAME = 'russian-lang'
|
|
528
528
|
|
|
@@ -752,7 +752,45 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
752
752
|
'只能读,任何写入都需要审批。': 'Только чтение, любая запись требует подтверждения.',
|
|
753
753
|
'工作区内可写;工作区外的操作请求人工审批。': 'Запись в рабочей области разрешена; операции вне рабочей области требуют подтверждения.',
|
|
754
754
|
'全放行,不弹审批。': 'Полный доступ, запросы на подтверждение не выводятся.',
|
|
755
|
-
'确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.'
|
|
755
|
+
'确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.',
|
|
756
|
+
'手动测试': 'Ручное тестирование',
|
|
757
|
+
'事件': 'Событие',
|
|
758
|
+
'模拟 (看匹配)': 'Симуляция (проверка)',
|
|
759
|
+
'执行 (真实触发)': 'Выполнение (реальный триггер)',
|
|
760
|
+
'通知渠道测试': 'Тест каналов уведомлений',
|
|
761
|
+
'发送测试通知': 'Отправить тестовое уведомление',
|
|
762
|
+
'发送测试卡片': 'Отправить тестовую карточку',
|
|
763
|
+
'飞书通知': 'Уведомления Feishu',
|
|
764
|
+
'已断开': 'Отключено',
|
|
765
|
+
'已连接': 'Подключено',
|
|
766
|
+
'扫码连接飞书': 'Подключить Feishu по QR-коду',
|
|
767
|
+
'确定断开飞书连接': 'Отключить Feishu',
|
|
768
|
+
'断开连接': 'Отключить',
|
|
769
|
+
'断开失败': 'Не удалось отключить',
|
|
770
|
+
'卡片预览': 'Предпросмотр карточки',
|
|
771
|
+
'卡片内容最长': 'Макс. длина карточки',
|
|
772
|
+
'卡片截断长度': 'Длина усечения карточки',
|
|
773
|
+
'截断长度即时生效': 'Длина усечения применяется мгновенно',
|
|
774
|
+
'按当前截断长度生成的卡片正文预览': 'Предпросмотр текста карточки при текущей длине усечения',
|
|
775
|
+
'执行历史': 'История выполнения',
|
|
776
|
+
'全部原因': 'Все причины',
|
|
777
|
+
'清空过滤': 'Очистить фильтр',
|
|
778
|
+
'暂无记录': 'Нет записей',
|
|
779
|
+
'系统通知': 'Системные уведомления',
|
|
780
|
+
'通知机器人': 'Бот уведомлений',
|
|
781
|
+
'飞书扫码授权二维码': 'QR-код авторизации Feishu',
|
|
782
|
+
'在浏览器中打开飞书授权链接': 'Открыть ссылку авторизации Feishu в браузере',
|
|
783
|
+
'重新扫码会覆盖现有应用凭据与本': 'Повторное сканирование перезапишет учётные данные',
|
|
784
|
+
'扫码者本人接收通知卡片': 'Получатель карточки уведомлений — авторизованный пользователь',
|
|
785
|
+
'如未立即生效请重启': 'Если изменения не применились, перезапустите DSH',
|
|
786
|
+
'网络请求失败': 'Сетевой запрос не удался',
|
|
787
|
+
'重试': 'Повторить',
|
|
788
|
+
'已保存': 'Сохранено',
|
|
789
|
+
'保存失败': 'Не удалось сохранить',
|
|
790
|
+
'编辑': 'Редактировать',
|
|
791
|
+
'取消编辑': 'Отмена',
|
|
792
|
+
'启用': 'Включить',
|
|
793
|
+
'停用': 'Отключить'
|
|
756
794
|
}
|
|
757
795
|
for (const [k, v] of Object.entries(CORE_ZH_PRESETS)) ZH_EXACT.set(k, v)
|
|
758
796
|
|
|
@@ -787,7 +825,10 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
787
825
|
}
|
|
788
826
|
const DOM_EN_ATTRS = {
|
|
789
827
|
'Streaming preview': 'Предпросмотр стриминга',
|
|
790
|
-
'Visualization streaming preview': 'Предпросмотр визуализации'
|
|
828
|
+
'Visualization streaming preview': 'Предпросмотр визуализации',
|
|
829
|
+
'Security Auditor Shield': 'Защитный щит аудитора',
|
|
830
|
+
'Shield: Safe': 'Щит: Безопасно',
|
|
831
|
+
'Shield: Alert': 'Щит: Тревога'
|
|
791
832
|
}
|
|
792
833
|
const DOM_EN_TEXT = {
|
|
793
834
|
'Low': 'Низкий',
|
|
@@ -799,7 +840,26 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
799
840
|
'X search only': 'Только поиск в X',
|
|
800
841
|
'Auto mode': 'Автоматический режим',
|
|
801
842
|
'Full access': 'Полный доступ',
|
|
802
|
-
'Read only': 'Только чтение'
|
|
843
|
+
'Read only': 'Только чтение',
|
|
844
|
+
'Security Auditor Shield': 'Защитный щит аудитора',
|
|
845
|
+
'Shield: Safe': 'Щит: Безопасно',
|
|
846
|
+
'Shield: Alert': 'Щит: Тревога',
|
|
847
|
+
'Lens': 'Линза',
|
|
848
|
+
'Gallery': 'Галерея',
|
|
849
|
+
'SSH: Local': 'SSH: Локально',
|
|
850
|
+
'SSH: Remote': 'SSH: Удалённо',
|
|
851
|
+
'7/7 rot': '7/7 рот.',
|
|
852
|
+
'Smoke chat': 'Тестовый чат',
|
|
853
|
+
'Register in Models': 'Зарегистрировать в моделях',
|
|
854
|
+
'Base URL': 'Базовый URL',
|
|
855
|
+
'API key env / credential name': 'Имя переменной окружения / ключа API',
|
|
856
|
+
'Default model': 'Модель по умолчанию',
|
|
857
|
+
'Enabled': 'Включено',
|
|
858
|
+
'Save': 'Сохранить',
|
|
859
|
+
'Saved': 'Сохранено',
|
|
860
|
+
'Refresh': 'Обновить',
|
|
861
|
+
'Loading settings…': 'Загрузка настроек…',
|
|
862
|
+
'Put the ar-… key in DSH credentials or process env under this name. Never paste the key into this form.': 'Укажите ключ ar-… в учётных данных DSH или переменной окружения. Не вставляйте ключ в форму.'
|
|
803
863
|
}
|
|
804
864
|
const ZH_WALKER = (root) => {
|
|
805
865
|
try {
|
|
@@ -812,7 +872,11 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
812
872
|
const trimmed = node.nodeValue.trim()
|
|
813
873
|
if (DOM_EN_TEXT[trimmed]) {
|
|
814
874
|
const p = node.parentElement
|
|
815
|
-
if (p && (
|
|
875
|
+
if (p && (
|
|
876
|
+
p.closest('[class*="effort"], [class*="slider"], [class*="reasoning"], [class*="selector"], [class*="Menu"], [class*="menu"], [role="menu"], [class*="status"], [class*="chip"], [class*="badge"], [class*="ar-"], [class*="card"], [class*="header"]') ||
|
|
877
|
+
p.getAttribute('role') === 'option' || p.getAttribute('role') === 'menuitem' ||
|
|
878
|
+
p.tagName === 'BUTTON' || p.tagName === 'LABEL' || p.tagName === 'SPAN' || p.tagName === 'A'
|
|
879
|
+
)) {
|
|
816
880
|
node.nodeValue = node.nodeValue.replace(trimmed, DOM_EN_TEXT[trimmed])
|
|
817
881
|
}
|
|
818
882
|
}
|
|
@@ -1505,62 +1569,21 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1505
1569
|
}
|
|
1506
1570
|
|
|
1507
1571
|
async function translateTurnContent(text) {
|
|
1508
|
-
if (!text || typeof text !== 'string') return ''
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
if (!
|
|
1517
|
-
|
|
1518
|
-
const translatedParas = []
|
|
1519
|
-
for (const para of paragraphs) {
|
|
1520
|
-
const pTrim = para.trim()
|
|
1521
|
-
if (!pTrim) continue
|
|
1522
|
-
|
|
1523
|
-
const sentences = pTrim.match(/[^.!?\n]+[.!?\n]*/g) || [pTrim]
|
|
1524
|
-
const translatedSentences = []
|
|
1525
|
-
|
|
1526
|
-
for (const s of sentences) {
|
|
1527
|
-
let translated = ''
|
|
1528
|
-
|
|
1529
|
-
try {
|
|
1530
|
-
const gurl = 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=ru&dt=t&q=' + encodeURIComponent(s)
|
|
1531
|
-
const gres = await fetch(gurl)
|
|
1532
|
-
if (gres.ok) {
|
|
1533
|
-
const gdata = await gres.json()
|
|
1534
|
-
if (Array.isArray(gdata) && Array.isArray(gdata[0])) {
|
|
1535
|
-
translated = gdata[0].map((it) => it[0]).join('')
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
} catch (e1) {}
|
|
1539
|
-
|
|
1540
|
-
if (!translated) {
|
|
1541
|
-
try {
|
|
1542
|
-
const isZh = /[\u3400-\u9fff]/.test(s)
|
|
1543
|
-
const langPair = isZh ? 'zh-CN|ru' : 'en|ru'
|
|
1544
|
-
const murl = 'https://api.mymemory.translated.net/get?q=' + encodeURIComponent(s.slice(0, 450)) + '&langpair=' + langPair
|
|
1545
|
-
const mres = await fetch(murl)
|
|
1546
|
-
if (mres.ok) {
|
|
1547
|
-
const mdata = await mres.json()
|
|
1548
|
-
if (mdata && mdata.responseData && mdata.responseData.translatedText) {
|
|
1549
|
-
const txtEl = document.createElement('textarea')
|
|
1550
|
-
txtEl.innerHTML = mdata.responseData.translatedText
|
|
1551
|
-
translated = txtEl.value
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
} catch (e2) {}
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
translatedSentences.push(translated || s)
|
|
1558
|
-
}
|
|
1559
|
-
translatedParas.push(translatedSentences.join(' '))
|
|
1572
|
+
if (!text || typeof text !== 'string') return { error: 'Текст для перевода пуст.' }
|
|
1573
|
+
try {
|
|
1574
|
+
const res = await fetch('/api/dsh-russian-lang/translate', {
|
|
1575
|
+
method: 'POST',
|
|
1576
|
+
headers: { 'content-type': 'application/json' },
|
|
1577
|
+
body: JSON.stringify({ text })
|
|
1578
|
+
})
|
|
1579
|
+
const data = await res.json().catch(() => ({}))
|
|
1580
|
+
if (!res.ok) {
|
|
1581
|
+
return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
|
|
1560
1582
|
}
|
|
1561
|
-
|
|
1583
|
+
return { translatedText: data.translatedText || text }
|
|
1584
|
+
} catch (err) {
|
|
1585
|
+
return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
|
|
1562
1586
|
}
|
|
1563
|
-
return parts.join('\n\n')
|
|
1564
1587
|
}
|
|
1565
1588
|
|
|
1566
1589
|
function TranslateTurnAction(props) {
|
|
@@ -1568,7 +1591,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1568
1591
|
const [loading, setLoading] = React.useState(false)
|
|
1569
1592
|
const [open, setOpen] = React.useState(false)
|
|
1570
1593
|
const [upStatus, setUpStatus] = React.useState({
|
|
1571
|
-
currentVersion: '0.2.
|
|
1594
|
+
currentVersion: '0.2.22',
|
|
1572
1595
|
latestVersion: undefined,
|
|
1573
1596
|
updateAvailable: false
|
|
1574
1597
|
})
|
|
@@ -1613,8 +1636,42 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1613
1636
|
})
|
|
1614
1637
|
}
|
|
1615
1638
|
|
|
1639
|
+
const [transStatus, setTransStatus] = React.useState(null)
|
|
1640
|
+
const [transStarting, setTransStarting] = React.useState(false)
|
|
1641
|
+
const [transMsg, setTransMsg] = React.useState(null)
|
|
1642
|
+
|
|
1643
|
+
const fetchTransStatus = () => {
|
|
1644
|
+
fetch('/api/dsh-russian-lang/translator/status')
|
|
1645
|
+
.then((r) => r.json())
|
|
1646
|
+
.then((data) => setTransStatus(data))
|
|
1647
|
+
.catch(() => setTransStatus(null))
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const startLibreTranslate = () => {
|
|
1651
|
+
setTransStarting(true)
|
|
1652
|
+
setTransMsg(null)
|
|
1653
|
+
fetch('/api/dsh-russian-lang/translator/setup', { method: 'POST' })
|
|
1654
|
+
.then((r) => r.json())
|
|
1655
|
+
.then((data) => {
|
|
1656
|
+
setTransStarting(false)
|
|
1657
|
+
if (data.ok) {
|
|
1658
|
+
setTransMsg({ type: 'ok', text: data.message || 'Контейнер LibreTranslate запущен.' })
|
|
1659
|
+
fetchTransStatus()
|
|
1660
|
+
} else {
|
|
1661
|
+
setTransMsg({ type: 'err', text: data.error || 'Ошибка запуска контейнера.' })
|
|
1662
|
+
}
|
|
1663
|
+
})
|
|
1664
|
+
.catch((err) => {
|
|
1665
|
+
setTransStarting(false)
|
|
1666
|
+
setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
|
|
1667
|
+
})
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1616
1670
|
React.useEffect(() => {
|
|
1617
|
-
if (open)
|
|
1671
|
+
if (open) {
|
|
1672
|
+
checkUpdate()
|
|
1673
|
+
fetchTransStatus()
|
|
1674
|
+
}
|
|
1618
1675
|
}, [open])
|
|
1619
1676
|
|
|
1620
1677
|
|
|
@@ -1676,29 +1733,36 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1676
1733
|
if (prose) rawText = prose.innerText.trim()
|
|
1677
1734
|
}
|
|
1678
1735
|
|
|
1679
|
-
|
|
1736
|
+
let transResult = { error: 'Не удалось обнаружить текст сообщения ассистента для перевода.' }
|
|
1737
|
+
if (rawText) {
|
|
1738
|
+
transResult = await translateTurnContent(rawText)
|
|
1739
|
+
}
|
|
1740
|
+
const isErr = !!transResult.error
|
|
1741
|
+
const displayText = transResult.translatedText || transResult.error || ''
|
|
1680
1742
|
|
|
1681
1743
|
box = document.createElement('div')
|
|
1682
1744
|
box.className = 'rl-turn-translation'
|
|
1683
1745
|
const key = tailFlowItem.getAttribute('data-chat-flow-key')
|
|
1684
1746
|
if (key) box.dataset.tailKey = key
|
|
1685
1747
|
box.innerHTML = '<div class="rl-trans-head">' +
|
|
1686
|
-
'<span class="rl-trans-title"
|
|
1748
|
+
'<span class="rl-trans-title">' + (isErr ? '⚠️ Машинный перевод' : '🌐 Перевод на русский') + '</span>' +
|
|
1687
1749
|
'<div class="rl-trans-tools">' +
|
|
1688
|
-
'<button type="button" class="rl-trans-btn rl-btn-copy" title="Скопировать перевод">📋 Копировать</button>' +
|
|
1750
|
+
(!isErr ? '<button type="button" class="rl-trans-btn rl-btn-copy" title="Скопировать перевод">📋 Копировать</button>' : '') +
|
|
1689
1751
|
'<button type="button" class="rl-trans-btn rl-btn-close" title="Закрыть">✕</button>' +
|
|
1690
1752
|
'</div>' +
|
|
1691
1753
|
'</div>' +
|
|
1692
|
-
'<div class="rl-trans-body"></div>'
|
|
1693
|
-
box.querySelector('.rl-trans-body').textContent =
|
|
1754
|
+
'<div class="rl-trans-body"' + (isErr ? ' style="color: var(--dsw-alias-color-warning, #eab308); font-size: 13px;"' : '') + '></div>'
|
|
1755
|
+
box.querySelector('.rl-trans-body').textContent = displayText
|
|
1694
1756
|
|
|
1695
1757
|
const copyBtn = box.querySelector('.rl-btn-copy')
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1758
|
+
if (copyBtn) {
|
|
1759
|
+
copyBtn.addEventListener('click', (e) => {
|
|
1760
|
+
e.stopPropagation()
|
|
1761
|
+
try { navigator.clipboard.writeText(displayText) } catch (err) {}
|
|
1762
|
+
copyBtn.textContent = '✓ Скопировано'
|
|
1763
|
+
setTimeout(() => { copyBtn.textContent = '📋 Копировать' }, 2000)
|
|
1764
|
+
})
|
|
1765
|
+
}
|
|
1702
1766
|
|
|
1703
1767
|
const closeBtn = box.querySelector('.rl-btn-close')
|
|
1704
1768
|
closeBtn.addEventListener('click', (e) => {
|
|
@@ -1968,7 +2032,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1968
2032
|
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },
|
|
1969
2033
|
React.createElement('div', null,
|
|
1970
2034
|
React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },
|
|
1971
|
-
t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.2.
|
|
2035
|
+
t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.2.22')),
|
|
1972
2036
|
upStatus.updateAvailable
|
|
1973
2037
|
? React.createElement('div', { style: { color: 'var(--dsw-alias-color-warning, #eab308)', marginTop: '2px', fontWeight: 500 } },
|
|
1974
2038
|
t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
|
|
@@ -1999,6 +2063,66 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
1999
2063
|
)
|
|
2000
2064
|
),
|
|
2001
2065
|
|
|
2066
|
+
React.createElement('div', { className: 'rl-section-card' },
|
|
2067
|
+
React.createElement('div', { className: 'rl-section-title' },
|
|
2068
|
+
React.createElement('span', null, t('secTranslator')),
|
|
2069
|
+
React.createElement('span', {
|
|
2070
|
+
className: 'rl-badge ' + (value.translateEngine === 'local' ? 'rl-badge-ok' : (value.translateEngine === 'google' ? 'rl-badge-warn' : 'rl-badge-dim'))
|
|
2071
|
+
}, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))
|
|
2072
|
+
),
|
|
2073
|
+
React.createElement('div', { className: 'rl-section-desc' }, t('secTranslatorDesc')),
|
|
2074
|
+
React.createElement('div', { className: 'rl-item-card' },
|
|
2075
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },
|
|
2076
|
+
React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('translateEngine')),
|
|
2077
|
+
React.createElement('select', {
|
|
2078
|
+
className: 'rl-select',
|
|
2079
|
+
value: value.translateEngine || 'off',
|
|
2080
|
+
onChange: (ev) => {
|
|
2081
|
+
try {
|
|
2082
|
+
const val = ev.target.value
|
|
2083
|
+
scope.set('translateEngine', val)
|
|
2084
|
+
if (val === 'local') fetchTransStatus()
|
|
2085
|
+
} catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }
|
|
2086
|
+
}
|
|
2087
|
+
},
|
|
2088
|
+
React.createElement('option', { value: 'off' }, t('engineOff')),
|
|
2089
|
+
React.createElement('option', { value: 'local' }, t('engineLocal')),
|
|
2090
|
+
React.createElement('option', { value: 'google' }, t('engineGoogle'))
|
|
2091
|
+
)
|
|
2092
|
+
),
|
|
2093
|
+
value.translateEngine === 'google' ? React.createElement('div', {
|
|
2094
|
+
style: {
|
|
2095
|
+
marginTop: '10px', padding: '8px 12px', borderRadius: '8px',
|
|
2096
|
+
background: 'rgba(234, 179, 8, 0.12)', border: '1px solid rgba(234, 179, 8, 0.3)',
|
|
2097
|
+
color: 'var(--dsw-alias-color-warning, #eab308)', fontSize: '12px', fontWeight: 500, lineHeight: 1.4
|
|
2098
|
+
}
|
|
2099
|
+
}, t('googleWarn')) : null,
|
|
2100
|
+
value.translateEngine === 'local' ? React.createElement('div', {
|
|
2101
|
+
style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }
|
|
2102
|
+
},
|
|
2103
|
+
React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, t('localInfo')),
|
|
2104
|
+
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },
|
|
2105
|
+
React.createElement('span', {
|
|
2106
|
+
className: 'rl-badge ' + (transStatus && transStatus.running ? 'rl-badge-ok' : 'rl-badge-dim')
|
|
2107
|
+
}, transStatus && transStatus.running ? t('localStatusRunning') : t('localStatusStopped')),
|
|
2108
|
+
(!transStatus || !transStatus.running) ? React.createElement('button', {
|
|
2109
|
+
type: 'button',
|
|
2110
|
+
className: 'rl-btn rl-btn-primary',
|
|
2111
|
+
disabled: transStarting,
|
|
2112
|
+
onClick: startLibreTranslate
|
|
2113
|
+
}, transStarting ? t('localStarting') : t('localStartBtn')) : null
|
|
2114
|
+
),
|
|
2115
|
+
transMsg ? React.createElement('div', {
|
|
2116
|
+
style: {
|
|
2117
|
+
marginTop: '6px', padding: '8px 12px', borderRadius: '8px',
|
|
2118
|
+
background: transMsg.type === 'ok' ? 'rgba(34, 197, 94, 0.12)' : 'rgba(239, 68, 68, 0.12)',
|
|
2119
|
+
color: transMsg.type === 'ok' ? '#16a34a' : '#dc2626', fontSize: '12px', fontWeight: 500
|
|
2120
|
+
}
|
|
2121
|
+
}, transMsg.text) : null
|
|
2122
|
+
) : null
|
|
2123
|
+
)
|
|
2124
|
+
),
|
|
2125
|
+
|
|
2002
2126
|
React.createElement('div', { className: 'rl-section-card' },
|
|
2003
2127
|
React.createElement('div', { className: 'rl-section-title' },
|
|
2004
2128
|
React.createElement('span', null, t('secSupport')),
|
|
@@ -2021,7 +2145,7 @@ const exportSessionToMarkdown = (session, options = {}) => {
|
|
|
2021
2145
|
),
|
|
2022
2146
|
React.createElement('div', { className: 'rl-actions-row' },
|
|
2023
2147
|
React.createElement('a', {
|
|
2024
|
-
href: makeIssueUrl({}, '0.2.
|
|
2148
|
+
href: makeIssueUrl({}, '0.2.22'),
|
|
2025
2149
|
target: '_blank',
|
|
2026
2150
|
rel: 'noopener noreferrer',
|
|
2027
2151
|
className: 'rl-btn rl-btn-primary'
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerPluginUpdater } from './plugin-updater.js'
|
|
2
|
+
import { registerTranslator } from './translator.js'
|
|
2
3
|
import { getPluginDictionaries, getZhRuMap } from './locales.js'
|
|
3
4
|
// dsh-russian-lang — серверная половина.
|
|
4
5
|
//
|
|
@@ -22,11 +23,15 @@ const RussianLangSettingsSchema = z.object({
|
|
|
22
23
|
agentPrompt: z.boolean().required(false),
|
|
23
24
|
agentPromptPreset: z.string().required(false),
|
|
24
25
|
slashAliases: z.boolean().required(false),
|
|
25
|
-
quickSwitch: z.boolean().required(false)
|
|
26
|
+
quickSwitch: z.boolean().required(false),
|
|
27
|
+
translateEngine: z.string().required(false),
|
|
28
|
+
localApiUrl: z.string().required(false)
|
|
26
29
|
})
|
|
27
30
|
|
|
28
31
|
export function apply(ctx) {
|
|
32
|
+
let globalSettingsCtx = null
|
|
29
33
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
34
|
+
globalSettingsCtx = settingsCtx
|
|
30
35
|
settingsCtx.settings.register('russian-lang', RussianLangSettingsSchema)
|
|
31
36
|
})
|
|
32
37
|
|
|
@@ -52,7 +57,7 @@ export function apply(ctx) {
|
|
|
52
57
|
sync()
|
|
53
58
|
})
|
|
54
59
|
|
|
55
|
-
// Web endpoints: One-click plugin updater
|
|
60
|
+
// Web endpoints: One-click plugin updater, modular plugin dictionary serving, & translator gateway
|
|
56
61
|
if (typeof ctx.inject === 'function') {
|
|
57
62
|
ctx.inject(['webServer'], (wctx) => {
|
|
58
63
|
try {
|
|
@@ -63,6 +68,8 @@ export function apply(ctx) {
|
|
|
63
68
|
manifestUrl: new URL('../package.json', import.meta.url),
|
|
64
69
|
})
|
|
65
70
|
|
|
71
|
+
const unregTranslator = registerTranslator(wctx, globalSettingsCtx)
|
|
72
|
+
|
|
66
73
|
let ws = null
|
|
67
74
|
try { ws = wctx?.webServer } catch (_) { ws = null }
|
|
68
75
|
|
|
@@ -90,6 +97,7 @@ export function apply(ctx) {
|
|
|
90
97
|
|
|
91
98
|
return () => {
|
|
92
99
|
if (typeof unregUpdater === 'function') unregUpdater()
|
|
100
|
+
if (typeof unregTranslator === 'function') unregTranslator()
|
|
93
101
|
if (typeof unregDict === 'function') unregDict()
|
|
94
102
|
}
|
|
95
103
|
}
|