@goodandready/dsh-russian-lang 0.3.28 → 0.3.30

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
@@ -5,7 +5,7 @@
5
5
  <h3>Полная русская локализация, умная типографика и исправление раскладки клавиатуры для DeepSeek Harness</h3>
6
6
 
7
7
  <p align="center">
8
- <a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.3.28-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.3.30-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
10
  <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
11
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
@@ -189,7 +189,7 @@ dsh-russian-lang:
189
189
  slashAliases: true # Русские алиасы слэш-команд (/помощь, /очистить) (по умолчанию: true)
190
190
  layoutConversion: true # Детектор неверной раскладки и подсказка Alt+L (по умолчанию: true)
191
191
  quickSwitch: true # Быстрый переключатель RU/EN в статусной строке (по умолчанию: true)
192
- translateEngine: local # Движок перевода реплик: local (LibreTranslate) или cloud (Google) (по умолчанию: local)
192
+ translateEngine: off # Движок перевода реплик: off, local (LibreTranslate) или google (Google) (по умолчанию: off)
193
193
  localApiUrl: http://localhost:5000 # URL локального инстанса LibreTranslate (по умолчанию: http://localhost:5000)
194
194
 
195
195
  # Легаси-формат (DSH <= 0.1.6, поддерживается автоматически как fallback):
package/bin/dsh-i18n.mjs CHANGED
@@ -54,9 +54,10 @@ function extractFromCode(code, result, defaultNs = 'common') {
54
54
  return idx
55
55
  }
56
56
 
57
- function readString(idx) {
58
- const quote = code[idx]
59
- if (quote !== "'" && quote !== '"' && quote !== '`') return null
57
+ // Parse static string literal: '...', "...", or static `...` (without ${...})
58
+ function readStaticString(idx) {
59
+ const q = code[idx]
60
+ if (q !== "'" && q !== '"' && q !== '`') return null
60
61
  let s = ''
61
62
  idx++
62
63
  while (idx < len) {
@@ -68,7 +69,11 @@ function extractFromCode(code, result, defaultNs = 'common') {
68
69
  continue
69
70
  }
70
71
  }
71
- if (ch === quote) {
72
+ if (q === '`' && ch === '$' && code[idx + 1] === '{') {
73
+ // Dynamic template string: non-literal, do not extract
74
+ return null
75
+ }
76
+ if (ch === q) {
72
77
  return { val: s, next: idx + 1 }
73
78
  }
74
79
  s += ch
@@ -77,48 +82,165 @@ function extractFromCode(code, result, defaultNs = 'common') {
77
82
  return null
78
83
  }
79
84
 
85
+ let lastToken = ''
86
+ let hadNewline = true
87
+ const stack = []
88
+
80
89
  while (i < len) {
81
90
  const ch = code[i]
82
91
 
92
+ if (ch === '\n') {
93
+ hadNewline = true
94
+ i++
95
+ continue
96
+ }
97
+
98
+ // Inside template literal (outside ${...})
99
+ if (stack.length > 0 && stack[stack.length - 1].type === 'TEMPLATE') {
100
+ if (ch === '\\') {
101
+ i += 2
102
+ continue
103
+ }
104
+ if (ch === '$' && code[i + 1] === '{') {
105
+ i += 2
106
+ stack.push({ type: 'EXPR', braceDepth: 1 })
107
+ lastToken = '${'
108
+ hadNewline = false
109
+ continue
110
+ }
111
+ if (ch === '`') {
112
+ i++
113
+ stack.pop()
114
+ lastToken = '`'
115
+ hadNewline = false
116
+ continue
117
+ }
118
+ i++
119
+ continue
120
+ }
121
+
83
122
  // Single-line comment
84
123
  if (ch === '/' && code[i + 1] === '/') {
85
124
  i += 2
86
125
  while (i < len && code[i] !== '\n') i++
126
+ hadNewline = true
87
127
  continue
88
128
  }
89
129
 
90
130
  // Multi-line comment
91
131
  if (ch === '/' && code[i + 1] === '*') {
92
132
  i += 2
93
- while (i < len && !(code[i] === '*' && code[i + 1] === '/')) i++
133
+ while (i < len && !(code[i] === '*' && code[i + 1] === '/')) {
134
+ if (code[i] === '\n') hadNewline = true
135
+ i++
136
+ }
94
137
  i += 2
95
138
  continue
96
139
  }
97
140
 
98
- // String literals in normal code
99
- if (ch === "'" || ch === '"' || ch === '`') {
100
- const strRes = readString(i)
101
- if (strRes) {
102
- i = strRes.next
103
- continue
141
+ // Regular expression literal
142
+ if (ch === '/') {
143
+ const isRegex = !lastToken || hadNewline ||
144
+ /^[(=:,;!&|?{}[\]~^%*+/<>-]$/.test(lastToken) ||
145
+ /^(return|throw|case|yield|await|typeof|void|delete)$/.test(lastToken)
146
+ if (isRegex) {
147
+ let j = i + 1
148
+ let inClass = false
149
+ while (j < len) {
150
+ if (code[j] === '\\') {
151
+ j += 2
152
+ continue
153
+ }
154
+ if (code[j] === '[') inClass = true
155
+ else if (code[j] === ']') inClass = false
156
+ else if (code[j] === '/' && !inClass) {
157
+ j++
158
+ while (j < len && /[a-z]/i.test(code[j])) j++
159
+ i = j
160
+ lastToken = 'regex'
161
+ hadNewline = false
162
+ break
163
+ } else if (code[j] === '\n') {
164
+ break
165
+ }
166
+ j++
167
+ }
168
+ if (lastToken === 'regex') continue
104
169
  }
105
170
  }
106
171
 
107
- // Check for word boundary identifier
172
+ // Quoted strings '...' or "..."
173
+ if (ch === "'" || ch === '"') {
174
+ const q = ch
175
+ let j = i + 1
176
+ while (j < len) {
177
+ if (code[j] === '\\') {
178
+ j += 2
179
+ continue
180
+ }
181
+ if (code[j] === q) {
182
+ j++
183
+ break
184
+ }
185
+ j++
186
+ }
187
+ i = j
188
+ lastToken = 'string'
189
+ hadNewline = false
190
+ continue
191
+ }
192
+
193
+ // Start of template literal `...`
194
+ if (ch === '`') {
195
+ i++
196
+ stack.push({ type: 'TEMPLATE' })
197
+ lastToken = '`'
198
+ hadNewline = false
199
+ continue
200
+ }
201
+
202
+ // Curly braces inside ${...}
203
+ if (ch === '{') {
204
+ if (stack.length > 0 && stack[stack.length - 1].type === 'EXPR') {
205
+ stack[stack.length - 1].braceDepth++
206
+ }
207
+ lastToken = '{'
208
+ hadNewline = false
209
+ i++
210
+ continue
211
+ }
212
+ if (ch === '}') {
213
+ if (stack.length > 0 && stack[stack.length - 1].type === 'EXPR') {
214
+ stack[stack.length - 1].braceDepth--
215
+ if (stack[stack.length - 1].braceDepth === 0) {
216
+ stack.pop() // Return to TEMPLATE
217
+ lastToken = '}'
218
+ hadNewline = false
219
+ i++
220
+ continue
221
+ }
222
+ }
223
+ lastToken = '}'
224
+ hadNewline = false
225
+ i++
226
+ continue
227
+ }
228
+
229
+ // Identifier or method call
108
230
  const prevChar = i > 0 ? code[i - 1] : ' '
109
- const isWordStart = !/[a-zA-Z0-9_$.]/.test(prevChar)
231
+ const isIdentStart = !/[a-zA-Z0-9_$]/.test(prevChar)
110
232
 
111
- if (isWordStart) {
233
+ if (isIdentStart) {
112
234
  if (code.startsWith('translate', i) && !/[a-zA-Z0-9_$]/.test(code[i + 9] || '')) {
113
235
  let j = skipWhitespace(i + 9)
114
236
  if (code[j] === '(') {
115
237
  j = skipWhitespace(j + 1)
116
- const nsStr = readString(j)
238
+ const nsStr = readStaticString(j)
117
239
  if (nsStr) {
118
240
  j = skipWhitespace(nsStr.next)
119
241
  if (code[j] === ',') {
120
242
  j = skipWhitespace(j + 1)
121
- const keyStr = readString(j)
243
+ const keyStr = readStaticString(j)
122
244
  if (keyStr) {
123
245
  j = skipWhitespace(keyStr.next)
124
246
  if (code[j] === ',' || code[j] === ')') {
@@ -126,7 +248,9 @@ function extractFromCode(code, result, defaultNs = 'common') {
126
248
  const key = keyStr.val
127
249
  if (!result[ns]) result[ns] = {}
128
250
  result[ns][key] = result[ns][key] || ''
129
- i = j
251
+ i = keyStr.next
252
+ lastToken = ')'
253
+ hadNewline = false
130
254
  continue
131
255
  }
132
256
  }
@@ -137,12 +261,14 @@ function extractFromCode(code, result, defaultNs = 'common') {
137
261
  let j = skipWhitespace(i + 1)
138
262
  if (code[j] === '(') {
139
263
  j = skipWhitespace(j + 1)
140
- const keyStr = readString(j)
264
+ const keyStr = readStaticString(j)
141
265
  if (keyStr) {
142
266
  j = skipWhitespace(keyStr.next)
143
267
  if (code[j] === ',' || code[j] === ')') {
144
268
  result[defaultNs][keyStr.val] = result[defaultNs][keyStr.val] || ''
145
- i = j
269
+ i = keyStr.next
270
+ lastToken = ')'
271
+ hadNewline = false
146
272
  continue
147
273
  }
148
274
  }
@@ -150,6 +276,20 @@ function extractFromCode(code, result, defaultNs = 'common') {
150
276
  }
151
277
  }
152
278
 
279
+ if (!/\s/.test(ch)) {
280
+ if (/[a-zA-Z0-9_$]/.test(ch)) {
281
+ let j = i
282
+ while (j < len && /[a-zA-Z0-9_$]/.test(code[j])) j++
283
+ lastToken = code.slice(i, j)
284
+ hadNewline = false
285
+ i = j
286
+ continue
287
+ } else {
288
+ lastToken = ch
289
+ hadNewline = false
290
+ }
291
+ }
292
+
153
293
  i++
154
294
  }
155
295
  }
package/lib/client.js CHANGED
@@ -395,8 +395,8 @@ return results.sort((a, b) => b.score - a.score).map((r) => r.item)
395
395
  }
396
396
  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":"Магазин плагинов"}}
397
397
  const ZH_RU = {}
398
- RU['russian-lang'] = {"cardTitle":"Русская локализация","cardSub":"Язык интерфейса, типографика, раскладка","badgeRu":"🟢 RU активен","badgeEn":"⚪ EN активен","badgeCoverage":"🟢 100% (7,902 ключа)","badgeSmartUx":"⚡ Smart UX активен","badgeSmartUxOff":"Smart UX выключен","secLanguage":"🌐 Язык интерфейса","secLanguageDesc":"Нативное переключение языка интерфейса DSH на русский без перезагрузки страницы.","enabled":"Русский язык включён","enabledDesc":"Переключает язык интерфейса DeepSeek Harness на русский.","quickSwitchNote":"Быстрый переключатель RU ⇄ EN доступен в шапке сессии рядом с кнопками диалога.","quickSwitch":"Быстрый переключатель языка","quickSwitchDesc":"Показывать переключатель RU ⇄ EN в шапке сессии.","secTypography":"✍️ Умная типографика и ввод (Smart UX)","secTypographyDesc":"Нормы русской типографики во время диалога и набора.","typography":"Типографика вывода","typographyDesc":"Кавычки «», тире — вместо дефисов, неразрывные пробелы.","yo":"Буква «ё»","yoDesc":"Восстановление «ё» в частых словах (ещё, идёт и др.).","liveInput":"Живая типографика инпута","liveInputDesc":"Кавычки «», тире — и неразрывные пробелы перед отправкой.","slashAliases":"Русские алиасы команд","slashAliasesDesc":"Команды: /цель, /сжать, /план, /справка, /память.","altLHintText":"Подсказки и конвертация раскладки","layoutConversionDesc":"Подсказки при вводе; ⌨ Alt+L и значок RU/EN конвертируют текст текущего поля (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: "}
399
- 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',badgeSmartUxOff: 'Smart UX off',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.',quickSwitch: 'Quick language switch',quickSwitchDesc: 'Show the RU ⇄ EN switch in the session header.',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: 'Keyboard layout hints and conversion',layoutConversionDesc: 'Hints while typing; ⌨ Alt+L and the RU/EN badge convert 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.',}
398
+ RU['russian-lang'] = {"cardTitle":"Русская локализация","cardSub":"Язык интерфейса, типографика, раскладка","badgeRu":"🟢 RU активен","badgeEn":"⚪ EN активен","badgeCoverage":"🟢 100% (8 584 ключа)","badgeSmartUx":"⚡ Smart UX активен","badgeSmartUxOff":"Smart UX выключен","secLanguage":"🌐 Язык интерфейса","secLanguageDesc":"Нативное переключение языка интерфейса DSH на русский без перезагрузки страницы.","enabled":"Русский язык включён","enabledDesc":"Переключает язык интерфейса DeepSeek Harness на русский.","quickSwitchNote":"Быстрый переключатель RU ⇄ EN доступен в шапке сессии рядом с кнопками диалога.","quickSwitch":"Быстрый переключатель языка","quickSwitchDesc":"Показывать переключатель RU ⇄ EN в шапке сессии.","secTypography":"✍️ Умная типографика и ввод (Smart UX)","secTypographyDesc":"Нормы русской типографики во время диалога и набора.","typography":"Типографика вывода","typographyDesc":"Кавычки «», тире — вместо дефисов, неразрывные пробелы.","yo":"Буква «ё»","yoDesc":"Восстановление «ё» в частых словах (ещё, идёт и др.).","liveInput":"Живая типографика инпута","liveInputDesc":"Кавычки «», тире — и неразрывные пробелы перед отправкой.","slashAliases":"Русские алиасы команд","slashAliasesDesc":"Команды: /цель, /сжать, /план, /справка, /память.","altLHintText":"Подсказки и конвертация раскладки","layoutConversionDesc":"Подсказки при вводе; ⌨ Alt+L и значок RU/EN конвертируют текст текущего поля (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: "}
399
+ const CARD_EN = {cardTitle: 'Russian localization',cardSub: 'Interface language, typography, keyboard layout',badgeRu: '🟢 RU active',badgeEn: '⚪ EN active',badgeCoverage: '🟢 100% (8,584 keys)',badgeSmartUx: '⚡ Smart UX active',badgeSmartUxOff: 'Smart UX off',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.',quickSwitch: 'Quick language switch',quickSwitchDesc: 'Show the RU ⇄ EN switch in the session header.',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: 'Keyboard layout hints and conversion',layoutConversionDesc: 'Hints while typing; ⌨ Alt+L and the RU/EN badge convert 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.',}
400
400
  const SETTINGS_NS_NAME = 'russian-lang'
401
401
  const FORM_NS = 'dsh-russian-lang'
402
402
  const LEGACY_NS = 'russian-lang'
@@ -415,15 +415,23 @@ const rawScope = resolveScope(ctx)
415
415
  const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
416
416
  }
417
417
  const legacyFlagMode = !(ctx && ctx.configForms)
418
- 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)
419
- }
420
- ctx.effect(() => {try { return ctx.locale.register(SETTINGS_NS_NAME, 'en', CARD_EN) } catch (err) { return () => {} }}, 'dsh-russian-lang: russian-lang-en')
421
- const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
422
- for (const ns of Object.keys(dictMap)) {if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
418
+ let disposed = false
419
+ let corePreloadTimer = null
420
+ let pluginBatchTimer = null
421
+ let zhDebounceTimer = null
422
+ for (const ns of Object.keys(RU)) {ctx.effect(() => {if (disposed) return () => {}
423
+ try { return ctx.locale.register(ns, 'ru', RU[ns]) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
424
+ }
425
+ ctx.effect(() => {if (disposed) return () => {}
426
+ try { return ctx.locale.register(SETTINGS_NS_NAME, 'en', CARD_EN) } catch (err) { return () => {} }}, 'dsh-russian-lang: russian-lang-en')
427
+ const registerDictMap = (dictMap) => {if (disposed || !dictMap || typeof dictMap !== 'object') return
428
+ for (const ns of Object.keys(dictMap)) {if (disposed) return
429
+ if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
423
430
  const dict = dictMap[ns]
424
431
  if (typeof dict !== 'object' || !dict) continue
425
432
  if (!RU[ns]) {RU[ns] = dict
426
- ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
433
+ ctx.effect(() => {if (disposed) return () => {}
434
+ try { return ctx.locale.register(ns, 'ru', dict) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
427
435
  } else {Object.assign(RU[ns], dict)
428
436
  try { ctx.locale.register(ns, 'ru', RU[ns]) } catch (_) { void _; }}}}
429
437
  const CACHE_NAME = 'dsh-ru-cache-v1'
@@ -440,17 +448,23 @@ const fetchCachedResource = async (url, cacheKey, onData) => {if (disposed) retu
440
448
  let etag = null
441
449
  let delivered = false
442
450
  if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
451
+ if (disposed) return
443
452
  const matched = await cache.match(url)
453
+ if (disposed) return
444
454
  if (matched) {etag = matched.headers.get('etag')
445
455
  const parsed = await matched.json()
456
+ if (disposed) return
446
457
  if (parsed) {delivered = true
447
458
  onData(parsed)
448
459
  }}} catch (_e) { void _e }}
460
+ if (disposed) return
449
461
  if (!delivered && cacheKey) {const local = loadLocalDict(cacheKey)
462
+ if (disposed) return
450
463
  if (local && local.data) {etag = local.etag
451
464
  delivered = true
452
465
  onData(local.data)
453
466
  }}
467
+ if (disposed) return
454
468
  if (typeof fetch !== 'function') {if (!delivered) throw new Error('Fetch API is not available')
455
469
  return
456
470
  }
@@ -458,26 +472,26 @@ let networkError = null
458
472
  try {const headers = { 'Accept': 'application/json' }
459
473
  if (etag) headers['If-None-Match'] = etag
460
474
  const res = await fetch(url, { headers })
475
+ if (disposed) return
461
476
  if (res.status === 304) return
462
477
  if (res.ok) {const newEtag = res.headers.get('etag')
463
478
  const cloned = res.clone()
464
479
  const data = await res.json()
480
+ if (disposed) return
465
481
  if (data) {onData(data)
482
+ if (disposed) return
466
483
  if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
467
- await cache.put(url, cloned)
484
+ if (!disposed) await cache.put(url, cloned)
468
485
  } catch (_e) { void _e }}
469
- if (cacheKey) saveLocalDict(cacheKey, newEtag, data)
486
+ if (cacheKey && !disposed) saveLocalDict(cacheKey, newEtag, data)
470
487
  }} else {networkError = new Error(`HTTP ${res.status}: ${res.statusText}`)
471
488
  }} catch (err) {networkError = err
472
489
  }
473
490
  if (!delivered && networkError) {throw networkError
474
491
  }}
475
- let disposed = false
476
- let corePreloadTimer = null
477
492
  const loadedPluginNames = new Set()
478
493
  const inFlightPluginNames = new Set()
479
494
  const queuedPluginNames = new Set()
480
- let pluginBatchTimer = null
481
495
  const loadPluginDictionaries = (names) => {if (disposed || typeof fetch !== 'function') return
482
496
  const isSelective = Array.isArray(names) && names.length > 0
483
497
  const toLoad = []
@@ -508,25 +522,31 @@ queuedPluginNames.clear()
508
522
  if (batch.length > 0) loadPluginDictionaries(batch)
509
523
  }, 50)
510
524
  }
511
- fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {if (!data) return
525
+ fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {if (disposed || !data) return
512
526
  if (data.core) registerDictMap(data.core)
527
+ if (disposed) return
513
528
  if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
514
529
  if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
515
530
  }
516
531
  if (typeof syncZhDom === 'function') syncZhDom()
517
- corePreloadTimer = setTimeout(() => { if (!disposed) loadPluginDictionaries([]) }, 100)
518
- }).catch((coreErr) => {if (typeof fetch === 'function') {fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } }).then((res) => {if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)
532
+ if (!disposed) {corePreloadTimer = setTimeout(() => {corePreloadTimer = null
533
+ if (!disposed) loadPluginDictionaries([])
534
+ }, 100)
535
+ }}).catch((coreErr) => {if (disposed || typeof fetch !== 'function') return
536
+ fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } }).then((res) => {if (disposed) return null
537
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)
519
538
  return res.json()
520
- }).then((data) => {if (!data) return
539
+ }).then((data) => {if (disposed || !data) return
521
540
  const allDicts = Object.assign({}, data.core || {}, data.plugins || {}, data)
522
541
  if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
523
542
  if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
524
543
  }
525
- registerDictMap(allDicts)
544
+ if (!disposed) registerDictMap(allDicts)
526
545
  if (typeof syncZhDom === 'function') syncZhDom()
527
- }).catch((allErr) => {if (typeof console !== 'undefined' && console.warn) {console.warn('[dsh-russian-lang] Не удалось загрузить словари: core (' + (coreErr?.message || coreErr) + ') и fallback /dict/all (' + (allErr?.message || allErr) + ')')
528
- }})
546
+ }).catch((allErr) => {if (disposed) return
547
+ if (typeof console !== 'undefined' && console.warn) {console.warn('[dsh-russian-lang] Dict fetch error', coreErr, allErr)
529
548
  }})
549
+ })
530
550
  const getOverrides = () => {try {const value = scope.getSnapshot().value
531
551
  return value && value.overrides ? value.overrides : {}} catch (err) { return {} }}
532
552
  const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
@@ -708,6 +728,7 @@ const DOM_EN_TEXT = {'Scheduled tasks': 'Задачи по расписанию'
708
728
  }
709
729
  const isProtectedTextNode = (node) => {const p = node.parentElement
710
730
  if (!p) return true
731
+ if (p.closest('[data-no-translate],[data-no-translation],.no-translate')) return true
711
732
  if (p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) return true
712
733
  if (p.closest('.katex, [data-latex], math')) return true
713
734
  if (p.closest('[data-chat-flow-status="running"], [data-turn-running], [data-turn-tail], [data-streaming="true"], .dsw-turn-running, [class*="streaming"], [class*="Streaming"]')) return true
@@ -737,7 +758,8 @@ if (trimmed.length >= 1 && ZH_CJK.test(val)) {if (!isProtectedContent) hits.push
737
758
  for (const node of hits) {const next = zhTranslateText(node.nodeValue)
738
759
  if (next && next !== node.nodeValue) node.nodeValue = next
739
760
  }
740
- for (const el of root.querySelectorAll ? root.querySelectorAll('[title],[placeholder],[aria-label]') : []) {for (const attr of ['title', 'placeholder', 'aria-label']) {const v = el.getAttribute && el.getAttribute(attr)
761
+ for (const el of root.querySelectorAll ? root.querySelectorAll('[title],[placeholder],[aria-label]') : []) {if (el.closest && el.closest('[data-no-translate],[data-no-translation],.no-translate')) continue
762
+ for (const attr of ['title', 'placeholder', 'aria-label']) {const v = el.getAttribute && el.getAttribute(attr)
741
763
  if (v) {const tr = v.trim()
742
764
  if (DOM_EN_TEXT[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
743
765
  } else if (DOM_EN_ATTRS[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
@@ -750,7 +772,6 @@ if (DOM_EN_TEXT[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
750
772
  else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
751
773
  }}}} catch (err) { void err; }}
752
774
  let zhObserver = null
753
- let zhDebounceTimer = null
754
775
  let zhWalking = false
755
776
  const queueZhWalk = () => {if (zhDebounceTimer) return
756
777
  zhDebounceTimer = setTimeout(() => {zhDebounceTimer = null
@@ -1199,7 +1220,7 @@ setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
1199
1220
  }}
1200
1221
  const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
1201
1222
  if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
1202
- ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.28' })
1223
+ ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.30' })
1203
1224
  : ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
1204
1225
  try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
1205
1226
  copyReportBtn.textContent = '✓ Скопировано'
@@ -1508,7 +1529,7 @@ const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope
1508
1529
  const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
1509
1530
  const [typo, setTypoState] = React.useState(() =>
1510
1531
  (snap.value && snap.value.typography) || {})
1511
- const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.28',latestVersion: undefined,updateAvailable: false
1532
+ const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.30',latestVersion: undefined,updateAvailable: false
1512
1533
  })
1513
1534
  const [upLoading, setUpLoading] = React.useState(false)
1514
1535
  const [upMsg, setUpMsg] = React.useState(null)
@@ -1575,10 +1596,17 @@ if (res !== false) {setTypoState(next)
1575
1596
  } else {console.warn('dsh-russian-lang: scope.set typography returned false')
1576
1597
  }} catch (err) {console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
1577
1598
  }}
1578
- const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
1579
- const checkbox = (checked, onChange, disabled) =>
1580
- h('input', {type: 'checkbox', checked: !!checked, disabled: !!disabled,className: 'rl-check', onChange: (ev) => onChange(ev),})
1581
1599
  const disabled = status !== 'ready' || snap.writable === false
1600
+ const safeSet = async (k, v) => {if (disabled) return false
1601
+ try {const res = await Promise.resolve(scope.set(k, v))
1602
+ if (res === false) throw new Error(k)
1603
+ return true
1604
+ } catch (err) {if (typeof console !== 'undefined' && console.warn) console.warn('dsh-russian-lang: set ' + k + ' failed', err)
1605
+ return false
1606
+ }}
1607
+ const onEnabled = (ev) => { if (disabled) return; if (toggleRu) toggleRu(ev.target.checked) }
1608
+ const checkbox = (checked, onChange, disabledState) =>
1609
+ h('input', {type: 'checkbox', checked: !!checked, disabled: !!disabledState,className: 'rl-check', onChange: (ev) => { if (disabledState) return; onChange(ev); },})
1582
1610
  const statusLine = status === 'ready'
1583
1611
  ? ''
1584
1612
  : (status === 'unavailable' ? t('statusUnavailable') : t('statusLoading'))
@@ -1593,46 +1621,43 @@ const presetInfo = typeof SYSTEM_PROMPT_PRESETS !== 'undefined' ? SYSTEM_PROMPT_
1593
1621
  if (props && props.view === 'summary') {return h('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
1594
1622
  }
1595
1623
  const page = !!(props && props.view === 'page')
1596
- return h('div', { className: page ? 'rl-page-seat' : 'rl-card' },h('button', {type: 'button',className: 'rl-head',style: page ? { display: 'none' } : undefined,'aria-expanded': page ? 'true' : String(open),onClick: () => setOpen(!open),},h('span', { className: 'rl-head-main' },h('div', { className: 'rl-title' },'🇷🇺 ' + t('cardTitle'),h('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-warn') },ruActive ? t('badgeRu') : t('badgeEn')),h('span', { className: 'rl-badge rl-badge-ok' }, t('badgeCoverage'))),h('div', { className: 'rl-sub' },statusLine || t('cardSub'))),h('span', {className: 'rl-chev' + (open ? ' rl-chev-open' : ''),}, h(Chevron, null))),(page || open) && h('div', { className: 'rl-body' },h('div', { className: 'rl-page' },h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secLanguage')),h('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-dim') },ruActive ? t('badgeRu') : t('badgeEn'))),h('div', { className: 'rl-section-desc' }, t('secLanguageDesc')),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(ruActive, onEnabled, disabled),t('enabled'))),h('div', { className: 'rl-item-desc' }, t('enabledDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.quickSwitch !== false,(ev) => { try { scope.set('quickSwitch', ev.target.checked) } catch (err) { console.warn('dsh-russian-lang: set quickSwitch failed', err) } }, disabled),t('quickSwitch'))),h('div', { className: 'rl-item-desc' }, t('quickSwitchDesc'))),overridesCount > 0 ? h('div', { className: 'rl-badge rl-badge-dim', style: { alignSelf: 'flex-start' } },t('overridesCount') + ': ' + overridesCount) : null
1624
+ return h('div', { className: page ? 'rl-page-seat' : 'rl-card' },h('button', {type: 'button',className: 'rl-head',style: page ? { display: 'none' } : undefined,'aria-expanded': page ? 'true' : String(open),onClick: () => setOpen(!open),},h('span', { className: 'rl-head-main' },h('div', { className: 'rl-title' },'🇷🇺 ' + t('cardTitle'),h('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-warn') },ruActive ? t('badgeRu') : t('badgeEn')),h('span', { className: 'rl-badge rl-badge-ok' }, t('badgeCoverage'))),h('div', { className: 'rl-sub' },statusLine || t('cardSub'))),h('span', {className: 'rl-chev' + (open ? ' rl-chev-open' : ''),}, h(Chevron, null))),(page || open) && h('div', { className: 'rl-body' },h('div', { className: 'rl-page' },h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secLanguage')),h('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-dim') },ruActive ? t('badgeRu') : t('badgeEn'))),h('div', { className: 'rl-section-desc' }, t('secLanguageDesc')),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(ruActive, onEnabled, disabled),t('enabled'))),h('div', { className: 'rl-item-desc' }, t('enabledDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.quickSwitch !== false,(ev) => safeSet('quickSwitch', ev.target.checked), disabled),t('quickSwitch'))),h('div', { className: 'rl-item-desc' }, t('quickSwitchDesc'))),overridesCount > 0 ? h('div', { className: 'rl-badge rl-badge-dim', style: { alignSelf: 'flex-start' } },t('overridesCount') + ': ' + overridesCount) : null
1597
1625
  ),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secTypography')),status === 'ready' && h('span', {className: 'rl-badge ' + (smartUxActive ? 'rl-badge-ok' : 'rl-badge-dim')
1598
- }, t(smartUxActive ? 'badgeSmartUx' : 'badgeSmartUxOff'))),h('div', { className: 'rl-section-desc' }, t('secTypographyDesc')),h('div', { className: 'rl-grid-2', style: { gridAutoRows: '1fr' } },h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(effectiveTypo.enabled && ruActive,(ev) => setTypo({ enabled: ev.target.checked }), !ruActive || disabled),t('typography'))),h('div', { className: 'rl-item-desc' }, t('typographyDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(effectiveTypo.liveInput && ruActive,(ev) => setTypo({ liveInput: ev.target.checked }), !ruActive || disabled),t('liveInput'))),h('div', { className: 'rl-item-desc' }, t('liveInputDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(effectiveTypo.yo && ruActive,(ev) => setTypo({ yo: ev.target.checked }), !ruActive || disabled),t('yo'))),h('div', { className: 'rl-item-desc' }, t('yoDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.slashAliases !== false && ruActive,(ev) => {try { scope.set('slashAliases', ev.target.checked) } catch (err) { void err; }}, !ruActive || disabled),t('slashAliases'))),h('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.layoutConversion !== false,(ev) => { try { scope.set('layoutConversion', ev.target.checked) } catch (err) { console.warn('dsh-russian-lang: set layoutConversion failed', err) } }, disabled),t('altLHintText'))),h('div', { className: 'rl-item-desc' }, t('layoutConversionDesc'))))),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secAgentPrompt')),h('span', { className: 'rl-badge ' + (value.agentPrompt ? 'rl-badge-ok' : 'rl-badge-dim') },value.agentPrompt ? 'Активен' : 'Выключен')),h('div', { className: 'rl-section-desc' }, t('secAgentPromptDesc')),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.agentPrompt === true,async (ev) => {if (disabled) return
1626
+ }, t(smartUxActive ? 'badgeSmartUx' : 'badgeSmartUxOff'))),h('div', { className: 'rl-section-desc' }, t('secTypographyDesc')),h('div', { className: 'rl-grid-2', style: { gridAutoRows: '1fr' } },h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(effectiveTypo.enabled && ruActive,(ev) => setTypo({ enabled: ev.target.checked }), !ruActive || disabled),t('typography'))),h('div', { className: 'rl-item-desc' }, t('typographyDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(effectiveTypo.liveInput && ruActive,(ev) => setTypo({ liveInput: ev.target.checked }), !ruActive || disabled),t('liveInput'))),h('div', { className: 'rl-item-desc' }, t('liveInputDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(effectiveTypo.yo && ruActive,(ev) => setTypo({ yo: ev.target.checked }), !ruActive || disabled),t('yo'))),h('div', { className: 'rl-item-desc' }, t('yoDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.slashAliases !== false && ruActive,(ev) => { if (ruActive) safeSet('slashAliases', ev.target.checked) }, !ruActive || disabled),t('slashAliases'))),h('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.layoutConversion !== false,(ev) => safeSet('layoutConversion', ev.target.checked), disabled),t('altLHintText'))),h('div', { className: 'rl-item-desc' }, t('layoutConversionDesc'))))),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secAgentPrompt')),h('span', { className: 'rl-badge ' + (value.agentPrompt ? 'rl-badge-ok' : 'rl-badge-dim') },value.agentPrompt ? 'Активен' : 'Выключен')),h('div', { className: 'rl-section-desc' }, t('secAgentPromptDesc')),h('div', { className: 'rl-item-card' },h('div', { className: 'rl-item-head' },h('label', { className: 'rl-item-label' },checkbox(value.agentPrompt === true,async (ev) => {if (disabled) return
1599
1627
  try {await Promise.resolve(scope.set('agentPrompt', ev.target.checked))
1600
- } catch (err) {console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
1601
- }}, disabled),t('agentPrompt'))),h('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))),value.agentPrompt ? h(React.Fragment, null,h('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },h('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('agentPromptPreset')),h('select', {className: 'rl-select',disabled: !!disabled,value: value.agentPromptPreset || 'technical_expert',onChange: (ev) => {try {const val = ev.target.value
1602
- scope.set('agentPromptPreset', val)
1603
- } catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }}},h('option', { value: 'technical_expert' }, t('presetExpert')),h('option', { value: 'tech_writer' }, t('presetWriter')),h('option', { value: 'concise' }, t('presetConcise')),h('option', { value: 'code_reviewer' }, t('presetReviewer')),h('option', { value: 'architect' }, t('presetArchitect')),h('option', { value: 'tutor' }, t('presetTutor')))),presetInfo ? h('div', { className: 'rl-preview-box' },h('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),presetInfo.text
1628
+ } catch (err) {if (typeof console !== 'undefined' && console.warn) console.warn(err)
1629
+ }}, disabled),t('agentPrompt'))),h('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))),value.agentPrompt ? h(React.Fragment, null,h('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },h('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('agentPromptPreset')),h('select', {className: 'rl-select',disabled: !!disabled,value: value.agentPromptPreset || 'technical_expert',onChange: (ev) => safeSet('agentPromptPreset', ev.target.value)
1630
+ },h('option', { value: 'technical_expert' }, t('presetExpert')),h('option', { value: 'tech_writer' }, t('presetWriter')),h('option', { value: 'concise' }, t('presetConcise')),h('option', { value: 'code_reviewer' }, t('presetReviewer')),h('option', { value: 'architect' }, t('presetArchitect')),h('option', { value: 'tutor' }, t('presetTutor')))),presetInfo ? h('div', { className: 'rl-preview-box' },h('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),presetInfo.text
1604
1631
  ) : null
1605
1632
  ) : null
1606
- ),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secUpdater')),h('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))),h('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),h('div', { className: 'rl-item-card' },h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },h('div', null,h('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.28')),upStatus.updateAvailable
1633
+ ),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secUpdater')),h('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))),h('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),h('div', { className: 'rl-item-card' },h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },h('div', null,h('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.30')),upStatus.updateAvailable
1607
1634
  ? h('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
1608
1635
  : h('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
1609
1636
  ? h('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
1610
1637
  : h('button', {type: 'button',className: 'rl-btn',disabled: upLoading,onClick: checkUpdate,}, upLoading ? t('updaterChecking') : t('updaterCheckBtn'))),upMsg ? h('div', {style: {marginTop: '10px', padding: '8px 12px', borderRadius: '8px',background: upMsg.type === 'ok' ? 'color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)' : 'color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)',color: upMsg.type === 'ok' ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontSize: '12px', fontWeight: 500
1611
1638
  }}, upMsg.text) : null
1612
1639
  )),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secTranslator')),h('span', {className: 'rl-badge ' + (value.translateEngine === 'local' ? 'rl-badge-ok' : (value.translateEngine === 'google' ? 'rl-badge-warn' : 'rl-badge-dim'))
1613
- }, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))),h('div', { className: 'rl-section-desc' }, t('secTranslatorDesc')),h('div', { className: 'rl-item-card' },h('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },h('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('translateEngine')),h('select', {className: 'rl-select',value: value.translateEngine || 'off',onChange: (ev) => {try {const val = ev.target.value
1614
- scope.set('translateEngine', val)
1615
- if (val === 'local') fetchTransStatus()
1616
- } catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }}},h('option', { value: 'off' }, t('engineOff')),h('option', { value: 'local' }, t('engineLocal')),h('option', { value: 'google' }, t('engineGoogle')))),value.translateEngine === 'google' ? h('div', {style: {marginTop: '10px', padding: '8px 12px', borderRadius: '8px',background: 'color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-state-warning-primary) 30%, transparent)',color: 'var(--dsw-alias-state-warning-primary)', fontSize: '12px', fontWeight: 500, lineHeight: 1.4
1640
+ }, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))),h('div', { className: 'rl-section-desc' }, t('secTranslatorDesc')),h('div', { className: 'rl-item-card' },h('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },h('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('translateEngine')),h('select', {className: 'rl-select',disabled: !!disabled,value: value.translateEngine || 'off',onChange: async (ev) => {const val = ev.target.value
1641
+ if (await safeSet('translateEngine', val)) {if (val === 'local') fetchTransStatus()
1642
+ }}},h('option', { value: 'off' }, t('engineOff')),h('option', { value: 'local' }, t('engineLocal')),h('option', { value: 'google' }, t('engineGoogle')))),value.translateEngine === 'google' ? h('div', {style: {marginTop: '10px', padding: '8px 12px', borderRadius: '8px',background: 'color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-state-warning-primary) 30%, transparent)',color: 'var(--dsw-alias-state-warning-primary)', fontSize: '12px', fontWeight: 500, lineHeight: 1.4
1617
1643
  }}, t('googleWarn')) : null,value.translateEngine === 'local' ? h('div', {style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }},h('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, t('localInfo')),h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },h('span', {className: 'rl-badge ' + (transStatus && transStatus.running ? 'rl-badge-ok' : 'rl-badge-dim')
1618
1644
  }, transStatus && transStatus.running ? t('localStatusRunning') : t('localStatusStopped')),(!transStatus || !transStatus.running) ? h('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: transStarting,onClick: startLibreTranslate
1619
1645
  }, transStarting ? t('localStarting') : t('localStartBtn')) : null
1620
1646
  ),transMsg ? h('div', {style: {marginTop: '6px', padding: '8px 12px', borderRadius: '8px',background: transMsg.type === 'ok' ? 'color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)' : 'color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)',color: transMsg.type === 'ok' ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontSize: '12px', fontWeight: 500
1621
1647
  }}, transMsg.text) : null
1622
1648
  ) : null
1623
- )),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secOverrides')),h('span', { className: 'rl-badge rl-badge-dim' },String(overridesCount) + ' ' + (typeof plural === 'function' ? plural(overridesCount, ['запись', 'записи', 'записей']) : 'записей'))),h('div', { className: 'rl-section-desc' }, t('secOverridesDesc')),h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },h('input', {className: 'rl-select',style: { flex: '1 1 180px', maxWidth: 'none' },placeholder: t('overrideKeyPlaceholder'),value: newKey,onChange: (e) => setNewKey(e.target.value),}),h('input', {className: 'rl-select',style: { flex: '2 1 240px', maxWidth: 'none' },placeholder: t('overrideValuePlaceholder'),value: newVal,onChange: (e) => setNewVal(e.target.value),}),h('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: !newKey.trim() || !newVal.trim() || !!disabled,onClick: () => {const k = newKey.trim()
1649
+ )),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secOverrides')),h('span', { className: 'rl-badge rl-badge-dim' },String(overridesCount) + ' ' + (typeof plural === 'function' ? plural(overridesCount, ['запись', 'записи', 'записей']) : 'записей'))),h('div', { className: 'rl-section-desc' }, t('secOverridesDesc')),h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },h('input', {className: 'rl-select',disabled: !!disabled,style: { flex: '1 1 180px', maxWidth: 'none' },placeholder: t('overrideKeyPlaceholder'),value: newKey,onChange: (e) => setNewKey(e.target.value),}),h('input', {className: 'rl-select',disabled: !!disabled,style: { flex: '2 1 240px', maxWidth: 'none' },placeholder: t('overrideValuePlaceholder'),value: newVal,onChange: (e) => setNewVal(e.target.value),}),h('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: !newKey.trim() || !newVal.trim() || !!disabled,onClick: async () => {const k = newKey.trim()
1624
1650
  const v = newVal.trim()
1625
1651
  if (!k || !v) return
1626
- const next = Object.assign({}, overrides, { [k]: v })
1627
- try { scope.set('overrides', next) } catch (_e) { void _e }
1628
- setNewKey('')
1652
+ if (await safeSet('overrides', Object.assign({}, overrides, { [k]: v }))) {setNewKey('')
1629
1653
  setNewVal('')
1630
- },}, t('overrideAddBtn'))),overridesCount === 0
1654
+ }},}, t('overrideAddBtn'))),overridesCount === 0
1631
1655
  ? h('div', { className: 'rl-hint-text', style: { fontStyle: 'italic', padding: '4px 0' } }, t('overrideEmpty'))
1632
1656
  : h('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },Object.keys(overrides).map((k) =>
1633
1657
  h('div', {key: k,style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 10px', background: 'var(--dsw-alias-bg-layer-3)', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', fontSize: '12px' },},h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', overflow: 'hidden' } },h('code', { style: { color: 'var(--dsw-alias-state-brand-primary)', fontWeight: 600 } }, k),h('span', { style: { color: 'var(--dsw-alias-label-secondary)' } }, '→'),h('span', { style: { color: 'var(--dsw-alias-label-primary)' } }, overrides[k])),h('button', {type: 'button',className: 'rl-btn',disabled: !!disabled,style: { padding: '2px 8px', height: '24px', fontSize: '11px', color: 'var(--dsw-alias-state-error-primary)' },onClick: () => {const next = Object.assign({}, overrides)
1634
1658
  delete next[k]
1635
- try { scope.set('overrides', next) } catch (_e) { void _e }},}, t('overrideDelete'))))),h('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip')),h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginTop: '10px' } },h('button', {type: 'button',className: 'rl-btn' + (isInspectorActive ? ' rl-btn-primary' : ''),onClick: () => {isInspectorActive = !isInspectorActive
1659
+ safeSet('overrides', next)
1660
+ },}, t('overrideDelete'))))),h('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip')),h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginTop: '10px' } },h('button', {type: 'button',className: 'rl-btn' + (isInspectorActive ? ' rl-btn-primary' : ''),onClick: () => {isInspectorActive = !isInspectorActive
1636
1661
  updateInspectorPill()
1637
1662
  },}, isInspectorActive ? t('inspectorToggleOn') : t('inspectorToggleOff')),h('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {try {navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
1638
1663
  alert(t('overridesCopied'))
@@ -1643,7 +1668,7 @@ try {const parsed = JSON.parse(input)
1643
1668
  if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
1644
1669
  const res = await Promise.resolve(scope.set('overrides', merged))
1645
1670
  if (res === false) throw new Error('Import rejected')
1646
- }} catch (err) { alert(t('overridesImportError') + (err && err.message || err)) }}}, t('importJsonBtn')))),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secSupport')),h('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')),h('div', { className: 'rl-section-desc' }, t('secSupportDesc')),h('div', { className: 'rl-grid-3' },h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '101'),h('div', { className: 'rl-stat-label' }, t('statNamespaces'))),h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '2 442'),h('div', { className: 'rl-stat-label' }, t('statCoreKeys'))),h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '6 142'),h('div', { className: 'rl-stat-label' }, t('statPluginKeys')))),h('div', { className: 'rl-actions-row' },h('a', {href: makeIssueUrl({}, '0.3.28'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1671
+ }} catch (err) { alert(t('overridesImportError') + (err && err.message || err)) }}}, t('importJsonBtn')))),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secSupport')),h('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')),h('div', { className: 'rl-section-desc' }, t('secSupportDesc')),h('div', { className: 'rl-grid-3' },h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '101'),h('div', { className: 'rl-stat-label' }, t('statNamespaces'))),h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '2 442'),h('div', { className: 'rl-stat-label' }, t('statCoreKeys'))),h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '6 142'),h('div', { className: 'rl-stat-label' }, t('statPluginKeys')))),h('div', { className: 'rl-actions-row' },h('a', {href: makeIssueUrl({}, '0.3.30'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1647
1672
  }, '💬 ' + t('reportIssue')),h('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
1648
1673
  }
1649
1674
  const RL_CSS = [':root{--rl-sb:var(--dsw-alias-state-brand-primary);--rl-ls:var(--dsw-alias-label-secondary);--rl-lp:var(--dsw-alias-label-primary);--rl-b:var(--dsw-alias-border-l2);--rl-l3:var(--dsw-alias-bg-layer-3);--rl-l2:var(--dsw-alias-bg-layer-2);--rl-l1:var(--dsw-alias-bg-layer-1)}.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(--rl-l2);color:var(--rl-lp);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(--rl-b);background:var(--rl-l3);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(--rl-l2)}.rl-head-main{flex:1;display:flex;flex-direction:column;gap:4px}.rl-title{color:var(--rl-lp);font-size:16px;font-weight:600;line-height:1.4;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.rl-sub{color:var(--rl-ls);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(--rl-b);padding:18px}.rl-page{display:flex;flex-direction:column;gap:16px;max-width:960px}.rl-section-card{border:1px solid var(--rl-b);background:var(--rl-l2);border-radius:10px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}.rl-section-title{font-size:14px;font-weight:600;color:var(--rl-lp);display:flex;align-items:center;justify-content:space-between;gap:8px}.rl-section-desc{font-size:12px;color:var(--rl-ls);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(--rl-b);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(--rl-b);color:var(--rl-ls);background:var(--rl-l3)}.rl-item-card{border:1px solid var(--rl-b);background:var(--rl-l3);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(--rl-lp);display:flex;align-items:center;gap:8px;cursor:pointer}.rl-item-desc{font-size:11px;color:var(--rl-ls);line-height:1.4}.rl-stat-box{border:1px solid var(--rl-b);background:var(--rl-l3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}.rl-stat-val{font-size:16px;font-weight:700;color:var(--rl-lp)}.rl-stat-label{font-size:11px;color:var(--rl-ls);letter-spacing:0.3px}.rl-preview-box{border:1px solid var(--rl-b);background:var(--rl-l3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--rl-ls);line-height:1.4}.rl-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--rl-b);border-radius:8px;padding:6px 12px;font-size:12px;background:var(--rl-l3);color:var(--rl-lp);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(--rl-l1);border-color:var(--rl-ls)}.rl-btn-primary{background:var(--rl-lp);color:var(--rl-l3);border-color:transparent}.rl-btn-primary:hover:not(:disabled){opacity:0.9}.rl-select{height:32px;border:1px solid var(--rl-b);background:var(--rl-l3);color:var(--rl-lp);border-radius:6px;padding:0 8px;font-size:12px;outline:none;width:100%;max-width:380px}.rl-select:focus{border-color:var(--rl-sb)}.rl-check{width:16px;height:16px;accent-color:var(--rl-lp);cursor:pointer;flex-shrink:0}.rl-hint-text{font-size:11px;color:var(--rl-ls);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(--rl-b);background:var(--rl-l3);color:var(--rl-ls);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(--rl-lp);color:var(--rl-lp)}.rl-lang-chip-active{color:var(--rl-lp);border-color:var(--rl-lp);background:var(--rl-l2)}.rl-action-btn{appearance:none;background:0 0;border:1px solid transparent;border-radius:4px;color:var(--rl-ls);cursor:pointer;font-size:11px;padding:2px 5px;display:inline-flex;align-items:center;transition:all .15s}.rl-action-btn:hover{color:var(--rl-lp);border-color:var(--rl-b);background:var(--rl-l3)}.rl-action-btn-active{color:var(--rl-lp);background:var(--rl-l2);border-color:var(--rl-b)}.rl-turn-translation{margin:8px 0;padding:10px 12px;border:1px solid var(--rl-b);background:var(--rl-l2);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(--rl-b);padding-bottom:4px}.rl-trans-title{font-size:11px;font-weight:600;color:var(--rl-ls);text-transform:uppercase;letter-spacing:0.5px}.rl-trans-tools{display:flex;gap:4px}.rl-trans-btn{appearance:none;background:var(--rl-l3);border:1px solid var(--rl-b);color:var(--rl-ls);cursor:pointer;padding:2px 7px;border-radius:4px;font-size:11px}.rl-trans-btn:hover{background:var(--rl-l1);color:var(--rl-lp)}.rl-trans-body{color:var(--rl-lp);white-space:pre-wrap;word-break:break-word;user-select:text}.rl-export-md-btn{appearance:none;border:1px solid var(--rl-b);height:32px;color:var(--rl-lp);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(--rl-sb);border-radius:4px;background:color-mix(in srgb,var(--rl-sb) 12%,transparent);box-shadow:0 0 10px color-mix(in srgb,var(--rl-sb) 30%,transparent);z-index:99998;transition:all .06s ease;display:none}.rl-inspector-badge{position:absolute;bottom:calc(100% + 4px);left:0;background:var(--rl-sb);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(--rl-l2);color:var(--rl-lp);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(--rl-sb);box-shadow:0 0 12px color-mix(in srgb,var(--rl-sb) 35%,transparent)}'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-russian-lang",
3
- "version": "0.3.28",
3
+ "version": "0.3.30",
4
4
  "description": "Russian localization for DeepSeek Harness (DSH v0.2.0-rc.2+) web UI: adds Русский as native language option. 100.0% coverage across 8,584 UI keys (101 namespaces: Core 2,442 + Plugins 6,142). Features full DSH v0.2.0-rc.2 core support and complete GoodAndReady plugin catalog.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",