@goodandready/dsh-russian-lang 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,77 +1,55 @@
1
- window.__ModuleLoader__.load({
2
- id: '@goodandready/dsh-russian-lang',
3
- factory: (require) => {
4
- var module = { exports: {} }
1
+ window.__ModuleLoader__.load({id: '@goodandready/dsh-russian-lang',factory: (require) => {var module = { exports: {} }
5
2
  var React = null
6
3
  try { React = require('react') } catch (e) { }
4
+ let isInspectorActive = false
5
+ let hoverBoxEl = null
6
+ let pillEl = null
7
7
  const numberFormat = new Intl.NumberFormat('ru-RU')
8
8
  const compactNumberFormat = new Intl.NumberFormat('ru-RU', { notation: 'compact', compactDisplay: 'short' })
9
9
  const relativeTimeFormat = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
10
- const datePresetFormats = {
11
- short: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }),
12
- shortDateTime: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
13
- medium: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
14
- long: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }),
15
- time: new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
16
- }
17
- const formatDate = (val, style = 'short') => {
18
- if (val === null || val === undefined || val === '') return ''
10
+ const datePresetFormats = {short: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }),shortDateTime: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }),medium: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }),long: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }),time: new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),}
11
+ const formatDate = (val, style = 'short') => {if (val === null || val === undefined || val === '') return ''
19
12
  const d = val instanceof Date ? val : new Date(typeof val === 'number' && val < 1e12 ? val * 1000 : val)
20
13
  if (isNaN(d.getTime())) return String(val)
21
- if (typeof style === 'string' && datePresetFormats[style]) {
22
- return datePresetFormats[style].format(d)
14
+ if (typeof style === 'string' && datePresetFormats[style]) {return datePresetFormats[style].format(d)
23
15
  }
24
- if (typeof style === 'object' && style !== null) {
25
- try {
26
- return new Intl.DateTimeFormat('ru-RU', style).format(d)
27
- } catch (_) {
28
- return datePresetFormats.short.format(d)
16
+ if (typeof style === 'object' && style !== null) {try {return new Intl.DateTimeFormat('ru-RU', style).format(d)
17
+ } catch (_) {return datePresetFormats.short.format(d)
29
18
  }
30
19
  }
31
20
  return datePresetFormats.short.format(d)
32
21
  }
33
- const formatCompactNumber = (val) => {
34
- if (val === null || val === undefined || val === '') return ''
22
+ const formatCompactNumber = (val) => {if (val === null || val === undefined || val === '') return ''
35
23
  const n = typeof val === 'number' ? val : Number(val)
36
24
  return isNaN(n) ? String(val) : compactNumberFormat.format(n)
37
25
  }
38
- const formatTokens = (count, compact = false) => {
39
- if (count === null || count === undefined || count === '') return ''
26
+ const formatTokens = (count, compact = false) => {if (count === null || count === undefined || count === '') return ''
40
27
  const n = typeof count === 'number' ? count : Number(count)
41
28
  if (isNaN(n)) return String(count)
42
29
  const tokenWord = plural(n, ['токен', 'токена', 'токенов'])
43
- if (compact && n >= 1000) {
44
- return `${compactNumberFormat.format(n)} токенов`
30
+ if (compact && n >= 1000) {return `${compactNumberFormat.format(n)} токенов`
45
31
  }
46
32
  return `${numberFormat.format(n)} ${tokenWord}`
47
33
  }
48
34
  const currencyFormats = new Map()
49
- const getCurrencyFormat = (cur) => {
50
- const c = (cur || 'RUB').toUpperCase()
51
- if (!currencyFormats.has(c)) {
52
- try {
53
- currencyFormats.set(c, new Intl.NumberFormat('ru-RU', { style: 'currency', currency: c }))
54
- } catch (e) {
55
- currencyFormats.set(c, numberFormat)
35
+ const getCurrencyFormat = (cur) => {const c = (cur || 'RUB').toUpperCase()
36
+ if (!currencyFormats.has(c)) {try {currencyFormats.set(c, new Intl.NumberFormat('ru-RU', { style: 'currency', currency: c }))
37
+ } catch (e) {currencyFormats.set(c, numberFormat)
56
38
  }
57
39
  }
58
40
  return currencyFormats.get(c)
59
41
  }
60
- const formatNumber = (val) => {
61
- if (val === null || val === undefined || val === '') return ''
42
+ const formatNumber = (val) => {if (val === null || val === undefined || val === '') return ''
62
43
  const n = typeof val === 'number' ? val : Number(val)
63
44
  return isNaN(n) ? String(val) : numberFormat.format(n)
64
45
  }
65
- const formatCurrency = (val, cur) => {
66
- if (val === null || val === undefined || val === '') return ''
46
+ const formatCurrency = (val, cur) => {if (val === null || val === undefined || val === '') return ''
67
47
  const n = typeof val === 'number' ? val : Number(val)
68
48
  if (isNaN(n)) return String(val)
69
49
  return getCurrencyFormat(cur).format(n)
70
50
  }
71
- const formatRelativeTime = (val, unit) => {
72
- if (val === null || val === undefined || val === '') return ''
73
- if (typeof val === 'number' && typeof unit === 'string') {
74
- return relativeTimeFormat.format(val, unit)
51
+ const formatRelativeTime = (val, unit) => {if (val === null || val === undefined || val === '') return ''
52
+ if (typeof val === 'number' && typeof unit === 'string') {return relativeTimeFormat.format(val, unit)
75
53
  }
76
54
  const ts = val instanceof Date ? val.getTime() : (typeof val === 'number' ? (val < 1e12 ? val * 1000 : val) : Number(val))
77
55
  if (isNaN(ts)) return String(val)
@@ -84,69 +62,45 @@ if (absSec < 2592000) return relativeTimeFormat.format(Math.round(diffSec / 8640
84
62
  if (absSec < 31536000) return relativeTimeFormat.format(Math.round(diffSec / 2592000), 'month')
85
63
  return relativeTimeFormat.format(Math.round(diffSec / 31536000), 'year')
86
64
  }
87
- const INFLECT_CUSTOM = {
88
- 'пользователь': { gen: 'пользователя', dat: 'пользователю', acc: 'пользователя', ins: 'пользователем', pre: 'пользователе' },
89
- 'агент': { gen: 'агента', dat: 'агенту', acc: 'агента', ins: 'агентом', pre: 'агенте' },
90
- 'субагент': { gen: 'субагента', dat: 'субагенту', acc: 'субагента', ins: 'субагентом', pre: 'субагенте' },
91
- 'модель': { gen: 'модели', dat: 'модели', acc: 'модель', ins: 'моделью', pre: 'модели' },
92
- 'промпт': { gen: 'промпта', dat: 'промпту', acc: 'промпт', ins: 'промптом', pre: 'промпте' },
93
- 'инструмент': { gen: 'инструмента', dat: 'инструменту', acc: 'инструмент', ins: 'инструментом', pre: 'инструменте' },
94
- 'сессия': { gen: 'сессии', dat: 'сессии', acc: 'сессию', ins: 'сессией', pre: 'сессии' },
95
- 'ветка': { gen: 'ветки', dat: 'ветке', acc: 'ветку', ins: 'веткой', pre: 'ветке' },
96
- 'файл': { gen: 'файла', dat: 'файлу', acc: 'файл', ins: 'файлом', pre: 'файле' },
97
- 'папка': { gen: 'папки', dat: 'папке', acc: 'папку', ins: 'папкой', pre: 'папке' }
65
+ const INFLECT_CUSTOM = {'пользователь': { gen: 'пользователя', dat: 'пользователю', acc: 'пользователя', ins: 'пользователем', pre: 'пользователе' },'агент': { gen: 'агента', dat: 'агенту', acc: 'агента', ins: 'агентом', pre: 'агенте' },'субагент': { gen: 'субагента', dat: 'субагенту', acc: 'субагента', ins: 'субагентом', pre: 'субагенте' },'модель': { gen: 'модели', dat: 'модели', acc: 'модель', ins: 'моделью', pre: 'модели' },'промпт': { gen: 'промпта', dat: 'промпту', acc: 'промпт', ins: 'промптом', pre: 'промпте' },'инструмент': { gen: 'инструмента', dat: 'инструменту', acc: 'инструмент', ins: 'инструментом', pre: 'инструменте' },'сессия': { gen: 'сессии', dat: 'сессии', acc: 'сессию', ins: 'сессией', pre: 'сессии' },'ветка': { gen: 'ветки', dat: 'ветке', acc: 'ветку', ins: 'веткой', pre: 'ветке' },'файл': { gen: 'файла', dat: 'файлу', acc: 'файл', ins: 'файлом', pre: 'файле' },'папка': { gen: 'папки', dat: 'папке', acc: 'папку', ins: 'папкой', pre: 'папке' }
98
66
  }
99
67
  const keepCase = (src, out) => (src[0] === src[0].toUpperCase() ? out[0].toUpperCase() + out.slice(1) : out)
100
- const inflectWord = (word, cName) => {
101
- if (!word || typeof word !== 'string') return word
68
+ const inflectWord = (word, cName) => {if (!word || typeof word !== 'string') return word
102
69
  const lower = word.toLowerCase()
103
- if (INFLECT_CUSTOM[lower] && INFLECT_CUSTOM[lower][cName]) {
104
- return keepCase(word, INFLECT_CUSTOM[lower][cName])
70
+ if (INFLECT_CUSTOM[lower] && INFLECT_CUSTOM[lower][cName]) {return keepCase(word, INFLECT_CUSTOM[lower][cName])
105
71
  }
106
72
  if (/[a-zA-Z0-9_-]/.test(word) || /^[А-ЯЁ]{2,}$/.test(word)) return word
107
73
  if (/[оеиую]$/i.test(word) && !/(ко|ло|но|то|во|ро|до|по|со|мо|го)$/i.test(word)) return word
108
74
  const w = lower
109
75
  const endings = [
110
- ['ия', 2, (s) => ({ gen: s + 'ии', dat: s + 'ии', acc: s + 'ию', ins: s + 'ией', pre: s + 'ии' })],
111
- ['а', 1, (s) => {
112
- const genEnd = /[гкхжшчщ]/.test(s.slice(-1)) ? 'и' : 'ы'
76
+ ['ия', 2, (s) => ({ gen: s + 'ии', dat: s + 'ии', acc: s + 'ию', ins: s + 'ией', pre: s + 'ии' })],['а', 1, (s) => {const genEnd = /[гкхжшчщ]/.test(s.slice(-1)) ? 'и' : 'ы'
113
77
  return { gen: s + genEnd, dat: s + 'е', acc: s + 'у', ins: s + 'ой', pre: s + 'е' }
114
- }],
115
- ['я', 1, (s) => ({ gen: s + 'и', dat: s + 'е', acc: s + 'ю', ins: s + 'ей', pre: s + 'е' })],
116
- ['ь', 1, (s) => ({ gen: s + 'и', dat: s + 'и', acc: s + 'ь', ins: s + 'ью', pre: s + 'и' })],
117
- ['й', 1, (s) => ({ gen: s + 'я', dat: s + 'ю', acc: s + 'я', ins: s + 'ем', pre: s + 'е' })]
78
+ }],['я', 1, (s) => ({ gen: s + 'и', dat: s + 'е', acc: s + 'ю', ins: s + 'ей', pre: s + 'е' })],['ь', 1, (s) => ({ gen: s + 'и', dat: s + 'и', acc: s + 'ь', ins: s + 'ью', pre: s + 'и' })],['й', 1, (s) => ({ gen: s + 'я', dat: s + 'ю', acc: s + 'я', ins: s + 'ем', pre: s + 'е' })]
118
79
  ]
119
- for (const [suffix, cut, build] of endings) {
120
- if (w.endsWith(suffix)) return keepCase(word, build(w.slice(0, -cut))[cName] || w)
80
+ for (const [suffix, cut, build] of endings) {if (w.endsWith(suffix)) return keepCase(word, build(w.slice(0, -cut))[cName] || w)
121
81
  }
122
- if (/[бвгджзклмнпрстфхцчшщ]$/.test(w)) {
123
- const map = { gen: w + 'а', dat: w + 'у', acc: w, ins: w + 'ом', pre: w + 'е' }
82
+ if (/[бвгджзклмнпрстфхцчшщ]$/.test(w)) {const map = { gen: w + 'а', dat: w + 'у', acc: w, ins: w + 'ом', pre: w + 'е' }
124
83
  return keepCase(word, map[cName] || w)
125
84
  }
126
85
  return word
127
86
  }
128
87
  const INFLECT_CASES = new Set(['gen', 'dat', 'acc', 'ins', 'pre'])
129
- const inflect = (phrase, cName) => {
130
- if (!phrase || typeof phrase !== 'string') return phrase
88
+ const inflect = (phrase, cName) => {if (!phrase || typeof phrase !== 'string') return phrase
131
89
  if (!INFLECT_CASES.has(cName)) return phrase
132
90
  return phrase.split(' ').map((w) => inflectWord(w, cName)).join(' ')
133
91
  }
134
92
  const pluralRules = new Intl.PluralRules('ru-RU')
135
93
  const pluralForm = (n) => pluralRules.select(n)
136
- function plural(count, forms, withCount = false) {
137
- const n = typeof count === 'number' ? count : (Number(count) || 0)
94
+ function plural(count, forms, withCount = false) {const n = typeof count === 'number' ? count : (Number(count) || 0)
138
95
  let one = '', few = '', many = ''
139
- if (Array.isArray(forms)) {
140
- one = forms[0] || ''
96
+ if (Array.isArray(forms)) {one = forms[0] || ''
141
97
  few = forms[1] || one
142
98
  many = forms[2] || few || one
143
- } else if (typeof forms === 'string') {
144
- one = forms
99
+ } else if (typeof forms === 'string') {one = forms
145
100
  few = arguments[2] || one
146
101
  many = arguments[3] || few || one
147
102
  withCount = arguments[4] || false
148
- } else {
149
- return ''
103
+ } else {return ''
150
104
  }
151
105
  const form = pluralRules.select(n)
152
106
  let word = many
@@ -155,10 +109,8 @@ else if (form === 'few') word = few
155
109
  else if (form === 'many') word = many
156
110
  return withCount ? `${formatNumber(n)} ${word}` : word
157
111
  }
158
- const fill = (template, params) => {
159
- if (!params || typeof params !== 'object') return String(template)
160
- return String(template).replace(/\{(\w+)(?::([^}]+))?\}/g, (match, name, spec) => {
161
- if (!(name in params)) return match
112
+ const fill = (template, params) => {if (!params || typeof params !== 'object') return String(template)
113
+ return String(template).replace(/\{(\w+)(?::([^}]+))?\}/g, (match, name, spec) => {if (!(name in params)) return match
162
114
  const val = params[name]
163
115
  if (!spec) return String(val)
164
116
  if (spec === 'number') return formatNumber(val)
@@ -169,16 +121,14 @@ if (spec === 'reltime') return formatRelativeTime(val)
169
121
  if (spec === 'currency') return formatCurrency(val, params.currency || 'RUB')
170
122
  if (spec === 'date') return formatDate(val)
171
123
  if (spec.startsWith('date:')) return formatDate(val, spec.slice(5))
172
- if (spec.startsWith('plural:')) {
173
- const parts = spec.slice(7).split(',')
124
+ if (spec.startsWith('plural:')) {const parts = spec.slice(7).split(',')
174
125
  return plural(val, parts)
175
126
  }
176
127
  if (INFLECT_CASES.has(spec)) return inflect(String(val), spec)
177
128
  return String(val)
178
129
  })
179
130
  }
180
- const stemRussian = (word) => {
181
- if (!word || typeof word !== 'string') return ''
131
+ const stemRussian = (word) => {if (!word || typeof word !== 'string') return ''
182
132
  let w = word.toLowerCase().trim()
183
133
  if (w.length < 4) return w
184
134
  w = w.replace(/(?:вшись|вши|ившись|ивши|ывшись|ывши|ив|ыв)$/, '')
@@ -188,14 +138,10 @@ w = w.replace(/(?:ила|ыла|ена|ейте|уйте|ите|или|ыли|е
188
138
  w = w.replace(/(?:ами|ями|иями|ией|иям|ием|ах|ях|иях|ев|ов|ие|ье|ей|ой|ий|ям|ем|ам|ом|а|е|и|о|у|ы|ь|ю|я)$/, '')
189
139
  return w.length >= 2 ? w : word.toLowerCase()
190
140
  }
191
- const EN_RU_KEYS = {
192
- 'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н', 'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', '[': 'х', ']': 'ъ',
193
- 'a': 'ф', 's': 'ы', 'd': 'в', 'f': 'а', 'g': 'п', 'h': 'р', 'j': 'о', 'k': 'л', 'l': 'д', ';': 'ж', "'": 'э',
194
- 'z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и', 'n': 'т', 'm': 'ь', ',': 'б', '.': 'ю'
141
+ const EN_RU_KEYS = {'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н', 'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', '[': 'х', ']': 'ъ','a': 'ф', 's': 'ы', 'd': 'в', 'f': 'а', 'g': 'п', 'h': 'р', 'j': 'о', 'k': 'л', 'l': 'д', ';': 'ж', "'": 'э','z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и', 'n': 'т', 'm': 'ь', ',': 'б', '.': 'ю'
195
142
  }
196
143
  const translitEnToRu = (str) => str.toLowerCase().split('').map((c) => EN_RU_KEYS[c] || c).join('')
197
- const fuzzyMatchRu = (query, target) => {
198
- if (!query || !target) return 0
144
+ const fuzzyMatchRu = (query, target) => {if (!query || !target) return 0
199
145
  const q = query.toLowerCase().trim()
200
146
  const t = target.toLowerCase().trim()
201
147
  if (t === q) return 100
@@ -204,44 +150,17 @@ if (t.includes(translitEnToRu(q))) return 85
204
150
  const qStems = q.split(/\s+/).map(stemRussian).filter(Boolean)
205
151
  const tStems = t.split(/\s+/).map(stemRussian).filter(Boolean)
206
152
  let matched = 0
207
- for (const qs of qStems) {
208
- if (tStems.some((ts) => ts.startsWith(qs) || qs.startsWith(ts))) matched++
153
+ for (const qs of qStems) {if (tStems.some((ts) => ts.startsWith(qs) || qs.startsWith(ts))) matched++
209
154
  }
210
155
  if (matched === qStems.length && qStems.length > 0) return 80
211
156
  if (matched > 0) return 50
212
157
  return 0
213
158
  }
214
- const ERROR_MAP = {
215
- ENOENT: { title: 'Файл не найден', message: 'Указанный файл или директория не существуют', hint: 'Проверьте правильность указанного пути к файлу.' },
216
- EACCES: { title: 'Отказано в доступе', message: 'Недостаточно прав для чтения или записи', hint: 'Проверьте права доступа к файлу или директории (chmod/chown).' },
217
- EPERM: { title: 'Операция запрещена', message: 'Недостаточно системных привилегий', hint: 'Запустите процесс с соответствующими правами.' },
218
- ECONNREFUSED: { title: 'Соединение отклонено', message: 'Целевой сервер или сервис не отвечает', hint: 'Убедитесь, что локальный или удаленный сервис запущен и слушает порт.' },
219
- ECONNRESET: { title: 'Сброс соединения', message: 'Соединение было принудительно разорвано удаленной стороной', hint: 'Проверьте стабильность сети и работу целевого сервера.' },
220
- ETIMEDOUT: { title: 'Таймаут соединения', message: 'Превышено время ожидания ответа', hint: 'Проверьте стабильность сети или увеличьте лимит ожидания.' },
221
- ENOTFOUND: { title: 'Хост не найден', message: 'Не удалось разрешить сетевой адрес', hint: 'Проверьте правильность URL или настройки DNS.' },
222
- EADDRINUSE: { title: 'Порт уже занят', message: 'Сетевой порт используется другим процессом', hint: 'Остановите конфликтующий процесс или выберите другой порт.' },
223
- ENOSPC: { title: 'Недостаточно места на диске', message: 'На устройстве закончилось свободное пространство', hint: 'Освободите место на диске и повторите операцию.' },
224
- 400: { title: 'Некорректный запрос (Bad Request)', message: 'Параметры запроса не соответствуют ожидаемому формату', hint: 'Проверьте синтаксис команды или переданные аргументы.' },
225
- 401: { title: 'Требуется авторизация', message: 'API-ключ или токен отсутствуют или недействительны', hint: 'Проверьте настройки учетных данных и актуальность токена.' },
226
- 403: { title: 'Доступ запрещен', message: 'Недостаточно прав для выполнения операции', hint: 'Проверьте область действия токена или права роли.' },
227
- 404: { title: 'Ресурс не найден', message: 'Запрошенный адрес или объект не существует', hint: 'Проверьте правильность пути или идентификатора ресурса.' },
228
- 429: { title: 'Превышен лимит запросов', message: 'Слишком много запросов (Rate Limit)', hint: 'Подождите несколько минут перед повторным запросом.' },
229
- 500: { title: 'Внутренняя ошибка сервера', message: 'На стороне сервера произошел сбой', hint: 'Попробуйте повторить запрос позже или проверьте серверные логи.' },
230
- 502: { title: 'Ошибочный шлюз (Bad Gateway)', message: 'Промежуточный прокси не получил корректный ответ', hint: 'Проверьте работу нижележащей службы или upstream-сервера.' },
231
- 503: { title: 'Служба временно недоступна', message: 'Сервер перегружен или находится на обслуживании', hint: 'Попробуйте повторить операцию через некоторое время.' },
232
- 504: { title: 'Шлюз не отвечает (Gateway Timeout)', message: 'Превышено время ожидания ответа от upstream-сервера', hint: 'Попробуйте повторить запрос позже.' },
233
- FETCH_FAILED: { title: 'Сетевой запрос не удался', message: 'Не удалось связаться с сервером (Failed to fetch)', hint: 'Проверьте сетевое подключение и доступность адреса.' },
234
- CORS_ERROR: { title: 'Ошибка CORS', message: 'Запрос заблокирован политикой безопасности браузера', hint: 'Проверьте заголовки разрешённых источников на сервере.' },
235
- WS_CLOSED: { title: 'Разрыв WebSocket', message: 'Связь с сервером по WebSocket прервана', hint: 'Проверьте работу сервера и сетевой статус.' },
236
- QUOTA_EXCEEDED: { title: 'Исчерпана квота API', message: 'Превышен лимит запросов или баланс провайдера', hint: 'Проверьте баланс API-ключа в кабинете провайдера.' },
237
- CONTEXT_OVERFLOW: { title: 'Превышен контекст', message: 'Объём диалога превышает размер контекстного окна', hint: 'Очистите или сожмите историю сообщений сессии.' },
238
- MODEL_NOT_FOUND: { title: 'Модель не найдена', message: 'Указанная модель отсутствует или недоступна', hint: 'Проверьте идентификатор модели в настройках.' }
239
- }
240
- const fromMap = (key, rawMsg) => ({
241
- code: String(key), title: ERROR_MAP[key].title, message: ERROR_MAP[key].message, hint: ERROR_MAP[key].hint, raw: rawMsg
159
+ const ERROR_MAP = {ENOENT: { title: 'Файл не найден', message: 'Указанный файл или директория не существуют', hint: 'Проверьте правильность указанного пути к файлу.' },EACCES: { title: 'Отказано в доступе', message: 'Недостаточно прав для чтения или записи', hint: 'Проверьте права доступа к файлу или директории (chmod/chown).' },EPERM: { title: 'Операция запрещена', message: 'Недостаточно системных привилегий', hint: 'Запустите процесс с соответствующими правами.' },ECONNREFUSED: { title: 'Соединение отклонено', message: 'Целевой сервер или сервис не отвечает', hint: 'Убедитесь, что локальный или удаленный сервис запущен и слушает порт.' },ECONNRESET: { title: 'Сброс соединения', message: 'Соединение было принудительно разорвано удаленной стороной', hint: 'Проверьте стабильность сети и работу целевого сервера.' },ETIMEDOUT: { title: 'Таймаут соединения', message: 'Превышено время ожидания ответа', hint: 'Проверьте стабильность сети или увеличьте лимит ожидания.' },ENOTFOUND: { title: 'Хост не найден', message: 'Не удалось разрешить сетевой адрес', hint: 'Проверьте правильность URL или настройки DNS.' },EADDRINUSE: { title: 'Порт уже занят', message: 'Сетевой порт используется другим процессом', hint: 'Остановите конфликтующий процесс или выберите другой порт.' },ENOSPC: { title: 'Недостаточно места на диске', message: 'На устройстве закончилось свободное пространство', hint: 'Освободите место на диске и повторите операцию.' },400: { title: 'Некорректный запрос (Bad Request)', message: 'Параметры запроса не соответствуют ожидаемому формату', hint: 'Проверьте синтаксис команды или переданные аргументы.' },401: { title: 'Требуется авторизация', message: 'API-ключ или токен отсутствуют или недействительны', hint: 'Проверьте настройки учетных данных и актуальность токена.' },403: { title: 'Доступ запрещен', message: 'Недостаточно прав для выполнения операции', hint: 'Проверьте область действия токена или права роли.' },404: { title: 'Ресурс не найден', message: 'Запрошенный адрес или объект не существует', hint: 'Проверьте правильность пути или идентификатора ресурса.' },429: { title: 'Превышен лимит запросов', message: 'Слишком много запросов (Rate Limit)', hint: 'Подождите несколько минут перед повторным запросом.' },500: { title: 'Внутренняя ошибка сервера', message: 'На стороне сервера произошел сбой', hint: 'Попробуйте повторить запрос позже или проверьте серверные логи.' },502: { title: 'Ошибочный шлюз (Bad Gateway)', message: 'Промежуточный прокси не получил корректный ответ', hint: 'Проверьте работу нижележащей службы или upstream-сервера.' },503: { title: 'Служба временно недоступна', message: 'Сервер перегружен или находится на обслуживании', hint: 'Попробуйте повторить операцию через некоторое время.' },504: { title: 'Шлюз не отвечает (Gateway Timeout)', message: 'Превышено время ожидания ответа от upstream-сервера', hint: 'Попробуйте повторить запрос позже.' },FETCH_FAILED: { title: 'Сетевой запрос не удался', message: 'Не удалось связаться с сервером (Failed to fetch)', hint: 'Проверьте сетевое подключение и доступность адреса.' },CORS_ERROR: { title: 'Ошибка CORS', message: 'Запрос заблокирован политикой безопасности браузера', hint: 'Проверьте заголовки разрешённых источников на сервере.' },WS_CLOSED: { title: 'Разрыв WebSocket', message: 'Связь с сервером по WebSocket прервана', hint: 'Проверьте работу сервера и сетевой статус.' },QUOTA_EXCEEDED: { title: 'Исчерпана квота API', message: 'Превышен лимит запросов или баланс провайдера', hint: 'Проверьте баланс API-ключа в кабинете провайдера.' },CONTEXT_OVERFLOW: { title: 'Превышен контекст', message: 'Объём диалога превышает размер контекстного окна', hint: 'Очистите или сожмите историю сообщений сессии.' },MODEL_NOT_FOUND: { title: 'Модель не найдена', message: 'Указанная модель отсутствует или недоступна', hint: 'Проверьте идентификатор модели в настройках.' }
160
+ }
161
+ const fromMap = (key, rawMsg) => ({code: String(key), title: ERROR_MAP[key].title, message: ERROR_MAP[key].message, hint: ERROR_MAP[key].hint, raw: rawMsg
242
162
  })
243
- const humanizeError = (err) => {
244
- if (!err) return null
163
+ const humanizeError = (err) => {if (!err) return null
245
164
  const rawMsg = typeof err === 'string' ? err : (err.message || String(err))
246
165
  const code = err.code || (rawMsg.match(/\b(E[A-Z]{2,20})\b/) || [])[1]
247
166
  const status = err.status || err.statusCode || (err.response && err.response.status) || (rawMsg.match(/\b([45]\d{2})\b/) || [])[1]
@@ -257,45 +176,26 @@ if (/context.*(?:exceeded|overflow|length)/i.test(rawMsg)) return fromMap('CONTE
257
176
  if (/model.*not.*found|model.*does not exist/i.test(rawMsg)) return fromMap('MODEL_NOT_FOUND', rawMsg)
258
177
  return { code: 'UNKNOWN', title: 'Ошибка операции', message: rawMsg, hint: 'Проверьте параметры операции и логи.', raw: rawMsg }
259
178
  }
260
- const formatErrorToast = (err) => {
261
- const h = humanizeError(err)
179
+ const formatErrorToast = (err) => {const h = humanizeError(err)
262
180
  if (!h) return null
263
- return {
264
- type: 'error',
265
- title: h.title,
266
- description: h.message + (h.hint ? ' ' + h.hint : '')
181
+ return {type: 'error',title: h.title,description: h.message + (h.hint ? ' ' + h.hint : '')
267
182
  }
268
183
  }
269
- const makeIssueUrl = (opts = {}, version = '') => {
270
- const repo = 'GooDAnDReaDY/dsh-russian-lang'
184
+ const makeIssueUrl = (opts = {}, version = '') => {const repo = 'GooDAnDReaDY/dsh-russian-lang'
271
185
  const title = opts.title || (opts.plugin
272
186
  ? `[Перевод] Запрос локализации для плагина ${opts.plugin}`
273
187
  : '[Ошибка перевода] Неточный перевод фразы')
274
188
  const bodyLines = [
275
- '### Описание проблемы',
276
- opts.description || (opts.plugin
189
+ '### Описание проблемы',opts.description || (opts.plugin
277
190
  ? `Просьба добавить русскую локализацию для плагина \`${opts.plugin}\`.`
278
- : 'Обнаружена неточность в переводе интерфейса.'),
279
- '',
280
- '### Технический контекст',
281
- opts.ns ? `- **Namespace**: \`${opts.ns}\`` : null,
282
- opts.key ? `- **Ключ**: \`${opts.key}\`` : null,
283
- opts.en ? `- **Оригинал (EN)**: ${opts.en}` : null,
284
- opts.ru ? `- **Текущий перевод (RU)**: ${opts.ru}` : null,
285
- opts.plugin ? `- **Плагин**: \`${opts.plugin}\`` : null,
286
- version ? `- **Версия dsh-russian-lang**: \`${version}\`` : null,
287
- typeof navigator !== 'undefined' ? `- **User Agent**: \`${navigator.userAgent}\`` : null,
288
- '',
289
- '### Предлагаемый вариант перевода',
290
- opts.proposal || '_Опишите ваш вариант перевода..._'
191
+ : 'Обнаружена неточность в переводе интерфейса.'),'','### Технический контекст',opts.ns ? `- **Namespace**: \`${opts.ns}\`` : null,opts.key ? `- **Ключ**: \`${opts.key}\`` : null,opts.en ? `- **Оригинал (EN)**: ${opts.en}` : null,opts.ru ? `- **Текущий перевод (RU)**: ${opts.ru}` : null,opts.plugin ? `- **Плагин**: \`${opts.plugin}\`` : null,version ? `- **Версия dsh-russian-lang**: \`${version}\`` : null,typeof navigator !== 'undefined' ? `- **User Agent**: \`${navigator.userAgent}\`` : null,'','### Предлагаемый вариант перевода',opts.proposal || '_Опишите ваш вариант перевода..._'
291
192
  ].filter(Boolean)
292
193
  const params = new URLSearchParams()
293
194
  params.set('title', title)
294
195
  params.set('body', bodyLines.join('\n'))
295
196
  return `https://github.com/${repo}/issues/new?` + params.toString()
296
197
  }
297
- const makePluginLocalizationStatus = (dicts) => (ns) => {
298
- const none = { status: 'none', count: 0, label: 'RU отсутствует' }
198
+ const makePluginLocalizationStatus = (dicts) => (ns) => {const none = { status: 'none', count: 0, label: 'RU отсутствует' }
299
199
  if (!ns) return none
300
200
  const count = Object.keys((dicts && dicts[ns]) || {}).length
301
201
  return count > 0 ? { status: 'full', count, label: `RU: ${count} строк` } : none
@@ -314,41 +214,32 @@ TYPO_SHORT.has(word) ? lead + word + ' ' : match
314
214
  ))
315
215
  const YO_EDGE_L = '(?<![а-яёА-ЯЁ])'
316
216
  const YO_EDGE_R = '(?![а-яёА-ЯЁ])'
317
- const yoCase = (src, repl) => {
318
- if (src === src.toUpperCase() && src !== src.toLowerCase()) return repl.toUpperCase()
217
+ const yoCase = (src, repl) => {if (src === src.toUpperCase() && src !== src.toLowerCase()) return repl.toUpperCase()
319
218
  if (src[0] === src[0].toUpperCase()) return repl[0].toUpperCase() + repl.slice(1)
320
219
  return repl
321
220
  }
322
- const makeTypoYo = (pairs) => {
323
- const list = typeof pairs === 'string'
221
+ const makeTypoYo = (pairs) => {const list = typeof pairs === 'string'
324
222
  ? pairs.split('|').filter(Boolean).map((s) => s.split(':'))
325
223
  : (pairs || [])
326
224
  const compiled = list.map((p) => [new RegExp(YO_EDGE_L + p[0] + YO_EDGE_R, 'gi'), p[1]])
327
- return (text) => {
328
- for (const [re, repl] of compiled) text = text.replace(re, (match) => yoCase(match, repl))
225
+ return (text) => {for (const [re, repl] of compiled) text = text.replace(re, (match) => yoCase(match, repl))
329
226
  return text
330
227
  }
331
228
  }
332
229
  const LAYOUT_LAT_TO_CYR = { ...EN_RU_KEYS, '/': '.', '`': 'ё' }
333
- const LAYOUT_CYR_TO_LAT = (() => {
334
- const out = {}
230
+ const LAYOUT_CYR_TO_LAT = (() => {const out = {}
335
231
  for (const k in LAYOUT_LAT_TO_CYR) out[LAYOUT_LAT_TO_CYR[k]] = k
336
232
  return out
337
233
  })()
338
- const translit = (word, map) => {
339
- let out = ''
234
+ const translit = (word, map) => {let out = ''
340
235
  for (const ch of word.toLowerCase()) out += map[ch] !== undefined ? map[ch] : ch
341
236
  return out
342
237
  }
343
- const makeLayout = (freq, localDict = new Set()) => {
344
- const ruWordFraction = (text) => {
345
- const words = text.toLowerCase().split(/[^а-яё]+/).filter(Boolean)
238
+ const makeLayout = (freq, localDict = new Set()) => {const ruWordFraction = (text) => {const words = text.toLowerCase().split(/[^а-яё]+/).filter(Boolean)
346
239
  if (!words.length) return 0
347
240
  return words.filter((w) => freq.has(w) || localDict.has(w)).length / words.length
348
241
  }
349
- const candidate = (value, direction) => {
350
- if (direction === 'lat2cyr') {
351
- const converted = translit(value, LAYOUT_LAT_TO_CYR)
242
+ const candidate = (value, direction) => {if (direction === 'lat2cyr') {const converted = translit(value, LAYOUT_LAT_TO_CYR)
352
243
  if (!/[а-яё]{2}/.test(converted)) return null
353
244
  if (ruWordFraction(converted) < 0.7) return null
354
245
  return { converted }
@@ -356,19 +247,15 @@ return { converted }
356
247
  const converted = translit(value, LAYOUT_CYR_TO_LAT)
357
248
  return converted.startsWith('/') ? { converted } : null
358
249
  }
359
- const learnWords = (text) => {
360
- for (const w of text.toLowerCase().split(/[^а-яё]+/).filter(Boolean)) {
361
- if (w.length >= 3) localDict.add(w)
250
+ const learnWords = (text) => {for (const w of text.toLowerCase().split(/[^а-яё]+/).filter(Boolean)) {if (w.length >= 3) localDict.add(w)
362
251
  }
363
252
  }
364
253
  return { ruWordFraction, candidate, learnWords, localDict }
365
254
  }
366
255
  const SHORT_WORDS_RE = /(^|[\s(«])([вВнНсСпПоОкКуУиИаА]|из|от|до|за|по|со|во|ко|об|на|под|над|при|про|без|для|не|ни|но)( )/g
367
- const formatInputLive = (text) => {
368
- if (!text || typeof text !== 'string') return text
256
+ const formatInputLive = (text) => {if (!text || typeof text !== 'string') return text
369
257
  const parts = text.split(/(```[\s\S]*?```|`[^`\n]*`)/g)
370
- for (let i = 0; i < parts.length; i += 2) {
371
- let s = parts[i]
258
+ for (let i = 0; i < parts.length; i += 2) {let s = parts[i]
372
259
  if (!s) continue
373
260
  s = s.replace(/--/g, '—')
374
261
  s = s.replace(/(^|[\s([{-])"/g, '$1«')
@@ -378,77 +265,44 @@ parts[i] = s
378
265
  }
379
266
  return parts.join('')
380
267
  }
381
- const RUSSIAN_SLASH_ALIASES = {
382
- '/цель': '/goal',
383
- '/справка': '/help',
384
- '/задача': '/task',
385
- '/контекст': '/context',
386
- '/сжать': '/compact',
387
- '/план': '/plan',
388
- '/экспорт': '/export',
389
- '/отзыв': '/feedback',
390
- '/разрешение': '/permission',
391
- '/разрешения': '/permission',
392
- '/память': '/memory',
393
- '/помощь': '/help',
394
- '/очистить': '/clear',
395
- '/сессия': '/session',
396
- '/модель': '/model'
397
- }
398
- const expandSlashAlias = (input) => {
399
- if (!input || typeof input !== 'string' || !input.startsWith('/')) return input
268
+ const RUSSIAN_SLASH_ALIASES = {'/цель': '/goal','/справка': '/help','/задача': '/task','/контекст': '/context','/сжать': '/compact','/план': '/plan','/экспорт': '/export','/отзыв': '/feedback','/разрешение': '/permission','/разрешения': '/permission','/память': '/memory','/помощь': '/help','/очистить': '/clear','/сессия': '/session','/модель': '/model'
269
+ }
270
+ const expandSlashAlias = (input) => {if (!input || typeof input !== 'string' || !input.startsWith('/')) return input
400
271
  const match = input.match(/^(\s*\/[^\s]+)(.*)$/)
401
272
  if (!match) return input
402
273
  const [, cmd, rest] = match
403
274
  const lowerCmd = cmd.trim().toLowerCase()
404
- if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {
405
- return RUSSIAN_SLASH_ALIASES[lowerCmd] + rest
275
+ if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {return RUSSIAN_SLASH_ALIASES[lowerCmd] + rest
406
276
  }
407
277
  return input
408
278
  }
409
279
  const PHONETIC_LAT_TO_CYR = [
410
- ['shch', 'щ'], ['yo', 'ё'], ['zh', 'ж'], ['ch', 'ч'], ['sh', 'ш'],
411
- ['yu', 'ю'], ['ya', 'я'], ['ts', 'ц'],
412
- ['a', 'а'], ['b', 'б'], ['v', 'в'], ['g', 'г'], ['d', 'д'], ['e', 'е'],
413
- ['z', 'з'], ['i', 'и'], ['j', 'й'], ['k', 'к'], ['l', 'л'], ['m', 'м'],
414
- ['n', 'н'], ['o', 'о'], ['p', 'п'], ['r', 'р'], ['s', 'с'], ['t', 'т'],
415
- ['u', 'у'], ['f', 'ф'], ['h', 'х'], ['c', 'ц'], ['y', 'ы'], ['x', 'кс']
280
+ ['shch', 'щ'], ['yo', 'ё'], ['zh', 'ж'], ['ch', 'ч'], ['sh', 'ш'],['yu', 'ю'], ['ya', 'я'], ['ts', 'ц'],['a', 'а'], ['b', 'б'], ['v', 'в'], ['g', 'г'], ['d', 'д'], ['e', 'е'],['z', 'з'], ['i', 'и'], ['j', 'й'], ['k', 'к'], ['l', 'л'], ['m', 'м'],['n', 'н'], ['o', 'о'], ['p', 'п'], ['r', 'р'], ['s', 'с'], ['t', 'т'],['u', 'у'], ['f', 'ф'], ['h', 'х'], ['c', 'ц'], ['y', 'ы'], ['x', 'кс']
416
281
  ]
417
282
  const PHONETIC_CYR_TO_LAT = [
418
- ['щ', 'shch'], ['ё', 'yo'], ['ж', 'zh'], ['ч', 'ch'], ['ш', 'sh'],
419
- ['ю', 'yu'], ['я', 'ya'], ['ц', 'ts'],
420
- ['а', 'a'], ['б', 'b'], ['в', 'v'], ['г', 'g'], ['д', 'd'], ['е', 'e'],
421
- ['з', 'z'], ['и', 'i'], ['й', 'j'], ['к', 'k'], ['л', 'l'], ['м', 'm'],
422
- ['н', 'n'], ['о', 'o'], ['п', 'p'], ['р', 'r'], ['с', 's'], ['т', 't'],
423
- ['у', 'u'], ['ф', 'f'], ['х', 'h'], ['ы', 'y'], ['э', 'e'], ['ъ', ''], ['ь', '']
283
+ ['щ', 'shch'], ['ё', 'yo'], ['ж', 'zh'], ['ч', 'ch'], ['ш', 'sh'],['ю', 'yu'], ['я', 'ya'], ['ц', 'ts'],['а', 'a'], ['б', 'b'], ['в', 'v'], ['г', 'g'], ['д', 'd'], ['е', 'e'],['з', 'z'], ['и', 'i'], ['й', 'j'], ['к', 'k'], ['л', 'l'], ['м', 'm'],['н', 'n'], ['о', 'o'], ['п', 'p'], ['р', 'r'], ['с', 's'], ['т', 't'],['у', 'u'], ['ф', 'f'], ['х', 'h'], ['ы', 'y'], ['э', 'e'], ['ъ', ''], ['ь', '']
424
284
  ]
425
- const phoneticTranslit = (text, direction = 'lat2cyr') => {
426
- if (!text || typeof text !== 'string') return text
285
+ const phoneticTranslit = (text, direction = 'lat2cyr') => {if (!text || typeof text !== 'string') return text
427
286
  let res = text
428
287
  const pairs = direction === 'lat2cyr' ? PHONETIC_LAT_TO_CYR : PHONETIC_CYR_TO_LAT
429
- for (const [from, to] of pairs) {
430
- const fromUpper = from.toUpperCase()
288
+ for (const [from, to] of pairs) {const fromUpper = from.toUpperCase()
431
289
  const fromTitle = from[0].toUpperCase() + from.slice(1)
432
290
  const toUpper = to.toUpperCase()
433
291
  const toTitle = to[0] ? to[0].toUpperCase() + to.slice(1) : ''
434
- if (from.length > 1) {
435
- res = res.replaceAll(fromUpper, toUpper)
292
+ if (from.length > 1) {res = res.replaceAll(fromUpper, toUpper)
436
293
  res = res.replaceAll(fromTitle, toTitle)
437
294
  }
438
295
  res = res.replaceAll(from, to)
439
- if (from.length === 1) {
440
- res = res.replaceAll(fromUpper, toUpper)
296
+ if (from.length === 1) {res = res.replaceAll(fromUpper, toUpper)
441
297
  }
442
298
  }
443
299
  return res
444
300
  }
445
- const detectInputLayout = (text) => {
446
- if (!text || typeof text !== 'string') return null
301
+ const detectInputLayout = (text) => {if (!text || typeof text !== 'string') return null
447
302
  const clean = text.replace(/`[^`]*`/g, '').replace(/https?:\/\/\S+/g, '')
448
303
  let cyr = 0
449
304
  let lat = 0
450
- for (const ch of clean) {
451
- const code = ch.charCodeAt(0)
305
+ for (const ch of clean) {const code = ch.charCodeAt(0)
452
306
  if ((code >= 0x0400 && code <= 0x04FF) || code === 0x0500) cyr++
453
307
  else if ((code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A)) lat++
454
308
  }
@@ -456,51 +310,18 @@ if (cyr > lat && cyr >= 2) return 'RU'
456
310
  if (lat > cyr && lat >= 2) return 'EN'
457
311
  return null
458
312
  }
459
- const SYSTEM_PROMPT_PRESETS = {
460
- technical_expert: {
461
- id: 'technical_expert',
462
- label: 'Технический эксперт',
463
- desc: 'Точная терминология, чистый код и русские комментарии',
464
- text: 'Отвечай пользователю на русском языке. Используй точную инженерную терминологию, пиши чистый код и русские комментарии.'
465
- },
466
- tech_writer: {
467
- id: 'tech_writer',
468
- label: 'Технический писатель',
469
- desc: 'Структурированные тексты, Markdown и выверенная типографика',
470
- text: 'Отвечай пользователю на русском языке. Оформляй документацию в Markdown, строго соблюдай правила русской типографики («ёлочки», тире, «ё»).'
471
- },
472
- concise: {
473
- id: 'concise',
474
- label: 'Лаконичный режим',
475
- desc: 'Краткие ёмкие ответы без вводных слов и воды',
476
- text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без вводных слов и воды, сразу код или решение.'
477
- },
478
- code_reviewer: {
479
- id: 'code_reviewer',
480
- label: 'Код-ревьюер',
481
- desc: 'Поиск багов, безопасность и архитектурный аудит',
482
- text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
483
- },
484
- architect: {
485
- id: 'architect',
486
- label: 'Системный архитектор',
487
- desc: 'Контракты API/CLI, модульность и надёжность',
488
- text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
489
- },
490
- tutor: {
491
- id: 'tutor',
492
- label: 'Наставник (ментор)',
493
- desc: 'Понятные объяснения сложных концепций и пошаговый разбор',
494
- text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
495
- }
496
- }
497
- const exportSessionToMarkdown = (session, options = {}) => {
498
- if (!session) return ''
313
+ const SYSTEM_PROMPT_PRESETS = {technical_expert: {id: 'technical_expert',label: 'Технический эксперт',desc: 'Точная терминология, чистый код и русские комментарии',text: 'Отвечай пользователю на русском языке. Используй точную инженерную терминологию, пиши чистый код и русские комментарии.'
314
+ },tech_writer: {id: 'tech_writer',label: 'Технический писатель',desc: 'Структурированные тексты, Markdown и выверенная типографика',text: 'Отвечай пользователю на русском языке. Оформляй документацию в Markdown, строго соблюдай правила русской типографики («ёлочки», тире, «ё»).'
315
+ },concise: {id: 'concise',label: 'Лаконичный режим',desc: 'Краткие ёмкие ответы без вводных слов и воды',text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без вводных слов и воды, сразу код или решение.'
316
+ },code_reviewer: {id: 'code_reviewer',label: 'Код-ревьюер',desc: 'Поиск багов, безопасность и архитектурный аудит',text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
317
+ },architect: {id: 'architect',label: 'Системный архитектор',desc: 'Контракты API/CLI, модульность и надёжность',text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
318
+ },tutor: {id: 'tutor',label: 'Наставник (ментор)',desc: 'Понятные объяснения сложных концепций и пошаговый разбор',text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
319
+ }
320
+ }
321
+ const exportSessionToMarkdown = (session, options = {}) => {if (!session) return ''
499
322
  const title = session.title || session.name || 'Диалог DSH'
500
323
  const date = session.createdAt ? new Date(session.createdAt) : new Date()
501
- const dateStr = new Intl.DateTimeFormat('ru-RU', {
502
- dateStyle: 'full',
503
- timeStyle: 'medium'
324
+ const dateStr = new Intl.DateTimeFormat('ru-RU', {dateStyle: 'full',timeStyle: 'medium'
504
325
  }).format(date)
505
326
  const messages = Array.isArray(session.messages) ? session.messages : []
506
327
  let md = `# 💬 ${title}\n\n`
@@ -508,20 +329,16 @@ md += `> **Дата экспорта:** ${dateStr}\n`
508
329
  if (session.model) md += `> **Модель:** \`${session.model}\`\n`
509
330
  if (session.workspace) md += `> **Рабочая область:** \`${session.workspace}\`\n`
510
331
  md += `> **Всего сообщений:** ${messages.length}\n\n---\n\n`
511
- for (let idx = 0; idx < messages.length; idx++) {
512
- const msg = messages[idx]
332
+ for (let idx = 0; idx < messages.length; idx++) {const msg = messages[idx]
513
333
  const role = msg.role || 'unknown'
514
334
  const roleName = role === 'user' ? '👤 Пользователь' :
515
335
  role === 'assistant' ? '🤖 Ассистент' :
516
336
  role === 'system' ? '⚙️ Система' : `🔧 Инструмент (${role})`
517
337
  md += `### ${roleName}\n\n`
518
- if (msg.content) {
519
- md += `${msg.content}\n\n`
338
+ if (msg.content) {md += `${msg.content}\n\n`
520
339
  }
521
- if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) {
522
- md += `*Вызовы инструментов:*\n`
523
- for (const tc of msg.toolCalls) {
524
- md += `- **${tc.name || 'tool'}**: \`${JSON.stringify(tc.arguments || {})}\`\n`
340
+ if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) {md += `*Вызовы инструментов:*\n`
341
+ for (const tc of msg.toolCalls) {md += `- **${tc.name || 'tool'}**: \`${JSON.stringify(tc.arguments || {})}\`\n`
525
342
  }
526
343
  md += `\n`
527
344
  }
@@ -530,94 +347,56 @@ md += `---\n\n`
530
347
  md += `*Сгенерировано с помощью [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) и @goodandready/dsh-russian-lang*\n`
531
348
  return md
532
349
  }
533
- const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}, domEn = {}, options = {}) => {
534
- if (!text || typeof text !== 'string') return null
350
+ const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}, domEn = {}, options = {}) => {if (!text || typeof text !== 'string') return null
535
351
  const trimmed = text.trim()
536
352
  if (!trimmed) return null
537
- if (overrides && typeof overrides === 'object') {
538
- if (overrides[trimmed] !== undefined) {
539
- return { key: trimmed, value: overrides[trimmed], source: 'override' }
353
+ if (overrides && typeof overrides === 'object') {if (overrides[trimmed] !== undefined) {return { key: trimmed, value: overrides[trimmed], source: 'override' }
540
354
  }
541
- for (const [k, v] of Object.entries(overrides)) {
542
- if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {
543
- return { key: k, value: v, source: 'override' }
355
+ for (const [k, v] of Object.entries(overrides)) {if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {return { key: k, value: v, source: 'override' }
544
356
  }
545
357
  }
546
358
  }
547
- if (dicts && typeof dicts === 'object') {
548
- for (const [ns, map] of Object.entries(dicts)) {
549
- if (!map || typeof map !== 'object') continue
550
- if (map[trimmed] !== undefined) {
551
- return { ns, key: trimmed, value: map[trimmed], source: 'dictionary' }
359
+ if (dicts && typeof dicts === 'object') {for (const [ns, map] of Object.entries(dicts)) {if (!map || typeof map !== 'object') continue
360
+ if (map[trimmed] !== undefined) {return { ns, key: trimmed, value: map[trimmed], source: 'dictionary' }
552
361
  }
553
- for (const [k, v] of Object.entries(map)) {
554
- if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {
555
- return { ns, key: k, value: v, source: 'dictionary' }
362
+ for (const [k, v] of Object.entries(map)) {if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {return { ns, key: k, value: v, source: 'dictionary' }
556
363
  }
557
364
  }
558
365
  }
559
366
  }
560
- if (zhRu && typeof zhRu === 'object') {
561
- for (const [zh, ru] of Object.entries(zhRu)) {
562
- if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {
563
- return { zh, key: zh, value: ru, source: 'dom_zh' }
367
+ if (zhRu && typeof zhRu === 'object') {for (const [zh, ru] of Object.entries(zhRu)) {if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {return { zh, key: zh, value: ru, source: 'dom_zh' }
564
368
  }
565
369
  }
566
- if (zhRu[trimmed] !== undefined) {
567
- return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }
370
+ if (zhRu[trimmed] !== undefined) {return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }
568
371
  }
569
372
  }
570
- if (domEn && typeof domEn === 'object') {
571
- for (const [en, ru] of Object.entries(domEn)) {
572
- if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {
573
- return { en, key: en, value: ru, source: 'dom_en' }
373
+ if (domEn && typeof domEn === 'object') {for (const [en, ru] of Object.entries(domEn)) {if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {return { en, key: en, value: ru, source: 'dom_en' }
574
374
  }
575
375
  }
576
- if (domEn[trimmed] !== undefined) {
577
- return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }
376
+ if (domEn[trimmed] !== undefined) {return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }
578
377
  }
579
378
  }
580
- if (options && options.detectUntranslated) {
581
- const hasRu = /[а-яё]/i.test(trimmed)
379
+ if (options && options.detectUntranslated) {const hasRu = /[а-яё]/i.test(trimmed)
582
380
  const hasZh = /[㐀-鿿豈-﫿]/.test(trimmed)
583
381
  const hasEn = /[a-z]/i.test(trimmed)
584
- if (!hasRu && hasZh) {
585
- return { key: trimmed, value: trimmed, source: 'untranslated_zh' }
382
+ if (!hasRu && hasZh) {return { key: trimmed, value: trimmed, source: 'untranslated_zh' }
586
383
  }
587
- if (!hasRu && hasEn) {
588
- return { key: trimmed, value: trimmed, source: 'untranslated_en' }
384
+ if (!hasRu && hasEn) {return { key: trimmed, value: trimmed, source: 'untranslated_en' }
589
385
  }
590
386
  }
591
387
  return null
592
388
  }
593
- const generateBugReportSnippet = (info = {}) => {
594
- const { key, ns, original, current, override, pkgVersion = '0.3.1' } = info
389
+ const generateBugReportSnippet = (info = {}) => {const { key, ns, original, current, override, pkgVersion = '0.3.1' } = info
595
390
  const lines = [
596
- '### 🌐 Неточность перевода / Пользовательское предложение',
597
- '- **Пакет:** `@goodandready/dsh-russian-lang@v' + pkgVersion + '`',
598
- ns ? '- **Пространство имён:** `' + ns + '`' : null,
599
- key ? '- **Ключ:** `' + key + '`' : null,
600
- original ? '- **Оригинальный текст:** `' + original + '`' : null,
601
- current ? '- **Текущий перевод:** `' + current + '`' : null,
602
- override ? '- **Предлагаемый перевод:** `' + override + '`' : null,
603
- ].filter(Boolean)
391
+ '### 🌐 Неточность перевода / Пользовательское предложение','- **Пакет:** `@goodandready/dsh-russian-lang@v' + pkgVersion + '`',ns ? '- **Пространство имён:** `' + ns + '`' : null,key ? '- **Ключ:** `' + key + '`' : null,original ? '- **Оригинальный текст:** `' + original + '`' : null,current ? '- **Текущий перевод:** `' + current + '`' : null,override ? '- **Предлагаемый перевод:** `' + override + '`' : null,].filter(Boolean)
604
392
  return lines.join('\n')
605
393
  }
606
- const bilingualCommandMatch = (query, command, options = {}) => {
607
- if (!query || !command) return { matched: false, score: 0 }
394
+ const bilingualCommandMatch = (query, command, options = {}) => {if (!query || !command) return { matched: false, score: 0 }
608
395
  const q = String(query).trim().toLowerCase()
609
396
  if (!q) return { matched: false, score: 0 }
610
397
  const cmdObj = typeof command === 'string' ? { title: command } : command
611
398
  const candidates = [
612
- cmdObj.title,
613
- cmdObj.label,
614
- cmdObj.name,
615
- cmdObj.originalTitle,
616
- cmdObj.enTitle,
617
- cmdObj.id,
618
- cmdObj.description,
619
- cmdObj.category,
620
- ...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
399
+ cmdObj.title,cmdObj.label,cmdObj.name,cmdObj.originalTitle,cmdObj.enTitle,cmdObj.id,cmdObj.description,cmdObj.category,...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
621
400
  ].filter((c) => typeof c === 'string' && c.trim())
622
401
  if (!candidates.length) return { matched: false, score: 0 }
623
402
  const queryVariants = [{ text: q, kind: 'direct' }]
@@ -626,155 +405,107 @@ if (lat2cyr !== q) queryVariants.push({ text: lat2cyr, kind: 'layout' })
626
405
  const cyr2lat = translit(q, LAYOUT_CYR_TO_LAT)
627
406
  if (cyr2lat !== q) queryVariants.push({ text: cyr2lat, kind: 'layout' })
628
407
  const phonLat2Cyr = phoneticTranslit(q, 'lat2cyr')
629
- if (phonLat2Cyr && phonLat2Cyr !== q && phonLat2Cyr !== lat2cyr) {
630
- queryVariants.push({ text: phonLat2Cyr, kind: 'phonetic' })
408
+ if (phonLat2Cyr && phonLat2Cyr !== q && phonLat2Cyr !== lat2cyr) {queryVariants.push({ text: phonLat2Cyr, kind: 'phonetic' })
631
409
  }
632
410
  const phonCyr2Lat = phoneticTranslit(q, 'cyr2lat')
633
- if (phonCyr2Lat && phonCyr2Lat !== q && phonCyr2Lat !== cyr2lat) {
634
- queryVariants.push({ text: phonCyr2Lat, kind: 'phonetic' })
411
+ if (phonCyr2Lat && phonCyr2Lat !== q && phonCyr2Lat !== cyr2lat) {queryVariants.push({ text: phonCyr2Lat, kind: 'phonetic' })
635
412
  }
636
413
  let bestScore = 0
637
414
  let bestVariant = null
638
415
  let bestCandidate = null
639
- for (const variant of queryVariants) {
640
- for (const cand of candidates) {
641
- const score = fuzzyMatchRu(variant.text, cand)
642
- if (score > bestScore) {
643
- bestScore = score
416
+ for (const variant of queryVariants) {for (const cand of candidates) {const score = fuzzyMatchRu(variant.text, cand)
417
+ if (score > bestScore) {bestScore = score
644
418
  bestVariant = variant
645
419
  bestCandidate = cand
646
420
  }
647
421
  }
648
422
  }
649
423
  const threshold = options.threshold ?? 50
650
- return {
651
- matched: bestScore >= threshold,
652
- score: bestScore,
653
- matchedText: bestCandidate,
654
- variant: bestVariant ? bestVariant.text : q,
655
- kind: bestVariant ? bestVariant.kind : 'none'
424
+ return {matched: bestScore >= threshold,score: bestScore,matchedText: bestCandidate,variant: bestVariant ? bestVariant.text : q,kind: bestVariant ? bestVariant.kind : 'none'
656
425
  }
657
426
  }
658
- const filterCommands = (query, commands = [], options = {}) => {
659
- if (!Array.isArray(commands)) return []
427
+ const filterCommands = (query, commands = [], options = {}) => {if (!Array.isArray(commands)) return []
660
428
  if (!query || !String(query).trim()) return commands
661
429
  const results = []
662
- for (const cmd of commands) {
663
- const res = bilingualCommandMatch(query, cmd, options)
664
- if (res.matched) {
665
- results.push({ item: cmd, ...res })
430
+ for (const cmd of commands) {const res = bilingualCommandMatch(query, cmd, options)
431
+ if (res.matched) {results.push({ item: cmd, ...res })
666
432
  }
667
433
  }
668
434
  return results.sort((a, b) => b.score - a.score).map((r) => r.item)
669
435
  }
670
-
671
436
  const RU = {"common":{"back":"Назад","brand.localBuild":"Локальная сборка DSH","cancel":"Отмена","close":"Закрыть","codeBlock.title":"Блок кода","codeBlock.unwrap":"Не переносить строки","codeBlock.wrap":"Переносить строки","collapse":"Свернуть","copied":"Скопировано","copy":"Копировать","copy.compactJson":"Копировать компактный JSON","copy.failed":"Не удалось скопировать","copy.json":"Копировать JSON","copy.optionsHint":"{action}; правый клик для вариантов копирования","copy.path":"Копировать путь к свойству","copy.prettyJson":"Копировать форматированный JSON","copy.value":"Копировать значение","delete":"Удалить","edit":"Изменить","expand":"Развернуть","json.collapseNode":"Свернуть узел JSON","json.expandNode":"Развернуть узел JSON","json.label":"JSON","load.failed":"Не удалось загрузить","loading":"Загрузка…","markdown.footnotes":"Сноски","markdown.truncatedCharacters":"… обрезано на {total} символах","more":"Ещё","next":"Дальше","none":"Нет","number.million":"{value}M","number.thousand":"{value}K","ok":"ОК","previous":"Назад","retry":"Повторить","save":"Сохранить","search":"Поиск","skip":"Пропустить","submit":"Отправить","submitting":"Отправка…","truncated":"Обрезано","unknown":"Неизвестно"},"dsh-cron":{"sidebar.label":"Задачи по расписанию"},"dsh-usage-stats":{"footer.todayLabel":"Сегодня"},"pluginMarket":{"trigger":"Магазин плагинов"}}
672
-
673
437
  const ZH_RU = {}
674
438
  RU['russian-lang'] = {"cardTitle":"Русская локализация","cardSub":"Язык интерфейса, типографика, раскладка","badgeRu":"🟢 RU активен","badgeEn":"⚪ EN активен","badgeCoverage":"🟢 100% (7,902 ключа)","badgeSmartUx":"⚡ Smart UX активен","secLanguage":"🌐 Язык интерфейса","secLanguageDesc":"Нативное переключение языка интерфейса DSH на русский без перезагрузки страницы.","enabled":"Русский язык включён","enabledDesc":"Переключает язык интерфейса DeepSeek Harness на русский.","quickSwitchNote":"Быстрый переключатель RU ⇄ EN доступен в шапке сессии рядом с кнопками диалога.","secTypography":"✍️ Умная типографика и ввод (Smart UX)","secTypographyDesc":"Автоматическое улучшение текстов по нормам русской типографики во время диалога и набора.","typography":"Типографика вывода","typographyDesc":"Исправляет типографику ответов модели: кавычки-«ёлочки», тире («—») вместо дефисов, неразрывные пробелы после предлогов.","yo":"Буква «ё»","yoDesc":"Восстанавливать «ё» в частых словах (ещё, чёрный, идёт и др.), написанных через «е». Неоднозначные слова (все/всё) не трогаются.","liveInput":"Живая типографика инпута","liveInputDesc":"Автоматически заменять \"\" на «» и -- на — прямо во время набора промпта (код в бэктиках игнорируется).","slashAliases":"Русские алиасы команд","slashAliasesDesc":"Поддержка русских команд: /цель -> /goal, /сжать -> /compact, /план -> /plan, /справка -> /help, /память -> /memory.","altLHintText":"Мгновенная конвертация раскладки текущего поля (ghbdtn ⇄ привет, /vjltkm ⇄ /model). В углу поля ввода также отображается метка раскладки.","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":"Настройка перевода ответов модели в чате на русский язык (локально в Docker или онлайн).","translateEngine":"Движок перевода","engineOff":"Выключен (по умолчанию, без сетевых вызовов)","engineLocal":"Локальный LibreTranslate (приватно, ~600 МБ RAM)","engineGoogle":"Google Translate (онлайн)","googleWarn":"⚠️ Внимание: при онлайн-переводе текст сообщений ассистента передаётся на публичные серверы Google для перевода на лету. Не используйте для конфиденциальных данных.","localStatusRunning":"🟢 Контейнер LibreTranslate активен","localStatusStopped":"⚪ Контейнер LibreTranslate не запущен","localStartBtn":"Запустить LibreTranslate в Docker (~600 МБ RAM)","localStarting":"Запуск контейнера…","localInfo":"LibreTranslate запускается изолированно в Docker на сервере, потребляет ~500–700 МБ RAM и переводит на 100% локально.","secOverrides":"✍️ Пользовательские переопределения (Custom Overrides)","secOverridesDesc":"Возможность заменить любую формулировку интерфейса DSH на собственную. Переопределения сохраняются в настройках и имеют наивысший приоритет.","overrideKeyPlaceholder":"Ключ (например, common.back или settings.title)","overrideValuePlaceholder":"Ваш русский перевод","overrideAddBtn":"Добавить","overrideEmpty":"Переопределений пока нет.","overrideDelete":"Удалить","overrideTip":"💡 Совет: зажмите Alt и кликните на любой переведённый элемент интерфейса, чтобы открыть всплывающий инспектор перевода и сразу изменить формулировку.","secSupport":"📊 Покрытие экосистемы и поддержка","secSupportDesc":"Словари синхронизированы с DSH v0.1.6-alpha.1. 100.0% UI-покрытие ядра и всех установленных плагинов (7,902 ключа) без черновых машинных переводов.","statNamespaces":"Пространств имён","statCoreKeys":"Ключей ядра","statPluginKeys":"Ключей плагинов","overridesCount":"Своих переопределений","statusLoading":"Настройки загружаются…","statusUnavailable":"Настройки недоступны на этом хосте","translateTurn":"Перевести на русский","reportIssue":"Сообщить о неточности перевода","exportMdHint":"Экспорт диалога в Markdown доступен по кнопке [ 📥 MD ] в шапке сессии.","translateTurnHint":"Перевод ответов ассистента на русский доступен по кнопке [ RU ↗ ] на блоках сообщений."}
675
439
  const SETTINGS_NS_NAME = 'russian-lang'
676
440
  const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
677
- function apply(ctx) {
678
- const runtime = ctx.locale
679
- const resolveScope = (c) => {
680
- try {
681
- if (c && c.configForms) return c.configForms.get(SETTINGS_NS_NAME)
441
+ function apply(ctx) {const runtime = ctx.locale
442
+ const resolveScope = (c) => {try {if (c && c.configForms) return c.configForms.get(SETTINGS_NS_NAME)
682
443
  if (c && c.settingsScope) return c.settingsScope.bind({ namespace: SETTINGS_NS_NAME })
683
444
  } catch (err) { void err }
684
445
  return null
685
446
  }
686
447
  const rawScope = resolveScope(ctx)
687
- const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {
688
- getSnapshot: () => ({ status: 'ready', value: {} }),
689
- subscribe: () => () => {},
690
- set: () => Promise.resolve()
691
- }
692
- for (const ns of Object.keys(RU)) {
693
- ctx.effect(() => {
694
- try { return ctx.locale.register(ns, 'ru', RU[ns]) }
448
+ const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
449
+ }
450
+ for (const ns of Object.keys(RU)) {ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', RU[ns]) }
695
451
  catch (err) { return () => {} }
696
452
  }, 'dsh-russian-lang: ' + ns)
697
453
  }
698
- const registerDictMap = (dictMap) => {
699
- if (!dictMap || typeof dictMap !== 'object') return
700
- for (const ns of Object.keys(dictMap)) {
701
- if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
454
+ const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
455
+ for (const ns of Object.keys(dictMap)) {if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
702
456
  const dict = dictMap[ns]
703
457
  if (typeof dict !== 'object' || !dict) continue
704
- if (!RU[ns]) {
705
- RU[ns] = dict
706
- ctx.effect(() => {
707
- try { return ctx.locale.register(ns, 'ru', dict) }
458
+ if (!RU[ns]) {RU[ns] = dict
459
+ ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) }
708
460
  catch (err) { return () => {} }
709
461
  }, 'dsh-russian-lang: ' + ns)
710
- } else {
711
- Object.assign(RU[ns], dict)
462
+ } else {Object.assign(RU[ns], dict)
712
463
  }
713
464
  }
714
465
  }
715
466
  const CACHE_NAME = 'dsh-ru-cache-v1'
716
- const loadLocalDict = (key) => {
717
- try {
718
- const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
467
+ const loadLocalDict = (key) => {try {const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
719
468
  return raw ? JSON.parse(raw) : null
720
469
  } catch (_) { return null }
721
470
  }
722
- const saveLocalDict = (key, etag, data) => {
723
- try {
724
- if (typeof localStorage !== 'undefined') {
725
- localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
471
+ const saveLocalDict = (key, etag, data) => {try {if (typeof localStorage !== 'undefined') {localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
726
472
  }
727
473
  } catch (_e) { void _e }
728
474
  }
729
- try {
730
- const cachedCore = loadLocalDict('core')
731
- if (cachedCore && cachedCore.data && cachedCore.data.core) {
732
- registerDictMap(cachedCore.data.core)
733
- if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {
734
- Object.assign(ZH_RU, cachedCore.data.zhRu)
475
+ try {const cachedCore = loadLocalDict('core')
476
+ if (cachedCore && cachedCore.data && cachedCore.data.core) {registerDictMap(cachedCore.data.core)
477
+ if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, cachedCore.data.zhRu)
735
478
  }
736
479
  }
737
480
  } catch (_e) { void _e }
738
- const fetchCachedResource = async (url, cacheKey, onData) => {
739
- let etag = null
481
+ const fetchCachedResource = async (url, cacheKey, onData) => {let etag = null
740
482
  let delivered = false
741
- if (typeof caches !== 'undefined') {
742
- try {
743
- const cache = await caches.open(CACHE_NAME)
483
+ if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
744
484
  const matched = await cache.match(url)
745
- if (matched) {
746
- etag = matched.headers.get('etag')
485
+ if (matched) {etag = matched.headers.get('etag')
747
486
  const parsed = await matched.json()
748
- if (parsed) {
749
- delivered = true
487
+ if (parsed) {delivered = true
750
488
  onData(parsed)
751
489
  }
752
490
  }
753
491
  } catch (_e) { void _e }
754
492
  }
755
- if (!delivered && cacheKey) {
756
- const local = loadLocalDict(cacheKey)
757
- if (local && local.data) {
758
- etag = local.etag
493
+ if (!delivered && cacheKey) {const local = loadLocalDict(cacheKey)
494
+ if (local && local.data) {etag = local.etag
759
495
  delivered = true
760
496
  onData(local.data)
761
497
  }
762
498
  }
763
499
  if (typeof fetch !== 'function') return
764
- try {
765
- const headers = { 'Accept': 'application/json' }
500
+ try {const headers = { 'Accept': 'application/json' }
766
501
  if (etag) headers['If-None-Match'] = etag
767
502
  const res = await fetch(url, { headers })
768
503
  if (res.status === 304) return
769
- if (res.ok) {
770
- const newEtag = res.headers.get('etag')
504
+ if (res.ok) {const newEtag = res.headers.get('etag')
771
505
  const cloned = res.clone()
772
506
  const data = await res.json()
773
- if (data) {
774
- onData(data)
775
- if (typeof caches !== 'undefined') {
776
- try {
777
- const cache = await caches.open(CACHE_NAME)
507
+ if (data) {onData(data)
508
+ if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
778
509
  await cache.put(url, cloned)
779
510
  } catch (_e) { void _e }
780
511
  }
@@ -786,11 +517,9 @@ if (cacheKey) saveLocalDict(cacheKey, newEtag, data)
786
517
  const loadedPluginNames = new Set()
787
518
  const pendingPluginLoads = new Set()
788
519
  let pluginBatchTimer = null
789
- const loadPluginDictionaries = (names) => {
790
- if (typeof fetch !== 'function') return
520
+ const loadPluginDictionaries = (names) => {if (typeof fetch !== 'function') return
791
521
  const toLoad = []
792
- for (const raw of names) {
793
- const n = String(raw).trim()
522
+ for (const raw of names) {const n = String(raw).trim()
794
523
  if (!n || loadedPluginNames.has(n) || pendingPluginLoads.has(n)) continue
795
524
  toLoad.push(n)
796
525
  pendingPluginLoads.add(n)
@@ -798,42 +527,31 @@ pendingPluginLoads.add(n)
798
527
  const qs = toLoad.length > 0 ? ('?names=' + encodeURIComponent(toLoad.join(','))) : ''
799
528
  const url = '/api/dsh-russian-lang/dict/plugins' + qs
800
529
  const cacheKey = toLoad.length > 0 ? ('plugins_' + toLoad.sort().join('_')) : 'plugins_all'
801
- fetchCachedResource(url, cacheKey, (data) => {
802
- if (data && data.plugins) {
803
- registerDictMap(data.plugins)
530
+ fetchCachedResource(url, cacheKey, (data) => {if (data && data.plugins) {registerDictMap(data.plugins)
804
531
  for (const n of toLoad) loadedPluginNames.add(n)
805
532
  }
806
- }).catch(() => {}).finally(() => {
807
- for (const n of toLoad) pendingPluginLoads.delete(n)
533
+ }).catch(() => {}).finally(() => {for (const n of toLoad) pendingPluginLoads.delete(n)
808
534
  })
809
535
  }
810
- const schedulePluginLoad = (name) => {
811
- if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
536
+ const schedulePluginLoad = (name) => {if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
812
537
  pendingPluginLoads.add(name)
813
538
  if (pluginBatchTimer) clearTimeout(pluginBatchTimer)
814
- pluginBatchTimer = setTimeout(() => {
815
- const batch = Array.from(pendingPluginLoads).filter((n) => !loadedPluginNames.has(n))
539
+ pluginBatchTimer = setTimeout(() => {const batch = Array.from(pendingPluginLoads).filter((n) => !loadedPluginNames.has(n))
816
540
  if (batch.length > 0) loadPluginDictionaries(batch)
817
541
  }, 50)
818
542
  }
819
- fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {
820
- if (!data) return
543
+ fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {if (!data) return
821
544
  if (data.core) registerDictMap(data.core)
822
- if (data.zhRu && typeof ZH_RU === 'object') {
823
- Object.assign(ZH_RU, data.zhRu)
545
+ if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
824
546
  if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
825
547
  }
826
548
  if (typeof syncZhDom === 'function') syncZhDom()
827
549
  setTimeout(() => { loadPluginDictionaries([]) }, 100)
828
- }).catch(() => {
829
- if (typeof fetch === 'function') {
830
- fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
550
+ }).catch(() => {if (typeof fetch === 'function') {fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
831
551
  .then((res) => res.ok ? res.json() : null)
832
- .then((data) => {
833
- if (!data) return
552
+ .then((data) => {if (!data) return
834
553
  const allDicts = Object.assign({}, data.core || {}, data.plugins || {}, data)
835
- if (data.zhRu && typeof ZH_RU === 'object') {
836
- Object.assign(ZH_RU, data.zhRu)
554
+ if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
837
555
  if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
838
556
  }
839
557
  registerDictMap(allDicts)
@@ -843,15 +561,12 @@ if (typeof syncZhDom === 'function') syncZhDom()
843
561
  }
844
562
  })
845
563
  const origTranslate = runtime.translate.bind(runtime)
846
- const getOverrides = () => {
847
- try {
848
- const value = scope.getSnapshot().value
564
+ const getOverrides = () => {try {const value = scope.getSnapshot().value
849
565
  return value && value.overrides ? value.overrides : {}
850
566
  } catch (err) { return {} }
851
567
  }
852
568
  const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
853
- try {
854
- runtime.formatNumber = formatNumber
569
+ try {runtime.formatNumber = formatNumber
855
570
  runtime.formatCompactNumber = formatCompactNumber
856
571
  runtime.formatDate = formatDate
857
572
  runtime.formatTokens = formatTokens
@@ -865,96 +580,67 @@ runtime.humanizeError = humanizeError
865
580
  runtime.plural = plural
866
581
  runtime.pluralForm = pluralForm
867
582
  } catch (err) { }
868
- const lookupChain = () => {
869
- try {
870
- const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
583
+ const lookupChain = () => {try {const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
871
584
  if (Array.isArray(chain) && chain.length) return chain
872
585
  } catch (err) { }
873
586
  return [runtime.getLocale().active]
874
587
  }
875
588
  const translationRegistry = new Map()
876
- const lookup = (ns, key) => {
877
- const chain = lookupChain()
589
+ const lookup = (ns, key) => {const chain = lookupChain()
878
590
  return runtime.lookup.length >= 3 ? runtime.lookup(ns, key, chain) : runtime.lookup(ns, key)
879
591
  }
880
- runtime.translate = function (ns, key, params) {
881
- if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
592
+ runtime.translate = function (ns, key, params) {if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
882
593
  const overrides = getOverrides()
883
- if (overrides[key] !== undefined) {
884
- return params ? fill(overrides[key], params) : overrides[key]
594
+ if (overrides[key] !== undefined) {return params ? fill(overrides[key], params) : overrides[key]
885
595
  }
886
- if (runtime.getLocale().active === 'ru' && params) {
887
- const n = params.n ?? params.count
888
- if (typeof n === 'number') {
889
- const form = pluralForm(n)
596
+ if (runtime.getLocale().active === 'ru' && params) {const n = params.n ?? params.count
597
+ if (typeof n === 'number') {const form = pluralForm(n)
890
598
  const m = /^(.*)[.](one|other)$/.exec(key)
891
- if (m) {
892
- if (form === 'few' || form === 'many') {
893
- const pluralKey = m[1] + '.' + form
599
+ if (m) {if (form === 'few' || form === 'many') {const pluralKey = m[1] + '.' + form
894
600
  const template = lookup(ns, pluralKey) ?? lookup('common', pluralKey)
895
- if (template !== undefined) {
896
- return fill(template, params)
601
+ if (template !== undefined) {return fill(template, params)
897
602
  }
898
603
  }
899
- } else if (form !== 'other' && !/[.](one|other|few|many)$/.test(key)) {
900
- const pluralKey = key + '.' + form
604
+ } else if (form !== 'other' && !/[.](one|other|few|many)$/.test(key)) {const pluralKey = key + '.' + form
901
605
  const template = lookup(ns, pluralKey) ?? lookup('common', pluralKey)
902
- if (template !== undefined) {
903
- return fill(template, params)
606
+ if (template !== undefined) {return fill(template, params)
904
607
  }
905
608
  }
906
609
  }
907
610
  }
908
611
  const res = origTranslate(ns, key, params)
909
- if (typeof res === 'string' && res && res.length < 300) {
910
- translationRegistry.set(res.trim(), { ns, key, params, value: res })
612
+ if (typeof res === 'string' && res && res.length < 300) {translationRegistry.set(res.trim(), { ns, key, params, value: res })
911
613
  }
912
614
  return res
913
615
  }
914
- const syncLang = () => {
915
- try {
916
- if (typeof document !== 'undefined' && document.documentElement
917
- && runtime.getLocale().active === 'ru') {
918
- document.documentElement.lang = 'ru-RU'
616
+ const syncLang = () => {try {if (typeof document !== 'undefined' && document.documentElement
617
+ && runtime.getLocale().active === 'ru') {document.documentElement.lang = 'ru-RU'
919
618
  }
920
619
  } catch (err) { }
921
620
  }
922
621
  const native = runtime.getLocale().locales.some((l) => l.id === 'ru')
923
- if (!native) {
924
- runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
622
+ if (!native) {runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
925
623
  syncLang()
926
624
  }
927
- const syncFlag = () => {
928
- try {
929
- const wantRu = runtime.getLocale().active === 'ru'
625
+ const syncFlag = () => {try {const wantRu = runtime.getLocale().active === 'ru'
930
626
  const value = scope.getSnapshot().value || {}
931
- if (!!value.enabled !== wantRu) {
932
- const r = scope.set('enabled', wantRu)
933
- if (r && typeof r.catch === 'function') {
934
- r.catch((err) => {
935
- console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
627
+ if (!!value.enabled !== wantRu) {const r = scope.set('enabled', wantRu)
628
+ if (r && typeof r.catch === 'function') {r.catch((err) => {console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
936
629
  })
937
630
  }
938
631
  }
939
- } catch (err) {
940
-
941
- }
632
+ } catch (err) {}
942
633
  }
943
- ctx.effect(() => {
944
- try { return runtime.subscribe(syncFlag) }
634
+ ctx.effect(() => {try { return runtime.subscribe(syncFlag) }
945
635
  catch (err) { return undefined }
946
636
  }, 'dsh-russian-lang: sync-flag')
947
- const activate = () => {
948
- try {
949
- if (runtime.getLocale().active === 'ru') return
637
+ const activate = () => {try {if (runtime.getLocale().active === 'ru') return
950
638
  runtime.setLocale('ru')
951
639
  } catch (err) { console.warn('dsh-russian-lang: activate failed', err) }
952
640
  }
953
641
  let booted = false
954
- const tryBoot = () => {
955
- if (booted) return
956
- try {
957
- const value = scope.getSnapshot().value
642
+ const tryBoot = () => {if (booted) return
643
+ try {const value = scope.getSnapshot().value
958
644
  if (value && value.enabled === true) { booted = true; activate() }
959
645
  } catch (err) { }
960
646
  }
@@ -968,20 +654,17 @@ const SPELL_WAS = 'data-russian-lang-spell-was'
968
654
  const LANG_WAS = 'data-russian-lang-lang-was'
969
655
  const EDITABLE = 'textarea, input[type=text], input[type=search], [contenteditable=""], [contenteditable="true"]'
970
656
  const MONO_RE = /mono|consol|courier/i
971
- const isMonoField = (el) => {
972
- try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
657
+ const isMonoField = (el) => {try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
973
658
  catch (err) { return false }
974
659
  }
975
- const spellOn = (el) => {
976
- if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
660
+ const spellOn = (el) => {if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
977
661
  el.setAttribute(SPELL_ON, '1')
978
662
  el.setAttribute(SPELL_WAS, el.getAttribute('spellcheck') ?? '')
979
663
  el.setAttribute(LANG_WAS, el.getAttribute('lang') ?? '')
980
664
  el.setAttribute('spellcheck', 'true')
981
665
  el.setAttribute('lang', 'ru-RU')
982
666
  }
983
- const spellOff = (el) => {
984
- if (!el.hasAttribute(SPELL_ON)) return
667
+ const spellOff = (el) => {if (!el.hasAttribute(SPELL_ON)) return
985
668
  const was = el.getAttribute(SPELL_WAS)
986
669
  if (was === '') el.removeAttribute('spellcheck')
987
670
  else el.setAttribute('spellcheck', was)
@@ -993,21 +676,15 @@ el.removeAttribute(SPELL_WAS)
993
676
  el.removeAttribute(LANG_WAS)
994
677
  }
995
678
  let spellObserver = null
996
- const syncSpell = () => {
997
- try {
998
- if (typeof document === 'undefined') return
679
+ const syncSpell = () => {try {if (typeof document === 'undefined') return
999
680
  const ru = runtime.getLocale().active === 'ru'
1000
- if (!ru) {
1001
- if (spellObserver) { spellObserver.disconnect(); spellObserver = null }
681
+ if (!ru) {if (spellObserver) { spellObserver.disconnect(); spellObserver = null }
1002
682
  document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff)
1003
683
  return
1004
684
  }
1005
685
  document.querySelectorAll(EDITABLE).forEach(spellOn)
1006
686
  if (spellObserver) return
1007
- spellObserver = new MutationObserver((records) => {
1008
- for (const record of records) {
1009
- for (const node of record.addedNodes) {
1010
- if (node.nodeType !== 1) continue
687
+ spellObserver = new MutationObserver((records) => {for (const record of records) {for (const node of record.addedNodes) {if (node.nodeType !== 1) continue
1011
688
  if (node.matches(EDITABLE)) spellOn(node)
1012
689
  node.querySelectorAll ? node.querySelectorAll(EDITABLE).forEach(spellOn) : null
1013
690
  }
@@ -1017,9 +694,7 @@ spellObserver.observe(document.body, { childList: true, subtree: true })
1017
694
  } catch (err) { }
1018
695
  }
1019
696
  const unsubscribeSpell = runtime.subscribe(syncSpell)
1020
- ctx.effect(() => {
1021
- return () => {
1022
- unsubscribeSpell()
697
+ ctx.effect(() => {return () => {unsubscribeSpell()
1023
698
  if (spellObserver) spellObserver.disconnect()
1024
699
  try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }
1025
700
  }
@@ -1029,326 +704,85 @@ const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
1029
704
  const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
1030
705
  const ZH_EXACT = new Map()
1031
706
  const ZH_PATTERNS = []
1032
- const CORE_ZH_PRESETS = {
1033
- '请求批准': 'Запрашивать подтверждение',
1034
- '完全放开': 'Полный доступ',
1035
- '只读': 'Только чтение',
1036
- '只能读,任何写入都需要审批。': 'Только чтение, любая запись требует подтверждения.',
1037
- '工作区内可写;工作区外的操作请求人工审批。': 'Запись в рабочей области разрешена; операции вне рабочей области требуют подтверждения.',
1038
- '全放行,不弹审批。': 'Полный доступ, запросы на подтверждение не выводятся.',
1039
- '确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.',
1040
- '手动测试': 'Ручное тестирование',
1041
- '事件': 'Событие',
1042
- '模拟 (看匹配)': 'Симуляция (проверка)',
1043
- '执行 (真实触发)': 'Выполнение (реальный триггер)',
1044
- '通知渠道测试': 'Тест каналов уведомлений',
1045
- '渠道': 'Канал',
1046
- 'Slack 风格单行摘要': 'Сводка в стиле Slack',
1047
- '发送测试通知': 'Отправить тестовое уведомление',
1048
- '飞书通知': 'Уведомления Feishu',
1049
- '扫码连接飞书': 'Подключить Feishu по QR-коду',
1050
- '将创建名为 [DSH 通知机器人] 的飞书应用': 'Будет создано приложение Feishu [DSH 通知机器人]',
1051
- '卡片截断长度': 'Длина обрезки карточки',
1052
- '复制 YAML': 'Копировать YAML',
1053
- '去抖': 'Дебаунс',
1054
- '已断开': 'Отключено',
1055
- '已连接': 'Подключено',
1056
- '已保存': 'Сохранено',
1057
- '保存失败': 'Не удалось сохранить',
1058
- '编辑': 'Редактировать',
1059
- '取消编辑': 'Отмена',
1060
- '启用': 'Включить',
1061
- '停用': 'Отключить',
1062
- '重试': 'Повторить',
1063
- '刷新': 'Обновить',
1064
- '网络请求失败': 'Сетевой запрос не удался',
1065
- '如未立即生效请重启': 'Если изменения не применились, перезапустите DSH',
1066
- '通知机器人': 'Бот уведомлений',
1067
- '飞书扫码授权二维码': 'QR-код авторизации Feishu',
1068
- '在浏览器中打开飞书授权链接': 'Открыть ссылку авторизации Feishu в браузере',
1069
- '重新扫码会覆盖现有应用凭据与本': 'Повторное сканирование перезапишет учётные данные',
1070
- '扫码者本人接收通知卡片': 'Получатель карточки уведомлений — авторизованный пользователь',
1071
- 'tool (可选)': 'Инструмент (опц.)',
1072
- 'runningSubagents (可选)': 'Подагенты (опц.)',
1073
- 'durationMs (可选)': 'Длительность мс (опц.)',
1074
- 'usage 输入 (可选)': 'Входные токены (опц.)',
1075
- 'usage 输出 (可选)': 'Выходные токены (опц.)',
1076
- 'profile (写入哪个 profile 的 cordis.patch.yml)': 'Профиль (куда записать cordis.patch.yml)',
1077
- 'URL (留空用 DSH_HOOKS_WEBHOOK_URL)': 'URL (по умолчанию DSH_HOOKS_WEBHOOK_URL)',
1078
- '当前已配置的 Hook 规则列表': 'Список текущих настроенных правил хуков',
1079
- '测试通道': 'Тест канала'
707
+ const CORE_ZH_PRESETS = {'请求批准': 'Запрашивать подтверждение','完全放开': 'Полный доступ','只读': 'Только чтение','只能读,任何写入都需要审批。': 'Только чтение, любая запись требует подтверждения.','工作区内可写;工作区外的操作请求人工审批。': 'Запись в рабочей области разрешена; операции вне рабочей области требуют подтверждения.','全放行,不弹审批。': 'Полный доступ, запросы на подтверждение не выводятся.','确定性规则 + 两阶段分类器自动决定;危险或故障时 fail-closed。': 'Детерминированные правила + двухэтапный классификатор; при рисках — безопасная блокировка.','手动测试': 'Ручное тестирование','事件': 'Событие','模拟 (看匹配)': 'Симуляция (проверка)','执行 (真实触发)': 'Выполнение (реальный триггер)','通知渠道测试': 'Тест каналов уведомлений','渠道': 'Канал','Slack 风格单行摘要': 'Сводка в стиле Slack','发送测试通知': 'Отправить тестовое уведомление','飞书通知': 'Уведомления Feishu','扫码连接飞书': 'Подключить Feishu по QR-коду','将创建名为 [DSH 通知机器人] 的飞书应用': 'Будет создано приложение Feishu [DSH 通知机器人]','卡片截断长度': 'Длина обрезки карточки','复制 YAML': 'Копировать YAML','去抖': 'Дебаунс','已断开': 'Отключено','已连接': 'Подключено','已保存': 'Сохранено','保存失败': 'Не удалось сохранить','编辑': 'Редактировать','取消编辑': 'Отмена','启用': 'Включить','停用': 'Отключить','重试': 'Повторить','刷新': 'Обновить','网络请求失败': 'Сетевой запрос не удался','如未立即生效请重启': 'Если изменения не применились, перезапустите DSH','通知机器人': 'Бот уведомлений','飞书扫码授权二维码': 'QR-код авторизации Feishu','在浏览器中打开飞书授权链接': 'Открыть ссылку авторизации Feishu в браузере','重新扫码会覆盖现有应用凭据与本': 'Повторное сканирование перезапишет учётные данные','扫码者本人接收通知卡片': 'Получатель карточки уведомлений — авторизованный пользователь','tool (可选)': 'Инструмент (опц.)','runningSubagents (可选)': 'Подагенты (опц.)','durationMs (可选)': 'Длительность мс (опц.)','usage 输入 (可选)': 'Входные токены (опц.)','usage 输出 (可选)': 'Выходные токены (опц.)','profile (写入哪个 profile 的 cordis.patch.yml)': 'Профиль (куда записать cordis.patch.yml)','URL (留空用 DSH_HOOKS_WEBHOOK_URL)': 'URL (по умолчанию DSH_HOOKS_WEBHOOK_URL)','当前已配置的 Hook 规则列表': 'Список текущих настроенных правил хуков','测试通道': 'Тест канала'
1080
708
  }
1081
709
  for (const [k, v] of Object.entries(CORE_ZH_PRESETS)) ZH_EXACT.set(k, v)
1082
710
  const zhSortedExact = []
1083
- const rebuildZhSorted = () => {
1084
- zhSortedExact.length = 0
711
+ const rebuildZhSorted = () => {zhSortedExact.length = 0
1085
712
  for (const [k, v] of ZH_EXACT.entries()) zhSortedExact.push([k, v])
1086
713
  zhSortedExact.sort((a, b) => b[0].length - a[0].length)
1087
714
  }
1088
715
  rebuildZhSorted()
1089
- const updateZhRu = (entries) => {
1090
- if (!entries || typeof entries !== 'object') return
1091
- for (const [zhText, ruText] of Object.entries(entries)) {
1092
- if (typeof zhText !== 'string' || !ZH_CJK.test(zhText) || !ruText) continue
1093
- if (/\{[a-zA-Z_]\w*\}/.test(zhText)) {
1094
- const parts = zhText.split(/\{[a-zA-Z_]\w*\}/g)
716
+ const updateZhRu = (entries) => {if (!entries || typeof entries !== 'object') return
717
+ for (const [zhText, ruText] of Object.entries(entries)) {if (typeof zhText !== 'string' || !ZH_CJK.test(zhText) || !ruText) continue
718
+ if (/\{[a-zA-Z_]\w*\}/.test(zhText)) {const parts = zhText.split(/\{[a-zA-Z_]\w*\}/g)
1095
719
  if (parts.some((p) => p.length === 0)) continue
1096
720
  ZH_PATTERNS.push({ re: new RegExp(parts.map(ZH_RE_ESC).join('([\\s\\S]*?)')), ruParts: ruText.split(/\{[a-zA-Z_]\w*\}/g) })
1097
- } else {
1098
- ZH_EXACT.set(zhText, ruText)
721
+ } else {ZH_EXACT.set(zhText, ruText)
1099
722
  }
1100
723
  }
1101
724
  ZH_PATTERNS.sort((a, b) => b.re.source.length - a.re.source.length)
1102
725
  rebuildZhSorted()
1103
726
  }
1104
727
  updateZhRu(ZH_RU)
1105
- const zhTranslateText = (text) => {
1106
- const overrides = getOverrides()
728
+ const zhTranslateText = (text) => {const overrides = getOverrides()
1107
729
  if (overrides[text] !== undefined) return overrides[text]
1108
730
  const trimmed = text.trim()
1109
731
  if (overrides[trimmed] !== undefined) return text.replace(trimmed, overrides[trimmed])
1110
732
  if (!ZH_CJK.test(text)) return null
1111
733
  const exact = ZH_EXACT.get(text)
1112
734
  if (exact !== undefined) return exact
1113
- if (trimmed !== text) {
1114
- const exactTrimmed = ZH_EXACT.get(trimmed)
735
+ if (trimmed !== text) {const exactTrimmed = ZH_EXACT.get(trimmed)
1115
736
  if (exactTrimmed !== undefined) return text.replace(trimmed, exactTrimmed)
1116
737
  }
1117
- for (const p of ZH_PATTERNS) {
1118
- const m = p.re.exec(trimmed)
1119
- if (m && m[0] === trimmed) {
1120
- let out = p.ruParts[0]
738
+ for (const p of ZH_PATTERNS) {const m = p.re.exec(trimmed)
739
+ if (m && m[0] === trimmed) {let out = p.ruParts[0]
1121
740
  for (let i = 1; i < p.ruParts.length; i++) out += m[i] + p.ruParts[i]
1122
741
  return text.replace(trimmed, out)
1123
742
  }
1124
743
  }
1125
744
  let replaced = text
1126
745
  let changed = false
1127
- for (const [zhPhrase, ruPhrase] of zhSortedExact) {
1128
- if (zhPhrase.length >= 2 && replaced.includes(zhPhrase)) {
1129
- replaced = replaced.split(zhPhrase).join(ruPhrase)
746
+ for (const [zhPhrase, ruPhrase] of zhSortedExact) {if (zhPhrase.length >= 2 && replaced.includes(zhPhrase)) {replaced = replaced.split(zhPhrase).join(ruPhrase)
1130
747
  changed = true
1131
748
  }
1132
749
  }
1133
750
  if (changed) return replaced
1134
751
  return null
1135
752
  }
1136
- const DOM_EN_ATTRS = {
1137
- 'Streaming preview': 'Предпросмотр стриминга',
1138
- 'Visualization streaming preview': 'Предпросмотр визуализации',
1139
- 'Security Auditor Shield': 'Защитный щит аудитора',
1140
- 'Shield: Safe': 'Щит: Безопасно',
1141
- 'Shield: Alert': 'Щит: Тревога',
1142
- 'e.g. Implement report export and cover with unit tests': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами',
1143
- 'e.g. Implement report export and cover with unit tests...': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами…',
1144
- 'e.g. Implement user profile settings card with theme tokens': 'Например: Реализовать карточку настроек профиля с токенами темы',
1145
- 'Detailed functional specs, acceptance criteria, constraints...': 'Детальная функциональная спецификация, критерии приёмки, ограничения…',
1146
- 'Specific instructions, questions, or requirements for this specialist...': 'Конкретные инструкции, вопросы или требования для данного специалиста…',
1147
- }
1148
- const DOM_EN_TEXT = {
1149
- 'Scheduled tasks': 'Задачи по расписанию',
1150
- 'Today': 'Сегодня',
1151
- 'Plugin Market': 'Магазин плагинов',
1152
- 'GitHub ops': 'Операции с GitHub',
1153
- 'Auto mode': 'Автоматический режим',
1154
- 'Full access': 'Полный доступ',
1155
- 'Read only': 'Только чтение',
1156
- 'Side card': 'Боковая панель',
1157
- 'panelName': 'Палитра команд',
1158
- 'Hooks': 'Хуки',
1159
- 'Manage what the side card shows and how it behaves': 'Настройка содержимого и поведения боковой панели',
1160
- 'Inject the sidebar-open tool for the model': 'Предоставить модели инструмент sidebar-open',
1161
- 'Position compatibility mode': 'Режим совместимости расположения',
1162
- 'Auto-detect': 'Автоопределение',
1163
- 'Sidebar content': 'Содержимое боковой панели',
1164
- 'Changes': 'Изменения',
1165
- 'Tasks': 'Задачи',
1166
- 'Time Machine': 'Машина времени',
1167
- 'Live Canvas': 'Живой холст',
1168
- 'Side Chat (beta)': 'Боковой чат (бета)',
1169
- 'Terminal': 'Терминал',
1170
- 'Feature settings': 'Настройки функции',
1171
- 'Low': 'Низкий',
1172
- 'Medium': 'Средний',
1173
- 'High': 'Высокий',
1174
- 'Effort': 'Рассуждения',
1175
- 'Search engine (ModSearch)': 'Поисковая система (ModSearch)',
1176
- 'Search engine provider configuration.': 'Настройка провайдера поисковой системы.',
1177
- 'X search only': 'Только поиск в X',
1178
- 'Security Auditor Shield': 'Защитный щит аудитора',
1179
- 'Shield: Safe': 'Щит: Безопасно',
1180
- 'Shield: Alert': 'Щит: Тревога',
1181
- 'Lens': 'Линза',
1182
- 'Gallery': 'Галерея',
1183
- 'SSH: Local': 'SSH: Локально',
1184
- 'SSH: Remote': 'SSH: Удалённо',
1185
- '7/7 rot': '7/7 рот.',
1186
- 'Smoke chat': 'Тестовый чат',
1187
- 'Quick Launch Goal': 'Быстрый запуск цели',
1188
- 'Define the objective for the agent in autonomous mode:': 'Сформулируйте задачу для автономной работы агента:',
1189
- 'Fix Bug': 'Исправление бага',
1190
- 'Refactor (YAGNI)': 'Рефакторинг (YAGNI)',
1191
- 'Tests & Coverage': 'Тесты и покрытие',
1192
- 'Code Review': 'Ревью кода',
1193
- 'New Feature': 'Новая функция',
1194
- 'Security Audit': 'Аудит безопасности',
1195
- 'Docs & Contract': 'Документация и контракт',
1196
- 'Upgrade Deps': 'Обновление зависимостей',
1197
- 'Dead Code': 'Мёртвый код',
1198
- 'Performance': 'Производительность',
1199
- 'Start Goal': 'Запустить цель',
1200
- 'Launch Multi-Agent Orchestrator': 'Запуск мультиагентного оркестратора',
1201
- 'Full DAG Pipeline': 'Полный DAG-пайплайн',
1202
- 'Direct Specialist Subagent': 'Прямой субагент-специалист',
1203
- 'Objective Title': 'Название задачи',
1204
- 'Scope & Requirements': 'Объём и требования',
1205
- 'Topology Scenario': 'Топологический сценарий',
1206
- 'Auto (Infer based on prompt complexity)': 'Авто (определить по сложности задачи)',
1207
- 'Hotfix (1 Stage: Triage & Minimal Fix)': 'Хотфикс (1 этап: анализ и точечный фикс)',
1208
- 'Simple (2 Stages: Spec + Exec)': 'Простой (2 этапа: ТЗ + реализация)',
1209
- 'Medium (4 Stages: Spec -> Design -> Code -> QA)': 'Средний (4 этапа: ТЗ -> Дизайн -> Код -> Тестирование)',
1210
- 'Complex (6 Stages: Full Engineering Lifecycle)': 'Сложный (6 этапов: полный инженерный цикл)',
1211
- 'Enterprise (7 Stages: R&D Spike -> Fullstack -> Gate)': 'Enterprise (7 этапов: R&D исследование -> Фулстек -> Гейт приёмки)',
1212
- 'Target Specialist Role': 'Роль целевого специалиста',
1213
- 'UI/UX Interface Designer': 'UI/UX дизайнер интерфейсов',
1214
- 'System Architect (DESIGN.md / ADR)': 'Системный архитектор (DESIGN.md / ADR)',
1215
- 'Technical Spec Analyst': 'Аналитик технических спецификаций',
1216
- 'Senior Frontend Developer': 'Ведущий frontend-разработчик',
1217
- 'Senior Backend Developer': 'Ведущий backend-разработчик',
1218
- 'QA Automation Engineer': 'Инженер автоматизации тестирования',
1219
- 'Refactoring & Complexity Specialist': 'Специалист по рефакторингу и сложности',
1220
- 'Hotfix & Diagnostic Engineer': 'Инженер хотфиксов и диагностики',
1221
- 'Documentation Specialist': 'Технический писатель / Документация',
1222
- 'Spike & R&D Researcher': 'Исследователь R&D и прототипирования',
1223
- 'DevOps & Tooling Specialist': 'DevOps и инфраструктурный специалист',
1224
- 'Instructions for Subagent': 'Инструкции для субагента',
1225
- 'Start Pipeline': 'Запустить пайплайн',
1226
- 'Delegate Subagent': 'Делегировать субагенту',
1227
- 'Dispatching...': 'Отправка…',
1228
- 'Discounted rate active': 'Действует сниженный тариф',
1229
- 'off-peak': 'непиковый',
1230
- 'peak': 'пиковый',
1231
- 'Context cache saved:': 'Сэкономлено на кэше:',
1232
- '1M tokens, $': '1 млн токенов, $',
1233
- 'Input (cache hit)': 'Ввод (попадание в кэш)',
1234
- 'Input (cache miss)': 'Ввод (промах кэша)',
1235
- 'Output': 'Вывод',
1236
- 'SESSION TOKENS': 'ТОКЕНЫ СЕССИИ',
1237
- 'SPEND BY MODEL': 'РАСХОД ПО МОДЕЛЯМ',
1238
- 'Session total:': 'Всего за сессию:',
1239
- 'Copy Summary': 'Скопировать сводку',
1240
- 'Context Lens': 'Линза контекста',
1241
- 'Saved tokens': 'Сэкономлено токенов',
1242
- '% Saved': '% экономии',
1243
- 'ops': 'операций',
1244
- 'Budget': 'Бюджет',
1245
- 'Active focus': 'Активный фокус',
1246
- 'No focus paths set': 'Пути фокусировки не заданы',
1247
- 'Status': 'Статус',
1248
- 'Graph & CI': 'Граф и CI',
1249
- 'Events & PRs': 'События и PR',
1250
- 'Branch': 'Ветка',
1251
- 'Sync': 'Синхронизация',
1252
- 'Up to date with remote (@{upstream})': 'Синхронизировано с удалённым репозиторием (@{upstream})',
1253
- 'Up to date with remote': 'Синхронизировано с удалённым репозиторием',
1254
- 'Clean': 'Чисто',
1255
- 'No modified or untracked files in the working directory.': 'В рабочем каталоге нет изменённых или неотслеживаемых файлов.',
1256
- 'RECENT COMMITS': 'ПОСЛЕДНИЕ КОММИТЫ',
1257
- 'Active jobs': 'Активные задачи',
1258
- 'No active jobs': 'Нет активных задач',
1259
- 'Register in Models': 'Зарегистрировать в моделях',
1260
- 'Base URL': 'Базовый URL',
1261
- 'API key env / credential name': 'Имя переменной окружения / ключа API',
1262
- 'Default model': 'Модель по умолчанию',
1263
- 'Enabled': 'Включено',
1264
- 'Save': 'Сохранить',
1265
- 'Saved': 'Сохранено',
1266
- 'Refresh': 'Обновить',
1267
- 'Loading settings…': 'Загрузка настроек…',
1268
- 'Preferred engine': 'Предпочитаемый движок',
1269
- 'API key': 'API-ключ',
1270
- 'stored, leave empty to keep it': 'сохранён, оставьте пустым для сохранения',
1271
- 'Built-in official endpoint, leave blank to use it': 'Встроенная официальная конечная точка, оставьте пустым',
1272
- 'Discard': 'Сбросить',
1273
- 'Model synchronization': 'Синхронизация моделей',
1274
- 'All API-key providers': 'Все провайдеры API-ключей',
1275
- 'Preview only (dry-run)': 'Только предпросмотр (dry-run)',
1276
- 'Confirm stale removal': 'Подтверждать удаление устаревших',
1277
- 'Refresh status': 'Обновить статус',
1278
- 'Refresh all': 'Обновить всё',
1279
- 'Discover': 'Обнаружить',
1280
- 'Check availability': 'Проверить доступность',
1281
- 'Check credentials': 'Проверить учётные данные',
1282
- 'Choose models': 'Выбрать модели',
1283
- 'Manual model selection': 'Выбор моделей вручную',
1284
- 'Mode': 'Режим',
1285
- 'Hybrid (auto-rewrite + tools)': 'Гибридный (авто-переписывание + инструменты)',
1286
- 'Describe strategy': 'Стратегия описания',
1287
- 'Auto (use vision LLM)': 'Авто (использовать Vision LLM)',
1288
- 'Escalation': 'Эскалация',
1289
- 'Simple only (one pass)': 'Только простая (один проход)',
1290
- 'Routing': 'Маршрутизация',
1291
- 'Channel order': 'Порядок каналов',
1292
- 'Issue Reporter': 'Репортёр проблем',
1293
- 'GitHub sign-in is not configured for this installation.': 'Вход через GitHub не настроен для этой установки.',
1294
- '0 plugins': '0 плагинов',
1295
- 'Catalog': 'Каталог',
1296
- 'Report Editor': 'Редактор отчёта',
1297
- 'My Reports': 'Мои отчёты',
1298
- 'Authorization': 'Авторизация',
1299
- 'Search installed plugins…': 'Поиск установленных плагинов…',
1300
- 'Search installed plugins...': 'Поиск установленных плагинов...',
1301
- 'Refresh inventory': 'Обновить список',
1302
- 'Loading inventory & status…': 'Загрузка списка и статуса…',
1303
- 'Loading inventory & status...': 'Загрузка списка и статуса...'
1304
- }
1305
- const ZH_WALKER = (root) => {
1306
- try {
1307
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
753
+ const DOM_EN_ATTRS = {'Streaming preview': 'Предпросмотр стриминга','Visualization streaming preview': 'Предпросмотр визуализации','Security Auditor Shield': 'Защитный щит аудитора','Shield: Safe': 'Щит: Безопасно','Shield: Alert': 'Щит: Тревога','e.g. Implement report export and cover with unit tests': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами','e.g. Implement report export and cover with unit tests...': 'Например: Реализовать экспорт отчёта и покрыть юнит-тестами…','e.g. Implement user profile settings card with theme tokens': 'Например: Реализовать карточку настроек профиля с токенами темы','Detailed functional specs, acceptance criteria, constraints...': 'Детальная функциональная спецификация, критерии приёмки, ограничения…','Specific instructions, questions, or requirements for this specialist...': 'Конкретные инструкции, вопросы или требования для данного специалиста…',}
754
+ const DOM_EN_TEXT = {'Scheduled tasks': 'Задачи по расписанию','Today': 'Сегодня','Plugin Market': 'Магазин плагинов','GitHub ops': 'Операции с GitHub','Auto mode': 'Автоматический режим','Full access': 'Полный доступ','Read only': 'Только чтение','Side card': 'Боковая панель','panelName': 'Палитра команд','Hooks': 'Хуки','Manage what the side card shows and how it behaves': 'Настройка содержимого и поведения боковой панели','Inject the sidebar-open tool for the model': 'Предоставить модели инструмент sidebar-open','Position compatibility mode': 'Режим совместимости расположения','Auto-detect': 'Автоопределение','Sidebar content': 'Содержимое боковой панели','Changes': 'Изменения','Tasks': 'Задачи','Time Machine': 'Машина времени','Live Canvas': 'Живой холст','Side Chat (beta)': 'Боковой чат (бета)','Terminal': 'Терминал','Feature settings': 'Настройки функции','Low': 'Низкий','Medium': 'Средний','High': 'Высокий','Effort': 'Рассуждения','Search engine (ModSearch)': 'Поисковая система (ModSearch)','Search engine provider configuration.': 'Настройка провайдера поисковой системы.','X search only': 'Только поиск в X','Security Auditor Shield': 'Защитный щит аудитора','Shield: Safe': 'Щит: Безопасно','Shield: Alert': 'Щит: Тревога','Lens': 'Линза','Gallery': 'Галерея','SSH: Local': 'SSH: Локально','SSH: Remote': 'SSH: Удалённо','7/7 rot': '7/7 рот.','Smoke chat': 'Тестовый чат','Quick Launch Goal': 'Быстрый запуск цели','Define the objective for the agent in autonomous mode:': 'Сформулируйте задачу для автономной работы агента:','Fix Bug': 'Исправление бага','Refactor (YAGNI)': 'Рефакторинг (YAGNI)','Tests & Coverage': 'Тесты и покрытие','Code Review': 'Ревью кода','New Feature': 'Новая функция','Security Audit': 'Аудит безопасности','Docs & Contract': 'Документация и контракт','Upgrade Deps': 'Обновление зависимостей','Dead Code': 'Мёртвый код','Performance': 'Производительность','Start Goal': 'Запустить цель','Launch Multi-Agent Orchestrator': 'Запуск мультиагентного оркестратора','Full DAG Pipeline': 'Полный DAG-пайплайн','Direct Specialist Subagent': 'Прямой субагент-специалист','Objective Title': 'Название задачи','Scope & Requirements': 'Объём и требования','Topology Scenario': 'Топологический сценарий','Auto (Infer based on prompt complexity)': 'Авто (определить по сложности задачи)','Hotfix (1 Stage: Triage & Minimal Fix)': 'Хотфикс (1 этап: анализ и точечный фикс)','Simple (2 Stages: Spec + Exec)': 'Простой (2 этапа: ТЗ + реализация)','Medium (4 Stages: Spec -> Design -> Code -> QA)': 'Средний (4 этапа: ТЗ -> Дизайн -> Код -> Тестирование)','Complex (6 Stages: Full Engineering Lifecycle)': 'Сложный (6 этапов: полный инженерный цикл)','Enterprise (7 Stages: R&D Spike -> Fullstack -> Gate)': 'Enterprise (7 этапов: R&D исследование -> Фулстек -> Гейт приёмки)','Target Specialist Role': 'Роль целевого специалиста','UI/UX Interface Designer': 'UI/UX дизайнер интерфейсов','System Architect (DESIGN.md / ADR)': 'Системный архитектор (DESIGN.md / ADR)','Technical Spec Analyst': 'Аналитик технических спецификаций','Senior Frontend Developer': 'Ведущий frontend-разработчик','Senior Backend Developer': 'Ведущий backend-разработчик','QA Automation Engineer': 'Инженер автоматизации тестирования','Refactoring & Complexity Specialist': 'Специалист по рефакторингу и сложности','Hotfix & Diagnostic Engineer': 'Инженер хотфиксов и диагностики','Documentation Specialist': 'Технический писатель / Документация','Spike & R&D Researcher': 'Исследователь R&D и прототипирования','DevOps & Tooling Specialist': 'DevOps и инфраструктурный специалист','Instructions for Subagent': 'Инструкции для субагента','Start Pipeline': 'Запустить пайплайн','Delegate Subagent': 'Делегировать субагенту','Dispatching...': 'Отправка…','Discounted rate active': 'Действует сниженный тариф','off-peak': 'непиковый','peak': 'пиковый','Context cache saved:': 'Сэкономлено на кэше:','1M tokens, $': '1 млн токенов, $','Input (cache hit)': 'Ввод (попадание в кэш)','Input (cache miss)': 'Ввод (промах кэша)','Output': 'Вывод','SESSION TOKENS': 'ТОКЕНЫ СЕССИИ','SPEND BY MODEL': 'РАСХОД ПО МОДЕЛЯМ','Session total:': 'Всего за сессию:','Copy Summary': 'Скопировать сводку','Context Lens': 'Линза контекста','Saved tokens': 'Сэкономлено токенов','% Saved': '% экономии','ops': 'операций','Budget': 'Бюджет','Active focus': 'Активный фокус','No focus paths set': 'Пути фокусировки не заданы','Status': 'Статус','Graph & CI': 'Граф и CI','Events & PRs': 'События и PR','Branch': 'Ветка','Sync': 'Синхронизация','Up to date with remote (@{upstream})': 'Синхронизировано с удалённым репозиторием (@{upstream})','Up to date with remote': 'Синхронизировано с удалённым репозиторием','Clean': 'Чисто','No modified or untracked files in the working directory.': 'В рабочем каталоге нет изменённых или неотслеживаемых файлов.','RECENT COMMITS': 'ПОСЛЕДНИЕ КОММИТЫ','Active jobs': 'Активные задачи','No active jobs': 'Нет активных задач','Register in Models': 'Зарегистрировать в моделях','Base URL': 'Базовый URL','API key env / credential name': 'Имя переменной окружения / ключа API','Default model': 'Модель по умолчанию','Enabled': 'Включено','Save': 'Сохранить','Saved': 'Сохранено','Refresh': 'Обновить','Loading settings…': 'Загрузка настроек…','Preferred engine': 'Предпочитаемый движок','API key': 'API-ключ','stored, leave empty to keep it': 'сохранён, оставьте пустым для сохранения','Built-in official endpoint, leave blank to use it': 'Встроенная официальная конечная точка, оставьте пустым','Discard': 'Сбросить','Model synchronization': 'Синхронизация моделей','All API-key providers': 'Все провайдеры API-ключей','Preview only (dry-run)': 'Только предпросмотр (dry-run)','Confirm stale removal': 'Подтверждать удаление устаревших','Refresh status': 'Обновить статус','Refresh all': 'Обновить всё','Discover': 'Обнаружить','Check availability': 'Проверить доступность','Check credentials': 'Проверить учётные данные','Choose models': 'Выбрать модели','Manual model selection': 'Выбор моделей вручную','Mode': 'Режим','Hybrid (auto-rewrite + tools)': 'Гибридный (авто-переписывание + инструменты)','Describe strategy': 'Стратегия описания','Auto (use vision LLM)': 'Авто (использовать Vision LLM)','Escalation': 'Эскалация','Simple only (one pass)': 'Только простая (один проход)','Routing': 'Маршрутизация','Channel order': 'Порядок каналов','Issue Reporter': 'Репортёр проблем','GitHub sign-in is not configured for this installation.': 'Вход через GitHub не настроен для этой установки.','0 plugins': '0 плагинов','Catalog': 'Каталог','Report Editor': 'Редактор отчёта','My Reports': 'Мои отчёты','Authorization': 'Авторизация','Search installed plugins…': 'Поиск установленных плагинов…','Search installed plugins...': 'Поиск установленных плагинов...','Refresh inventory': 'Обновить список','Loading inventory & status…': 'Загрузка списка и статуса…','Loading inventory & status...': 'Загрузка списка и статуса...'
755
+ }
756
+ const ZH_WALKER = (root) => {try {const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
1308
757
  const hits = []
1309
758
  const curOverrides = getOverrides()
1310
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
1311
- const val = node.nodeValue
759
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {const val = node.nodeValue
1312
760
  if (!val) continue
1313
761
  const trimmed = val.trim()
1314
- if (curOverrides[trimmed] !== undefined) {
1315
- node.nodeValue = val.replace(trimmed, curOverrides[trimmed])
762
+ if (curOverrides[trimmed] !== undefined) {node.nodeValue = val.replace(trimmed, curOverrides[trimmed])
1316
763
  continue
1317
764
  }
1318
- if (trimmed.length >= 1 && ZH_CJK.test(val)) {
1319
- hits.push(node)
1320
- } else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {
1321
- const p = node.parentElement
1322
- if (p && !p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) {
1323
- node.nodeValue = val.replace(trimmed, DOM_EN_TEXT[trimmed])
765
+ if (trimmed.length >= 1 && ZH_CJK.test(val)) {hits.push(node)
766
+ } else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {const p = node.parentElement
767
+ if (p && !p.closest('code, pre, script, style, textarea, input, select, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) {node.nodeValue = val.replace(trimmed, DOM_EN_TEXT[trimmed])
1324
768
  }
1325
769
  }
1326
770
  }
1327
- for (const node of hits) {
1328
- const next = zhTranslateText(node.nodeValue)
771
+ for (const node of hits) {const next = zhTranslateText(node.nodeValue)
1329
772
  if (next && next !== node.nodeValue) node.nodeValue = next
1330
773
  }
1331
- for (const el of root.querySelectorAll ? root.querySelectorAll('[title],[placeholder],[aria-label]') : []) {
1332
- for (const attr of ['title', 'placeholder', 'aria-label']) {
1333
- const v = el.getAttribute && el.getAttribute(attr)
1334
- if (v) {
1335
- const tr = v.trim()
1336
- if (DOM_EN_TEXT[tr]) {
1337
- el.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
1338
- } else if (DOM_EN_ATTRS[tr]) {
1339
- el.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
1340
- } else if (ZH_CJK.test(v)) {
1341
- const next = zhTranslateText(v)
774
+ 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)
775
+ if (v) {const tr = v.trim()
776
+ if (DOM_EN_TEXT[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
777
+ } else if (DOM_EN_ATTRS[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
778
+ } else if (ZH_CJK.test(v)) {const next = zhTranslateText(v)
1342
779
  if (next) el.setAttribute(attr, next)
1343
780
  }
1344
781
  }
1345
782
  }
1346
783
  }
1347
- if (root.getAttribute) {
1348
- for (const attr of ['title', 'placeholder', 'aria-label']) {
1349
- const v = root.getAttribute(attr)
1350
- if (v) {
1351
- const tr = v.trim()
784
+ if (root.getAttribute) {for (const attr of ['title', 'placeholder', 'aria-label']) {const v = root.getAttribute(attr)
785
+ if (v) {const tr = v.trim()
1352
786
  if (DOM_EN_TEXT[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
1353
787
  else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
1354
788
  }
@@ -1359,35 +793,22 @@ else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr
1359
793
  let zhObserver = null
1360
794
  let zhDebounceTimer = null
1361
795
  let zhWalking = false
1362
- const queueZhWalk = (target) => {
1363
- if (zhDebounceTimer) return
1364
- zhDebounceTimer = setTimeout(() => {
1365
- zhDebounceTimer = null
796
+ const queueZhWalk = (target) => {if (zhDebounceTimer) return
797
+ zhDebounceTimer = setTimeout(() => {zhDebounceTimer = null
1366
798
  if (zhWalking) return
1367
799
  zhWalking = true
1368
- try {
1369
- ZH_WALKER(target || document.body)
1370
- } finally {
1371
- zhWalking = false
800
+ try {ZH_WALKER(target || document.body)
801
+ } finally {zhWalking = false
1372
802
  }
1373
803
  }, 120)
1374
804
  }
1375
- const syncZhDom = () => {
1376
- try {
1377
- if (typeof document === 'undefined') return
805
+ const syncZhDom = () => {try {if (typeof document === 'undefined') return
1378
806
  const ru = runtime.getLocale().active === 'ru'
1379
- if (!ru) {
1380
- if (zhObserver) { zhObserver.disconnect(); zhObserver = null }
807
+ if (!ru) {if (zhObserver) { zhObserver.disconnect(); zhObserver = null }
1381
808
  if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
1382
809
  return
1383
810
  }
1384
- if (!zhObserver) {
1385
- zhObserver = new MutationObserver((records) => {
1386
- for (const record of records) {
1387
- if (record.type === 'childList') {
1388
- for (const node of record.addedNodes) {
1389
- if (node.nodeType === 1) {
1390
- if (node.closest && node.closest('.chat-message, .markdown-body, pre, code, [data-stream]')) continue
811
+ if (!zhObserver) {zhObserver = new MutationObserver((records) => {for (const record of records) {if (record.type === 'childList') {for (const node of record.addedNodes) {if (node.nodeType === 1) {if (node.closest && node.closest('.chat-message, .markdown-body, pre, code, [data-stream]')) continue
1391
812
  queueZhWalk(node)
1392
813
  return
1393
814
  }
@@ -1401,9 +822,7 @@ queueZhWalk(document.body)
1401
822
  } catch (err) { }
1402
823
  }
1403
824
  const unsubscribeZh = runtime.subscribe(syncZhDom)
1404
- ctx.effect(() => {
1405
- return () => {
1406
- unsubscribeZh()
825
+ ctx.effect(() => {return () => {unsubscribeZh()
1407
826
  if (zhObserver) zhObserver.disconnect()
1408
827
  if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
1409
828
  }
@@ -1411,28 +830,21 @@ if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
1411
830
  syncZhDom()
1412
831
  const TYPO_YO_PAIRS = [["еще","ещё"],["ее","её"],["черный","чёрный"],["черная","чёрная"],["черные","чёрные"],["зеленый","зелёный"],["желтый","жёлтый"],["легкий","лёгкий"],["тяжелый","тяжёлый"],["надежный","надёжный"],["дешевый","дешёвый"],["идет","идёт"],["дает","даёт"],["ведет","ведёт"],["несет","несёт"],["живет","живёт"],["привел","привёл"],["шел","шёл"],["нее","неё"],["мое","моё"],["серьезно","серьёзно"],["придется","придётся"],["насчет","насчёт"],["твое","твоё"],["свое","своё"],["пойдем","пойдём"],["пришел","пришёл"],["нашел","нашёл"],["идем","идём"],["вперед","вперёд"],["пошел","пошёл"],["ребенка","ребёнка"],["ребенок","ребёнок"],["счет","счёт"],["своем","своём"],["придет","придёт"],["ушел","ушёл"],["ждет","ждёт"],["мертв","мёртв"],["твоем","твоём"],["вернется","вернётся"],["днем","днём"],["найдем","найдём"],["пойдет","пойдёт"],["начнем","начнём"],["вернемся","вернёмся"],["остается","остаётся"],["принес","принёс"],["идешь","идёшь"],["трех","трёх"],["ребенком","ребёнком"],["самолет","самолёт"],["определенно","определённо"],["пойдешь","пойдёшь"],["умрет","умрёт"],["прием","приём"],["прошел","прошёл"],["убьет","убьёт"],["тетя","тётя"],["провел","провёл"],["произойдет","произойдёт"],["семьей","семьёй"],["пойдемте","пойдёмте"],["пройдет","пройдёт"],["возьмем","возьмём"],["вернешься","вернёшься"],["найдешь","найдёшь"],["живешь","живёшь"],["отчет","отчёт"],["займет","займёт"],["ждем","ждём"],["сошел","сошёл"],["вдвоем","вдвоём"],["найдет","найдёт"],["вел","вёл"],["живем","живём"],["зашел","зашёл"],["начнется","начнётся"],["путем","путём"],["подойдет","подойдёт"],["режиссер","режиссёр"],["уйдет","уйдёт"],["придешь","придёшь"],["идемте","идёмте"],["ведешь","ведёшь"],["пес","пёс"],["умрешь","умрёшь"],["ждешь","ждёшь"],["найдете","найдёте"],["найдется","найдётся"],["клево","клёво"],["поймешь","поймёшь"],["разберемся","разберёмся"],["времен","времён"],["введен","введён"],["включен","включён"],["включенных","включённых"],["возьмется","возьмётся"],["завершен","завершён"],["завершенного","завершённого"],["задает","задаёт"],["задается","задаётся"],["заменен","заменён"],["звезд","звёзд"],["звезды","звёзды"],["изменен","изменён"],["истек","истёк"],["незавершенная","незавершённая"],["неподтвержденные","неподтверждённые"],["несохраненные","несохранённые"],["обновлен","обновлён"],["обновленные","обновлённые"],["определен","определён"],["отклонен","отклонён"],["отменен","отменён"],["очередность","очерёдность"],["поврежден","повреждён"],["повторен","повторён"],["подключен","подключён"],["подтвержденные","подтверждённые"],["приемки","приёмки"],["раздает","раздаёт"],["разрешенная","разрешённая"],["разрешенные","разрешённые"],["создаем","создаём"],["сойдется","сойдётся"],["сохранен","сохранён"],["сохраненный","сохранённый"],["счету","счёту"],["тяжелые","тяжёлые"],["темная","тёмная"],["уберет","уберёт"],["удален","удалён"],["учетные","учётные"],["учетных","учётных"],["емкости","ёмкости"],["емкость","ёмкость"]]
1413
832
  const typoYo = makeTypoYo(TYPO_YO_PAIRS)
1414
- const getTypoConf = () => {
1415
- try {
1416
- const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
833
+ const getTypoConf = () => {try {const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
1417
834
  if (!t || t.enabled === false) return null
1418
835
  return { yo: t.yo === true }
1419
836
  } catch (err) { return null }
1420
837
  }
1421
- const typoNode = (node, conf) => {
1422
- const before = node.nodeValue
838
+ const typoNode = (node, conf) => {const before = node.nodeValue
1423
839
  if (!before || !before.match || (before.match(/[\u0400-\u04FF]/g) || []).length < 3) return
1424
840
  if (node.parentElement && node.parentElement.closest('code, pre, a, script, style, textarea, input, select, button, kbd, samp, [contenteditable="true"], [data-composer-input], [role="textbox"]')) return
1425
841
  if (node.parentElement && node.parentElement.closest('.katex, [data-latex], math')) return
1426
842
  if (/\$[^$\n]+\$/.test(before)) return
1427
- if (node.parentElement && node.parentElement.closest('[data-chat-flow-status="running"], [data-turn-running], [data-turn-tail], [data-streaming="true"], .dsw-turn-running, [class*="streaming"], [class*="Streaming"]')) {
1428
- return
843
+ if (node.parentElement && node.parentElement.closest('[data-chat-flow-status="running"], [data-turn-running], [data-turn-tail], [data-streaming="true"], .dsw-turn-running, [class*="streaming"], [class*="Streaming"]')) {return
1429
844
  }
1430
845
  const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1431
- if (sel && !sel.isCollapsed && sel.rangeCount > 0) {
1432
- try {
1433
- const range = sel.getRangeAt(0)
1434
- if (range.intersectsNode ? range.intersectsNode(node) : (sel.containsNode && sel.containsNode(node, true))) {
1435
- return
846
+ if (sel && !sel.isCollapsed && sel.rangeCount > 0) {try {const range = sel.getRangeAt(0)
847
+ if (range.intersectsNode ? range.intersectsNode(node) : (sel.containsNode && sel.containsNode(node, true))) {return
1436
848
  }
1437
849
  } catch (e) { void e; }
1438
850
  }
@@ -1443,44 +855,32 @@ after = typoNbsp(after)
1443
855
  if (conf.yo) after = typoYo(after)
1444
856
  if (after !== before) node.nodeValue = after
1445
857
  }
1446
- const typoWalk = (root, conf) => {
1447
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
858
+ const typoWalk = (root, conf) => {const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
1448
859
  for (let node = walker.nextNode(); node; node = walker.nextNode()) typoNode(node, conf)
1449
860
  }
1450
861
  let typoObserver = null
1451
862
  let typoQueued = null
1452
- const flushTypo = () => {
1453
- typoQueued = null
1454
- try {
1455
- const conf = getTypoConf()
863
+ const flushTypo = () => {typoQueued = null
864
+ try {const conf = getTypoConf()
1456
865
  if (!conf || !typoPending.size) return
1457
- for (const root of typoPending) {
1458
- if (root.nodeType === 3) typoNode(root, conf)
866
+ for (const root of typoPending) {if (root.nodeType === 3) typoNode(root, conf)
1459
867
  else typoWalk(root, conf)
1460
868
  }
1461
869
  typoPending.clear()
1462
870
  } catch (err) { }
1463
871
  }
1464
872
  const typoPending = new Set()
1465
- const queueTypo = (roots) => {
1466
- for (const r of roots) typoPending.add(r)
873
+ const queueTypo = (roots) => {for (const r of roots) typoPending.add(r)
1467
874
  if (!typoQueued) typoQueued = requestAnimationFrame(flushTypo)
1468
875
  }
1469
- const syncTypo = () => {
1470
- try {
1471
- if (typeof document === 'undefined') return
1472
- if (runtime.getLocale().active !== 'ru' || !getTypoConf()) {
1473
- if (typoObserver) { typoObserver.disconnect(); typoObserver = null }
876
+ const syncTypo = () => {try {if (typeof document === 'undefined') return
877
+ if (runtime.getLocale().active !== 'ru' || !getTypoConf()) {if (typoObserver) { typoObserver.disconnect(); typoObserver = null }
1474
878
  return
1475
879
  }
1476
880
  if (typoObserver) return
1477
881
  typoWalk(document.body, getTypoConf())
1478
- typoObserver = new MutationObserver((records) => {
1479
- const roots = []
1480
- for (const record of records) {
1481
- if (record.type === 'childList') {
1482
- for (const added of record.addedNodes) {
1483
- if (added.nodeType === 3 || added.nodeType === 1) roots.push(added)
882
+ typoObserver = new MutationObserver((records) => {const roots = []
883
+ for (const record of records) {if (record.type === 'childList') {for (const added of record.addedNodes) {if (added.nodeType === 3 || added.nodeType === 1) roots.push(added)
1484
884
  }
1485
885
  }
1486
886
  }
@@ -1490,9 +890,7 @@ typoObserver.observe(document.body, { childList: true, subtree: true })
1490
890
  } catch (err) { }
1491
891
  }
1492
892
  const unsubscribeTypo = runtime.subscribe(syncTypo)
1493
- ctx.effect(() => {
1494
- return () => {
1495
- unsubscribeTypo()
893
+ ctx.effect(() => {return () => {unsubscribeTypo()
1496
894
  if (typoObserver) typoObserver.disconnect()
1497
895
  }
1498
896
  }, 'dsh-russian-lang: typography')
@@ -1500,51 +898,37 @@ syncTypo()
1500
898
  const layout = makeLayout(new Set(["не","что","ты","это","на","он","мы","как","вы","да","мне","нет","меня","так","но","его","все","она","тебя","если","за","бы","тебе","они","чтобы","же","есть","просто","из","для"]), new Set())
1501
899
  const layoutFixCandidate = layout.candidate
1502
900
  const learnWords = layout.learnWords
1503
- function setNativeInputValue(el, value, cursorStart, cursorEnd) {
1504
- if (!el) return
901
+ function setNativeInputValue(el, value, cursorStart, cursorEnd) {if (!el) return
1505
902
  const oldVal = el.value || ''
1506
903
  if (oldVal === value) return
1507
904
  const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype
1508
905
  const desc = Object.getOwnPropertyDescriptor(proto, 'value')
1509
- if (desc && desc.set) {
1510
- desc.set.call(el, value)
1511
- } else {
1512
- el.value = value
906
+ if (desc && desc.set) {desc.set.call(el, value)
907
+ } else {el.value = value
1513
908
  }
1514
- if (el._valueTracker) {
1515
- el._valueTracker.setValue(value === '' ? '__force__' : '')
909
+ if (el._valueTracker) {el._valueTracker.setValue(value === '' ? '__force__' : '')
1516
910
  }
1517
- try {
1518
- const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$') || k.startsWith('__reactEventHandlers$'))
1519
- if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {
1520
- el[propsKey].onChange({ target: el, currentTarget: el })
911
+ try {const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$') || k.startsWith('__reactEventHandlers$'))
912
+ if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {el[propsKey].onChange({ target: el, currentTarget: el })
1521
913
  }
1522
914
  } catch (e) { void e; }
1523
- try {
1524
- el.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
1525
- } catch (e) {
1526
- el.dispatchEvent(new Event('input', { bubbles: true }))
915
+ try {el.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
916
+ } catch (e) {el.dispatchEvent(new Event('input', { bubbles: true }))
1527
917
  }
1528
918
  el.dispatchEvent(new Event('change', { bubbles: true }))
1529
- if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {
1530
- const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
919
+ if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
1531
920
  try { el.setSelectionRange(cursorStart, end) } catch (e) { void e; }
1532
921
  }
1533
922
  }
1534
923
  let layoutHintEl = null
1535
- const layoutCurrentInput = (ev) => {
1536
- if (ev && ev.target) {
1537
- const t = ev.target
1538
- if (t.tagName === 'TEXTAREA' || (t.tagName === 'INPUT' && (t.type === 'text' || !t.type))) {
1539
- return t
924
+ const layoutCurrentInput = (ev) => {if (ev && ev.target) {const t = ev.target
925
+ if (t.tagName === 'TEXTAREA' || (t.tagName === 'INPUT' && (t.type === 'text' || !t.type))) {return t
1540
926
  }
1541
927
  const c = t.closest ? t.closest('[data-composer-input], [contenteditable="true"], [role="textbox"]') : null
1542
928
  if (c) return c
1543
929
  }
1544
930
  const el = document.activeElement
1545
- if (el) {
1546
- if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && (el.type === 'text' || !el.type))) {
1547
- return el
931
+ if (el) {if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && (el.type === 'text' || !el.type))) {return el
1548
932
  }
1549
933
  const c = el.closest ? el.closest('[data-composer-input], [contenteditable="true"], [role="textbox"]') : null
1550
934
  if (c) return c
@@ -1553,43 +937,35 @@ const focused = document.querySelector('[data-composer-input], [contenteditable=
1553
937
  if (focused) return focused
1554
938
  return null
1555
939
  }
1556
- function getCaretCharacterOffset(root) {
1557
- if (!root) return -1
940
+ function getCaretCharacterOffset(root) {if (!root) return -1
1558
941
  const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1559
942
  if (!sel || !sel.rangeCount) return -1
1560
- try {
1561
- const range = sel.getRangeAt(0)
943
+ try {const range = sel.getRangeAt(0)
1562
944
  if (!root.contains(range.startContainer)) return -1
1563
945
  const preCaretRange = range.cloneRange()
1564
946
  preCaretRange.selectNodeContents(root)
1565
947
  preCaretRange.setEnd(range.startContainer, range.startOffset)
1566
948
  return preCaretRange.toString().length
1567
- } catch (e) {
1568
- return -1
949
+ } catch (e) {return -1
1569
950
  }
1570
951
  }
1571
- function setCaretCharacterOffset(root, offset) {
1572
- if (!root || offset < 0) return
952
+ function setCaretCharacterOffset(root, offset) {if (!root || offset < 0) return
1573
953
  const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1574
954
  if (!sel) return
1575
- try {
1576
- let current = 0
955
+ try {let current = 0
1577
956
  let targetNode = null
1578
957
  let targetOffset = 0
1579
958
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null)
1580
959
  let node = walker.nextNode()
1581
- while (node) {
1582
- const len = node.nodeValue.length
1583
- if (current + len >= offset) {
1584
- targetNode = node
960
+ while (node) {const len = node.nodeValue.length
961
+ if (current + len >= offset) {targetNode = node
1585
962
  targetOffset = Math.max(0, offset - current)
1586
963
  break
1587
964
  }
1588
965
  current += len
1589
966
  node = walker.nextNode()
1590
967
  }
1591
- if (targetNode) {
1592
- const range = document.createRange()
968
+ if (targetNode) {const range = document.createRange()
1593
969
  range.setStart(targetNode, targetOffset)
1594
970
  range.collapse(true)
1595
971
  sel.removeAllRanges()
@@ -1597,12 +973,10 @@ sel.addRange(range)
1597
973
  }
1598
974
  } catch (e) { void e; }
1599
975
  }
1600
- const isCaretInCode = (el, value, caretOffset) => {
1601
- if (!el) return false
976
+ const isCaretInCode = (el, value, caretOffset) => {if (!el) return false
1602
977
  if (caretOffset == null || caretOffset < 0) caretOffset = (value || '').length
1603
978
  const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1604
- if (sel && sel.anchorNode && sel.anchorNode.parentElement) {
1605
- if (sel.anchorNode.parentElement.closest('code, pre, .katex, [data-latex], math')) return true
979
+ if (sel && sel.anchorNode && sel.anchorNode.parentElement) {if (sel.anchorNode.parentElement.closest('code, pre, .katex, [data-latex], math')) return true
1606
980
  }
1607
981
  const textBefore = (value || '').slice(0, caretOffset)
1608
982
  const triple = textBefore.match(/```/g)
@@ -1612,28 +986,22 @@ const single = lastLine.match(/`/g)
1612
986
  if (single && single.length % 2 === 1) return true
1613
987
  return false
1614
988
  }
1615
- const getComposerText = (el) => {
1616
- if (!el) return ''
1617
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
1618
- return el.value || ''
989
+ const getComposerText = (el) => {if (!el) return ''
990
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {return el.value || ''
1619
991
  }
1620
992
  const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
1621
993
  const raw = host.innerText !== undefined ? host.innerText : (host.textContent || '')
1622
994
  return raw.replace(/\r/g, '').replace(/[\u200B\uFEFF]/g, '').replace(/\n+$/, '')
1623
995
  }
1624
- const setComposerText = (el, value, cursorStart, cursorEnd) => {
1625
- if (!el) return
1626
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
1627
- setNativeInputValue(el, value, cursorStart, cursorEnd)
996
+ const setComposerText = (el, value, cursorStart, cursorEnd) => {if (!el) return
997
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {setNativeInputValue(el, value, cursorStart, cursorEnd)
1628
998
  return
1629
999
  }
1630
1000
  const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
1631
1001
  const initialOffset = typeof cursorStart === 'number' ? cursorStart : getCaretCharacterOffset(host)
1632
- try {
1633
- host.focus()
1002
+ try {host.focus()
1634
1003
  const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1635
- if (sel) {
1636
- const range = document.createRange()
1004
+ if (sel) {const range = document.createRange()
1637
1005
  range.selectNodeContents(host)
1638
1006
  sel.removeAllRanges()
1639
1007
  sel.addRange(range)
@@ -1643,25 +1011,18 @@ const targetOffset = typeof cursorStart === 'number' ? cursorStart : (initialOff
1643
1011
  setCaretCharacterOffset(host, targetOffset)
1644
1012
  } catch (e) { void e; }
1645
1013
  }
1646
- const layoutDismiss = () => {
1647
- if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
1014
+ const layoutDismiss = () => {if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
1648
1015
  }
1649
- const layoutShowHint = (inputEl, converted, direction) => {
1650
- layoutDismiss()
1016
+ const layoutShowHint = (inputEl, converted, direction) => {layoutDismiss()
1651
1017
  layoutHintEl = document.createElement('div')
1652
1018
  layoutHintEl.dataset.russianLangLayout = '1'
1653
- Object.assign(layoutHintEl.style, {
1654
- position: 'fixed', zIndex: '99999', background: 'var(--dsw-alias-bg-layer-3)',
1655
- color: 'var(--dsw-alias-label-primary)',
1656
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '8px', padding: '6px 10px',
1657
- fontSize: '13px', boxShadow: 'var(--dsw-alias-shadow-l2)', cursor: 'pointer'
1019
+ Object.assign(layoutHintEl.style, {position: 'fixed', zIndex: '99999', background: 'var(--dsw-alias-bg-layer-3)',color: 'var(--dsw-alias-label-primary)',border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '8px', padding: '6px 10px',fontSize: '13px', boxShadow: 'var(--dsw-alias-shadow-l2)', cursor: 'pointer'
1658
1020
  })
1659
1021
  const label = direction === 'cyr2lat' ? 'Команда, не та раскладка' : 'Не та раскладка'
1660
1022
  layoutHintEl.textContent = label + ': ' + converted
1661
- layoutHintEl.addEventListener('mousedown', (ev) => {
1662
- ev.preventDefault()
1023
+ layoutHintEl.addEventListener('mousedown', (ev) => {ev.preventDefault()
1663
1024
  setComposerText(inputEl, converted)
1664
- learnWords(converted) // #67: запомнить принятые слова
1025
+ learnWords(converted)
1665
1026
  layoutDismiss()
1666
1027
  })
1667
1028
  document.body.appendChild(layoutHintEl)
@@ -1670,30 +1031,23 @@ layoutHintEl.style.left = (r.left + 8) + 'px'
1670
1031
  layoutHintEl.style.bottom = (window.innerHeight - r.top + 6) + 'px'
1671
1032
  }
1672
1033
  let layoutBadgeEl = null
1673
- const layoutBadge = (el) => {
1674
- const value = getComposerText(el)
1034
+ const layoutBadge = (el) => {const value = getComposerText(el)
1035
+ const detected = typeof detectInputLayout === 'function' ? detectInputLayout(value) : null
1675
1036
  const last = value.trim().slice(-1)
1676
1037
  const isCyr = /[\u0430-\u044f\u0451]/.test(last)
1677
1038
  const isLat = /[a-z]/i.test(last)
1678
- const label = isCyr ? 'RU' : (isLat ? 'EN' : '')
1039
+ const label = detected || (isCyr ? 'RU' : (isLat ? 'EN' : ''))
1679
1040
  if (!label) { layoutBadgeHide(); return }
1680
- if (!layoutBadgeEl) {
1681
- layoutBadgeEl = document.createElement('button')
1041
+ if (!layoutBadgeEl) {layoutBadgeEl = document.createElement('button')
1682
1042
  layoutBadgeEl.type = 'button'
1683
1043
  layoutBadgeEl.dataset.russianLangLayoutBadge = '1'
1684
- Object.assign(layoutBadgeEl.style, {
1685
- position: 'fixed', zIndex: '99998', background: 'var(--dsw-alias-bg-layer-3)',
1686
- color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)',
1687
- borderRadius: '6px', padding: '1px 6px', fontSize: '11px', cursor: 'pointer',
1688
- fontFamily: 'monospace', lineHeight: '1.4', fontWeight: '600'
1044
+ Object.assign(layoutBadgeEl.style, {position: 'fixed', zIndex: '99998', background: 'var(--dsw-alias-bg-layer-3)',color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)',borderRadius: '6px', padding: '1px 6px', fontSize: '11px', cursor: 'pointer',fontFamily: 'monospace', lineHeight: '1.4', fontWeight: '600'
1689
1045
  })
1690
1046
  layoutBadgeEl.title = 'Раскладка — клик: конвертировать (Alt+L)'
1691
- layoutBadgeEl.addEventListener('mousedown', (ev) => {
1692
- ev.preventDefault()
1047
+ layoutBadgeEl.addEventListener('mousedown', (ev) => {ev.preventDefault()
1693
1048
  const v = getComposerText(el)
1694
1049
  const c = layoutFixCandidate(v, 'lat2cyr') || layoutFixCandidate(v, 'cyr2lat')
1695
- if (c) {
1696
- setComposerText(el, c.converted)
1050
+ if (c) {setComposerText(el, c.converted)
1697
1051
  learnWords(c.converted)
1698
1052
  }
1699
1053
  })
@@ -1704,138 +1058,108 @@ const r = el.getBoundingClientRect()
1704
1058
  layoutBadgeEl.style.left = (r.right - 36) + 'px'
1705
1059
  layoutBadgeEl.style.top = (r.top - 22) + 'px'
1706
1060
  }
1707
- const layoutBadgeHide = () => {
1708
- if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
1061
+ const layoutBadgeHide = () => {if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
1709
1062
  }
1710
1063
  let isFormatting = false
1711
- const layoutOnInput = (ev) => {
1712
- if (isFormatting) return
1713
- try {
1714
- const el = layoutCurrentInput(ev)
1064
+ const layoutOnInput = (ev) => {if (isFormatting) return
1065
+ try {const el = layoutCurrentInput(ev)
1715
1066
  if (!el) { layoutDismiss(); layoutBadgeHide(); return }
1716
- layoutBadge(el) // #66: метка раскладки
1067
+ layoutBadge(el)
1717
1068
  const value = getComposerText(el)
1718
1069
  if (value.trim().length < 4) { layoutDismiss(); return }
1719
1070
  const latCount = (value.match(/[a-z]/g) || []).length
1720
1071
  const cyrCount = (value.match(/[\u0430-\u044f\u0451]/g) || []).length
1721
- if (latCount > cyrCount && cyrCount === 0) {
1722
- const c = layoutFixCandidate(value, 'lat2cyr')
1072
+ if (latCount > cyrCount && cyrCount === 0) {const c = layoutFixCandidate(value, 'lat2cyr')
1723
1073
  if (c) { layoutShowHint(el, c.converted, 'ru'); return }
1724
1074
  }
1725
- if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {
1726
- const c = layoutFixCandidate(value, 'cyr2lat')
1075
+ if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {const c = layoutFixCandidate(value, 'cyr2lat')
1727
1076
  if (c) { layoutShowHint(el, c.converted, 'cmd'); return }
1728
1077
  }
1729
1078
  layoutDismiss()
1730
1079
  } catch (err) { }
1731
1080
  }
1732
1081
  const unsubscribeLayout = runtime.subscribe(layoutOnInput)
1733
- const layoutOnKeydown = (ev) => {
1734
- const el = layoutCurrentInput(ev)
1082
+ const layoutOnKeydown = (ev) => {const el = layoutCurrentInput(ev)
1735
1083
  if (!el) return
1736
1084
  const value = getComposerText(el)
1737
1085
  const isL = ev.code === 'KeyL' || ev.key.toLowerCase() === 'l' || ev.key.toLowerCase() === 'д'
1738
- if (ev.altKey && !ev.ctrlKey && !ev.metaKey && isL) {
1739
- const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1086
+ if (ev.altKey && !ev.ctrlKey && !ev.metaKey && isL) {const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
1740
1087
  const selectedText = (sel && !sel.isCollapsed) ? sel.toString() : ''
1741
- if (selectedText) {
1742
- const c = layoutFixCandidate(selectedText, 'lat2cyr') || layoutFixCandidate(selectedText, 'cyr2lat')
1743
- if (c) {
1744
- ev.preventDefault()
1088
+ if (selectedText) {const c = layoutFixCandidate(selectedText, 'lat2cyr') || layoutFixCandidate(selectedText, 'cyr2lat')
1089
+ if (c) {ev.preventDefault()
1745
1090
  isFormatting = true
1746
- try {
1747
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
1748
- const sStart = el.selectionStart || 0
1091
+ try {if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {const sStart = el.selectionStart || 0
1749
1092
  const sEnd = el.selectionEnd || sStart
1750
1093
  const nextVal = value.slice(0, sStart) + c.converted + value.slice(sEnd)
1751
1094
  setNativeInputValue(el, nextVal, sStart, sStart + c.converted.length)
1752
- } else {
1753
- document.execCommand('insertText', false, c.converted)
1095
+ } else {document.execCommand('insertText', false, c.converted)
1754
1096
  }
1755
1097
  learnWords(c.converted)
1756
- } finally {
1757
- isFormatting = false
1098
+ } finally {isFormatting = false
1758
1099
  }
1759
1100
  return
1760
1101
  }
1761
1102
  }
1762
1103
  const c = layoutFixCandidate(value, 'lat2cyr') || layoutFixCandidate(value, 'cyr2lat')
1763
- if (c) {
1764
- ev.preventDefault()
1104
+ if (c) {ev.preventDefault()
1765
1105
  isFormatting = true
1766
- try {
1767
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
1768
- const sStart = el.selectionStart || 0
1106
+ try {if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {const sStart = el.selectionStart || 0
1769
1107
  const sEnd = el.selectionEnd || sStart
1770
1108
  setNativeInputValue(el, c.converted, sStart, sEnd)
1771
- } else {
1772
- const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
1109
+ } else {const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
1773
1110
  const offset = getCaretCharacterOffset(host)
1774
1111
  setComposerText(el, c.converted, offset, offset)
1775
1112
  }
1776
- learnWords(c.converted) // #67
1777
- } finally {
1778
- isFormatting = false
1113
+ learnWords(c.converted)
1114
+ } finally {isFormatting = false
1779
1115
  }
1780
1116
  }
1781
1117
  return
1782
1118
  }
1783
- if (ev.key === ' ' || ev.key === 'Enter') {
1784
- if (value && value.startsWith('/') && typeof expandSlashAlias === 'function') {
1785
- const expanded = expandSlashAlias(value)
1786
- if (expanded !== value) {
1787
- if (ev.key === ' ') {
1788
- ev.preventDefault()
1119
+ if (ev.key === ' ' || ev.key === 'Enter') {if (value && value.startsWith('/') && typeof expandSlashAlias === 'function') {const expanded = expandSlashAlias(value)
1120
+ if (expanded !== value) {if (ev.key === ' ') {ev.preventDefault()
1789
1121
  isFormatting = true
1790
- try {
1791
- const nextVal = expanded + ' '
1122
+ try {const nextVal = expanded + ' '
1792
1123
  setComposerText(el, nextVal, nextVal.length, nextVal.length)
1793
- } finally {
1794
- isFormatting = false
1124
+ } finally {isFormatting = false
1795
1125
  }
1796
1126
  return
1797
- } else if (ev.key === 'Enter') {
1798
- isFormatting = true
1799
- try {
1800
- setComposerText(el, expanded, expanded.length, expanded.length)
1801
- } finally {
1802
- isFormatting = false
1127
+ } else if (ev.key === 'Enter') {isFormatting = true
1128
+ try {setComposerText(el, expanded, expanded.length, expanded.length)
1129
+ } finally {isFormatting = false
1803
1130
  }
1804
1131
  }
1805
1132
  }
1806
1133
  }
1807
1134
  }
1808
- try {
1809
- const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
1135
+ if (ev.key === 'Enter' && !ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
1810
1136
  const typoLive = snapVal.typography ? snapVal.typography.liveInput !== false : true
1811
- if (typoLive && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
1812
- const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
1813
- if (isTextarea) {
1814
- const caretPos = el.selectionStart || 0
1815
- if (!isCaretInCode(el, value, caretPos)) {
1816
- const textBefore = caretPos >= 0 ? value.slice(0, caretPos) : value
1817
- if (ev.key === '-' && textBefore.endsWith('-')) {
1818
- ev.preventDefault()
1137
+ if (typoLive && typeof formatInputLive === 'function' && (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT')) {const formatted = formatInputLive(value)
1138
+ if (formatted && formatted !== value) {setNativeInputValue(el, formatted, formatted.length, formatted.length)
1139
+ }
1140
+ }
1141
+ }
1142
+ try {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
1143
+ const typoLive = snapVal.typography ? snapVal.typography.liveInput !== false : true
1144
+ if (typoLive && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
1145
+ if (isTextarea) {const caretPos = el.selectionStart || 0
1146
+ if (!isCaretInCode(el, value, caretPos)) {const textBefore = caretPos >= 0 ? value.slice(0, caretPos) : value
1147
+ if (ev.key === '-' && textBefore.endsWith('-')) {ev.preventDefault()
1819
1148
  isFormatting = true
1820
- try {
1821
- const nextVal = value.slice(0, caretPos - 1) + '—' + value.slice(el.selectionEnd || caretPos)
1149
+ try {const nextVal = value.slice(0, caretPos - 1) + '—' + value.slice(el.selectionEnd || caretPos)
1822
1150
  setNativeInputValue(el, nextVal, caretPos, caretPos)
1823
- } finally {
1824
- isFormatting = false
1151
+ } finally {isFormatting = false
1825
1152
  }
1826
1153
  return
1827
1154
  }
1828
- if (ev.key === '"') {
1829
- ev.preventDefault()
1155
+ if (ev.key === '"') {ev.preventDefault()
1830
1156
  isFormatting = true
1831
- try {
1832
- const prevChar = textBefore.slice(-1)
1157
+ try {const prevChar = textBefore.slice(-1)
1833
1158
  const isOpening = !textBefore || /[\s([{-]/.test(prevChar)
1834
1159
  const quoteChar = isOpening ? '«' : '»'
1835
1160
  const nextVal = value.slice(0, caretPos) + quoteChar + value.slice(el.selectionEnd || caretPos)
1836
1161
  setNativeInputValue(el, nextVal, caretPos + 1, caretPos + 1)
1837
- } finally {
1838
- isFormatting = false
1162
+ } finally {isFormatting = false
1839
1163
  }
1840
1164
  return
1841
1165
  }
@@ -1844,13 +1168,8 @@ return
1844
1168
  }
1845
1169
  } catch (e) { void e; }
1846
1170
  }
1847
- let isInspectorActive = false
1848
- let hoverBoxEl = null
1849
- let pillEl = null
1850
- const getHoverBox = () => {
1851
- if (typeof document === 'undefined') return null
1852
- if (!hoverBoxEl) {
1853
- hoverBoxEl = document.createElement('div')
1171
+ const getHoverBox = () => {if (typeof document === 'undefined') return null
1172
+ if (!hoverBoxEl) {hoverBoxEl = document.createElement('div')
1854
1173
  hoverBoxEl.id = 'dsh-ru-inspector-hover'
1855
1174
  hoverBoxEl.className = 'rl-inspector-hover'
1856
1175
  hoverBoxEl.innerHTML = '<span class="rl-inspector-badge"></span>'
@@ -1858,16 +1177,13 @@ document.body.appendChild(hoverBoxEl)
1858
1177
  }
1859
1178
  return hoverBoxEl
1860
1179
  }
1861
- const updateInspectorPill = () => {
1862
- if (typeof document === 'undefined') return
1863
- if (!pillEl) {
1864
- pillEl = document.createElement('div')
1180
+ const updateInspectorPill = () => {if (typeof document === 'undefined') return
1181
+ if (!pillEl) {pillEl = document.createElement('div')
1865
1182
  pillEl.id = 'dsh-ru-inspector-pill'
1866
1183
  pillEl.className = 'rl-inspector-pill'
1867
1184
  pillEl.innerHTML = '<span>🔍 Инспектор</span>' +
1868
1185
  '<button type="button" class="rl-btn rl-pill-exit" style="padding:2px 8px;font-size:11px;height:22px;">✕ Выйти (Alt+I)</button>'
1869
- pillEl.querySelector('.rl-pill-exit').onclick = () => {
1870
- isInspectorActive = false
1186
+ pillEl.querySelector('.rl-pill-exit').onclick = () => {isInspectorActive = false
1871
1187
  updateInspectorPill()
1872
1188
  if (hoverBoxEl) hoverBoxEl.style.display = 'none'
1873
1189
  }
@@ -1876,8 +1192,7 @@ document.body.appendChild(pillEl)
1876
1192
  pillEl.style.display = isInspectorActive ? 'flex' : 'none'
1877
1193
  if (isInspectorActive) pillEl.classList.add('rl-inspector-pill-active')
1878
1194
  }
1879
- const openInspectorModal = (meta, rawText, targetElement) => {
1880
- if (typeof document === 'undefined') return
1195
+ const openInspectorModal = (meta, rawText, targetElement) => {if (typeof document === 'undefined') return
1881
1196
  if (hoverBoxEl) hoverBoxEl.style.display = 'none'
1882
1197
  const old = document.getElementById('dsh-ru-inspector-modal')
1883
1198
  if (old) old.remove()
@@ -1888,8 +1203,7 @@ const k = meta ? (meta.key || (meta.ns ? meta.ns + '.' + meta.key : '')) : ''
1888
1203
  const ns = meta && meta.ns ? meta.ns : ''
1889
1204
  const origText = meta ? (meta.zh || meta.en || '') : ''
1890
1205
  let srcLabel = 'Текст'
1891
- if (meta) {
1892
- if (meta.source === 'override') srcLabel = '✍️ Оверрайд'
1206
+ if (meta) {if (meta.source === 'override') srcLabel = '✍️ Оверрайд'
1893
1207
  else if (meta.source === 'dictionary') srcLabel = '🟢 Словарь' + (ns ? ' ' + ns : '')
1894
1208
  else if (meta.source === 'dom_zh') srcLabel = '🟡 DOM ZH'
1895
1209
  else if (meta.source === 'dom_zh_original') srcLabel = '🟡 DOM ZH (Оригинал)'
@@ -1903,7 +1217,7 @@ modal.innerHTML = '<div class="rl-modal-box">' +
1903
1217
  '<div style="display:flex;justify-content:space-between;align-items:center;">' +
1904
1218
  '<div style="font-weight:600;font-size:14px;display:flex;align-items:center;gap:6px;">' +
1905
1219
  '<span>🔍 Инспектор перевода</span>' +
1906
- '<span class="rl-badge rl-badge-dim" style="font-size:11px;">' + srcLabel + '</span>' +
1220
+ '<span class="rl-badge rl-badge-dim" style="font-size:11px;"></span>' +
1907
1221
  '</div>' +
1908
1222
  '<button type="button" class="rl-btn-close" style="background:none;border:none;color:var(--dsw-alias-label-secondary);font-size:18px;cursor:pointer;">&times;</button>' +
1909
1223
  '</div>' +
@@ -1911,11 +1225,11 @@ modal.innerHTML = '<div class="rl-modal-box">' +
1911
1225
  '<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;">' +
1912
1226
  '<span style="color:var(--dsw-alias-label-secondary);">Ключ:</span>' +
1913
1227
  '<div style="display:flex;gap:6px;align-items:center;">' +
1914
- '<code class="rl-modal-key" style="color:var(--dsw-alias-state-brand-primary);font-weight:600;font-size:12px;">' + (k || '(текстовый узел)') + '</code>' +
1228
+ '<code class="rl-modal-key" style="color:var(--dsw-alias-state-brand-primary);font-weight:600;font-size:12px;"></code>' +
1915
1229
  '<button type="button" class="rl-btn rl-copy-key-btn" style="padding:1px 6px;height:20px;font-size:10px;" title="Скопировать ключ">📋</button>' +
1916
1230
  '</div>' +
1917
1231
  '</div>' +
1918
- (origText ? '<div style="color:var(--dsw-alias-label-secondary);word-break:break-word;">Оригинал: <b style="color:var(--dsw-alias-label-primary);">' + origText + '</b></div>' : '') +
1232
+ '<div class="rl-modal-orig-row" style="color:var(--dsw-alias-label-secondary);word-break:break-word;display:none;">Оригинал: <b class="rl-orig-text" style="color:var(--dsw-alias-label-primary);"></b></div>' +
1919
1233
  '<div style="color:var(--dsw-alias-label-secondary);word-break:break-word;">Текущий текст: <b class="rl-raw-text" style="color:var(--dsw-alias-label-primary)"></b></div>' +
1920
1234
  '</div>' +
1921
1235
  '<div style="display:flex;flex-direction:column;gap:6px;">' +
@@ -1931,6 +1245,13 @@ modal.innerHTML = '<div class="rl-modal-box">' +
1931
1245
  '</div>' +
1932
1246
  '</div>' +
1933
1247
  '</div>'
1248
+ modal.querySelector('.rl-badge-dim').textContent = srcLabel
1249
+ modal.querySelector('.rl-modal-key').textContent = k || '(текстовый узел)'
1250
+ if (origText) {const row = modal.querySelector('.rl-modal-orig-row')
1251
+ if (row) row.style.display = 'block'
1252
+ const el = modal.querySelector('.rl-orig-text')
1253
+ if (el) el.textContent = origText
1254
+ }
1934
1255
  modal.querySelector('.rl-raw-text').textContent = rawText
1935
1256
  const textarea = modal.querySelector('.rl-edit-val')
1936
1257
  textarea.value = currentTranslation
@@ -1938,18 +1259,14 @@ modal.querySelector('.rl-btn-close').onclick = () => modal.remove()
1938
1259
  modal.querySelector('.rl-cancel-btn').onclick = () => modal.remove()
1939
1260
  modal.onclick = (e) => { if (e.target === modal) modal.remove() }
1940
1261
  const copyKeyBtn = modal.querySelector('.rl-copy-key-btn')
1941
- if (copyKeyBtn) {
1942
- copyKeyBtn.onclick = () => {
1943
- try { navigator.clipboard.writeText(k || rawText) } catch (err) { void err; }
1262
+ if (copyKeyBtn) {copyKeyBtn.onclick = () => {try { navigator.clipboard.writeText(k || rawText) } catch (err) { void err; }
1944
1263
  copyKeyBtn.textContent = '✓'
1945
1264
  setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
1946
1265
  }
1947
1266
  }
1948
1267
  const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
1949
- if (copyReportBtn) {
1950
- copyReportBtn.onclick = () => {
1951
- const snippet = typeof generateBugReportSnippet === 'function'
1952
- ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.5' })
1268
+ if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
1269
+ ? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.8' })
1953
1270
  : ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
1954
1271
  try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
1955
1272
  copyReportBtn.textContent = '✓ Скопировано'
@@ -1957,9 +1274,7 @@ setTimeout(() => { copyReportBtn.textContent = '📋 Скопировать ре
1957
1274
  }
1958
1275
  }
1959
1276
  const resetBtn = modal.querySelector('.rl-reset-override-btn')
1960
- if (resetBtn) {
1961
- resetBtn.onclick = () => {
1962
- const targetKey = k || rawText
1277
+ if (resetBtn) {resetBtn.onclick = () => {const targetKey = k || rawText
1963
1278
  const cur = getOverrides()
1964
1279
  const updated = Object.assign({}, cur)
1965
1280
  delete updated[targetKey]
@@ -1969,19 +1284,14 @@ if (typeof queueZhWalk === 'function') queueZhWalk()
1969
1284
  }
1970
1285
  }
1971
1286
  const saveBtn = modal.querySelector('.rl-save-btn')
1972
- saveBtn.onclick = () => {
1973
- const val = textarea.value.trim()
1287
+ saveBtn.onclick = () => {const val = textarea.value.trim()
1974
1288
  const targetKey = k || rawText
1975
1289
  if (!targetKey || !val) return
1976
1290
  const cur = getOverrides()
1977
1291
  const updated = Object.assign({}, cur, { [targetKey]: val })
1978
1292
  try { scope.set('overrides', updated) } catch (_e) { void _e }
1979
- if (targetElement) {
1980
- try {
1981
- if (targetElement.childNodes && targetElement.childNodes.length === 1 && targetElement.childNodes[0].nodeType === 3) {
1982
- targetElement.childNodes[0].nodeValue = val
1983
- } else {
1984
- targetElement.textContent = val
1293
+ if (targetElement) {try {if (targetElement.childNodes && targetElement.childNodes.length === 1 && targetElement.childNodes[0].nodeType === 3) {targetElement.childNodes[0].nodeValue = val
1294
+ } else {targetElement.textContent = val
1985
1295
  }
1986
1296
  } catch (err) { void err; }
1987
1297
  }
@@ -1993,8 +1303,7 @@ setTimeout(() => modal.remove(), 500)
1993
1303
  document.body.appendChild(modal)
1994
1304
  setTimeout(() => textarea.focus(), 50)
1995
1305
  }
1996
- const inspectorOnClick = (e) => {
1997
- if (!e.altKey && !isInspectorActive) return
1306
+ const inspectorOnClick = (e) => {if (!e.altKey && !isInspectorActive) return
1998
1307
  if (e.target && e.target.closest && (e.target.closest('#dsh-ru-inspector-modal') || e.target.closest('#dsh-ru-inspector-pill'))) return
1999
1308
  const raw = (e.target && (e.target.innerText || e.target.textContent) || '').trim()
2000
1309
  if (!raw) return
@@ -2003,15 +1312,12 @@ e.stopPropagation()
2003
1312
  const meta = translationRegistry.get(raw) || (typeof findTranslationKey === 'function' ? findTranslationKey(raw, RU, getOverrides(), ZH_RU, DOM_EN_TEXT, { detectUntranslated: true }) : null)
2004
1313
  openInspectorModal(meta, raw, e.target)
2005
1314
  }
2006
- const inspectorOnMouseMove = (e) => {
2007
- if (!isInspectorActive && !e.altKey) {
2008
- if (hoverBoxEl && hoverBoxEl.style.display !== 'none') hoverBoxEl.style.display = 'none'
1315
+ const inspectorOnMouseMove = (e) => {if (!isInspectorActive && !e.altKey) {if (hoverBoxEl && hoverBoxEl.style.display !== 'none') hoverBoxEl.style.display = 'none'
2009
1316
  return
2010
1317
  }
2011
1318
  if (!e.target || (e.target.closest && (e.target.closest('#dsh-ru-inspector-modal') || e.target.closest('#dsh-ru-inspector-pill') || e.target.closest('#dsh-ru-inspector-hover')))) return
2012
1319
  const raw = (e.target.innerText || e.target.textContent || '').trim()
2013
- if (!raw || raw.length > 200) {
2014
- if (hoverBoxEl) hoverBoxEl.style.display = 'none'
1320
+ if (!raw || raw.length > 200) {if (hoverBoxEl) hoverBoxEl.style.display = 'none'
2015
1321
  return
2016
1322
  }
2017
1323
  const box = getHoverBox()
@@ -2023,11 +1329,9 @@ box.style.width = rect.width + 'px'
2023
1329
  box.style.height = rect.height + 'px'
2024
1330
  box.style.display = 'block'
2025
1331
  const badge = box.querySelector('.rl-inspector-badge')
2026
- if (badge) {
2027
- const meta = translationRegistry.get(raw) || (typeof findTranslationKey === 'function' ? findTranslationKey(raw, RU, getOverrides(), ZH_RU, DOM_EN_TEXT, { detectUntranslated: true }) : null)
1332
+ if (badge) {const meta = translationRegistry.get(raw) || (typeof findTranslationKey === 'function' ? findTranslationKey(raw, RU, getOverrides(), ZH_RU, DOM_EN_TEXT, { detectUntranslated: true }) : null)
2028
1333
  let title = '🔍 Клик: инспекция'
2029
- if (meta) {
2030
- if (meta.source === 'override') title = '✍️ ' + meta.key
1334
+ if (meta) {if (meta.source === 'override') title = '✍️ ' + meta.key
2031
1335
  else if (meta.source === 'dictionary') title = '🟢 ' + (meta.ns ? meta.ns + '.' : '') + meta.key
2032
1336
  else if (meta.source === 'dom_zh') title = '🟡 ' + meta.zh
2033
1337
  else if (meta.source === 'dom_en') title = '🟡 ' + meta.en
@@ -2037,26 +1341,20 @@ else if (meta.source === 'untranslated_en') title = '🔴 EN'
2037
1341
  badge.textContent = title
2038
1342
  }
2039
1343
  }
2040
- const inspectorOnKeyDown = (e) => {
2041
- if (e.altKey && (e.key === 'i' || e.key === 'I' || e.code === 'KeyI')) {
2042
- e.preventDefault()
1344
+ const inspectorOnKeyDown = (e) => {if (e.altKey && (e.key === 'i' || e.key === 'I' || e.code === 'KeyI')) {e.preventDefault()
2043
1345
  isInspectorActive = !isInspectorActive
2044
1346
  updateInspectorPill()
2045
1347
  if (!isInspectorActive && hoverBoxEl) hoverBoxEl.style.display = 'none'
2046
1348
  }
2047
1349
  }
2048
- const inspectorOnKeyUp = (e) => {
2049
- if (e.key === 'Alt' && !isInspectorActive) {
2050
- if (hoverBoxEl) hoverBoxEl.style.display = 'none'
1350
+ const inspectorOnKeyUp = (e) => {if (e.key === 'Alt' && !isInspectorActive) {if (hoverBoxEl) hoverBoxEl.style.display = 'none'
2051
1351
  }
2052
1352
  }
2053
- ctx.effect(() => {
2054
- document.addEventListener('click', inspectorOnClick, true)
1353
+ ctx.effect(() => {document.addEventListener('click', inspectorOnClick, true)
2055
1354
  document.addEventListener('mousemove', inspectorOnMouseMove, true)
2056
1355
  document.addEventListener('keydown', inspectorOnKeyDown, true)
2057
1356
  document.addEventListener('keyup', inspectorOnKeyUp, true)
2058
- return () => {
2059
- document.removeEventListener('click', inspectorOnClick, true)
1357
+ return () => {document.removeEventListener('click', inspectorOnClick, true)
2060
1358
  document.removeEventListener('mousemove', inspectorOnMouseMove, true)
2061
1359
  document.removeEventListener('keydown', inspectorOnKeyDown, true)
2062
1360
  document.removeEventListener('keyup', inspectorOnKeyUp, true)
@@ -2073,16 +1371,10 @@ runtime.bilingualCommandMatch = (query, cmd, opts) => (typeof bilingualCommandMa
2073
1371
  runtime.filterCommands = (query, cmds, opts) => (typeof filterCommands === 'function' ? filterCommands(query, cmds, opts) : cmds)
2074
1372
  runtime.humanizeError = (err) => (typeof humanizeError === 'function' ? humanizeError(err) : null)
2075
1373
  runtime.formatErrorToast = (err) => (typeof formatErrorToast === 'function' ? formatErrorToast(err) : null)
2076
- const toastObserver = typeof MutationObserver !== 'undefined' ? new MutationObserver((mutations) => {
2077
- for (const m of mutations) {
2078
- for (const node of m.addedNodes) {
2079
- if (!node || node.nodeType !== 1) continue
2080
- if (node.matches && (node.matches('.dsw-toast, [role="alert"], .toast, .ant-message-notice'))) {
2081
- const text = (node.innerText || node.textContent || '').trim()
2082
- if (/failed to fetch|network error|econnrefused|etimedout/i.test(text)) {
2083
- const h = typeof humanizeError === 'function' ? humanizeError(text) : null
2084
- if (h && h.title) {
2085
- const targetEl = node.querySelector('.toast-body, .ant-message-custom-content') || node
1374
+ const toastObserver = typeof MutationObserver !== 'undefined' ? new MutationObserver((mutations) => {for (const m of mutations) {for (const node of m.addedNodes) {if (!node || node.nodeType !== 1) continue
1375
+ if (node.matches && (node.matches('.dsw-toast, [role="alert"], .toast, .ant-message-notice'))) {const text = (node.innerText || node.textContent || '').trim()
1376
+ if (/failed to fetch|network error|econnrefused|etimedout/i.test(text)) {const h = typeof humanizeError === 'function' ? humanizeError(text) : null
1377
+ if (h && h.title) {const targetEl = node.querySelector('.toast-body, .ant-message-custom-content') || node
2086
1378
  targetEl.textContent = h.title + ': ' + h.message
2087
1379
  }
2088
1380
  }
@@ -2090,17 +1382,13 @@ targetEl.textContent = h.title + ': ' + h.message
2090
1382
  }
2091
1383
  }
2092
1384
  }) : null
2093
- ctx.effect(() => {
2094
- if (toastObserver && typeof document !== 'undefined' && document.body) {
2095
- toastObserver.observe(document.body, { childList: true, subtree: true })
1385
+ ctx.effect(() => {if (toastObserver && typeof document !== 'undefined' && document.body) {toastObserver.observe(document.body, { childList: true, subtree: true })
2096
1386
  }
2097
1387
  return () => { if (toastObserver) toastObserver.disconnect() }
2098
1388
  }, 'dsh-russian-lang: toast-humanizer')
2099
- ctx.effect(() => {
2100
- document.addEventListener('input', layoutOnInput, true)
1389
+ ctx.effect(() => {document.addEventListener('input', layoutOnInput, true)
2101
1390
  document.addEventListener('keydown', layoutOnKeydown, true)
2102
- return () => {
2103
- unsubscribeLayout()
1391
+ return () => {unsubscribeLayout()
2104
1392
  document.removeEventListener('input', layoutOnInput, true)
2105
1393
  document.removeEventListener('keydown', layoutOnKeydown, true)
2106
1394
  layoutDismiss()
@@ -2108,125 +1396,61 @@ layoutBadgeHide()
2108
1396
  }
2109
1397
  }, 'dsh-russian-lang: layout')
2110
1398
  if (!ctx.slots || !React) return
2111
- const toggleRu = (wantRu) => {
2112
- try {
2113
- if (runtime.getLocale().active === wantRu) return
1399
+ const toggleRu = (wantRu) => {try {if (runtime.getLocale().active === wantRu) return
2114
1400
  runtime.setLocale(wantRu ? 'ru' : 'en')
2115
1401
  } catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }
2116
1402
  }
2117
- try {
2118
- ctx.slots.inject('plugins.item', () =>
2119
- ctx.slots.register({
2120
- name: 'plugins.item',
2121
- id: SETTINGS_NS_NAME,
2122
- order: 60,
2123
- label: () => 'Russian language',
2124
- locale: SETTINGS_NS_NAME,
2125
- inject: () => ({ scope, runtime, toggleRu }),
2126
- }, SettingsCard),
2127
- )
1403
+ try {ctx.slots.inject('plugins.item', () =>
1404
+ ctx.slots.register({name: 'plugins.item',id: SETTINGS_NS_NAME,order: 60,label: () => 'Russian language',locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
2128
1405
  ctx.slots.inject('plugins.row.config', () =>
2129
- ctx.slots.register({
2130
- name: 'plugins.row.config',
2131
- key: ROW_CONFIG_KEY,
2132
- locale: SETTINGS_NS_NAME,
2133
- inject: () => ({ scope, runtime, toggleRu }),
2134
- }, SettingsCard),
2135
- )
1406
+ ctx.slots.register({name: 'plugins.row.config',key: ROW_CONFIG_KEY,locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
2136
1407
  ctx.slots.inject('settings.plugin.item', () =>
2137
- ctx.slots.register({
2138
- name: 'settings.plugin.item',
2139
- key: SETTINGS_NS_NAME,
2140
- locale: SETTINGS_NS_NAME,
2141
- inject: () => ({ scope, runtime, toggleRu }),
2142
- }, SettingsCard),
2143
- )
1408
+ ctx.slots.register({name: 'settings.plugin.item',key: SETTINGS_NS_NAME,locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
2144
1409
  } catch (err) { console.warn('dsh-russian-lang: settings slot unavailable', err) }
2145
- try {
2146
- ctx.slots.inject('conversation.session.header.utilities', () =>
2147
- ctx.slots.register({
2148
- name: 'conversation.session.header.utilities',
2149
- id: 'dsh-russian-lang-quick-switch',
2150
- order: 100,
2151
- locale: SETTINGS_NS_NAME,
2152
- inject: () => ({ runtime, toggleRu }),
2153
- }, QuickLangSwitch),
2154
- )
1410
+ try {ctx.slots.inject('conversation.session.header.utilities', () =>
1411
+ ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-quick-switch',order: 100,locale: SETTINGS_NS_NAME,inject: () => ({ runtime, toggleRu }),}, QuickLangSwitch),)
2155
1412
  } catch (err) { }
2156
- try {
2157
- ctx.slots.inject('conversation.chat.assistant-actions', () =>
2158
- ctx.slots.register({
2159
- name: 'conversation.chat.assistant-actions',
2160
- id: 'dsh-russian-lang-translate-action',
2161
- order: 50,
2162
- locale: SETTINGS_NS_NAME,
2163
- inject: () => ({ runtime }),
2164
- }, TranslateTurnAction),
2165
- )
1413
+ try {ctx.slots.inject('conversation.chat.assistant-actions', () =>
1414
+ ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian-lang-translate-action',order: 50,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, TranslateTurnAction),)
2166
1415
  } catch (err) { }
2167
- try {
2168
- ctx.slots.inject('conversation.session.header.utilities', () =>
2169
- ctx.slots.register({
2170
- name: 'conversation.session.header.utilities',
2171
- id: 'dsh-russian-lang-md-export',
2172
- order: 101,
2173
- locale: SETTINGS_NS_NAME,
2174
- inject: () => ({ runtime }),
2175
- }, ExportMarkdownButton),
2176
- )
1416
+ try {ctx.slots.inject('conversation.session.header.utilities', () =>
1417
+ ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
2177
1418
  } catch (err) { }
2178
1419
  }
2179
- function QuickLangSwitch(props) {
2180
- const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
1420
+ function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
2181
1421
  const runtime = props.runtime || inj.runtime
2182
1422
  const toggleRu = props.toggleRu || inj.toggleRu
2183
1423
  const [locale, setLocaleState] = React.useState(runtime ? (runtime.getLocale().active || 'en') : 'ru')
2184
- React.useEffect(() => {
2185
- if (!runtime) return
2186
- return runtime.subscribe(() => {
2187
- try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }
1424
+ React.useEffect(() => {if (!runtime) return
1425
+ return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }
2188
1426
  })
2189
1427
  }, [runtime])
2190
1428
  const isRu = locale === 'ru'
2191
- return React.createElement('button', {
2192
- type: 'button',
2193
- className: 'rl-lang-chip' + (isRu ? ' rl-lang-chip-active' : ''),
2194
- title: isRu ? 'Интерфейс: Русский (нажмите для переключения на EN)' : 'Interface: English (click for RU)',
2195
- onClick: () => { if (toggleRu) toggleRu(!isRu) },
2196
- }, React.createElement('span', { className: 'rl-lang-text' }, isRu ? 'RU' : 'EN'))
2197
- }
2198
- function ExportMarkdownButton(props) {
2199
- const [done, setDone] = React.useState(false)
2200
- const onExport = () => {
2201
- try {
2202
- const titleEl = document.querySelector('.dsw-session-title, [data-session-title], header h1, header h2, [class*="title"]')
1429
+ return React.createElement('button', {type: 'button',className: 'rl-lang-chip' + (isRu ? ' rl-lang-chip-active' : ''),title: isRu ? 'Интерфейс: Русский (нажмите для переключения на EN)' : 'Interface: English (click for RU)',onClick: () => { if (toggleRu) toggleRu(!isRu) },}, React.createElement('span', { className: 'rl-lang-text' }, isRu ? 'RU' : 'EN'))
1430
+ }
1431
+ function ExportMarkdownButton(props) {const [done, setDone] = React.useState(false)
1432
+ const onExport = () => {try {const titleEl = document.querySelector('.dsw-session-title, [data-session-title], header h1, header h2, [class*="title"]')
2203
1433
  const rawTitle = (titleEl && titleEl.textContent.trim()) || document.title || 'Диалог DSH'
2204
1434
  const title = rawTitle.replace(/\s*—\s*DeepSeek Harness\s*$/, '').trim() || 'Диалог DSH'
2205
1435
  const flow = document.querySelector('[data-chat-flow]') || document.querySelector('[data-chat-flow-scroll]') || document.body
2206
1436
  const flowItems = Array.from(flow.querySelectorAll('[data-chat-flow-kind]'))
2207
1437
  const messages = []
2208
- if (flowItems.length > 0) {
2209
- flowItems.forEach((node) => {
2210
- const kind = node.getAttribute('data-chat-flow-kind')
2211
- if (kind === 'user' || kind === 'steering') {
2212
- const bubble = node.querySelector('[class*="bubble"]') || node
1438
+ if (flowItems.length > 0) {flowItems.forEach((node) => {const kind = node.getAttribute('data-chat-flow-kind')
1439
+ if (kind === 'user' || kind === 'steering') {const bubble = node.querySelector('[class*="bubble"]') || node
2213
1440
  const clone = bubble.cloneNode(true)
2214
1441
  clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
2215
1442
  const text = clone.innerText.trim()
2216
1443
  if (text) messages.push({ role: 'user', content: text })
2217
- } else if (kind === 'assistant-step') {
2218
- const clone = node.cloneNode(true)
1444
+ } else if (kind === 'assistant-step') {const clone = node.cloneNode(true)
2219
1445
  clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"], .rl-turn-translation').forEach((b) => b.remove())
2220
1446
  const text = clone.innerText.trim()
2221
1447
  if (text) messages.push({ role: 'assistant', content: text })
2222
1448
  }
2223
1449
  })
2224
1450
  }
2225
- if (messages.length === 0) {
2226
- const allElements = Array.from(document.querySelectorAll('[class*="userRow"], [class*="UserRow"], [class*="assistant-step"], [class*="AssistantMarkdown"], .dsw-turn-node, [data-role]'))
1451
+ if (messages.length === 0) {const allElements = Array.from(document.querySelectorAll('[class*="userRow"], [class*="UserRow"], [class*="assistant-step"], [class*="AssistantMarkdown"], .dsw-turn-node, [data-role]'))
2227
1452
  const seen = new Set()
2228
- allElements.forEach((node) => {
2229
- const isUser = node.matches('[class*="userRow"], [class*="UserRow"], [data-role="user"]') || !!node.querySelector('[data-role="user"]')
1453
+ allElements.forEach((node) => {const isUser = node.matches('[class*="userRow"], [class*="UserRow"], [data-role="user"]') || !!node.querySelector('[data-role="user"]')
2230
1454
  const role = isUser ? 'user' : 'assistant'
2231
1455
  const clone = node.cloneNode(true)
2232
1456
  clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"], .rl-turn-translation, [data-turn-tail]').forEach((b) => b.remove())
@@ -2236,10 +1460,7 @@ seen.add(text)
2236
1460
  messages.push({ role, content: text })
2237
1461
  })
2238
1462
  }
2239
- const session = {
2240
- title,
2241
- createdAt: new Date().toISOString(),
2242
- messages
1463
+ const session = {title,createdAt: new Date().toISOString(),messages
2243
1464
  }
2244
1465
  const md = exportSessionToMarkdown(session)
2245
1466
  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' })
@@ -2252,175 +1473,65 @@ a.download = safeTitle + '-' + new Date().toISOString().slice(0, 10) + '.md'
2252
1473
  document.body.appendChild(a)
2253
1474
  a.click()
2254
1475
  setDone(true)
2255
- setTimeout(() => {
2256
- try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }
1476
+ setTimeout(() => {try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }
2257
1477
  }, 30000)
2258
1478
  setTimeout(() => setDone(false), 2000)
2259
- } catch (err) {
2260
- console.warn('dsh-russian-lang: export md failed', err)
2261
- }
2262
- }
2263
- return React.createElement('button', {
2264
- type: 'button',
2265
- className: 'rl-lang-chip rl-export-md-btn',
2266
- title: 'Экспорт диалога в Markdown (.md)',
2267
- onClick: onExport,
2268
- }, React.createElement('span', { className: 'rl-lang-text' }, done ? '✓ MD' : '📥 MD'))
2269
- }
2270
- async function translateTurnContent(text) {
2271
- if (!text || typeof text !== 'string') return { error: 'Текст для перевода пуст.' }
2272
- try {
2273
- const res = await fetch('/api/dsh-russian-lang/translate', {
2274
- method: 'POST',
2275
- headers: { 'content-type': 'application/json' },
2276
- body: JSON.stringify({ text })
1479
+ } catch (err) {console.warn('dsh-russian-lang: export md failed', err)
1480
+ }
1481
+ }
1482
+ return React.createElement('button', {type: 'button',className: 'rl-lang-chip rl-export-md-btn',title: 'Экспорт диалога в Markdown (.md)',onClick: onExport,}, React.createElement('span', { className: 'rl-lang-text' }, done ? '✓ MD' : '📥 MD'))
1483
+ }
1484
+ async function translateTurnContent(text) {if (!text || typeof text !== 'string') return { error: 'Текст для перевода пуст.' }
1485
+ try {const res = await fetch('/api/dsh-russian-lang/translate', {headers: { 'content-type': 'application/json', 'x-dsh-translator': '1' },method: 'POST',headers: { 'content-type': 'application/json' },body: JSON.stringify({ text })
2277
1486
  })
2278
1487
  const data = await res.json().catch(() => ({}))
2279
- if (!res.ok) {
2280
- return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
1488
+ if (!res.ok) {return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
2281
1489
  }
2282
1490
  return { translatedText: data.translatedText || text }
2283
- } catch (err) {
2284
- return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
1491
+ } catch (err) {return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
2285
1492
  }
2286
1493
  }
2287
- function TranslateTurnAction(props) {
2288
- const t = typeof props.t === 'function' ? props.t : ((k) => k)
1494
+ function TranslateTurnAction(props) {const t = typeof props.t === 'function' ? props.t : ((k) => k)
2289
1495
  const [loading, setLoading] = React.useState(false)
2290
1496
  const [open, setOpen] = React.useState(false)
2291
- const [upStatus, setUpStatus] = React.useState({
2292
- currentVersion: '0.3.5',
2293
- latestVersion: undefined,
2294
- updateAvailable: false
2295
- })
2296
- const [upLoading, setUpLoading] = React.useState(false)
2297
- const [upMsg, setUpMsg] = React.useState(null)
2298
- const checkUpdate = () => {
2299
- setUpLoading(true)
2300
- setUpMsg(null)
2301
- fetch('/api/dsh-russian-lang/update')
2302
- .then((r) => r.json())
2303
- .then((data) => {
2304
- setUpStatus(data)
2305
- setUpLoading(false)
2306
- })
2307
- .catch(() => {
2308
- setUpLoading(false)
2309
- setUpMsg({ type: 'err', text: t('updaterFailed') })
2310
- })
2311
- }
2312
- const triggerUpdate = () => {
2313
- setUpLoading(true)
2314
- setUpMsg(null)
2315
- fetch('/api/dsh-russian-lang/update', {
2316
- method: 'POST',
2317
- headers: { 'x-dsh-plugin-update': '1' }
2318
- })
2319
- .then((r) => r.json())
2320
- .then((data) => {
2321
- setUpLoading(false)
2322
- if (data.restartRequired || data.updatedVersion) {
2323
- setUpStatus(data)
2324
- setUpMsg({ type: 'ok', text: t('updaterSuccess') })
2325
- } else if (data.error) {
2326
- setUpMsg({ type: 'err', text: data.error })
2327
- }
2328
- })
2329
- .catch(() => {
2330
- setUpLoading(false)
2331
- setUpMsg({ type: 'err', text: t('updaterFailed') })
2332
- })
2333
- }
2334
- const [transStatus, setTransStatus] = React.useState(null)
2335
- const [transStarting, setTransStarting] = React.useState(false)
2336
- const [transMsg, setTransMsg] = React.useState(null)
2337
- const fetchTransStatus = () => {
2338
- fetch('/api/dsh-russian-lang/translator/status')
2339
- .then((r) => r.json())
2340
- .then((data) => setTransStatus(data))
2341
- .catch(() => setTransStatus(null))
2342
- }
2343
- const startLibreTranslate = () => {
2344
- setTransStarting(true)
2345
- setTransMsg(null)
2346
- fetch('/api/dsh-russian-lang/translator/setup', { method: 'POST' })
2347
- .then((r) => r.json())
2348
- .then((data) => {
2349
- setTransStarting(false)
2350
- if (data.ok) {
2351
- setTransMsg({ type: 'ok', text: data.message || 'Контейнер LibreTranslate запущен.' })
2352
- fetchTransStatus()
2353
- } else {
2354
- setTransMsg({ type: 'err', text: data.error || 'Ошибка запуска контейнера.' })
2355
- }
2356
- })
2357
- .catch((err) => {
2358
- setTransStarting(false)
2359
- setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
2360
- })
2361
- }
2362
- React.useEffect(() => {
2363
- if (open) {
2364
- checkUpdate()
2365
- fetchTransStatus()
2366
- }
2367
- }, [open])
2368
- return React.createElement('button', {
2369
- type: 'button',
2370
- className: 'rl-action-btn' + (open ? ' rl-action-btn-active' : ''),
2371
- title: t('translateTurn'),
2372
- disabled: loading,
2373
- onClick: async (ev) => {
2374
- ev.stopPropagation()
1497
+ return React.createElement('button', {type: 'button',className: 'rl-action-btn' + (open ? ' rl-action-btn-active' : ''),title: t('translateTurn'),disabled: loading,onClick: async (ev) => {ev.stopPropagation()
2375
1498
  const btn = ev.currentTarget
2376
1499
  const tailFlowItem = btn.closest('[data-chat-flow-kind="turn-tail"]') || btn.closest('[data-chat-flow-kind]') || btn.closest('[data-turn-tail]')?.closest('[data-chat-flow-kind]') || btn.closest('[data-turn-tail]')
2377
1500
  if (!tailFlowItem) return
2378
1501
  const parentContainer = tailFlowItem.parentElement
2379
1502
  let box = parentContainer ? parentContainer.querySelector('.rl-turn-translation[data-tail-key="' + (tailFlowItem.getAttribute('data-chat-flow-key') || '') + '"]') : null
2380
- if (!box) {
2381
- box = tailFlowItem.querySelector('.rl-turn-translation')
1503
+ if (!box) {box = tailFlowItem.querySelector('.rl-turn-translation')
2382
1504
  }
2383
- if (box) {
2384
- box.style.display = box.style.display === 'none' ? 'block' : 'none'
1505
+ if (box) {box.style.display = box.style.display === 'none' ? 'block' : 'none'
2385
1506
  setOpen(box.style.display !== 'none')
2386
1507
  return
2387
1508
  }
2388
1509
  setLoading(true)
2389
- try {
2390
- const assistantNodes = []
1510
+ try {const assistantNodes = []
2391
1511
  let prev = tailFlowItem.previousElementSibling
2392
- while (prev && prev.getAttribute('data-chat-flow-kind') !== 'user') {
2393
- if (prev.getAttribute('data-chat-flow-kind') === 'assistant-step') {
2394
- assistantNodes.unshift(prev)
1512
+ while (prev && prev.getAttribute('data-chat-flow-kind') !== 'user') {if (prev.getAttribute('data-chat-flow-kind') === 'assistant-step') {assistantNodes.unshift(prev)
2395
1513
  }
2396
1514
  prev = prev.previousElementSibling
2397
1515
  }
2398
1516
  let rawText = ''
2399
- if (assistantNodes.length > 0) {
2400
- rawText = assistantNodes.map((node) => {
2401
- const clone = node.cloneNode(true)
1517
+ if (assistantNodes.length > 0) {rawText = assistantNodes.map((node) => {const clone = node.cloneNode(true)
2402
1518
  clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
2403
1519
  return clone.innerText.trim()
2404
1520
  }).filter(Boolean).join('\n\n')
2405
- } else if (parentContainer) {
2406
- const allSteps = Array.from(parentContainer.querySelectorAll('[data-chat-flow-kind="assistant-step"]'))
2407
- if (allSteps.length > 0) {
2408
- const beforeTail = allSteps.filter((s) => (s.compareDocumentPosition(tailFlowItem) & Node.DOCUMENT_POSITION_FOLLOWING))
1521
+ } else if (parentContainer) {const allSteps = Array.from(parentContainer.querySelectorAll('[data-chat-flow-kind="assistant-step"]'))
1522
+ if (allSteps.length > 0) {const beforeTail = allSteps.filter((s) => (s.compareDocumentPosition(tailFlowItem) & Node.DOCUMENT_POSITION_FOLLOWING))
2409
1523
  const targetSteps = beforeTail.length > 0 ? [beforeTail[beforeTail.length - 1]] : [allSteps[allSteps.length - 1]]
2410
- rawText = targetSteps.map((s) => {
2411
- const clone = s.cloneNode(true)
1524
+ rawText = targetSteps.map((s) => {const clone = s.cloneNode(true)
2412
1525
  clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
2413
1526
  return clone.innerText.trim()
2414
1527
  }).filter(Boolean).join('\n\n')
2415
1528
  }
2416
1529
  }
2417
- if (!rawText) {
2418
- const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
1530
+ if (!rawText) {const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
2419
1531
  if (prose) rawText = prose.innerText.trim()
2420
1532
  }
2421
1533
  let transResult = { error: 'Не удалось обнаружить текст сообщения ассистента для перевода.' }
2422
- if (rawText) {
2423
- transResult = await translateTurnContent(rawText)
1534
+ if (rawText) {transResult = await translateTurnContent(rawText)
2424
1535
  }
2425
1536
  const isErr = !!transResult.error
2426
1537
  const displayText = transResult.translatedText || transResult.error || ''
@@ -2438,36 +1549,27 @@ box.innerHTML = '<div class="rl-trans-head">' +
2438
1549
  '<div class="rl-trans-body"' + (isErr ? ' style="color: var(--dsw-alias-state-warning-primary); font-size: 13px;"' : '') + '></div>'
2439
1550
  box.querySelector('.rl-trans-body').textContent = displayText
2440
1551
  const copyBtn = box.querySelector('.rl-btn-copy')
2441
- if (copyBtn) {
2442
- copyBtn.addEventListener('click', (e) => {
2443
- e.stopPropagation()
2444
- try { navigator.clipboard.writeText(displayText) } catch (err) { void e; }
1552
+ if (copyBtn) {copyBtn.addEventListener('click', (e) => {e.stopPropagation()
1553
+ try { navigator.clipboard.writeText(displayText) } catch (err) { void err; }
2445
1554
  copyBtn.textContent = '✓ Скопировано'
2446
1555
  setTimeout(() => { copyBtn.textContent = '📋 Копировать' }, 2000)
2447
1556
  })
2448
1557
  }
2449
1558
  const closeBtn = box.querySelector('.rl-btn-close')
2450
- closeBtn.addEventListener('click', (e) => {
2451
- e.stopPropagation()
1559
+ closeBtn.addEventListener('click', (e) => {e.stopPropagation()
2452
1560
  box.style.display = 'none'
2453
1561
  setOpen(false)
2454
1562
  })
2455
- if (parentContainer) {
2456
- parentContainer.insertBefore(box, tailFlowItem)
2457
- } else {
2458
- tailFlowItem.appendChild(box)
1563
+ if (parentContainer) {parentContainer.insertBefore(box, tailFlowItem)
1564
+ } else {tailFlowItem.appendChild(box)
2459
1565
  }
2460
1566
  setOpen(true)
2461
- } catch (err) {
2462
- console.warn('dsh-russian-lang: translate turn failed', err)
2463
- } finally {
2464
- setLoading(false)
1567
+ } catch (err) {console.warn('dsh-russian-lang: translate turn failed', err)
1568
+ } finally {setLoading(false)
2465
1569
  }
2466
- },
2467
- }, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
1570
+ },}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
2468
1571
  }
2469
- function SettingsCard(props) {
2470
- const inj = typeof props.inject === 'function'
1572
+ function SettingsCard(props) {const inj = typeof props.inject === 'function'
2471
1573
  ? (props.inject() || {})
2472
1574
  : (props.inject || {})
2473
1575
  const scope = props.scope || inj.scope
@@ -2481,20 +1583,73 @@ const [ruActive, setRuActive] = React.useState(
2481
1583
  () => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
2482
1584
  const [typo, setTypoState] = React.useState(() =>
2483
1585
  (snap.value && snap.value.typography) || {})
2484
- React.useEffect(() => {
2485
- if (!scope || !scope.subscribe) return undefined
2486
- const un = scope.subscribe(() => {
2487
- const s = scope.getSnapshot()
1586
+ const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.8',latestVersion: undefined,updateAvailable: false
1587
+ })
1588
+ const [upLoading, setUpLoading] = React.useState(false)
1589
+ const [upMsg, setUpMsg] = React.useState(null)
1590
+ const checkUpdate = () => {setUpLoading(true)
1591
+ setUpMsg(null)
1592
+ fetch('/api/dsh-russian-lang/update', {headers: { 'x-dsh-plugin-update': '1' }
1593
+ })
1594
+ .then((r) => r.json())
1595
+ .then((data) => {setUpStatus(data)
1596
+ setUpLoading(false)
1597
+ })
1598
+ .catch(() => {setUpLoading(false)
1599
+ setUpMsg({ type: 'err', text: t('updaterFailed') })
1600
+ })
1601
+ }
1602
+ const triggerUpdate = () => {setUpLoading(true)
1603
+ setUpMsg(null)
1604
+ fetch('/api/dsh-russian-lang/update', {method: 'POST',headers: { 'x-dsh-plugin-update': '1' }
1605
+ })
1606
+ .then((r) => r.json())
1607
+ .then((data) => {setUpLoading(false)
1608
+ if (data.restartRequired || data.updatedVersion) {setUpStatus(data)
1609
+ setUpMsg({ type: 'ok', text: t('updaterSuccess') })
1610
+ } else if (data.error) {setUpMsg({ type: 'err', text: data.error })
1611
+ }
1612
+ })
1613
+ .catch(() => {setUpLoading(false)
1614
+ setUpMsg({ type: 'err', text: t('updaterFailed') })
1615
+ })
1616
+ }
1617
+ const [transStatus, setTransStatus] = React.useState(null)
1618
+ const [transStarting, setTransStarting] = React.useState(false)
1619
+ const [transMsg, setTransMsg] = React.useState(null)
1620
+ const fetchTransStatus = () => {fetch('/api/dsh-russian-lang/translator/status')
1621
+ .then((r) => r.json())
1622
+ .then((data) => setTransStatus(data))
1623
+ .catch(() => setTransStatus(null))
1624
+ }
1625
+ const startLibreTranslate = () => {setTransStarting(true)
1626
+ setTransMsg(null)
1627
+ fetch('/api/dsh-russian-lang/translator/setup', {method: 'POST',headers: { 'x-dsh-translator': '1' }
1628
+ })
1629
+ .then((r) => r.json())
1630
+ .then((data) => {setTransStarting(false)
1631
+ if (data.ok) {setTransMsg({ type: 'ok', text: data.message || 'Контейнер LibreTranslate запущен.' })
1632
+ fetchTransStatus()
1633
+ } else {setTransMsg({ type: 'err', text: data.error || 'Ошибка запуска контейнера.' })
1634
+ }
1635
+ })
1636
+ .catch((err) => {setTransStarting(false)
1637
+ setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
1638
+ })
1639
+ }
1640
+ React.useEffect(() => {if (open || (props && props.view === 'page')) {checkUpdate()
1641
+ fetchTransStatus()
1642
+ }
1643
+ }, [open, props])
1644
+ React.useEffect(() => {if (!scope || !scope.subscribe) return undefined
1645
+ const un = scope.subscribe(() => {const s = scope.getSnapshot()
2488
1646
  setSnap(s)
2489
1647
  if (s.value && s.value.typography) setTypoState(s.value.typography)
2490
1648
  })
2491
1649
  setSnap(scope.getSnapshot())
2492
1650
  return un
2493
1651
  }, [scope])
2494
- React.useEffect(() => {
2495
- try {
2496
- const un = runtime.subscribe(() => {
2497
- try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }
1652
+ React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }
2498
1653
  })
2499
1654
  return un
2500
1655
  } catch (e) { return undefined }
@@ -2506,428 +1661,146 @@ const overrides = value.overrides || {}
2506
1661
  const overridesCount = Object.keys(overrides).length
2507
1662
  const [newKey, setNewKey] = React.useState('')
2508
1663
  const [newVal, setNewVal] = React.useState('')
2509
- const setTypo = (patch) => {
2510
- const next = Object.assign({}, typo, patch)
1664
+ const setTypo = (patch) => {const next = Object.assign({}, typo, patch)
2511
1665
  setTypoState(next)
2512
- try {
2513
- const r = scope.set('typography', next)
2514
- if (r && typeof r.catch === 'function') r.catch((err) => {
2515
- console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
1666
+ try {const r = scope.set('typography', next)
1667
+ if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
2516
1668
  })
2517
- } catch (err) {
2518
- console.warn('dsh-russian-lang: scope.set typography sync threw', err && err.message || err)
1669
+ } catch (err) {console.warn('dsh-russian-lang: scope.set typography sync threw', err && err.message || err)
2519
1670
  }
2520
1671
  }
2521
1672
  const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
2522
1673
  const checkbox = (checked, onChange, disabled) =>
2523
- React.createElement('input', {
2524
- type: 'checkbox', checked: !!checked, disabled: !!disabled,
2525
- className: 'rl-check', onChange: (ev) => onChange(ev),
2526
- })
1674
+ React.createElement('input', {type: 'checkbox', checked: !!checked, disabled: !!disabled,className: 'rl-check', onChange: (ev) => onChange(ev),})
2527
1675
  const disabled = status !== 'ready'
2528
1676
  const statusLine = status === 'ready'
2529
1677
  ? ''
2530
1678
  : (status === 'unavailable' ? t('statusUnavailable') : t('statusLoading'))
2531
1679
  let ChevronIcon = null
2532
- try {
2533
- const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
1680
+ try {const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
2534
1681
  ChevronIcon = primitives && primitives.IconChevronDownOutline14
2535
1682
  } catch (_) { ChevronIcon = null }
2536
- const FallbackChevron = () => React.createElement('svg', {
2537
- width: 14, height: 14, viewBox: '0 0 14 14', fill: 'none',
2538
- 'aria-hidden': 'true',
2539
- }, React.createElement('path', {
2540
- d: 'M3.5 5.25 7 8.75l3.5-3.5', stroke: 'currentColor',
2541
- strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
2542
- }))
1683
+ const FallbackChevron = () => React.createElement('svg', {width: 14, height: 14, viewBox: '0 0 14 14', fill: 'none','aria-hidden': 'true',}, React.createElement('path', {d: 'M3.5 5.25 7 8.75l3.5-3.5', stroke: 'currentColor',strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',}))
2543
1684
  const Chevron = ChevronIcon || FallbackChevron
2544
1685
  const presetKey = value.agentPromptPreset || 'technical_expert'
2545
1686
  const presetInfo = typeof SYSTEM_PROMPT_PRESETS !== 'undefined' ? SYSTEM_PROMPT_PRESETS[presetKey] : null
2546
- if (props && props.view === 'summary') {
2547
- return React.createElement('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
1687
+ if (props && props.view === 'summary') {return React.createElement('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
2548
1688
  }
2549
1689
  const page = !!(props && props.view === 'page')
2550
- return React.createElement('div', { className: page ? 'rl-page-seat' : 'rl-card' },
2551
- React.createElement('button', {
2552
- type: 'button',
2553
- className: 'rl-head',
2554
- style: page ? { display: 'none' } : undefined,
2555
- 'aria-expanded': page ? 'true' : String(open),
2556
- onClick: () => setOpen(!open),
2557
- },
2558
- React.createElement('span', { className: 'rl-head-main' },
2559
- React.createElement('div', { className: 'rl-title' },
2560
- '🇷🇺 ' + t('cardTitle'),
2561
- React.createElement('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-warn') },
2562
- ruActive ? t('badgeRu') : t('badgeEn')),
2563
- React.createElement('span', { className: 'rl-badge rl-badge-ok' }, t('badgeCoverage'))
2564
- ),
2565
- React.createElement('div', { className: 'rl-sub' },
2566
- statusLine || t('cardSub'))),
2567
- React.createElement('span', {
2568
- className: 'rl-chev' + (open ? ' rl-chev-open' : ''),
2569
- }, React.createElement(Chevron, null))),
2570
- (page || open) && React.createElement('div', { className: 'rl-body' },
2571
- React.createElement('div', { className: 'rl-page' },
2572
- React.createElement('div', { className: 'rl-section-card' },
2573
- React.createElement('div', { className: 'rl-section-title' },
2574
- React.createElement('span', null, t('secLanguage')),
2575
- React.createElement('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-dim') },
2576
- ruActive ? t('badgeRu') : t('badgeEn'))
2577
- ),
2578
- React.createElement('div', { className: 'rl-section-desc' }, t('secLanguageDesc')),
2579
- React.createElement('div', { className: 'rl-item-card' },
2580
- React.createElement('div', { className: 'rl-item-head' },
2581
- React.createElement('label', { className: 'rl-item-label' },
2582
- checkbox(ruActive, onEnabled, disabled),
2583
- t('enabled')
1690
+ return React.createElement('div', { className: page ? 'rl-page-seat' : 'rl-card' },React.createElement('button', {type: 'button',className: 'rl-head',style: page ? { display: 'none' } : undefined,'aria-expanded': page ? 'true' : String(open),onClick: () => setOpen(!open),},React.createElement('span', { className: 'rl-head-main' },React.createElement('div', { className: 'rl-title' },'🇷🇺 ' + t('cardTitle'),React.createElement('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-warn') },ruActive ? t('badgeRu') : t('badgeEn')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, t('badgeCoverage'))
1691
+ ),React.createElement('div', { className: 'rl-sub' },statusLine || t('cardSub'))),React.createElement('span', {className: 'rl-chev' + (open ? ' rl-chev-open' : ''),}, React.createElement(Chevron, null))),(page || open) && React.createElement('div', { className: 'rl-body' },React.createElement('div', { className: 'rl-page' },React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secLanguage')),React.createElement('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-dim') },ruActive ? t('badgeRu') : t('badgeEn'))
1692
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secLanguageDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(ruActive, onEnabled, disabled),t('enabled')
2584
1693
  )
2585
- ),
2586
- React.createElement('div', { className: 'rl-item-desc' }, t('enabledDesc'))
2587
- ),
2588
- React.createElement('div', { className: 'rl-hint-text' }, t('quickSwitchNote')),
2589
- overridesCount > 0 ? React.createElement('div', { className: 'rl-badge rl-badge-dim', style: { alignSelf: 'flex-start' } },
2590
- t('overridesCount') + ': ' + overridesCount) : null
2591
- ),
2592
- React.createElement('div', { className: 'rl-section-card' },
2593
- React.createElement('div', { className: 'rl-section-title' },
2594
- React.createElement('span', null, t('secTypography')),
2595
- React.createElement('span', { className: 'rl-badge rl-badge-ok' }, t('badgeSmartUx'))
2596
- ),
2597
- React.createElement('div', { className: 'rl-section-desc' }, t('secTypographyDesc')),
2598
- React.createElement('div', { className: 'rl-grid-2' },
2599
- React.createElement('div', { className: 'rl-item-card' },
2600
- React.createElement('div', { className: 'rl-item-head' },
2601
- React.createElement('label', { className: 'rl-item-label' },
2602
- checkbox(typography.enabled !== false && ruActive,
2603
- (ev) => setTypo({ enabled: ev.target.checked }), !ruActive || disabled),
2604
- t('typography')
1694
+ ),React.createElement('div', { className: 'rl-item-desc' }, t('enabledDesc'))
1695
+ ),React.createElement('div', { className: 'rl-hint-text' }, t('quickSwitchNote')),overridesCount > 0 ? React.createElement('div', { className: 'rl-badge rl-badge-dim', style: { alignSelf: 'flex-start' } },t('overridesCount') + ': ' + overridesCount) : null
1696
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secTypography')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, t('badgeSmartUx'))
1697
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secTypographyDesc')),React.createElement('div', { className: 'rl-grid-2' },React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(typography.enabled !== false && ruActive,(ev) => setTypo({ enabled: ev.target.checked }), !ruActive || disabled),t('typography')
2605
1698
  )
2606
- ),
2607
- React.createElement('div', { className: 'rl-item-desc' }, t('typographyDesc'))
2608
- ),
2609
- React.createElement('div', { className: 'rl-item-card' },
2610
- React.createElement('div', { className: 'rl-item-head' },
2611
- React.createElement('label', { className: 'rl-item-label' },
2612
- checkbox(typography.liveInput !== false && ruActive,
2613
- (ev) => setTypo({ liveInput: ev.target.checked }), !ruActive || disabled),
2614
- t('liveInput')
1699
+ ),React.createElement('div', { className: 'rl-item-desc' }, t('typographyDesc'))
1700
+ ),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(typography.liveInput !== false && ruActive,(ev) => setTypo({ liveInput: ev.target.checked }), !ruActive || disabled),t('liveInput')
2615
1701
  )
2616
- ),
2617
- React.createElement('div', { className: 'rl-item-desc' }, t('liveInputDesc'))
2618
- ),
2619
- React.createElement('div', { className: 'rl-item-card' },
2620
- React.createElement('div', { className: 'rl-item-head' },
2621
- React.createElement('label', { className: 'rl-item-label' },
2622
- checkbox(typography.yo === true && ruActive,
2623
- (ev) => setTypo({ yo: ev.target.checked }), !ruActive || disabled),
2624
- t('yo')
1702
+ ),React.createElement('div', { className: 'rl-item-desc' }, t('liveInputDesc'))
1703
+ ),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(typography.yo === true && ruActive,(ev) => setTypo({ yo: ev.target.checked }), !ruActive || disabled),t('yo')
2625
1704
  )
2626
- ),
2627
- React.createElement('div', { className: 'rl-item-desc' }, t('yoDesc'))
2628
- ),
2629
- React.createElement('div', { className: 'rl-item-card' },
2630
- React.createElement('div', { className: 'rl-item-head' },
2631
- React.createElement('label', { className: 'rl-item-label' },
2632
- checkbox(value.slashAliases !== false && ruActive,
2633
- (ev) => {
2634
- try { scope.set('slashAliases', ev.target.checked) } catch (err) { void e; }
2635
- }, !ruActive || disabled),
2636
- t('slashAliases')
1705
+ ),React.createElement('div', { className: 'rl-item-desc' }, t('yoDesc'))
1706
+ ),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(value.slashAliases !== false && ruActive,(ev) => {try { scope.set('slashAliases', ev.target.checked) } catch (err) { void e; }
1707
+ }, !ruActive || disabled),t('slashAliases')
2637
1708
  )
2638
- ),
2639
- React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))
1709
+ ),React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))
2640
1710
  )
2641
- ),
2642
- React.createElement('div', { className: 'rl-hotkey-box' },
2643
- React.createElement('span', { className: 'rl-hotkey-tag' }, '⌨️ Alt+L'),
2644
- React.createElement('span', null, t('altLHintText'))
1711
+ ),React.createElement('div', { className: 'rl-hotkey-box' },React.createElement('span', { className: 'rl-hotkey-tag' }, '⌨️ Alt+L'),React.createElement('span', null, t('altLHintText'))
2645
1712
  )
2646
- ),
2647
- React.createElement('div', { className: 'rl-section-card' },
2648
- React.createElement('div', { className: 'rl-section-title' },
2649
- React.createElement('span', null, t('secAgentPrompt')),
2650
- React.createElement('span', { className: 'rl-badge ' + (value.agentPrompt ? 'rl-badge-ok' : 'rl-badge-dim') },
2651
- value.agentPrompt ? 'Активен' : 'Выключен')
2652
- ),
2653
- React.createElement('div', { className: 'rl-section-desc' }, t('secAgentPromptDesc')),
2654
- React.createElement('div', { className: 'rl-item-card' },
2655
- React.createElement('div', { className: 'rl-item-head' },
2656
- React.createElement('label', { className: 'rl-item-label' },
2657
- checkbox(value.agentPrompt === true,
2658
- (ev) => {
2659
- try {
2660
- const r = scope.set('agentPrompt', ev.target.checked)
2661
- if (r && typeof r.catch === 'function') r.catch((err) => {
2662
- console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
1713
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secAgentPrompt')),React.createElement('span', { className: 'rl-badge ' + (value.agentPrompt ? 'rl-badge-ok' : 'rl-badge-dim') },value.agentPrompt ? 'Активен' : 'Выключен')
1714
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secAgentPromptDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(value.agentPrompt === true,(ev) => {try {const r = scope.set('agentPrompt', ev.target.checked)
1715
+ if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
2663
1716
  })
2664
- } catch (err) {
2665
- console.warn('dsh-russian-lang: scope.set agentPrompt sync threw', err && err.message || err)
1717
+ } catch (err) {console.warn('dsh-russian-lang: scope.set agentPrompt sync threw', err && err.message || err)
2666
1718
  }
2667
- }, false),
2668
- t('agentPrompt')
1719
+ }, false),t('agentPrompt')
2669
1720
  )
2670
- ),
2671
- React.createElement('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))
2672
- ),
2673
- value.agentPrompt ? React.createElement(React.Fragment, null,
2674
- React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },
2675
- React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('agentPromptPreset')),
2676
- React.createElement('select', {
2677
- className: 'rl-select',
2678
- value: value.agentPromptPreset || 'technical_expert',
2679
- onChange: (ev) => {
2680
- try {
2681
- const val = ev.target.value
1721
+ ),React.createElement('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))
1722
+ ),value.agentPrompt ? React.createElement(React.Fragment, null,React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('agentPromptPreset')),React.createElement('select', {className: 'rl-select',value: value.agentPromptPreset || 'technical_expert',onChange: (ev) => {try {const val = ev.target.value
2682
1723
  scope.set('agentPromptPreset', val)
2683
1724
  } catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }
2684
1725
  }
2685
- },
2686
- React.createElement('option', { value: 'technical_expert' }, t('presetExpert')),
2687
- React.createElement('option', { value: 'tech_writer' }, t('presetWriter')),
2688
- React.createElement('option', { value: 'concise' }, t('presetConcise')),
2689
- React.createElement('option', { value: 'code_reviewer' }, t('presetReviewer')),
2690
- React.createElement('option', { value: 'architect' }, t('presetArchitect')),
2691
- React.createElement('option', { value: 'tutor' }, t('presetTutor'))
1726
+ },React.createElement('option', { value: 'technical_expert' }, t('presetExpert')),React.createElement('option', { value: 'tech_writer' }, t('presetWriter')),React.createElement('option', { value: 'concise' }, t('presetConcise')),React.createElement('option', { value: 'code_reviewer' }, t('presetReviewer')),React.createElement('option', { value: 'architect' }, t('presetArchitect')),React.createElement('option', { value: 'tutor' }, t('presetTutor'))
2692
1727
  )
2693
- ),
2694
- presetInfo ? React.createElement('div', { className: 'rl-preview-box' },
2695
- React.createElement('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),
2696
- presetInfo.text
1728
+ ),presetInfo ? React.createElement('div', { className: 'rl-preview-box' },React.createElement('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),presetInfo.text
2697
1729
  ) : null
2698
1730
  ) : null
2699
- ),
2700
- React.createElement('div', { className: 'rl-section-card' },
2701
- React.createElement('div', { className: 'rl-section-title' },
2702
- React.createElement('span', null, t('secUpdater')),
2703
- React.createElement('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },
2704
- upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))
2705
- ),
2706
- React.createElement('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),
2707
- React.createElement('div', { className: 'rl-item-card' },
2708
- React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },
2709
- React.createElement('div', null,
2710
- React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },
2711
- t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.5')),
2712
- upStatus.updateAvailable
2713
- ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },
2714
- t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
2715
- : React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },
2716
- t('updaterUpToDate'))
2717
- ),
2718
- upStatus.updateAvailable
2719
- ? React.createElement('button', {
2720
- type: 'button',
2721
- className: 'rl-btn rl-btn-primary',
2722
- disabled: upLoading,
2723
- onClick: triggerUpdate,
2724
- }, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
2725
- : React.createElement('button', {
2726
- type: 'button',
2727
- className: 'rl-btn',
2728
- disabled: upLoading,
2729
- onClick: checkUpdate,
2730
- }, upLoading ? t('updaterChecking') : t('updaterCheckBtn'))
2731
- ),
2732
- upMsg ? React.createElement('div', {
2733
- style: {
2734
- marginTop: '10px', padding: '8px 12px', borderRadius: '8px',
2735
- 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)',
2736
- color: upMsg.type === 'ok' ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontSize: '12px', fontWeight: 500
1731
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secUpdater')),React.createElement('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))
1732
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },React.createElement('div', null,React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.8')),upStatus.updateAvailable
1733
+ ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
1734
+ : React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))
1735
+ ),upStatus.updateAvailable
1736
+ ? React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
1737
+ : React.createElement('button', {type: 'button',className: 'rl-btn',disabled: upLoading,onClick: checkUpdate,}, upLoading ? t('updaterChecking') : t('updaterCheckBtn'))
1738
+ ),upMsg ? React.createElement('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
2737
1739
  }
2738
1740
  }, upMsg.text) : null
2739
1741
  )
2740
- ),
2741
- React.createElement('div', { className: 'rl-section-card' },
2742
- React.createElement('div', { className: 'rl-section-title' },
2743
- React.createElement('span', null, t('secTranslator')),
2744
- React.createElement('span', {
2745
- className: 'rl-badge ' + (value.translateEngine === 'local' ? 'rl-badge-ok' : (value.translateEngine === 'google' ? 'rl-badge-warn' : 'rl-badge-dim'))
1742
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secTranslator')),React.createElement('span', {className: 'rl-badge ' + (value.translateEngine === 'local' ? 'rl-badge-ok' : (value.translateEngine === 'google' ? 'rl-badge-warn' : 'rl-badge-dim'))
2746
1743
  }, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))
2747
- ),
2748
- React.createElement('div', { className: 'rl-section-desc' }, t('secTranslatorDesc')),
2749
- React.createElement('div', { className: 'rl-item-card' },
2750
- React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },
2751
- React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('translateEngine')),
2752
- React.createElement('select', {
2753
- className: 'rl-select',
2754
- value: value.translateEngine || 'off',
2755
- onChange: (ev) => {
2756
- try {
2757
- const val = ev.target.value
1744
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secTranslatorDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('translateEngine')),React.createElement('select', {className: 'rl-select',value: value.translateEngine || 'off',onChange: (ev) => {try {const val = ev.target.value
2758
1745
  scope.set('translateEngine', val)
2759
1746
  if (val === 'local') fetchTransStatus()
2760
1747
  } catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }
2761
1748
  }
2762
- },
2763
- React.createElement('option', { value: 'off' }, t('engineOff')),
2764
- React.createElement('option', { value: 'local' }, t('engineLocal')),
2765
- React.createElement('option', { value: 'google' }, t('engineGoogle'))
1749
+ },React.createElement('option', { value: 'off' }, t('engineOff')),React.createElement('option', { value: 'local' }, t('engineLocal')),React.createElement('option', { value: 'google' }, t('engineGoogle'))
2766
1750
  )
2767
- ),
2768
- value.translateEngine === 'google' ? React.createElement('div', {
2769
- style: {
2770
- marginTop: '10px', padding: '8px 12px', borderRadius: '8px',
2771
- 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)',
2772
- color: 'var(--dsw-alias-state-warning-primary)', fontSize: '12px', fontWeight: 500, lineHeight: 1.4
2773
- }
2774
- }, t('googleWarn')) : null,
2775
- value.translateEngine === 'local' ? React.createElement('div', {
2776
- style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }
2777
- },
2778
- React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, t('localInfo')),
2779
- React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },
2780
- React.createElement('span', {
2781
- className: 'rl-badge ' + (transStatus && transStatus.running ? 'rl-badge-ok' : 'rl-badge-dim')
2782
- }, transStatus && transStatus.running ? t('localStatusRunning') : t('localStatusStopped')),
2783
- (!transStatus || !transStatus.running) ? React.createElement('button', {
2784
- type: 'button',
2785
- className: 'rl-btn rl-btn-primary',
2786
- disabled: transStarting,
2787
- onClick: startLibreTranslate
1751
+ ),value.translateEngine === 'google' ? React.createElement('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
1752
+ }
1753
+ }, t('googleWarn')) : null,value.translateEngine === 'local' ? React.createElement('div', {style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }
1754
+ },React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, t('localInfo')),React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },React.createElement('span', {className: 'rl-badge ' + (transStatus && transStatus.running ? 'rl-badge-ok' : 'rl-badge-dim')
1755
+ }, transStatus && transStatus.running ? t('localStatusRunning') : t('localStatusStopped')),(!transStatus || !transStatus.running) ? React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: transStarting,onClick: startLibreTranslate
2788
1756
  }, transStarting ? t('localStarting') : t('localStartBtn')) : null
2789
- ),
2790
- transMsg ? React.createElement('div', {
2791
- style: {
2792
- marginTop: '6px', padding: '8px 12px', borderRadius: '8px',
2793
- 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)',
2794
- color: transMsg.type === 'ok' ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)', fontSize: '12px', fontWeight: 500
1757
+ ),transMsg ? React.createElement('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
2795
1758
  }
2796
1759
  }, transMsg.text) : null
2797
1760
  ) : null
2798
1761
  )
2799
- ),
2800
- React.createElement('div', { className: 'rl-section-card' },
2801
- React.createElement('div', { className: 'rl-section-title' },
2802
- React.createElement('span', null, t('secOverrides')),
2803
- React.createElement('span', { className: 'rl-badge rl-badge-dim' },
2804
- String(overridesCount) + ' ' + (typeof plural === 'function' ? plural(overridesCount, ['запись', 'записи', 'записей']) : 'записей'))
2805
- ),
2806
- React.createElement('div', { className: 'rl-section-desc' }, t('secOverridesDesc')),
2807
- React.createElement('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },
2808
- React.createElement('input', {
2809
- className: 'rl-select',
2810
- style: { flex: '1 1 180px', maxWidth: 'none' },
2811
- placeholder: t('overrideKeyPlaceholder'),
2812
- value: newKey,
2813
- onChange: (e) => setNewKey(e.target.value),
2814
- }),
2815
- React.createElement('input', {
2816
- className: 'rl-select',
2817
- style: { flex: '2 1 240px', maxWidth: 'none' },
2818
- placeholder: t('overrideValuePlaceholder'),
2819
- value: newVal,
2820
- onChange: (e) => setNewVal(e.target.value),
2821
- }),
2822
- React.createElement('button', {
2823
- type: 'button',
2824
- className: 'rl-btn rl-btn-primary',
2825
- disabled: !newKey.trim() || !newVal.trim(),
2826
- onClick: () => {
2827
- const k = newKey.trim()
1762
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secOverrides')),React.createElement('span', { className: 'rl-badge rl-badge-dim' },String(overridesCount) + ' ' + (typeof plural === 'function' ? plural(overridesCount, ['запись', 'записи', 'записей']) : 'записей'))
1763
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secOverridesDesc')),React.createElement('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },React.createElement('input', {className: 'rl-select',style: { flex: '1 1 180px', maxWidth: 'none' },placeholder: t('overrideKeyPlaceholder'),value: newKey,onChange: (e) => setNewKey(e.target.value),}),React.createElement('input', {className: 'rl-select',style: { flex: '2 1 240px', maxWidth: 'none' },placeholder: t('overrideValuePlaceholder'),value: newVal,onChange: (e) => setNewVal(e.target.value),}),React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: !newKey.trim() || !newVal.trim(),onClick: () => {const k = newKey.trim()
2828
1764
  const v = newVal.trim()
2829
1765
  if (!k || !v) return
2830
1766
  const next = Object.assign({}, overrides, { [k]: v })
2831
1767
  try { scope.set('overrides', updated) } catch (_e) { void _e }
2832
1768
  setNewKey('')
2833
1769
  setNewVal('')
2834
- },
2835
- }, t('overrideAddBtn'))
2836
- ),
2837
- overridesCount === 0
1770
+ },}, t('overrideAddBtn'))
1771
+ ),overridesCount === 0
2838
1772
  ? React.createElement('div', { className: 'rl-hint-text', style: { fontStyle: 'italic', padding: '4px 0' } }, t('overrideEmpty'))
2839
- : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },
2840
- Object.keys(overrides).map((k) =>
2841
- React.createElement('div', {
2842
- key: k,
2843
- 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' },
2844
- },
2845
- React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', overflow: 'hidden' } },
2846
- React.createElement('code', { style: { color: 'var(--dsw-alias-state-brand-primary)', fontWeight: 600 } }, k),
2847
- React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } }, '→'),
2848
- React.createElement('span', { style: { color: 'var(--dsw-alias-label-primary)' } }, overrides[k])
2849
- ),
2850
- React.createElement('button', {
2851
- type: 'button',
2852
- className: 'rl-btn',
2853
- style: { padding: '2px 8px', height: '24px', fontSize: '11px', color: 'var(--dsw-alias-state-error-primary)' },
2854
- onClick: () => {
2855
- const next = Object.assign({}, overrides)
1773
+ : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },Object.keys(overrides).map((k) =>
1774
+ React.createElement('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' },},React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', overflow: 'hidden' } },React.createElement('code', { style: { color: 'var(--dsw-alias-state-brand-primary)', fontWeight: 600 } }, k),React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } }, '→'),React.createElement('span', { style: { color: 'var(--dsw-alias-label-primary)' } }, overrides[k])
1775
+ ),React.createElement('button', {type: 'button',className: 'rl-btn',style: { padding: '2px 8px', height: '24px', fontSize: '11px', color: 'var(--dsw-alias-state-error-primary)' },onClick: () => {const next = Object.assign({}, overrides)
2856
1776
  delete next[k]
2857
1777
  try { scope.set('overrides', updated) } catch (_e) { void _e }
2858
- },
2859
- }, t('overrideDelete'))
1778
+ },}, t('overrideDelete'))
2860
1779
  )
2861
1780
  )
2862
- ),
2863
- React.createElement('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip')),
2864
- React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginTop: '10px' } },
2865
- React.createElement('button', {
2866
- type: 'button',
2867
- className: 'rl-btn' + (isInspectorActive ? ' rl-btn-primary' : ''),
2868
- onClick: () => {
2869
- isInspectorActive = !isInspectorActive
1781
+ ),React.createElement('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip')),React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginTop: '10px' } },React.createElement('button', {type: 'button',className: 'rl-btn' + (isInspectorActive ? ' rl-btn-primary' : ''),onClick: () => {isInspectorActive = !isInspectorActive
2870
1782
  updateInspectorPill()
2871
- },
2872
- }, isInspectorActive ? '🔍 Инспектор перевода (включён)' : '🔍 Включить инспектор перевода (Alt+I)'),
2873
- React.createElement('button', {
2874
- type: 'button',
2875
- className: 'rl-btn',
2876
- style: { fontSize: '11px' },
2877
- onClick: () => {
2878
- try {
2879
- navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
1783
+ },}, isInspectorActive ? '🔍 Инспектор перевода (включён)' : '🔍 Включить инспектор перевода (Alt+I)'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {try {navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
2880
1784
  alert('Оверрайды скопированы в буфер обмена как JSON!')
2881
1785
  } catch (err) { void err; }
2882
1786
  }
2883
- }, '📤 Экспорт JSON'),
2884
- React.createElement('button', {
2885
- type: 'button',
2886
- className: 'rl-btn',
2887
- style: { fontSize: '11px' },
2888
- onClick: () => {
2889
- const input = prompt('Вставьте JSON с оверрайдами:')
1787
+ }, '📤 Экспорт JSON'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = prompt('Вставьте JSON с оверрайдами:')
2890
1788
  if (!input) return
2891
- try {
2892
- const parsed = JSON.parse(input)
2893
- if (parsed && typeof parsed === 'object') {
2894
- const merged = Object.assign({}, overrides, parsed)
1789
+ try {const parsed = JSON.parse(input)
1790
+ if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
2895
1791
  scope.set('overrides', merged)
2896
1792
  }
2897
1793
  } catch (err) { alert('Ошибка разбора JSON: ' + (err && err.message || err)) }
2898
1794
  }
2899
1795
  }, '📥 Импорт JSON')
2900
1796
  )
2901
- ),
2902
- React.createElement('div', { className: 'rl-section-card' },
2903
- React.createElement('div', { className: 'rl-section-title' },
2904
- React.createElement('span', null, t('secSupport')),
2905
- React.createElement('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')
2906
- ),
2907
- React.createElement('div', { className: 'rl-section-desc' }, t('secSupportDesc')),
2908
- React.createElement('div', { className: 'rl-grid-3' },
2909
- React.createElement('div', { className: 'rl-stat-box' },
2910
- React.createElement('div', { className: 'rl-stat-val' }, '113'),
2911
- React.createElement('div', { className: 'rl-stat-label' }, t('statNamespaces'))
2912
- ),
2913
- React.createElement('div', { className: 'rl-stat-box' },
2914
- React.createElement('div', { className: 'rl-stat-val' }, '1 225'),
2915
- React.createElement('div', { className: 'rl-stat-label' }, t('statCoreKeys'))
2916
- ),
2917
- React.createElement('div', { className: 'rl-stat-box' },
2918
- React.createElement('div', { className: 'rl-stat-val' }, '5 924'),
2919
- React.createElement('div', { className: 'rl-stat-label' }, t('statPluginKeys'))
1797
+ ),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secSupport')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')
1798
+ ),React.createElement('div', { className: 'rl-section-desc' }, t('secSupportDesc')),React.createElement('div', { className: 'rl-grid-3' },React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '113'),React.createElement('div', { className: 'rl-stat-label' }, t('statNamespaces'))
1799
+ ),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '1 225'),React.createElement('div', { className: 'rl-stat-label' }, t('statCoreKeys'))
1800
+ ),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '5 924'),React.createElement('div', { className: 'rl-stat-label' }, t('statPluginKeys'))
2920
1801
  )
2921
- ),
2922
- React.createElement('div', { className: 'rl-actions-row' },
2923
- React.createElement('a', {
2924
- href: makeIssueUrl({}, '0.3.5'),
2925
- target: '_blank',
2926
- rel: 'noopener noreferrer',
2927
- className: 'rl-btn rl-btn-primary'
2928
- }, '💬 ' + t('reportIssue')),
2929
- React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },
2930
- t('exportMdHint')
1802
+ ),React.createElement('div', { className: 'rl-actions-row' },React.createElement('a', {href: makeIssueUrl({}, '0.3.8'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
1803
+ }, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')
2931
1804
  )
2932
1805
  )
2933
1806
  )
@@ -2936,69 +1809,9 @@ t('exportMdHint')
2936
1809
  )
2937
1810
  }
2938
1811
  const RL_CSS = [
2939
- '.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)}',
2940
- '.rl-modal-box{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;width:calc(100vw - 40px);max-width:500px;box-shadow:var(--dsw-alias-shadow-l3);display:flex;flex-direction:column;gap:12px;padding:18px}',
2941
- '.rl-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;overflow:hidden;transition:border-color .15s ease}',
2942
- '.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}',
2943
- '.rl-head:hover{background:var(--dsw-alias-bg-layer-2)}',
2944
- '.rl-head-main{flex:1;display:flex;flex-direction:column;gap:4px}',
2945
- '.rl-title{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4;display:flex;align-items:center;flex-wrap:wrap;gap:8px}',
2946
- '.rl-sub{color:var(--dsw-alias-label-secondary);font-size:13px}',
2947
- '.rl-chev{margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary);display:inline-flex;transition:transform .16s}',
2948
- '.rl-chev-open{transform:rotate(180deg)}',
2949
- '.rl-body{border-top:1px solid var(--dsw-alias-border-l2);padding:18px}',
2950
- '.rl-page{display:flex;flex-direction:column;gap:16px;max-width:960px}',
2951
- '.rl-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}',
2952
- '.rl-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px}',
2953
- '.rl-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}',
2954
- '.rl-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:10px}',
2955
- '.rl-grid-3{display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:10px}',
2956
- '.rl-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}',
2957
- '.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)}',
2958
- '.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)}',
2959
- '.rl-badge-dim{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}',
2960
- '.rl-item-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}',
2961
- '.rl-item-head{display:flex;align-items:center;justify-content:space-between;gap:8px}',
2962
- '.rl-item-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px;cursor:pointer}',
2963
- '.rl-item-desc{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}',
2964
- '.rl-stat-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}',
2965
- '.rl-stat-val{font-size:16px;font-weight:700;color:var(--dsw-alias-label-primary)}',
2966
- '.rl-stat-label{font-size:11px;color:var(--dsw-alias-label-secondary);letter-spacing:0.3px}',
2967
- '.rl-hotkey-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4;display:flex;align-items:flex-start;gap:10px}',
2968
- '.rl-hotkey-tag{font-size:11px;font-weight:700;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);white-space:nowrap}',
2969
- '.rl-preview-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}',
2970
- '.rl-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease;text-decoration:none}',
2971
- '.rl-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-secondary)}',
2972
- '.rl-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}',
2973
- '.rl-btn-primary:hover:not(:disabled){opacity:0.9}',
2974
- '.rl-select{height:32px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;outline:none;width:100%;max-width:380px}',
2975
- '.rl-select:focus{border-color:var(--dsw-alias-state-brand-primary)}',
2976
- '.rl-check{width:16px;height:16px;accent-color:var(--dsw-alias-label-primary);cursor:pointer;flex-shrink:0}',
2977
- '.rl-hint-text{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}',
2978
- '.rl-actions-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding-top:4px}',
2979
- '.rl-lang-chip{appearance:none;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:700;padding:2px 7px;border-radius:6px;display:inline-flex;align-items:center;transition:all .15s;margin:0 4px}',
2980
- '.rl-lang-chip:hover{border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-label-primary)}',
2981
- '.rl-lang-chip-active{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}',
2982
- '.rl-action-btn{appearance:none;background:0 0;border:1px solid transparent;border-radius:4px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;padding:2px 5px;display:inline-flex;align-items:center;transition:all .15s}',
2983
- '.rl-action-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3)}',
2984
- '.rl-action-btn-active{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}',
2985
- '.rl-turn-translation{margin:8px 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:8px;font-size:13px;line-height:1.5}',
2986
- '.rl-trans-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px}',
2987
- '.rl-trans-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:0.5px}',
2988
- '.rl-trans-tools{display:flex;gap:4px}',
2989
- '.rl-trans-btn{appearance:none;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;padding:2px 7px;border-radius:4px;font-size:11px}',
2990
- '.rl-trans-btn:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}',
2991
- '.rl-trans-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;user-select:text}',
2992
- '.rl-export-md-btn{appearance:none;border:1px solid var(--dsw-alias-border-l2);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:transparent;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:500;display:inline-flex;white-space:nowrap;margin-left:6px;transition:all .15s ease}',
2993
- '.rl-export-md-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}',
2994
- '.rl-inspector-hover{position:absolute;pointer-events:none;border:2px solid var(--dsw-alias-state-brand-primary);border-radius:4px;background:color-mix(in srgb,var(--dsw-alias-state-brand-primary) 12%,transparent);box-shadow:0 0 10px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 30%,transparent);z-index:99998;transition:all .06s ease;display:none}',
2995
- '.rl-inspector-badge{position:absolute;bottom:calc(100% + 4px);left:0;background:var(--dsw-alias-state-brand-primary);color:var(--dsw-alias-label-inverse);font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;white-space:nowrap;box-shadow:var(--dsw-alias-shadow-l2);pointer-events:none}',
2996
- '.rl-inspector-pill{position:fixed;bottom:18px;right:18px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:20px;padding:6px 12px;box-shadow:var(--dsw-alias-shadow-l3);display:none;align-items:center;gap:8px;font-size:12px;z-index:99990;user-select:none}',
2997
- '.rl-inspector-pill-active{border-color:var(--dsw-alias-state-brand-primary);box-shadow:0 0 12px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 35%,transparent)}',
2998
- ].join('')
1812
+ '.rl-modal-mask{position:fixed;top:0;left:0;width:100vw;height:100vh;background:var(--dsw-alias-bg-mask);display:flex;align-items:center;justify-content:center;z-index:99999;backdrop-filter:blur(2px)}','.rl-modal-box{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;width:calc(100vw - 40px);max-width:500px;box-shadow:var(--dsw-alias-shadow-l3);display:flex;flex-direction:column;gap:12px;padding:18px}','.rl-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;overflow:hidden;transition:border-color .15s ease}','.rl-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 18px}','.rl-head:hover{background:var(--dsw-alias-bg-layer-2)}','.rl-head-main{flex:1;display:flex;flex-direction:column;gap:4px}','.rl-title{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4;display:flex;align-items:center;flex-wrap:wrap;gap:8px}','.rl-sub{color:var(--dsw-alias-label-secondary);font-size:13px}','.rl-chev{margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary);display:inline-flex;transition:transform .16s}','.rl-chev-open{transform:rotate(180deg)}','.rl-body{border-top:1px solid var(--dsw-alias-border-l2);padding:18px}','.rl-page{display:flex;flex-direction:column;gap:16px;max-width:960px}','.rl-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}','.rl-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px}','.rl-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}','.rl-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:10px}','.rl-grid-3{display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:10px}','.rl-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}','.rl-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}','.rl-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}','.rl-badge-dim{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}','.rl-item-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}','.rl-item-head{display:flex;align-items:center;justify-content:space-between;gap:8px}','.rl-item-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px;cursor:pointer}','.rl-item-desc{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}','.rl-stat-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}','.rl-stat-val{font-size:16px;font-weight:700;color:var(--dsw-alias-label-primary)}','.rl-stat-label{font-size:11px;color:var(--dsw-alias-label-secondary);letter-spacing:0.3px}','.rl-hotkey-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4;display:flex;align-items:flex-start;gap:10px}','.rl-hotkey-tag{font-size:11px;font-weight:700;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);white-space:nowrap}','.rl-preview-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}','.rl-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease;text-decoration:none}','.rl-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-secondary)}','.rl-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}','.rl-btn-primary:hover:not(:disabled){opacity:0.9}','.rl-select{height:32px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;outline:none;width:100%;max-width:380px}','.rl-select:focus{border-color:var(--dsw-alias-state-brand-primary)}','.rl-check{width:16px;height:16px;accent-color:var(--dsw-alias-label-primary);cursor:pointer;flex-shrink:0}','.rl-hint-text{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}','.rl-actions-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding-top:4px}','.rl-lang-chip{appearance:none;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:700;padding:2px 7px;border-radius:6px;display:inline-flex;align-items:center;transition:all .15s;margin:0 4px}','.rl-lang-chip:hover{border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-label-primary)}','.rl-lang-chip-active{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}','.rl-action-btn{appearance:none;background:0 0;border:1px solid transparent;border-radius:4px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;padding:2px 5px;display:inline-flex;align-items:center;transition:all .15s}','.rl-action-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3)}','.rl-action-btn-active{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}','.rl-turn-translation{margin:8px 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:8px;font-size:13px;line-height:1.5}','.rl-trans-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px}','.rl-trans-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:0.5px}','.rl-trans-tools{display:flex;gap:4px}','.rl-trans-btn{appearance:none;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;padding:2px 7px;border-radius:4px;font-size:11px}','.rl-trans-btn:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}','.rl-trans-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;user-select:text}','.rl-export-md-btn{appearance:none;border:1px solid var(--dsw-alias-border-l2);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:transparent;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:500;display:inline-flex;white-space:nowrap;margin-left:6px;transition:all .15s ease}','.rl-export-md-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}','.rl-inspector-hover{position:absolute;pointer-events:none;border:2px solid var(--dsw-alias-state-brand-primary);border-radius:4px;background:color-mix(in srgb,var(--dsw-alias-state-brand-primary) 12%,transparent);box-shadow:0 0 10px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 30%,transparent);z-index:99998;transition:all .06s ease;display:none}','.rl-inspector-badge{position:absolute;bottom:calc(100% + 4px);left:0;background:var(--dsw-alias-state-brand-primary);color:var(--dsw-alias-label-inverse);font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;white-space:nowrap;box-shadow:var(--dsw-alias-shadow-l2);pointer-events:none}','.rl-inspector-pill{position:fixed;bottom:18px;right:18px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:20px;padding:6px 12px;box-shadow:var(--dsw-alias-shadow-l3);display:none;align-items:center;gap:8px;font-size:12px;z-index:99990;user-select:none}','.rl-inspector-pill-active{border-color:var(--dsw-alias-state-brand-primary);box-shadow:0 0 12px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 35%,transparent)}',].join('')
2999
1813
  const STYLE_ID = 'dsh-russian-lang-styles'
3000
- if (typeof document !== 'undefined' && (document.getElementById && !document.getElementById(STYLE_ID))) {
3001
- const tag = document.createElement('style')
1814
+ if (typeof document !== 'undefined' && (document.getElementById && !document.getElementById(STYLE_ID))) {const tag = document.createElement('style')
3002
1815
  tag.id = STYLE_ID
3003
1816
  tag.dataset.dshPlugin = 'dsh-russian-lang'
3004
1817
  tag.dataset.plugin = '@goodandready/dsh-russian-lang'
@@ -3008,5 +1821,4 @@ document.head.appendChild(tag)
3008
1821
  }
3009
1822
  module.exports = { apply, inject: ['slots', 'locale', 'configForms'] }
3010
1823
  return module.exports
3011
- },
3012
- })
1824
+ },})