@goodandready/dsh-russian-lang 0.3.27 → 0.3.29

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.27-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.29-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>
@@ -52,9 +52,9 @@ graph LR
52
52
  DSH[Web UI Интерфейс] --> Loader[Загрузчик локализации dsh-russian-lang]
53
53
  end
54
54
 
55
- subgraph TranslationLayer [Слой перевода и адаптации — 8576 ключей / 100% вычитано]
56
- Loader --> CoreDict[58 Пространств имён ядра DSH / 2442 ключа]
57
- Loader --> PluginDict[64 Плагина экосистемы DSH / 6134 ключа]
55
+ subgraph TranslationLayer [Слой перевода и адаптации — 8 584 ключа / 100% вычитано]
56
+ Loader --> CoreDict[58 Пространств имён ядра DSH / 2 442 ключа]
57
+ Loader --> PluginDict[43 Пространства имён плагинов DSH / 6 142 ключа]
58
58
  Loader --> PluralEngine[Движок грамматических Plural-форм: 1/2/5]
59
59
  end
60
60
 
@@ -178,12 +178,19 @@ dsh plugin --profile web add @goodandready/dsh-russian-lang
178
178
  ```yaml
179
179
  # Актуальный формат (DSH v0.2.0-rc.2+ / v0.1.7+):
180
180
  dsh-russian-lang:
181
- enabled: true # Русский язык интерфейса
182
- typography:
183
- enabled: true # Ёлочки, длинные тире, неразрывные пробелы
184
- yo: false # Восстанавливать «ё» (по умолчанию выключено)
185
- agentPrompt: false # Просить агента отвечать по-русски
186
- overrides: {} # Свои значения для любых ключей перевода
181
+ enabled: true # Русский язык интерфейса (по умолчанию: true)
182
+ overrides: {} # Пользовательские оверрайды ключей (по умолчанию: {})
183
+ typography: # Интеллектуальная типографика (по умолчанию: все opt-in false)
184
+ enabled: false # Ёлочки, длинные тире, неразрывные пробелы
185
+ yo: false # Восстановление безопасной буквы «ё»
186
+ liveInput: false # Живая типографика при вводе в текстовых полях
187
+ agentPrompt: false # Системная инструкция агенту отвечать по-русски (по умолчанию: false)
188
+ agentPromptPreset: technical_expert # Пресет системного промпта (по умолчанию: technical_expert)
189
+ slashAliases: true # Русские алиасы слэш-команд (/помощь, /очистить) (по умолчанию: true)
190
+ layoutConversion: true # Детектор неверной раскладки и подсказка Alt+L (по умолчанию: true)
191
+ quickSwitch: true # Быстрый переключатель RU/EN в статусной строке (по умолчанию: true)
192
+ translateEngine: off # Движок перевода реплик: off, local (LibreTranslate) или google (Google) (по умолчанию: off)
193
+ localApiUrl: http://localhost:5000 # URL локального инстанса LibreTranslate (по умолчанию: http://localhost:5000)
187
194
 
188
195
  # Легаси-формат (DSH <= 0.1.6, поддерживается автоматически как fallback):
189
196
  # russian-lang:
@@ -193,16 +200,15 @@ dsh-russian-lang:
193
200
 
194
201
  Удобнее всего настраивать параметры визуально в интерфейсе: **Настройки → Плагины → Русская локализация**.
195
202
 
196
- Исправление раскладки (<kbd>Alt+L</kbd>) и браузерный спелчек включаются вместе с
197
- русским языком и отдельных ключей не имеют.
203
+ Исправление раскладки (<kbd>Alt+L</kbd>) и русские алиасы слэш-команд включены по умолчанию (`true`) и могут быть индивидуально отключены в настройках (`layoutConversion: false`, `slashAliases: false`). Браузерный спелчек (`lang="ru-RU"`) активируется автоматически при выборе русской локали.
198
204
 
199
205
  ---
200
206
 
201
207
  ## 🛠 Разработка и зеркало репозитория
202
208
 
203
- Публичный репозиторий на GitHub является дистрибутивным зеркалом релизов `@goodandready/dsh-russian-lang`. Основная разработка, сборочный пайплайн и полный тестовый контур ведутся в закрытом репозитории проекта.
209
+ Публичный репозиторий на GitHub является дистрибутивным зеркалом релизов `@goodandready/dsh-russian-lang`. Основная разработка, сборочный пайплайн и полный тестовый контур ведутся в основном Gitea-репозитории проекта.
204
210
 
205
- Pull Requests и Issues на GitHub приветствуются и обрабатываются в обычном порядке! Для внесения правок в логику клиента используйте `lib/client.template.js`. Запуск `npm test` в клоне репозитория проверяет синтаксис бандла через `node --check`.
211
+ Pull Requests и Issues на GitHub приветствуются и обрабатываются в обычном порядке! Для внесения правок в логику клиента используйте `lib/client.template.js`. Запуск `npm test` в репозитории выполняет полную проверку (270+ модульных тестов Node.js, тесты сборщика, линтеры переводов и глоссария).
206
212
 
207
213
  ---
208
214
 
package/bin/dsh-i18n.mjs CHANGED
@@ -45,6 +45,255 @@ dsh-i18n (v${getPkgVersion()}) — Инструмент локализации
45
45
  `)
46
46
  }
47
47
 
48
+ function extractFromCode(code, result, defaultNs = 'common') {
49
+ let i = 0
50
+ const len = code.length
51
+
52
+ function skipWhitespace(idx) {
53
+ while (idx < len && /\s/.test(code[idx])) idx++
54
+ return idx
55
+ }
56
+
57
+ // Parse static string literal: '...', "...", or static `...` (without ${...})
58
+ function readStaticString(idx) {
59
+ const q = code[idx]
60
+ if (q !== "'" && q !== '"' && q !== '`') return null
61
+ let s = ''
62
+ idx++
63
+ while (idx < len) {
64
+ const ch = code[idx]
65
+ if (ch === '\\') {
66
+ if (idx + 1 < len) {
67
+ s += code[idx + 1]
68
+ idx += 2
69
+ continue
70
+ }
71
+ }
72
+ if (q === '`' && ch === '$' && code[idx + 1] === '{') {
73
+ // Dynamic template string: non-literal, do not extract
74
+ return null
75
+ }
76
+ if (ch === q) {
77
+ return { val: s, next: idx + 1 }
78
+ }
79
+ s += ch
80
+ idx++
81
+ }
82
+ return null
83
+ }
84
+
85
+ let lastToken = ''
86
+ let hadNewline = true
87
+ const stack = []
88
+
89
+ while (i < len) {
90
+ const ch = code[i]
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
+
122
+ // Single-line comment
123
+ if (ch === '/' && code[i + 1] === '/') {
124
+ i += 2
125
+ while (i < len && code[i] !== '\n') i++
126
+ hadNewline = true
127
+ continue
128
+ }
129
+
130
+ // Multi-line comment
131
+ if (ch === '/' && code[i + 1] === '*') {
132
+ i += 2
133
+ while (i < len && !(code[i] === '*' && code[i + 1] === '/')) {
134
+ if (code[i] === '\n') hadNewline = true
135
+ i++
136
+ }
137
+ i += 2
138
+ continue
139
+ }
140
+
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
169
+ }
170
+ }
171
+
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
230
+ const prevChar = i > 0 ? code[i - 1] : ' '
231
+ const isIdentStart = !/[a-zA-Z0-9_$]/.test(prevChar)
232
+
233
+ if (isIdentStart) {
234
+ if (code.startsWith('translate', i) && !/[a-zA-Z0-9_$]/.test(code[i + 9] || '')) {
235
+ let j = skipWhitespace(i + 9)
236
+ if (code[j] === '(') {
237
+ j = skipWhitespace(j + 1)
238
+ const nsStr = readStaticString(j)
239
+ if (nsStr) {
240
+ j = skipWhitespace(nsStr.next)
241
+ if (code[j] === ',') {
242
+ j = skipWhitespace(j + 1)
243
+ const keyStr = readStaticString(j)
244
+ if (keyStr) {
245
+ j = skipWhitespace(keyStr.next)
246
+ if (code[j] === ',' || code[j] === ')') {
247
+ const ns = nsStr.val
248
+ const key = keyStr.val
249
+ if (!result[ns]) result[ns] = {}
250
+ result[ns][key] = result[ns][key] || ''
251
+ i = keyStr.next
252
+ lastToken = ')'
253
+ hadNewline = false
254
+ continue
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ } else if (code.startsWith('t', i) && !/[a-zA-Z0-9_$]/.test(code[i + 1] || '')) {
261
+ let j = skipWhitespace(i + 1)
262
+ if (code[j] === '(') {
263
+ j = skipWhitespace(j + 1)
264
+ const keyStr = readStaticString(j)
265
+ if (keyStr) {
266
+ j = skipWhitespace(keyStr.next)
267
+ if (code[j] === ',' || code[j] === ')') {
268
+ result[defaultNs][keyStr.val] = result[defaultNs][keyStr.val] || ''
269
+ i = keyStr.next
270
+ lastToken = ')'
271
+ hadNewline = false
272
+ continue
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
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
+
293
+ i++
294
+ }
295
+ }
296
+
48
297
  function extractKeys(targetDir, defaultNs = 'common') {
49
298
  const result = { [defaultNs]: {} }
50
299
  const extensions = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx'])
@@ -59,17 +308,7 @@ function extractKeys(targetDir, defaultNs = 'common') {
59
308
  walk(fullPath)
60
309
  } else if (entry.isFile() && extensions.has(path.extname(entry.name))) {
61
310
  const code = fs.readFileSync(fullPath, 'utf8')
62
- const tMatches = code.matchAll(/\bt\(\s*['"`]([a-zA-Z0-9_.-]+)['"`]\s*[,)]/g)
63
- for (const m of tMatches) {
64
- result[defaultNs][m[1]] = result[defaultNs][m[1]] || ''
65
- }
66
- const transMatches = code.matchAll(/\btranslate\(\s*['"`]([a-zA-Z0-9_.-]+)['"`]\s*,\s*['"`]([a-zA-Z0-9_.-]+)['"`]/g)
67
- for (const m of transMatches) {
68
- const ns = m[1]
69
- const key = m[2]
70
- if (!result[ns]) result[ns] = {}
71
- result[ns][key] = result[ns][key] || ''
72
- }
311
+ extractFromCode(code, result, defaultNs)
73
312
  }
74
313
  }
75
314
  }
@@ -102,11 +341,26 @@ function validateDict(filePath, glossaryPath) {
102
341
  let totalKeys = 0
103
342
 
104
343
  let glossary = null
105
- const gPath = glossaryPath || path.join(ROOT, 'glossary.json')
106
- if (fs.existsSync(gPath)) {
344
+ if (glossaryPath) {
345
+ if (!fs.existsSync(glossaryPath)) {
346
+ console.error(`Ошибка: Файл глоссария не найден: ${glossaryPath}`)
347
+ return false
348
+ }
107
349
  try {
108
- glossary = JSON.parse(fs.readFileSync(gPath, 'utf8')).terms || {}
109
- } catch (_) {}
350
+ const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'))
351
+ glossary = parsed.terms || parsed
352
+ } catch (err) {
353
+ console.error(`Ошибка чтения глоссария ${glossaryPath}: ${err.message}`)
354
+ return false
355
+ }
356
+ } else {
357
+ const defaultGPath = path.join(ROOT, 'glossary.json')
358
+ if (fs.existsSync(defaultGPath)) {
359
+ try {
360
+ const parsed = JSON.parse(fs.readFileSync(defaultGPath, 'utf8'))
361
+ glossary = parsed.terms || parsed
362
+ } catch (_) {}
363
+ }
110
364
  }
111
365
 
112
366
  function checkString(ns, key, val) {
@@ -224,7 +478,8 @@ if (command === 'extract') {
224
478
  if (out) {
225
479
  fs.mkdirSync(path.dirname(path.resolve(out)), { recursive: true })
226
480
  fs.writeFileSync(path.resolve(out), jsonStr + '\n', 'utf8')
227
- console.log(`✓ Извлечено ключей: ${Object.keys(extracted[ns] || {}).length} -> ${out}`)
481
+ const totalCount = Object.values(extracted).reduce((sum, nObj) => sum + Object.keys(nObj || {}).length, 0)
482
+ console.log(`✓ Извлечено ключей: ${totalCount} -> ${out}`)
228
483
  } else {
229
484
  console.log(jsonStr)
230
485
  }
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,27 +1220,33 @@ 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.27' })
1223
+ ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.29' })
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 = '✓ Скопировано'
1206
1227
  setTimeout(() => { copyReportBtn.textContent = '📋 Скопировать репорт' }, 2000)
1207
1228
  }}
1229
+ const saveBtn = modal.querySelector('.rl-save-btn')
1208
1230
  const resetBtn = modal.querySelector('.rl-reset-override-btn')
1209
- if (resetBtn) {resetBtn.onclick = () => {const targetKey = k || rawText
1231
+ if (resetBtn) {resetBtn.onclick = async () => {const targetKey = k || rawText
1210
1232
  const cur = getOverrides()
1211
1233
  const updated = Object.assign({}, cur)
1212
1234
  delete updated[targetKey]
1213
- try { scope.set('overrides', updated) } catch (err) { void err; }
1235
+ resetBtn.disabled = true
1236
+ try {const res = await Promise.resolve(scope.set('overrides', updated))
1237
+ if (res === false) throw new Error('Reset rejected')
1214
1238
  modal.remove()
1215
1239
  if (typeof queueZhWalk === 'function') queueZhWalk()
1216
- }}
1240
+ } catch (err) {resetBtn.disabled = false
1241
+ console.warn('dsh-russian-lang: reset override failed', err && err.message || err)
1242
+ }}}
1217
1243
  const inspSnap = typeof scope?.getSnapshot === 'function' ? scope.getSnapshot() : { status: 'ready', writable: true }
1218
- if (inspSnap.status !== 'ready' || inspSnap.writable === false) {saveBtn.disabled = true
1244
+ if (inspSnap.status !== 'ready' || inspSnap.writable === false) {if (saveBtn) {saveBtn.disabled = true
1219
1245
  saveBtn.title = 'Настройки доступны только для чтения'
1246
+ }
1220
1247
  if (resetBtn) resetBtn.disabled = true
1221
1248
  }
1222
- saveBtn.onclick = async () => {const val = textarea.value.trim()
1249
+ if (saveBtn) {saveBtn.onclick = async () => {const val = textarea.value.trim()
1223
1250
  const targetKey = k || rawText
1224
1251
  if (!targetKey || !val || saveBtn.disabled) return
1225
1252
  const cur = getOverrides()
@@ -1237,7 +1264,7 @@ setTimeout(() => modal.remove(), 500)
1237
1264
  } catch (err) {saveBtn.disabled = false
1238
1265
  saveBtn.textContent = 'Ошибка сохранения'
1239
1266
  console.warn('dsh-russian-lang: save override failed', err && err.message || err)
1240
- }}
1267
+ }}}
1241
1268
  document.body.appendChild(modal)
1242
1269
  setTimeout(() => textarea.focus(), 50)
1243
1270
  }
@@ -1336,7 +1363,10 @@ ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian
1336
1363
  } catch (err) { void err; }
1337
1364
  try {ctx.slots.inject('conversation.session.header.utilities', () =>
1338
1365
  ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
1339
- } catch (err) { void err; }}
1366
+ } catch (err) { void err; }
1367
+ module.exports.isProtectedTextNode = isProtectedTextNode
1368
+ module.exports.ZH_WALKER = ZH_WALKER
1369
+ }
1340
1370
  function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
1341
1371
  const scope = props.scope || inj.scope
1342
1372
  const runtime = props.runtime || inj.runtime
@@ -1499,7 +1529,7 @@ const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope
1499
1529
  const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
1500
1530
  const [typo, setTypoState] = React.useState(() =>
1501
1531
  (snap.value && snap.value.typography) || {})
1502
- const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.27',latestVersion: undefined,updateAvailable: false
1532
+ const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.29',latestVersion: undefined,updateAvailable: false
1503
1533
  })
1504
1534
  const [upLoading, setUpLoading] = React.useState(false)
1505
1535
  const [upMsg, setUpMsg] = React.useState(null)
@@ -1566,10 +1596,17 @@ if (res !== false) {setTypoState(next)
1566
1596
  } else {console.warn('dsh-russian-lang: scope.set typography returned false')
1567
1597
  }} catch (err) {console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
1568
1598
  }}
1569
- const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
1570
- const checkbox = (checked, onChange, disabled) =>
1571
- h('input', {type: 'checkbox', checked: !!checked, disabled: !!disabled,className: 'rl-check', onChange: (ev) => onChange(ev),})
1572
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); },})
1573
1610
  const statusLine = status === 'ready'
1574
1611
  ? ''
1575
1612
  : (status === 'unavailable' ? t('statusUnavailable') : t('statusLoading'))
@@ -1584,55 +1621,54 @@ const presetInfo = typeof SYSTEM_PROMPT_PRESETS !== 'undefined' ? SYSTEM_PROMPT_
1584
1621
  if (props && props.view === 'summary') {return h('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
1585
1622
  }
1586
1623
  const page = !!(props && props.view === 'page')
1587
- 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
1588
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')
1589
- }, 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
1590
1627
  try {await Promise.resolve(scope.set('agentPrompt', ev.target.checked))
1591
- } catch (err) {console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
1592
- }}, 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
1593
- scope.set('agentPromptPreset', val)
1594
- } 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
1595
1631
  ) : null
1596
1632
  ) : null
1597
- ),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.27')),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.29')),upStatus.updateAvailable
1598
1634
  ? h('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
1599
1635
  : h('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
1600
1636
  ? h('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
1601
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
1602
1638
  }}, upMsg.text) : null
1603
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'))
1604
- }, 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
1605
- scope.set('translateEngine', val)
1606
- if (val === 'local') fetchTransStatus()
1607
- } 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
1608
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')
1609
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
1610
1645
  }, transStarting ? t('localStarting') : t('localStartBtn')) : null
1611
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
1612
1647
  }}, transMsg.text) : null
1613
1648
  ) : null
1614
- )),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()
1615
1650
  const v = newVal.trim()
1616
1651
  if (!k || !v) return
1617
- const next = Object.assign({}, overrides, { [k]: v })
1618
- try { scope.set('overrides', next) } catch (_e) { void _e }
1619
- setNewKey('')
1652
+ if (await safeSet('overrides', Object.assign({}, overrides, { [k]: v }))) {setNewKey('')
1620
1653
  setNewVal('')
1621
- },}, t('overrideAddBtn'))),overridesCount === 0
1654
+ }},}, t('overrideAddBtn'))),overridesCount === 0
1622
1655
  ? h('div', { className: 'rl-hint-text', style: { fontStyle: 'italic', padding: '4px 0' } }, t('overrideEmpty'))
1623
1656
  : h('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },Object.keys(overrides).map((k) =>
1624
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)
1625
1658
  delete next[k]
1626
- 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
1627
1661
  updateInspectorPill()
1628
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))
1629
1663
  alert(t('overridesCopied'))
1630
- } catch (err) { void err; }}}, t('exportJsonBtn')),h('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt(t('overridesImportPrompt')) : null
1664
+ } catch (err) { void err; }}}, t('exportJsonBtn')),h('button', {type: 'button',className: 'rl-btn',disabled: !!disabled,style: { fontSize: '11px' },onClick: async () => {if (disabled) return
1665
+ const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt(t('overridesImportPrompt')) : null
1631
1666
  if (!input) return
1632
1667
  try {const parsed = JSON.parse(input)
1633
1668
  if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
1634
- scope.set('overrides', merged)
1635
- }} 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.27'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1669
+ const res = await Promise.resolve(scope.set('overrides', merged))
1670
+ if (res === false) throw new Error('Import rejected')
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.29'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1636
1672
  }, '💬 ' + t('reportIssue')),h('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
1637
1673
  }
1638
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)}'
@@ -1646,6 +1682,6 @@ tag.dataset.pluginCss = 'rl-card'
1646
1682
  tag.textContent = RL_CSS
1647
1683
  document.head.appendChild(tag)
1648
1684
  }
1649
- module.exports = { apply, inject: ['slots', 'locale', 'configForms'] }
1685
+ module.exports = { apply, inject: ['slots', 'locale', 'configForms'], resolveTypography, SettingsCard }
1650
1686
  return module.exports
1651
1687
  },})
@@ -1 +1 @@
1
- {"dsh-task-tracker":{"board.back":"В чат","board.backHint":"Закрыть трекер задач и вернуться в чат","field.base":"URL сервера Task Tracker","field.filePath":"Пользовательский путь к файлу tasks.json (необязательно)","field.filePathPlaceholder":"~/.dsh/task-tracker/tasks.json","field.pass":"Пароль / Токен","field.passFile":"Путь к файлу пароля","field.user":"Пользователь","filePath":"Путь к файлу","loading":"Загрузка настроек трекера…","passHint":"Токен доступа к API Task Tracker","passPlaceholder":"Секретный токен","save":"Сохранить","saved":"Настройки сохранены","saving":"Сохранение…","sessions":"Сессии","sub":"Интеграция с внутренним трекером задач и тикетов","subtitle":"API входящих задач (базовый URL и Basic-аутентификация)","tasks.active":"Активные","tasks.add":"+ Добавить","tasks.addTask":"+ Добавить задачу","tasks.all":"Все","tasks.backToChat":"К чату","tasks.cancel":"Отмена","tasks.changeIcon":"Сменить иконку","tasks.chatWithAgent":"Чат с агентом","tasks.close":"Закрыть","tasks.completed":"Выполнено","tasks.confirmDelete":"Удалить эту задачу?","tasks.create":"+ Создать","tasks.createFolder":"Создать папку","tasks.delete":"Удалить","tasks.descPlaceholder":"Подробное описание задачи…","tasks.description":"Описание","tasks.empty":"Задачи не найдены","tasks.emptyInFolder":"В папке нет задач ","tasks.folder":"Папка","tasks.folderBtn":"Папка","tasks.folders":"Папки","tasks.form.desc":"Описание","tasks.form.descPlaceholder":"Подробное описание задачи…","tasks.form.folder":"Папка","tasks.form.priority":"Приоритет","tasks.form.title":"Название","tasks.form.titlePlaceholder":"Название задачи","tasks.loadingTasks":"Загрузка задач…","tasks.markComplete":"Отметить как выполненную","tasks.markIncomplete":"Вернуть в работу","tasks.meta.author":"Автор:","tasks.meta.created":"Создано:","tasks.modal.title":"Задача","tasks.newFolderPrompt":"Название новой папки:","tasks.newPlaceholder":"Что нужно сделать?","tasks.newTask":"Новая задача","tasks.openAgentChat":"💬 Обсудить задачу с агентом","tasks.priority":"Приоритет","tasks.priority.high":"Высокий","tasks.priority.low":"Низкий","tasks.priority.medium":"Средний","tasks.priority.normal":"Обычный","tasks.priorityHigh":"Высокий","tasks.priorityLow":"Низкий","tasks.priorityMedium":"Средний","tasks.priorityNone":"Обычный","tasks.promptTemplate":"Шаблон промпта","tasks.quickAddPlaceholder":"Что нужно сделать? Нажмите Enter для добавления","tasks.refresh":"Обновить","tasks.search":"Поиск задач…","tasks.showAllFolders":"Показать все папки","tasks.startChatWithAgent":"💬 Начать чат с агентом","tasks.status":"Статус","tasks.status.completed":"Завершено","tasks.statusActive":"Активно","tasks.statusCompleted":"Выполнено","tasks.taskDetails":"Детали задачи","tasks.tasksCountSuffix":"задач","tasks.title":"Задачи","tasks.titleLabel":"Название *","title":"Трекер задач (Task Tracker)","unavailable":"Область настроек недоступна","updater.available":"Доступно обновление: v{v}","updater.checkFailed":"Не удалось проверить обновления","updater.checking":"Проверка обновлений…","updater.upToDate":"Актуальная версия","updater.updateFailed":"Ошибка при обновлении","updater.updateNow":"Обновить сейчас","updater.updated":"Успешно обновлено до v{v}. Перезапустите DSH для применения.","updater.updating":"Обновление…","updater.version":"Версия","userHint":"Логин сервисного аккаунта"}}
1
+ {"dsh-task-tracker":{"board.back":"В чат","board.backHint":"Закрыть трекер задач и вернуться в чат","field.base":"URL сервера Task Tracker","field.filePath":"Пользовательский путь к файлу tasks.json (необязательно)","field.filePathPlaceholder":"~/.dsh/task-tracker/tasks.json","field.pass":"Пароль / Токен","field.passFile":"Путь к файлу пароля","field.user":"Пользователь","filePath":"Путь к файлу","loading":"Загрузка настроек трекера…","passHint":"Токен доступа к API Task Tracker","passPlaceholder":"Секретный токен","save":"Сохранить","saved":"Настройки сохранены","saving":"Сохранение…","sessions":"Сессии","sub":"Интеграция с внутренним трекером задач и тикетов","subtitle":"API входящих задач (базовый URL и Basic-аутентификация)","tasks.active":"Активные","tasks.add":"+ Добавить","tasks.addTask":"+ Добавить задачу","tasks.all":"Все","tasks.backToChat":"К чату","tasks.cancel":"Отмена","tasks.changeIcon":"Сменить иконку","tasks.chatWithAgent":"Чат с агентом","tasks.close":"Закрыть","tasks.completed":"Выполнено","tasks.confirmDelete":"Удалить эту задачу?","tasks.create":"+ Создать","tasks.createFolder":"Создать папку","tasks.delete":"Удалить","tasks.descPlaceholder":"Подробное описание задачи…","tasks.description":"Описание","tasks.empty":"Задачи не найдены","tasks.emptyInFolder":"В папке «%s» нет задач","tasks.folder":"Папка","tasks.folderBtn":"Папка","tasks.folders":"Папки","tasks.form.desc":"Описание","tasks.form.descPlaceholder":"Подробное описание задачи…","tasks.form.folder":"Папка","tasks.form.priority":"Приоритет","tasks.form.title":"Название","tasks.form.titlePlaceholder":"Название задачи","tasks.loadingTasks":"Загрузка задач…","tasks.markComplete":"Отметить как выполненную","tasks.markIncomplete":"Вернуть в работу","tasks.meta.author":"Автор:","tasks.meta.created":"Создано:","tasks.modal.title":"Задача","tasks.newFolderPrompt":"Название новой папки:","tasks.newPlaceholder":"Что нужно сделать?","tasks.newTask":"Новая задача","tasks.openAgentChat":"💬 Обсудить задачу с агентом","tasks.priority":"Приоритет","tasks.priority.high":"Высокий","tasks.priority.low":"Низкий","tasks.priority.medium":"Средний","tasks.priority.normal":"Обычный","tasks.priorityHigh":"Высокий","tasks.priorityLow":"Низкий","tasks.priorityMedium":"Средний","tasks.priorityNone":"Обычный","tasks.promptTemplate":"Шаблон промпта","tasks.quickAddPlaceholder":"Что нужно сделать? Нажмите Enter для добавления","tasks.refresh":"Обновить","tasks.search":"Поиск задач…","tasks.showAllFolders":"Показать все папки","tasks.startChatWithAgent":"💬 Начать чат с агентом","tasks.status":"Статус","tasks.status.completed":"Завершено","tasks.statusActive":"Активно","tasks.statusCompleted":"Выполнено","tasks.taskDetails":"Детали задачи","tasks.tasksCountSuffix":"задач","tasks.title":"Задачи","tasks.titleLabel":"Название *","title":"Трекер задач (Task Tracker)","unavailable":"Область настроек недоступна","updater.available":"Доступно обновление: v{v}","updater.checkFailed":"Не удалось проверить обновления","updater.checking":"Проверка обновлений…","updater.upToDate":"Актуальная версия","updater.updateFailed":"Ошибка при обновлении","updater.updateNow":"Обновить сейчас","updater.updated":"Успешно обновлено до v{v}. Перезапустите DSH для применения.","updater.updating":"Обновление…","updater.version":"Версия","userHint":"Логин сервисного аккаунта"}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-russian-lang",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
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",