@goodandready/dsh-russian-lang 0.3.18 → 0.3.19

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 CHANGED
@@ -189,6 +189,14 @@ russian-lang:
189
189
 
190
190
  ---
191
191
 
192
+ ## 🛠 Разработка и зеркало репозитория
193
+
194
+ Публичный репозиторий на GitHub является дистрибутивным зеркалом релизов `@goodandready/dsh-russian-lang`. Основная разработка, сборочный пайплайн и полный тестовый контур ведутся в закрытом репозитории проекта.
195
+
196
+ Pull Requests и Issues на GitHub приветствуются и обрабатываются в обычном порядке! Для внесения правок в логику клиента используйте `lib/client.template.js`. Запуск `npm test` в клоне репозитория проверяет синтаксис бандла через `node --check`.
197
+
198
+ ---
199
+
192
200
  ## 📄 Лицензия
193
201
 
194
202
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/lib/client.js CHANGED
@@ -195,11 +195,12 @@ return count > 0 ? { status: 'full', count, label: `RU: ${count} строк` } :
195
195
  const typoQuotes = (text) => text
196
196
  .replace(/"([^"\n]{1,200})"/g, '«$1»').replace(/[“„]([^“”\n]{1,200})[”"]/g, '«$1»').replace(/[『「]([^』」\n]{1,200})[』」]/g, '«$1»')
197
197
  const typoDash = (text) => text
198
- .replace(/(^|[\s(\[«])--(?=\s|$)/g, '$1—').replace(/(^|[\s(\[«])-(?=\s)/g, '$1—')
198
+ .replace(/(^|[\s(\[«])--(?=\s|$)/g, '$1—').replace(/(?<=[\p{L}\p{N}»\)]) - (?=[\p{L}\p{N}«\(])/gu, ' — ')
199
199
  const typoPunct = (text) => text.replace(/\s+([,.:;!?])(?=\s|$)/g, '$1')
200
200
  const TYPO_SHORT = new Set(['в', 'с', 'к', 'о', 'у', 'а', 'и', 'но', 'не', 'ни', 'на', 'по', 'до', 'из', 'за', 'от', 'об'])
201
- const typoNbsp = (text) => text.replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (TYPO_SHORT.has(word) ? lead + word + ' ' : match
202
- ))
201
+ const typoNbsp = (text) => text
202
+ .replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (TYPO_SHORT.has(word) ? lead + word + ' ' : match
203
+ )).replace(/(\d) ([а-яё]{1,24})(?![а-яё])/giu, '$1 $2').replace(/(\d) ([%°₽$€£]|кб|КБ|МБ|ГБ)(?=\s|$)/giu, '$1 $2')
203
204
  const YO_EDGE_L = '(?<![а-яёА-ЯЁ])'
204
205
  const YO_EDGE_R = '(?![а-яёА-ЯЁ])'
205
206
  const yoCase = (src, repl) => {if (src === src.toUpperCase() && src !== src.toLowerCase()) return repl.toUpperCase()
@@ -393,7 +394,8 @@ return results.sort((a, b) => b.score - a.score).map((r) => r.item)
393
394
  }
394
395
  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":"Магазин плагинов"}}
395
396
  const ZH_RU = {}
396
- 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 ↗ ] на блоках сообщений."}
397
+ 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.","statNamespaces":"Пространств имён","statCoreKeys":"Ключей ядра","statPluginKeys":"Ключей плагинов","overridesCount":"Своих переопределений","statusLoading":"Настройки загружаются…","statusUnavailable":"Настройки недоступны на этом хосте","translateTurn":"Перевести на русский","reportIssue":"Сообщить о неточности перевода","exportMdHint":"Экспорт диалога в Markdown доступен по кнопке [ 📥 MD ] в шапке сессии.","translateTurnHint":"Перевод ответов ассистента на русский доступен по кнопке [ RU ↗ ] на блоках сообщений.","inspectorToggleOff":"🔍 Включить инспектор перевода (Alt+I)","inspectorToggleOn":"🔍 Инспектор перевода (включён)","exportJsonBtn":"📤 Экспорт JSON","importJsonBtn":"📥 Импорт JSON","overridesCopied":"Оверрайды скопированы в буфер обмена как JSON!","overridesImportPrompt":"Вставьте JSON с оверрайдами:","overridesImportError":"Ошибка разбора JSON: "}
398
+ const CARD_EN = {cardTitle: 'Russian localization',cardSub: 'Interface language, typography, keyboard layout',badgeRu: '🟢 RU active',badgeEn: '⚪ EN active',badgeCoverage: '🟢 100% (7,902 keys)',badgeSmartUx: '⚡ Smart UX active',secLanguage: '🌐 Interface language',secLanguageDesc: 'Switch the DSH interface language to Russian natively, without reloading the page.',enabled: 'Russian language enabled',enabledDesc: 'Switches the DeepSeek Harness interface to Russian.',quickSwitchNote: 'The RU ⇄ EN quick switch is available in the session header next to the dialog buttons.',secTypography: '✍️ Smart typography & input (Smart UX)',secTypographyDesc: 'Russian typography rules applied while you chat and type.',typography: 'Output typography',typographyDesc: 'Guillemets «», em dashes — instead of hyphens, non-breaking spaces.',yo: 'Letter «ё»',yoDesc: 'Restores «ё» in frequent words (ещё, идёт, etc.).',liveInput: 'Live input typography',liveInputDesc: 'Guillemets «», em dashes — and non-breaking spaces before sending.',slashAliases: 'Russian command aliases',slashAliasesDesc: 'Commands: /цель, /сжать, /план, /справка, /память.',altLHintText: 'Converts the keyboard layout of the current field (ghbdtn ⇄ привет).',secAgentPrompt: '🤖 Agent system prompt',secAgentPromptDesc: 'An official DSH systemPrompt extension section for conversing in Russian.',agentPrompt: 'Russian agent prompt',agentPromptDesc: 'Adds an instruction to reply in Russian in the selected style to the system prompt.',agentPromptPreset: 'Reply style',presetExpert: 'Technical expert (strict terminology, clean code)',presetWriter: 'Technical writer (Markdown, tables, GOST)',presetConcise: 'Concise mode (brief, no fluff)',presetReviewer: 'Code reviewer (bug hunting, security, audit)',presetArchitect: 'System architect (contracts, scalability)',presetTutor: 'Mentor (clear explanations, step-by-step)',secUpdater: '🔄 Language pack updates',secUpdaterDesc: 'Check for new releases in the npm registry and update in one click.',updaterCurrent: 'Current version: v{version}',updaterLatest: 'New version available: v{version}',updaterUpToDate: 'You are on the latest version',updaterChecking: 'Checking…',updaterCheckBtn: 'Check for updates',updaterBtn: 'Update to v{version} in 1 click',updaterUpdating: 'Installing update…',updaterSuccess: '✅ Plugin updated successfully! Restart DSH to apply.',updaterFailed: '❌ Failed to check or update the plugin. Check your network or server logs.',badgeUpdateAvailable: 'Update available',badgeUpToDate: 'Up to date',secTranslator: '🌐 Assistant message translation',secTranslatorDesc: 'Translate model replies in chat into Russian.',translateEngine: 'Translation engine',engineOff: 'Off (default, no network calls)',engineLocal: 'Local LibreTranslate (private, ~600 MB RAM)',engineGoogle: 'Google Translate (online)',googleWarn: '⚠️ Message text is sent to public Google servers.',localStatusRunning: '🟢 LibreTranslate container is running',localStatusStopped: '⚪ LibreTranslate container is not running',localStartBtn: 'Start LibreTranslate in Docker (~600 MB RAM)',localStarting: 'Starting container…',localInfo: 'LibreTranslate in Docker: isolated, ~600 MB RAM.',secOverrides: '✍️ Custom overrides',secOverridesDesc: 'Replace any DSH interface wording with your own.',overrideKeyPlaceholder: 'Key (e.g. common.back or settings.title)',overrideValuePlaceholder: 'Your translation',overrideAddBtn: 'Add',overrideEmpty: 'No overrides yet.',overrideDelete: 'Delete',overrideTip: '💡 Hold Alt and click a UI element for quick editing.',secSupport: '📊 Ecosystem coverage & support',secSupportDesc: '100% UI coverage of the DSH core and plugins.',inspectorToggleOff: '🔍 Enable translation inspector (Alt+I)',inspectorToggleOn: '🔍 Translation inspector (on)',exportJsonBtn: '📤 Export JSON',importJsonBtn: '📥 Import JSON',overridesCopied: 'Overrides copied to clipboard as JSON!',overridesImportPrompt: 'Paste overrides JSON:',overridesImportError: 'JSON parse error: ',statNamespaces: 'Namespaces',statCoreKeys: 'Core keys',statPluginKeys: 'Plugin keys',overridesCount: 'Custom overrides',statusLoading: 'Loading settings…',statusUnavailable: 'Settings unavailable on this host',translateTurn: 'Translate to Russian',reportIssue: 'Report a translation issue',exportMdHint: 'Export the dialog to Markdown via the [ 📥 MD ] button in the session header.',translateTurnHint: 'Translate assistant replies to Russian via the [ RU ↗ ] button on message blocks.',}
397
399
  const SETTINGS_NS_NAME = 'russian-lang'
398
400
  const FORM_NS = 'dsh-russian-lang'
399
401
  const LEGACY_NS = 'russian-lang'
@@ -411,6 +413,7 @@ const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScop
411
413
  const legacyFlagMode = !(ctx && ctx.configForms)
412
414
  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)
413
415
  }
416
+ ctx.effect(() => {try { return ctx.locale.register(SETTINGS_NS_NAME, 'en', CARD_EN) } catch (err) { return () => {} }}, 'dsh-russian-lang: russian-lang-en')
414
417
  const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
415
418
  for (const ns of Object.keys(dictMap)) {if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
416
419
  const dict = dictMap[ns]
@@ -1129,7 +1132,7 @@ setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
1129
1132
  }}
1130
1133
  const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
1131
1134
  if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
1132
- ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.18' })
1135
+ ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.19' })
1133
1136
  : ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
1134
1137
  try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
1135
1138
  copyReportBtn.textContent = '✓ Скопировано'
@@ -1417,7 +1420,7 @@ const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope
1417
1420
  const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
1418
1421
  const [typo, setTypoState] = React.useState(() =>
1419
1422
  (snap.value && snap.value.typography) || {})
1420
- const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.18',latestVersion: undefined,updateAvailable: false
1423
+ const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.19',latestVersion: undefined,updateAvailable: false
1421
1424
  })
1422
1425
  const [upLoading, setUpLoading] = React.useState(false)
1423
1426
  const [upMsg, setUpMsg] = React.useState(null)
@@ -1510,7 +1513,7 @@ scope.set('agentPromptPreset', val)
1510
1513
  } 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
1511
1514
  ) : null
1512
1515
  ) : null
1513
- ),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.18')),upStatus.updateAvailable
1516
+ ),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.19')),upStatus.updateAvailable
1514
1517
  ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
1515
1518
  : React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
1516
1519
  ? React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
@@ -1541,14 +1544,14 @@ React.createElement('div', {key: k,style: { display: 'flex', justifyContent: 'sp
1541
1544
  delete next[k]
1542
1545
  try { scope.set('overrides', next) } catch (_e) { void _e }},}, t('overrideDelete'))))),React.createElement('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip')),React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginTop: '10px' } },React.createElement('button', {type: 'button',className: 'rl-btn' + (isInspectorActive ? ' rl-btn-primary' : ''),onClick: () => {isInspectorActive = !isInspectorActive
1543
1546
  updateInspectorPill()
1544
- },}, isInspectorActive ? '🔍 Инспектор перевода (включён)' : '🔍 Включить инспектор перевода (Alt+I)'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {try {navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
1545
- alert('Оверрайды скопированы в буфер обмена как JSON!')
1546
- } catch (err) { void err; }}}, '📤 Экспорт JSON'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt('Вставьте JSON с оверрайдами:') : null
1547
+ },}, isInspectorActive ? t('inspectorToggleOn') : t('inspectorToggleOff')),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {try {navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
1548
+ alert(t('overridesCopied'))
1549
+ } catch (err) { void err; }}}, t('exportJsonBtn')),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt(t('overridesImportPrompt')) : null
1547
1550
  if (!input) return
1548
1551
  try {const parsed = JSON.parse(input)
1549
1552
  if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
1550
1553
  scope.set('overrides', merged)
1551
- }} 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.18'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1554
+ }} catch (err) { alert(t('overridesImportError') + (err && err.message || err)) }}}, t('importJsonBtn')))),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.19'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1552
1555
  }, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
1553
1556
  }
1554
1557
  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,94 @@ window.__ModuleLoader__.load({
33
33
  // Подписи собственной карточки настроек (namespace russian-lang).
34
34
  RU['russian-lang'] = /*__CARD_RU__*/{}
35
35
 
36
+ const CARD_EN = {
37
+ cardTitle: 'Russian localization',
38
+ cardSub: 'Interface language, typography, keyboard layout',
39
+ badgeRu: '🟢 RU active',
40
+ badgeEn: '⚪ EN active',
41
+ badgeCoverage: '🟢 100% (7,902 keys)',
42
+ badgeSmartUx: '⚡ Smart UX active',
43
+ secLanguage: '🌐 Interface language',
44
+ secLanguageDesc: 'Switch the DSH interface language to Russian natively, without reloading the page.',
45
+ enabled: 'Russian language enabled',
46
+ enabledDesc: 'Switches the DeepSeek Harness interface to Russian.',
47
+ quickSwitchNote: 'The RU ⇄ EN quick switch is available in the session header next to the dialog buttons.',
48
+ secTypography: '✍️ Smart typography & input (Smart UX)',
49
+ secTypographyDesc: 'Russian typography rules applied while you chat and type.',
50
+ typography: 'Output typography',
51
+ typographyDesc: 'Guillemets «», em dashes — instead of hyphens, non-breaking spaces.',
52
+ yo: 'Letter «ё»',
53
+ yoDesc: 'Restores «ё» in frequent words (ещё, идёт, etc.).',
54
+ liveInput: 'Live input typography',
55
+ liveInputDesc: 'Guillemets «», em dashes — and non-breaking spaces before sending.',
56
+ slashAliases: 'Russian command aliases',
57
+ slashAliasesDesc: 'Commands: /цель, /сжать, /план, /справка, /память.',
58
+ altLHintText: 'Converts the keyboard layout of the current field (ghbdtn ⇄ привет).',
59
+ secAgentPrompt: '🤖 Agent system prompt',
60
+ secAgentPromptDesc: 'An official DSH systemPrompt extension section for conversing in Russian.',
61
+ agentPrompt: 'Russian agent prompt',
62
+ agentPromptDesc: 'Adds an instruction to reply in Russian in the selected style to the system prompt.',
63
+ agentPromptPreset: 'Reply style',
64
+ presetExpert: 'Technical expert (strict terminology, clean code)',
65
+ presetWriter: 'Technical writer (Markdown, tables, GOST)',
66
+ presetConcise: 'Concise mode (brief, no fluff)',
67
+ presetReviewer: 'Code reviewer (bug hunting, security, audit)',
68
+ presetArchitect: 'System architect (contracts, scalability)',
69
+ presetTutor: 'Mentor (clear explanations, step-by-step)',
70
+ secUpdater: '🔄 Language pack updates',
71
+ secUpdaterDesc: 'Check for new releases in the npm registry and update in one click.',
72
+ updaterCurrent: 'Current version: v{version}',
73
+ updaterLatest: 'New version available: v{version}',
74
+ updaterUpToDate: 'You are on the latest version',
75
+ updaterChecking: 'Checking…',
76
+ updaterCheckBtn: 'Check for updates',
77
+ updaterBtn: 'Update to v{version} in 1 click',
78
+ updaterUpdating: 'Installing update…',
79
+ updaterSuccess: '✅ Plugin updated successfully! Restart DSH to apply.',
80
+ updaterFailed: '❌ Failed to check or update the plugin. Check your network or server logs.',
81
+ badgeUpdateAvailable: 'Update available',
82
+ badgeUpToDate: 'Up to date',
83
+ secTranslator: '🌐 Assistant message translation',
84
+ secTranslatorDesc: 'Translate model replies in chat into Russian.',
85
+ translateEngine: 'Translation engine',
86
+ engineOff: 'Off (default, no network calls)',
87
+ engineLocal: 'Local LibreTranslate (private, ~600 MB RAM)',
88
+ engineGoogle: 'Google Translate (online)',
89
+ googleWarn: '⚠️ Message text is sent to public Google servers.',
90
+ localStatusRunning: '🟢 LibreTranslate container is running',
91
+ localStatusStopped: '⚪ LibreTranslate container is not running',
92
+ localStartBtn: 'Start LibreTranslate in Docker (~600 MB RAM)',
93
+ localStarting: 'Starting container…',
94
+ localInfo: 'LibreTranslate in Docker: isolated, ~600 MB RAM.',
95
+ secOverrides: '✍️ Custom overrides',
96
+ secOverridesDesc: 'Replace any DSH interface wording with your own.',
97
+ overrideKeyPlaceholder: 'Key (e.g. common.back or settings.title)',
98
+ overrideValuePlaceholder: 'Your translation',
99
+ overrideAddBtn: 'Add',
100
+ overrideEmpty: 'No overrides yet.',
101
+ overrideDelete: 'Delete',
102
+ overrideTip: '💡 Hold Alt and click a UI element for quick editing.',
103
+ secSupport: '📊 Ecosystem coverage & support',
104
+ secSupportDesc: '100% UI coverage of the DSH core and plugins.',
105
+ inspectorToggleOff: '🔍 Enable translation inspector (Alt+I)',
106
+ inspectorToggleOn: '🔍 Translation inspector (on)',
107
+ exportJsonBtn: '📤 Export JSON',
108
+ importJsonBtn: '📥 Import JSON',
109
+ overridesCopied: 'Overrides copied to clipboard as JSON!',
110
+ overridesImportPrompt: 'Paste overrides JSON:',
111
+ overridesImportError: 'JSON parse error: ',
112
+ statNamespaces: 'Namespaces',
113
+ statCoreKeys: 'Core keys',
114
+ statPluginKeys: 'Plugin keys',
115
+ overridesCount: 'Custom overrides',
116
+ statusLoading: 'Loading settings…',
117
+ statusUnavailable: 'Settings unavailable on this host',
118
+ translateTurn: 'Translate to Russian',
119
+ reportIssue: 'Report a translation issue',
120
+ exportMdHint: 'Export the dialog to Markdown via the [ 📥 MD ] button in the session header.',
121
+ translateTurnHint: 'Translate assistant replies to Russian via the [ RU ↗ ] button on message blocks.',
122
+ }
123
+
36
124
  const SETTINGS_NS_NAME = 'russian-lang'
37
125
  // Namespace формы настроек: на 0.1.7+ это id записи загрузчика
38
126
  // ('dsh-russian-lang' из cordis.patch.yml), а не имя, выбранное плагином
@@ -71,6 +159,13 @@ window.__ModuleLoader__.load({
71
159
  }, 'dsh-russian-lang: ' + ns)
72
160
  }
73
161
 
162
+ // 1-en. Английский фолбэк собственной карточки (см. CARD_EN выше):
163
+ // пара ('russian-lang', 'en') никем не занята, конфликта с 'ru' нет.
164
+ ctx.effect(() => {
165
+ try { return ctx.locale.register(SETTINGS_NS_NAME, 'en', CARD_EN) }
166
+ catch (err) { return () => {} }
167
+ }, 'dsh-russian-lang: russian-lang-en')
168
+
74
169
  // 1a. Двухфазная быстрая загрузка и клиентский кэш словарей (CacheStorage / localStorage) (Issue #284, #285)
75
170
  const registerDictMap = (dictMap) => {
76
171
  if (!dictMap || typeof dictMap !== 'object') return
@@ -2575,7 +2670,7 @@ window.__ModuleLoader__.load({
2575
2670
  isInspectorActive = !isInspectorActive
2576
2671
  updateInspectorPill()
2577
2672
  },
2578
- }, isInspectorActive ? '🔍 Инспектор перевода (включён)' : '🔍 Включить инспектор перевода (Alt+I)'),
2673
+ }, isInspectorActive ? t('inspectorToggleOn') : t('inspectorToggleOff')),
2579
2674
  React.createElement('button', {
2580
2675
  type: 'button',
2581
2676
  className: 'rl-btn',
@@ -2583,16 +2678,16 @@ window.__ModuleLoader__.load({
2583
2678
  onClick: () => {
2584
2679
  try {
2585
2680
  navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
2586
- alert('Оверрайды скопированы в буфер обмена как JSON!')
2681
+ alert(t('overridesCopied'))
2587
2682
  } catch (err) { /* bestEffort */ void err; }
2588
2683
  }
2589
- }, '📤 Экспорт JSON'),
2684
+ }, t('exportJsonBtn')),
2590
2685
  React.createElement('button', {
2591
2686
  type: 'button',
2592
2687
  className: 'rl-btn',
2593
2688
  style: { fontSize: '11px' },
2594
2689
  onClick: () => {
2595
- const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt('Вставьте JSON с оверрайдами:') : null
2690
+ const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt(t('overridesImportPrompt')) : null
2596
2691
  if (!input) return
2597
2692
  try {
2598
2693
  const parsed = JSON.parse(input)
@@ -2600,9 +2695,9 @@ window.__ModuleLoader__.load({
2600
2695
  const merged = Object.assign({}, overrides, parsed)
2601
2696
  scope.set('overrides', merged)
2602
2697
  }
2603
- } catch (err) { alert('Ошибка разбора JSON: ' + (err && err.message || err)) }
2698
+ } catch (err) { alert(t('overridesImportError') + (err && err.message || err)) }
2604
2699
  }
2605
- }, '📥 Импорт JSON')
2700
+ }, t('importJsonBtn'))
2606
2701
  )
2607
2702
  ),
2608
2703
 
package/lib/pure.js CHANGED
@@ -371,15 +371,18 @@ export const typoQuotes = (text) => text
371
371
 
372
372
  export const typoDash = (text) => text
373
373
  .replace(/(^|[\s(\[«])--(?=\s|$)/g, '$1—')
374
- .replace(/(^|[\s(\[«])-(?=\s)/g, '$1—')
374
+ .replace(/(?<=[\p{L}\p{N}»\)]) - (?=[\p{L}\p{N}«\(])/gu, ' — ')
375
375
 
376
376
  export const typoPunct = (text) => text.replace(/\s+([,.:;!?])(?=\s|$)/g, '$1')
377
377
 
378
378
  export const TYPO_SHORT = new Set(['в', 'с', 'к', 'о', 'у', 'а', 'и', 'но', 'не', 'ни', 'на', 'по', 'до', 'из', 'за', 'от', 'об'])
379
379
 
380
- export const typoNbsp = (text) => text.replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (
381
- TYPO_SHORT.has(word) ? lead + word + ' ' : match
382
- ))
380
+ export const typoNbsp = (text) => text
381
+ .replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (
382
+ TYPO_SHORT.has(word) ? lead + word + ' ' : match
383
+ ))
384
+ .replace(/(\d) ([а-яё]{1,24})(?![а-яё])/giu, '$1 $2')
385
+ .replace(/(\d) ([%°₽$€£]|кб|КБ|МБ|ГБ)(?=\s|$)/giu, '$1 $2')
383
386
 
384
387
  // \b здесь не годится: он определён через \w = [A-Za-z0-9_], кириллица в \w не
385
388
  // входит, поэтому границы слова перед «в» в «все» просто нет и ни одна якорная
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-russian-lang",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
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",