@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 +20 -14
- package/bin/dsh-i18n.mjs +271 -16
- package/lib/client.js +91 -55
- package/lib/locales/plugins/48-task-tracker.json +1 -1
- package/package.json +1 -1
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.
|
|
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 [Слой перевода и адаптации —
|
|
56
|
-
Loader --> CoreDict[58 Пространств имён ядра DSH /
|
|
57
|
-
Loader --> PluginDict[
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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` в
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
344
|
+
if (glossaryPath) {
|
|
345
|
+
if (!fs.existsSync(glossaryPath)) {
|
|
346
|
+
console.error(`Ошибка: Файл глоссария не найден: ${glossaryPath}`)
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
107
349
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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% (
|
|
399
|
-
const CARD_EN = {cardTitle: 'Russian localization',cardSub: 'Interface language, typography, keyboard layout',badgeRu: '🟢 RU active',badgeEn: '⚪ EN active',badgeCoverage: '🟢 100% (
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
for (const ns of Object.keys(
|
|
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(() => {
|
|
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(() => {
|
|
518
|
-
|
|
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 (
|
|
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]') : []) {
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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) =>
|
|
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) => {
|
|
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) {
|
|
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) =>
|
|
1593
|
-
|
|
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.
|
|
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) => {
|
|
1605
|
-
|
|
1606
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: () => {
|
|
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
|
-
|
|
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":"В папке нет задач
|
|
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.
|
|
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",
|