@goodandready/dsh-russian-lang 0.3.14 → 0.3.16

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
@@ -1,9 +1,10 @@
1
1
  window.__ModuleLoader__.load({id: '@goodandready/dsh-russian-lang',factory: (require) => {var module = { exports: {} }
2
2
  var React = null
3
- try { React = require('react') } catch (e) { }
3
+ try { React = require('react') } catch (e) { void e; }
4
4
  let isInspectorActive = false
5
5
  let hoverBoxEl = null
6
6
  let pillEl = null
7
+ let updateInspectorPill = () => {}
7
8
  const numberFormat = new Intl.NumberFormat('ru-RU')
8
9
  const compactNumberFormat = new Intl.NumberFormat('ru-RU', { notation: 'compact', compactDisplay: 'short' })
9
10
  const relativeTimeFormat = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
@@ -394,16 +395,20 @@ const RU = {"common":{"back":"Назад","cancel":"Отмена","close":"За
394
395
  const ZH_RU = {}
395
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 ↗ ] на блоках сообщений."}
396
397
  const SETTINGS_NS_NAME = 'russian-lang'
397
- const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
398
+ const FORM_NS = 'dsh-russian-lang'
399
+ const LEGACY_NS = 'russian-lang'
400
+ const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#' + FORM_NS
398
401
  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 })
402
+ const configForms = ctx && ctx.configForms
403
+ const resolveScope = (c) => {try {if (c && c.configForms) return c.configForms.get(FORM_NS)
404
+ if (c && c.settingsScope) return c.settingsScope.bind({ namespace: LEGACY_NS })
401
405
  } catch (err) { void err }
402
406
  return null
403
407
  }
404
408
  const rawScope = resolveScope(ctx)
405
409
  const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
406
410
  }
411
+ const legacyFlagMode = !(ctx && ctx.configForms)
407
412
  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
413
  }
409
414
  const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
@@ -413,7 +418,7 @@ if (typeof dict !== 'object' || !dict) continue
413
418
  if (!RU[ns]) {RU[ns] = dict
414
419
  ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
415
420
  } else {Object.assign(RU[ns], dict)
416
- try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) {}}}}
421
+ try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) { void _; }}}}
417
422
  const CACHE_NAME = 'dsh-ru-cache-v1'
418
423
  const loadLocalDict = (key) => {try {const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
419
424
  return raw ? JSON.parse(raw) : null
@@ -498,13 +503,13 @@ return value && value.overrides ? value.overrides : {}} catch (err) { return {}
498
503
  const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
499
504
  const lookupChain = () => {try {const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
500
505
  if (Array.isArray(chain) && chain.length) return chain
501
- } catch (err) { }
506
+ } catch (err) { void err; }
502
507
  return [runtime.getLocale().active]
503
508
  }
504
509
  const translationRegistry = new Map()
505
510
  const lookup = (ns, key) => (runtime.lookup.length >= 3 ? runtime.lookup(ns, key, lookupChain()) : runtime.lookup(ns, key))
506
511
  const syncLang = () => {try {if (typeof document !== 'undefined' && document.documentElement && runtime.getLocale().active === 'ru') document.documentElement.lang = 'ru-RU'
507
- } catch (err) { }}
512
+ } catch (err) { void err; }}
508
513
  const RUNTIME_PROP_NAMES = ['plural','pluralForm','formatDate','formatCompactNumber','formatTokens','formatNumber','formatRelativeTime','formatCurrency','inflect','getPluginLocalizationStatus','stemRussian','fuzzyMatchRu','humanizeError']
509
514
  ctx.effect(() => {const origTranslate = runtime.translate
510
515
  const origProps = {}
@@ -539,8 +544,9 @@ return () => {runtime.translate = origTranslate
539
544
  for (const p of RUNTIME_PROP_NAMES) {if (origProps[p] !== undefined) runtime[p] = origProps[p]
540
545
  else delete runtime[p]
541
546
  }
542
- if (typeof removeLang === 'function') { try { removeLang() } catch (_) { } }}}, 'dsh-russian-lang: runtime-lifecycle')
543
- const syncFlag = () => {try {const wantRu = runtime.getLocale().active === 'ru'
547
+ if (typeof removeLang === 'function') { try { removeLang() } catch (_) { void _; } }}}, 'dsh-russian-lang: runtime-lifecycle')
548
+ const syncFlag = () => {if (!legacyFlagMode) return
549
+ try {const wantRu = runtime.getLocale().active === 'ru'
544
550
  const value = scope.getSnapshot().value || {}
545
551
  if (!!value.enabled !== wantRu) {const r = scope.set('enabled', wantRu)
546
552
  if (r && typeof r.catch === 'function') {r.catch((err) => {console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
@@ -554,7 +560,7 @@ runtime.setLocale('ru')
554
560
  let booted = false
555
561
  const tryBoot = () => {if (booted) return
556
562
  try {const value = scope.getSnapshot().value
557
- if (value && value.enabled === true) { booted = true; activate() }} catch (err) { }}
563
+ if (legacyFlagMode && value && value.enabled === true) { booted = true; activate() }} catch (err) { void err; }}
558
564
  ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
559
565
  tryBoot()
560
566
  const unsubscribeLang = runtime.subscribe(syncLang)
@@ -598,11 +604,11 @@ if (node.matches(EDITABLE)) spellOn(node)
598
604
  node.querySelectorAll ? node.querySelectorAll(EDITABLE).forEach(spellOn) : null
599
605
  }}})
600
606
  spellObserver.observe(document.body, { childList: true, subtree: true })
601
- } catch (err) { }}
607
+ } catch (err) { void err; }}
602
608
  const unsubscribeSpell = runtime.subscribe(syncSpell)
603
609
  ctx.effect(() => {return () => {unsubscribeSpell()
604
610
  if (spellObserver) spellObserver.disconnect()
605
- try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }}}, 'dsh-russian-lang: spellcheck')
611
+ try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { void err; }}}, 'dsh-russian-lang: spellcheck')
606
612
  syncSpell()
607
613
  const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
608
614
  const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
@@ -663,9 +669,10 @@ const trimmed = val.trim()
663
669
  if (curOverrides[trimmed] !== undefined) {node.nodeValue = val.replace(trimmed, curOverrides[trimmed])
664
670
  continue
665
671
  }
666
- if (trimmed.length >= 1 && ZH_CJK.test(val)) {hits.push(node)
667
- } else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {const p = node.parentElement
668
- if (p && !p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) {node.nodeValue = val.replace(trimmed, DOM_EN_TEXT[trimmed])
672
+ const p = node.parentElement
673
+ const isProtected = p && p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"], .chat-message, .markdown-body')
674
+ if (trimmed.length >= 1 && ZH_CJK.test(val)) {if (!isProtected) hits.push(node)
675
+ } else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {if (!isProtected) {node.nodeValue = val.replace(trimmed, DOM_EN_TEXT[trimmed])
669
676
  }}}
670
677
  for (const node of hits) {const next = zhTranslateText(node.nodeValue)
671
678
  if (next && next !== node.nodeValue) node.nodeValue = next
@@ -681,7 +688,7 @@ if (root.getAttribute) {for (const attr of ['title', 'placeholder', 'aria-label'
681
688
  if (v) {const tr = v.trim()
682
689
  if (DOM_EN_TEXT[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
683
690
  else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
684
- }}}} catch (err) { }}
691
+ }}}} catch (err) { void err; }}
685
692
  let zhObserver = null
686
693
  let zhDebounceTimer = null
687
694
  let zhWalking = false
@@ -711,7 +718,7 @@ document.addEventListener('pointerup', onInteraction, { passive: true })
711
718
  document.addEventListener('click', onInteraction, { passive: true })
712
719
  }
713
720
  queueZhWalk()
714
- } catch (err) { }}
721
+ } catch (err) { void err; }}
715
722
  const unsubscribeZh = runtime.subscribe(syncZhDom)
716
723
  ctx.effect(() => {return () => {unsubscribeZh()
717
724
  if (zhObserver) zhObserver.disconnect()
@@ -755,7 +762,7 @@ for (const root of typoPending) {if (root.nodeType === 3) typoNode(root, conf)
755
762
  else typoWalk(root, conf)
756
763
  }
757
764
  typoPending.clear()
758
- } catch (err) { }}
765
+ } catch (err) { void err; }}
759
766
  const typoPending = new Set()
760
767
  const queueTypo = (roots) => {for (const r of roots) typoPending.add(r)
761
768
  if (!typoQueued) typoQueued = requestAnimationFrame(flushTypo)
@@ -772,7 +779,7 @@ for (const record of records) {if (record.type === 'childList') {for (const adde
772
779
  if (roots.length > 0) queueTypo(roots)
773
780
  })
774
781
  typoObserver.observe(document.body, { childList: true, subtree: true })
775
- } catch (err) { }}
782
+ } catch (err) { void err; }}
776
783
  const unsubscribeTypo = runtime.subscribe(syncTypo)
777
784
  ctx.effect(() => {return () => {unsubscribeTypo()
778
785
  if (typoObserver) typoObserver.disconnect()
@@ -947,7 +954,7 @@ if (c) { layoutShowHint(el, c.converted, 'ru'); return }}
947
954
  if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {const c = layoutFixCandidate(value, 'cyr2lat')
948
955
  if (c) { layoutShowHint(el, c.converted, 'cmd'); return }}
949
956
  layoutDismiss()
950
- } catch (err) { }}
957
+ } catch (err) { void err; }}
951
958
  const unsubscribeLayout = runtime.subscribe(layoutOnInput)
952
959
  const layoutOnKeydown = (ev) => {const el = layoutCurrentInput(ev)
953
960
  if (!el) return
@@ -1034,7 +1041,7 @@ document.body.appendChild(hoverBoxEl)
1034
1041
  }
1035
1042
  return hoverBoxEl
1036
1043
  }
1037
- const updateInspectorPill = () => {if (typeof document === 'undefined') return
1044
+ updateInspectorPill = () => {if (typeof document === 'undefined') return
1038
1045
  if (!pillEl) {pillEl = document.createElement('div')
1039
1046
  pillEl.id = 'dsh-ru-inspector-pill'
1040
1047
  pillEl.className = 'rl-inspector-pill'
@@ -1047,7 +1054,7 @@ if (hoverBoxEl) hoverBoxEl.style.display = 'none'
1047
1054
  document.body.appendChild(pillEl)
1048
1055
  }
1049
1056
  pillEl.style.display = isInspectorActive ? 'flex' : 'none'
1050
- if (isInspectorActive) pillEl.classList.add('rl-inspector-pill-active')
1057
+ if (isInspectorActive && pillEl.classList) pillEl.classList.add('rl-inspector-pill-active')
1051
1058
  }
1052
1059
  const openInspectorModal = (meta, rawText, targetElement) => {if (typeof document === 'undefined') return
1053
1060
  if (hoverBoxEl) hoverBoxEl.style.display = 'none'
@@ -1122,7 +1129,7 @@ setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
1122
1129
  }}
1123
1130
  const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
1124
1131
  if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
1125
- ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.14' })
1132
+ ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.16' })
1126
1133
  : ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
1127
1134
  try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
1128
1135
  copyReportBtn.textContent = '✓ Скопировано'
@@ -1239,28 +1246,29 @@ if (!ctx.slots || !React) return
1239
1246
  const toggleRu = (wantRu) => {try {if (runtime.getLocale().active === wantRu) return
1240
1247
  runtime.setLocale(wantRu ? 'ru' : 'en')
1241
1248
  } catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }}
1242
- try {ctx.slots.inject('plugins.item', () =>
1243
- ctx.slots.register({name: 'plugins.item',id: SETTINGS_NS_NAME,order: 60,label: () => 'Russian language',locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
1249
+ const registerPluginsItem = () => {ctx.slots.inject('plugins.item', () =>
1250
+ ctx.slots.register({name: 'plugins.item',id: FORM_NS,order: 60,label: () => 'Russian language',locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
1251
+ }
1252
+ if (typeof configForms?.whileServed === 'function') {ctx.effect(() => configForms.whileServed([FORM_NS], registerPluginsItem), 'dsh-russian-lang: plugins.item')
1253
+ } else {registerPluginsItem()
1254
+ }
1244
1255
  ctx.slots.inject('plugins.row.config', () =>
1245
1256
  ctx.slots.register({name: 'plugins.row.config',key: ROW_CONFIG_KEY,locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
1246
- ctx.slots.inject('settings.plugin.item', () =>
1247
- ctx.slots.register({name: 'settings.plugin.item',key: SETTINGS_NS_NAME,locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
1248
- } catch (err) { console.warn('dsh-russian-lang: settings slot unavailable', err) }
1249
1257
  try {ctx.slots.inject('conversation.session.header.utilities', () =>
1250
1258
  ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-quick-switch',order: 100,locale: SETTINGS_NS_NAME,inject: () => ({ runtime, toggleRu }),}, QuickLangSwitch),)
1251
- } catch (err) { }
1259
+ } catch (err) { void err; }
1252
1260
  try {ctx.slots.inject('conversation.chat.assistant-actions', () =>
1253
1261
  ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian-lang-translate-action',order: 50,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, TranslateTurnAction),)
1254
- } catch (err) { }
1262
+ } catch (err) { void err; }
1255
1263
  try {ctx.slots.inject('conversation.session.header.utilities', () =>
1256
1264
  ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
1257
- } catch (err) { }}
1265
+ } catch (err) { void err; }}
1258
1266
  function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
1259
1267
  const runtime = props.runtime || inj.runtime
1260
1268
  const toggleRu = props.toggleRu || inj.toggleRu
1261
1269
  const [locale, setLocaleState] = React.useState(runtime ? (runtime.getLocale().active || 'en') : 'ru')
1262
1270
  React.useEffect(() => {if (!runtime) return
1263
- return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }})
1271
+ return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { void e; }})
1264
1272
  }, [runtime])
1265
1273
  const isRu = locale === 'ru'
1266
1274
  return React.createElement('button', {type: 'button',className: 'rl-lang-chip' + (isRu ? ' rl-lang-chip-active' : ''),title: isRu ? 'Интерфейс: Русский (нажмите для переключения на EN)' : 'Interface: English (click for RU)',onClick: () => { if (toggleRu) toggleRu(!isRu) },}, React.createElement('span', { className: 'rl-lang-text' }, isRu ? 'RU' : 'EN'))
@@ -1409,7 +1417,7 @@ const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope
1409
1417
  const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
1410
1418
  const [typo, setTypoState] = React.useState(() =>
1411
1419
  (snap.value && snap.value.typography) || {})
1412
- const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.14',latestVersion: undefined,updateAvailable: false
1420
+ const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.16',latestVersion: undefined,updateAvailable: false
1413
1421
  })
1414
1422
  const [upLoading, setUpLoading] = React.useState(false)
1415
1423
  const [upMsg, setUpMsg] = React.useState(null)
@@ -1457,7 +1465,7 @@ if (s.value && s.value.typography) setTypoState(s.value.typography)
1457
1465
  setSnap(scope.getSnapshot())
1458
1466
  return un
1459
1467
  }, [scope])
1460
- React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }})
1468
+ React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { void e; }})
1461
1469
  return un
1462
1470
  } catch (e) { return undefined }}, [runtime])
1463
1471
  const status = snap.status || 'loading'
@@ -1502,7 +1510,7 @@ scope.set('agentPromptPreset', val)
1502
1510
  } 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
1503
1511
  ) : null
1504
1512
  ) : null
1505
- ),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.14')),upStatus.updateAvailable
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.16')),upStatus.updateAvailable
1506
1514
  ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
1507
1515
  : React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
1508
1516
  ? React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
@@ -1535,12 +1543,12 @@ try { scope.set('overrides', next) } catch (_e) { void _e }},}, t('overrideDelet
1535
1543
  updateInspectorPill()
1536
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))
1537
1545
  alert('Оверрайды скопированы в буфер обмена как JSON!')
1538
- } catch (err) { void err; }}}, '📤 Экспорт JSON'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = prompt('Вставьте 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
1539
1547
  if (!input) return
1540
1548
  try {const parsed = JSON.parse(input)
1541
1549
  if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
1542
1550
  scope.set('overrides', merged)
1543
- }} 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.14'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
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.16'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1544
1552
  }, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
1545
1553
  }
1546
1554
  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)}'
@@ -15,11 +15,12 @@ window.__ModuleLoader__.load({
15
15
  factory: (require) => {
16
16
  var module = { exports: {} }
17
17
  var React = null
18
- try { React = require('react') } catch (e) { /* карточка настроек необязательна */ }
18
+ try { React = require('react') } catch (e) { /* карточка настроек необязательна */ void e; }
19
19
 
20
20
  let isInspectorActive = false
21
21
  let hoverBoxEl = null
22
22
  let pillEl = null
23
+ let updateInspectorPill = () => {}
23
24
 
24
25
  //__PURE_JS__
25
26
 
@@ -33,15 +34,21 @@ window.__ModuleLoader__.load({
33
34
  RU['russian-lang'] = /*__CARD_RU__*/{}
34
35
 
35
36
  const SETTINGS_NS_NAME = 'russian-lang'
37
+ // Namespace формы настроек: на 0.1.7+ это id записи загрузчика
38
+ // ('dsh-russian-lang' из cordis.patch.yml), а не имя, выбранное плагином
39
+ // ('russian-lang'), как было на 0.1.6 и ниже. Это разные вещи.
40
+ const FORM_NS = 'dsh-russian-lang'
41
+ const LEGACY_NS = 'russian-lang'
36
42
  // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>'.
37
- const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
43
+ const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#' + FORM_NS
38
44
 
39
45
  function apply(ctx) {
40
46
  const runtime = ctx.locale
47
+ const configForms = ctx && ctx.configForms
41
48
  const resolveScope = (c) => {
42
49
  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 })
50
+ if (c && c.configForms) return c.configForms.get(FORM_NS)
51
+ if (c && c.settingsScope) return c.settingsScope.bind({ namespace: LEGACY_NS })
45
52
  } catch (err) { void err }
46
53
  return null
47
54
  }
@@ -51,6 +58,10 @@ window.__ModuleLoader__.load({
51
58
  subscribe: () => () => {},
52
59
  set: () => Promise.resolve()
53
60
  }
61
+ // На 0.1.7+ выбор языка хранит сам DSH (locale.preference): собственный
62
+ // флаг там не читаем и не пишем, виджет работает просто с локалью.
63
+ // На 0.1.6 и ниже поведение прежнее — флаг живёт в настройках плагина.
64
+ const legacyFlagMode = !(ctx && ctx.configForms)
54
65
 
55
66
  // 1. Словари ядра DSH: каждый namespace — свой эффект, снимается вместе с плагином
56
67
  for (const ns of Object.keys(RU)) {
@@ -75,7 +86,7 @@ window.__ModuleLoader__.load({
75
86
  }, 'dsh-russian-lang: ' + ns)
76
87
  } else {
77
88
  Object.assign(RU[ns], dict)
78
- try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) {}
89
+ try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) { /* bestEffort */ void _; }
79
90
  }
80
91
  }
81
92
  }
@@ -239,7 +250,7 @@ window.__ModuleLoader__.load({
239
250
  try {
240
251
  const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
241
252
  if (Array.isArray(chain) && chain.length) return chain
242
- } catch (err) { /* ignore */ }
253
+ } catch (err) { /* ignore */ void err; }
243
254
  return [runtime.getLocale().active]
244
255
  }
245
256
  const translationRegistry = new Map()
@@ -247,7 +258,7 @@ window.__ModuleLoader__.load({
247
258
  const syncLang = () => {
248
259
  try {
249
260
  if (typeof document !== 'undefined' && document.documentElement && runtime.getLocale().active === 'ru') document.documentElement.lang = 'ru-RU'
250
- } catch (err) { /* ignore */ }
261
+ } catch (err) { /* ignore */ void err; }
251
262
  }
252
263
  const RUNTIME_PROP_NAMES = ['plural','pluralForm','formatDate','formatCompactNumber','formatTokens','formatNumber','formatRelativeTime','formatCurrency','inflect','getPluginLocalizationStatus','stemRussian','fuzzyMatchRu','humanizeError']
253
264
  ctx.effect(() => {
@@ -296,7 +307,7 @@ window.__ModuleLoader__.load({
296
307
  if (origProps[p] !== undefined) runtime[p] = origProps[p]
297
308
  else delete runtime[p]
298
309
  }
299
- if (typeof removeLang === 'function') { try { removeLang() } catch (_) { /* bestEffort */ } }
310
+ if (typeof removeLang === 'function') { try { removeLang() } catch (_) { /* bestEffort */ void _; } }
300
311
  }
301
312
  }, 'dsh-russian-lang: runtime-lifecycle')
302
313
 
@@ -305,6 +316,7 @@ window.__ModuleLoader__.load({
305
316
  // scope.set возвращает Promise<void> в v0.1.2-alpha.2, ошибка приходит
306
317
  // через rejection и try/catch её не ловит — обрабатываем оба канала.
307
318
  const syncFlag = () => {
319
+ if (!legacyFlagMode) return
308
320
  try {
309
321
  const wantRu = runtime.getLocale().active === 'ru'
310
322
  const value = scope.getSnapshot().value || {}
@@ -338,8 +350,8 @@ window.__ModuleLoader__.load({
338
350
  if (booted) return
339
351
  try {
340
352
  const value = scope.getSnapshot().value
341
- if (value && value.enabled === true) { booted = true; activate() }
342
- } catch (err) { /* ignore */ }
353
+ if (legacyFlagMode && value && value.enabled === true) { booted = true; activate() }
354
+ } catch (err) { /* ignore */ void err; }
343
355
  }
344
356
  ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
345
357
  tryBoot()
@@ -408,14 +420,14 @@ window.__ModuleLoader__.load({
408
420
  }
409
421
  })
410
422
  spellObserver.observe(document.body, { childList: true, subtree: true })
411
- } catch (err) { /* ignore */ }
423
+ } catch (err) { /* ignore */ void err; }
412
424
  }
413
425
  const unsubscribeSpell = runtime.subscribe(syncSpell)
414
426
  ctx.effect(() => {
415
427
  return () => {
416
428
  unsubscribeSpell()
417
429
  if (spellObserver) spellObserver.disconnect()
418
- try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { /* ignore */ }
430
+ try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { /* ignore */ void err; }
419
431
  }
420
432
  }, 'dsh-russian-lang: spellcheck')
421
433
  syncSpell()
@@ -785,11 +797,12 @@ window.__ModuleLoader__.load({
785
797
  node.nodeValue = val.replace(trimmed, curOverrides[trimmed])
786
798
  continue
787
799
  }
800
+ const p = node.parentElement
801
+ const isProtected = p && p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"], .chat-message, .markdown-body')
788
802
  if (trimmed.length >= 1 && ZH_CJK.test(val)) {
789
- hits.push(node)
803
+ if (!isProtected) hits.push(node)
790
804
  } else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {
791
- const p = node.parentElement
792
- if (p && !p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) {
805
+ if (!isProtected) {
793
806
  node.nodeValue = val.replace(trimmed, DOM_EN_TEXT[trimmed])
794
807
  }
795
808
  }
@@ -824,7 +837,7 @@ window.__ModuleLoader__.load({
824
837
  }
825
838
  }
826
839
  }
827
- } catch (err) { /* ignore */ }
840
+ } catch (err) { /* ignore */ void err; }
828
841
  }
829
842
 
830
843
  let zhObserver = null
@@ -876,7 +889,7 @@ window.__ModuleLoader__.load({
876
889
  document.addEventListener('click', onInteraction, { passive: true })
877
890
  }
878
891
  queueZhWalk()
879
- } catch (err) { /* ignore */ }
892
+ } catch (err) { /* ignore */ void err; }
880
893
  }
881
894
  const unsubscribeZh = runtime.subscribe(syncZhDom)
882
895
  ctx.effect(() => {
@@ -955,7 +968,7 @@ window.__ModuleLoader__.load({
955
968
  else typoWalk(root, conf)
956
969
  }
957
970
  typoPending.clear()
958
- } catch (err) { /* ignore */ }
971
+ } catch (err) { /* ignore */ void err; }
959
972
  }
960
973
  const typoPending = new Set()
961
974
  const queueTypo = (roots) => {
@@ -983,7 +996,7 @@ window.__ModuleLoader__.load({
983
996
  if (roots.length > 0) queueTypo(roots)
984
997
  })
985
998
  typoObserver.observe(document.body, { childList: true, subtree: true })
986
- } catch (err) { /* ignore */ }
999
+ } catch (err) { /* ignore */ void err; }
987
1000
  }
988
1001
  const unsubscribeTypo = runtime.subscribe(syncTypo)
989
1002
  ctx.effect(() => {
@@ -1264,7 +1277,7 @@ window.__ModuleLoader__.load({
1264
1277
  if (c) { layoutShowHint(el, c.converted, 'cmd'); return }
1265
1278
  }
1266
1279
  layoutDismiss()
1267
- } catch (err) { /* ignore */ }
1280
+ } catch (err) { /* ignore */ void err; }
1268
1281
  }
1269
1282
  const unsubscribeLayout = runtime.subscribe(layoutOnInput)
1270
1283
 
@@ -1428,7 +1441,7 @@ window.__ModuleLoader__.load({
1428
1441
  return hoverBoxEl
1429
1442
  }
1430
1443
 
1431
- const updateInspectorPill = () => {
1444
+ updateInspectorPill = () => {
1432
1445
  if (typeof document === 'undefined') return
1433
1446
  if (!pillEl) {
1434
1447
  pillEl = document.createElement('div')
@@ -1444,7 +1457,7 @@ window.__ModuleLoader__.load({
1444
1457
  document.body.appendChild(pillEl)
1445
1458
  }
1446
1459
  pillEl.style.display = isInspectorActive ? 'flex' : 'none'
1447
- if (isInspectorActive) pillEl.classList.add('rl-inspector-pill-active')
1460
+ if (isInspectorActive && pillEl.classList) pillEl.classList.add('rl-inspector-pill-active')
1448
1461
  }
1449
1462
 
1450
1463
  const openInspectorModal = (meta, rawText, targetElement) => {
@@ -1724,34 +1737,37 @@ window.__ModuleLoader__.load({
1724
1737
  // ядро (0.1.6-alpha.2) рендерит как страницу плагина с настройками. label —
1725
1738
  // статичная строка: он резолвится во время рендера страницы, и обращение к
1726
1739
  // локали там роняет весь клиентский батч. Прежние посадки сохранены фолбэками.
1727
- try {
1740
+ // Посадочные места карточки настроек плагина.
1741
+ // DSH 0.2.0-rc.1:
1742
+ // - plugins.item (#388): id = FORM_NS ('dsh-russian-lang'), монтируется через whileServed([FORM_NS])
1743
+ // - plugins.row.config (#387): key = '@goodandready/dsh-russian-lang#' + FORM_NS
1744
+ // - settings-plugin-item (#389): устаревший слот упразднен в DSH 0.2.0-rc.1, удален
1745
+ const registerPluginsItem = () => {
1728
1746
  ctx.slots.inject('plugins.item', () =>
1729
1747
  ctx.slots.register({
1730
1748
  name: 'plugins.item',
1731
- id: SETTINGS_NS_NAME,
1749
+ id: FORM_NS,
1732
1750
  order: 60,
1733
1751
  label: () => 'Russian language',
1734
1752
  locale: SETTINGS_NS_NAME,
1735
1753
  inject: () => ({ scope, runtime, toggleRu }),
1736
1754
  }, SettingsCard),
1737
1755
  )
1738
- ctx.slots.inject('plugins.row.config', () =>
1739
- ctx.slots.register({
1740
- name: 'plugins.row.config',
1741
- key: ROW_CONFIG_KEY,
1742
- locale: SETTINGS_NS_NAME,
1743
- inject: () => ({ scope, runtime, toggleRu }),
1744
- }, SettingsCard),
1745
- )
1746
- ctx.slots.inject('settings.plugin.item', () =>
1747
- ctx.slots.register({
1748
- name: 'settings.plugin.item',
1749
- key: SETTINGS_NS_NAME,
1750
- locale: SETTINGS_NS_NAME,
1751
- inject: () => ({ scope, runtime, toggleRu }),
1752
- }, SettingsCard),
1753
- )
1754
- } catch (err) { console.warn('dsh-russian-lang: settings slot unavailable', err) }
1756
+ }
1757
+ if (typeof configForms?.whileServed === 'function') {
1758
+ ctx.effect(() => configForms.whileServed([FORM_NS], registerPluginsItem), 'dsh-russian-lang: plugins.item')
1759
+ } else {
1760
+ registerPluginsItem()
1761
+ }
1762
+
1763
+ ctx.slots.inject('plugins.row.config', () =>
1764
+ ctx.slots.register({
1765
+ name: 'plugins.row.config',
1766
+ key: ROW_CONFIG_KEY,
1767
+ locale: SETTINGS_NS_NAME,
1768
+ inject: () => ({ scope, runtime, toggleRu }),
1769
+ }, SettingsCard),
1770
+ )
1755
1771
 
1756
1772
  // #158: Быстрый переключатель RU ⇄ EN в шапке сессии
1757
1773
  try {
@@ -1764,7 +1780,7 @@ window.__ModuleLoader__.load({
1764
1780
  inject: () => ({ runtime, toggleRu }),
1765
1781
  }, QuickLangSwitch),
1766
1782
  )
1767
- } catch (err) { /* ignore if slot not declared */ }
1783
+ } catch (err) { /* ignore if slot not declared */ void err; }
1768
1784
 
1769
1785
  // #158: Кнопка перевода реплики на русский в действиях ассистента
1770
1786
  try {
@@ -1777,7 +1793,7 @@ window.__ModuleLoader__.load({
1777
1793
  inject: () => ({ runtime }),
1778
1794
  }, TranslateTurnAction),
1779
1795
  )
1780
- } catch (err) { /* ignore if slot not declared */ }
1796
+ } catch (err) { /* ignore if slot not declared */ void err; }
1781
1797
 
1782
1798
  // #158: Экспорт сессии в Markdown
1783
1799
  try {
@@ -1790,7 +1806,7 @@ window.__ModuleLoader__.load({
1790
1806
  inject: () => ({ runtime }),
1791
1807
  }, ExportMarkdownButton),
1792
1808
  )
1793
- } catch (err) { /* ignore if slot not declared */ }
1809
+ } catch (err) { /* ignore if slot not declared */ void err; }
1794
1810
  }
1795
1811
 
1796
1812
  // Карточка настроек: React-компонент вне apply.
@@ -1810,7 +1826,7 @@ window.__ModuleLoader__.load({
1810
1826
  React.useEffect(() => {
1811
1827
  if (!runtime) return
1812
1828
  return runtime.subscribe(() => {
1813
- try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { /* ignore */ }
1829
+ try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { /* ignore */ void e; }
1814
1830
  })
1815
1831
  }, [runtime])
1816
1832
  const isRu = locale === 'ru'
@@ -2156,7 +2172,7 @@ window.__ModuleLoader__.load({
2156
2172
  React.useEffect(() => {
2157
2173
  try {
2158
2174
  const un = runtime.subscribe(() => {
2159
- try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { /* ignore */ }
2175
+ try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { /* ignore */ void e; }
2160
2176
  })
2161
2177
  return un
2162
2178
  } catch (e) { return undefined }
@@ -2576,7 +2592,7 @@ window.__ModuleLoader__.load({
2576
2592
  className: 'rl-btn',
2577
2593
  style: { fontSize: '11px' },
2578
2594
  onClick: () => {
2579
- const input = prompt('Вставьте JSON с оверрайдами:')
2595
+ const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt('Вставьте JSON с оверрайдами:') : null
2580
2596
  if (!input) return
2581
2597
  try {
2582
2598
  const parsed = JSON.parse(input)
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 }
package/lib/locales.js CHANGED
@@ -79,7 +79,7 @@ export function getAllDictionaries() {
79
79
  export function getZhRuMap() {
80
80
  if (existsSync(ZH_RU_FILE)) {
81
81
  try {
82
- return JSON.parse(readFileSync(ZG_RU_FILE, "utf8"))
82
+ return JSON.parse(readFileSync(ZH_RU_FILE, "utf8"))
83
83
  } catch (_) { /* bestEffort */ void _; }
84
84
  }
85
85
  return {}
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.14",
3
+ "version": "0.3.16",
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",