@goodandready/dsh-russian-lang 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/lib/client.js +548 -1732
- package/lib/client.template.js +2685 -0
- package/lib/locales/core.json +1 -1
- package/lib/pure.js +6 -11
- package/lib/translator.js +14 -16
- package/package.json +2 -4
- package/README.ru.md +0 -194
- package/docs/media/banner.jpg +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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 ===
|
|
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 (!
|
|
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
|
-
|
|
216
|
-
|
|
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,99 +247,66 @@ 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
|
|
260
|
+
const lq = [], cf = []
|
|
261
|
+
s = s.replace(/"([^"\n]*[a-zA-Z][^"\n]*)"/g, (m) => `\x01L${lq.push(m) - 1}\x01`)
|
|
262
|
+
s = s.replace(/(^|\s)(--[a-zA-Z0-9_\u0400-\u04FF-]+)/g, (m, p1, p2) => `${p1}\x01C${cf.push(p2) - 1}\x01`)
|
|
263
|
+
s = s.replace(/(^|\s)(--)(?=\s|$)/g, (m, p1, p2) => /[a-zA-Z]/.test(text) ? `${p1}\x01C${cf.push(p2) - 1}\x01` : m)
|
|
373
264
|
s = s.replace(/--/g, '—')
|
|
374
|
-
s = s.replace(/(^|[\s([{-])"/g, '$1«')
|
|
375
|
-
s = s.replace(/"/g, '»')
|
|
265
|
+
s = s.replace(/(^|[\s([{-])"/g, '$1«').replace(/"/g, '»')
|
|
376
266
|
s = s.replace(SHORT_WORDS_RE, '$1$2\u00A0')
|
|
267
|
+
s = s.replace(/\x01C(\d+)\x01/g, (_, j) => cf[+j]).replace(/\x01L(\d+)\x01/g, (_, j) => lq[+j])
|
|
377
268
|
parts[i] = s
|
|
378
269
|
}
|
|
379
270
|
return parts.join('')
|
|
380
271
|
}
|
|
381
|
-
const RUSSIAN_SLASH_ALIASES = {
|
|
382
|
-
|
|
383
|
-
'
|
|
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
|
|
272
|
+
const RUSSIAN_SLASH_ALIASES = {'/цель': '/goal','/справка': '/help','/задача': '/task','/контекст': '/context','/сжать': '/compact','/план': '/plan','/экспорт': '/export','/отзыв': '/feedback','/разрешение': '/permission','/разрешения': '/permission','/память': '/memory','/помощь': '/help','/очистить': '/clear','/сессия': '/session','/модель': '/model'
|
|
273
|
+
}
|
|
274
|
+
const expandSlashAlias = (input) => {if (!input || typeof input !== 'string' || !input.startsWith('/')) return input
|
|
400
275
|
const match = input.match(/^(\s*\/[^\s]+)(.*)$/)
|
|
401
276
|
if (!match) return input
|
|
402
277
|
const [, cmd, rest] = match
|
|
403
278
|
const lowerCmd = cmd.trim().toLowerCase()
|
|
404
|
-
if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {
|
|
405
|
-
return RUSSIAN_SLASH_ALIASES[lowerCmd] + rest
|
|
279
|
+
if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {return RUSSIAN_SLASH_ALIASES[lowerCmd] + rest
|
|
406
280
|
}
|
|
407
281
|
return input
|
|
408
282
|
}
|
|
409
283
|
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', 'кс']
|
|
284
|
+
['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
285
|
]
|
|
417
286
|
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'], ['ъ', ''], ['ь', '']
|
|
287
|
+
['щ', '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
288
|
]
|
|
425
|
-
const phoneticTranslit = (text, direction = 'lat2cyr') => {
|
|
426
|
-
if (!text || typeof text !== 'string') return text
|
|
289
|
+
const phoneticTranslit = (text, direction = 'lat2cyr') => {if (!text || typeof text !== 'string') return text
|
|
427
290
|
let res = text
|
|
428
291
|
const pairs = direction === 'lat2cyr' ? PHONETIC_LAT_TO_CYR : PHONETIC_CYR_TO_LAT
|
|
429
|
-
for (const [from, to] of pairs) {
|
|
430
|
-
const fromUpper = from.toUpperCase()
|
|
292
|
+
for (const [from, to] of pairs) {const fromUpper = from.toUpperCase()
|
|
431
293
|
const fromTitle = from[0].toUpperCase() + from.slice(1)
|
|
432
294
|
const toUpper = to.toUpperCase()
|
|
433
295
|
const toTitle = to[0] ? to[0].toUpperCase() + to.slice(1) : ''
|
|
434
|
-
if (from.length > 1) {
|
|
435
|
-
res = res.replaceAll(fromUpper, toUpper)
|
|
296
|
+
if (from.length > 1) {res = res.replaceAll(fromUpper, toUpper)
|
|
436
297
|
res = res.replaceAll(fromTitle, toTitle)
|
|
437
298
|
}
|
|
438
299
|
res = res.replaceAll(from, to)
|
|
439
|
-
if (from.length === 1) {
|
|
440
|
-
res = res.replaceAll(fromUpper, toUpper)
|
|
300
|
+
if (from.length === 1) {res = res.replaceAll(fromUpper, toUpper)
|
|
441
301
|
}
|
|
442
302
|
}
|
|
443
303
|
return res
|
|
444
304
|
}
|
|
445
|
-
const detectInputLayout = (text) => {
|
|
446
|
-
if (!text || typeof text !== 'string') return null
|
|
305
|
+
const detectInputLayout = (text) => {if (!text || typeof text !== 'string') return null
|
|
447
306
|
const clean = text.replace(/`[^`]*`/g, '').replace(/https?:\/\/\S+/g, '')
|
|
448
307
|
let cyr = 0
|
|
449
308
|
let lat = 0
|
|
450
|
-
for (const ch of clean) {
|
|
451
|
-
const code = ch.charCodeAt(0)
|
|
309
|
+
for (const ch of clean) {const code = ch.charCodeAt(0)
|
|
452
310
|
if ((code >= 0x0400 && code <= 0x04FF) || code === 0x0500) cyr++
|
|
453
311
|
else if ((code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A)) lat++
|
|
454
312
|
}
|
|
@@ -456,51 +314,18 @@ if (cyr > lat && cyr >= 2) return 'RU'
|
|
|
456
314
|
if (lat > cyr && lat >= 2) return 'EN'
|
|
457
315
|
return null
|
|
458
316
|
}
|
|
459
|
-
const SYSTEM_PROMPT_PRESETS = {
|
|
460
|
-
|
|
461
|
-
id: '
|
|
462
|
-
label: '
|
|
463
|
-
desc: '
|
|
464
|
-
text: 'Отвечай
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
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 ''
|
|
317
|
+
const SYSTEM_PROMPT_PRESETS = {technical_expert: {id: 'technical_expert',label: 'Технический эксперт',desc: 'Точная терминология, чистый код и русские комментарии',text: 'Отвечай пользователю на русском языке. Используй точную инженерную терминологию, пиши чистый код и русские комментарии.'
|
|
318
|
+
},tech_writer: {id: 'tech_writer',label: 'Технический писатель',desc: 'Структурированные тексты, Markdown и выверенная типографика',text: 'Отвечай пользователю на русском языке. Оформляй документацию в Markdown, строго соблюдай правила русской типографики («ёлочки», тире, «ё»).'
|
|
319
|
+
},concise: {id: 'concise',label: 'Лаконичный режим',desc: 'Краткие ёмкие ответы без вводных слов и воды',text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без вводных слов и воды, сразу код или решение.'
|
|
320
|
+
},code_reviewer: {id: 'code_reviewer',label: 'Код-ревьюер',desc: 'Поиск багов, безопасность и архитектурный аудит',text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
|
|
321
|
+
},architect: {id: 'architect',label: 'Системный архитектор',desc: 'Контракты API/CLI, модульность и надёжность',text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
|
|
322
|
+
},tutor: {id: 'tutor',label: 'Наставник (ментор)',desc: 'Понятные объяснения сложных концепций и пошаговый разбор',text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const exportSessionToMarkdown = (session, options = {}) => {if (!session) return ''
|
|
499
326
|
const title = session.title || session.name || 'Диалог DSH'
|
|
500
327
|
const date = session.createdAt ? new Date(session.createdAt) : new Date()
|
|
501
|
-
const dateStr = new Intl.DateTimeFormat('ru-RU', {
|
|
502
|
-
dateStyle: 'full',
|
|
503
|
-
timeStyle: 'medium'
|
|
328
|
+
const dateStr = new Intl.DateTimeFormat('ru-RU', {dateStyle: 'full',timeStyle: 'medium'
|
|
504
329
|
}).format(date)
|
|
505
330
|
const messages = Array.isArray(session.messages) ? session.messages : []
|
|
506
331
|
let md = `# 💬 ${title}\n\n`
|
|
@@ -508,20 +333,16 @@ md += `> **Дата экспорта:** ${dateStr}\n`
|
|
|
508
333
|
if (session.model) md += `> **Модель:** \`${session.model}\`\n`
|
|
509
334
|
if (session.workspace) md += `> **Рабочая область:** \`${session.workspace}\`\n`
|
|
510
335
|
md += `> **Всего сообщений:** ${messages.length}\n\n---\n\n`
|
|
511
|
-
for (let idx = 0; idx < messages.length; idx++) {
|
|
512
|
-
const msg = messages[idx]
|
|
336
|
+
for (let idx = 0; idx < messages.length; idx++) {const msg = messages[idx]
|
|
513
337
|
const role = msg.role || 'unknown'
|
|
514
338
|
const roleName = role === 'user' ? '👤 Пользователь' :
|
|
515
339
|
role === 'assistant' ? '🤖 Ассистент' :
|
|
516
340
|
role === 'system' ? '⚙️ Система' : `🔧 Инструмент (${role})`
|
|
517
341
|
md += `### ${roleName}\n\n`
|
|
518
|
-
if (msg.content) {
|
|
519
|
-
md += `${msg.content}\n\n`
|
|
342
|
+
if (msg.content) {md += `${msg.content}\n\n`
|
|
520
343
|
}
|
|
521
|
-
if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) {
|
|
522
|
-
md +=
|
|
523
|
-
for (const tc of msg.toolCalls) {
|
|
524
|
-
md += `- **${tc.name || 'tool'}**: \`${JSON.stringify(tc.arguments || {})}\`\n`
|
|
344
|
+
if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) {md += `*Вызовы инструментов:*\n`
|
|
345
|
+
for (const tc of msg.toolCalls) {md += `- **${tc.name || 'tool'}**: \`${JSON.stringify(tc.arguments || {})}\`\n`
|
|
525
346
|
}
|
|
526
347
|
md += `\n`
|
|
527
348
|
}
|
|
@@ -530,94 +351,56 @@ md += `---\n\n`
|
|
|
530
351
|
md += `*Сгенерировано с помощью [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) и @goodandready/dsh-russian-lang*\n`
|
|
531
352
|
return md
|
|
532
353
|
}
|
|
533
|
-
const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}, domEn = {}, options = {}) => {
|
|
534
|
-
if (!text || typeof text !== 'string') return null
|
|
354
|
+
const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}, domEn = {}, options = {}) => {if (!text || typeof text !== 'string') return null
|
|
535
355
|
const trimmed = text.trim()
|
|
536
356
|
if (!trimmed) return null
|
|
537
|
-
if (overrides && typeof overrides === 'object') {
|
|
538
|
-
if (overrides[trimmed] !== undefined) {
|
|
539
|
-
return { key: trimmed, value: overrides[trimmed], source: 'override' }
|
|
357
|
+
if (overrides && typeof overrides === 'object') {if (overrides[trimmed] !== undefined) {return { key: trimmed, value: overrides[trimmed], source: 'override' }
|
|
540
358
|
}
|
|
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' }
|
|
359
|
+
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
360
|
}
|
|
545
361
|
}
|
|
546
362
|
}
|
|
547
|
-
if (dicts && typeof dicts === 'object') {
|
|
548
|
-
|
|
549
|
-
if (!map || typeof map !== 'object') continue
|
|
550
|
-
if (map[trimmed] !== undefined) {
|
|
551
|
-
return { ns, key: trimmed, value: map[trimmed], source: 'dictionary' }
|
|
363
|
+
if (dicts && typeof dicts === 'object') {for (const [ns, map] of Object.entries(dicts)) {if (!map || typeof map !== 'object') continue
|
|
364
|
+
if (map[trimmed] !== undefined) {return { ns, key: trimmed, value: map[trimmed], source: 'dictionary' }
|
|
552
365
|
}
|
|
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' }
|
|
366
|
+
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
367
|
}
|
|
557
368
|
}
|
|
558
369
|
}
|
|
559
370
|
}
|
|
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' }
|
|
371
|
+
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
372
|
}
|
|
565
373
|
}
|
|
566
|
-
if (zhRu[trimmed] !== undefined) {
|
|
567
|
-
return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }
|
|
374
|
+
if (zhRu[trimmed] !== undefined) {return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }
|
|
568
375
|
}
|
|
569
376
|
}
|
|
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' }
|
|
377
|
+
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
378
|
}
|
|
575
379
|
}
|
|
576
|
-
if (domEn[trimmed] !== undefined) {
|
|
577
|
-
return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }
|
|
380
|
+
if (domEn[trimmed] !== undefined) {return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }
|
|
578
381
|
}
|
|
579
382
|
}
|
|
580
|
-
if (options && options.detectUntranslated) {
|
|
581
|
-
const hasRu = /[а-яё]/i.test(trimmed)
|
|
383
|
+
if (options && options.detectUntranslated) {const hasRu = /[а-яё]/i.test(trimmed)
|
|
582
384
|
const hasZh = /[㐀-鿿豈-]/.test(trimmed)
|
|
583
385
|
const hasEn = /[a-z]/i.test(trimmed)
|
|
584
|
-
if (!hasRu && hasZh) {
|
|
585
|
-
return { key: trimmed, value: trimmed, source: 'untranslated_zh' }
|
|
386
|
+
if (!hasRu && hasZh) {return { key: trimmed, value: trimmed, source: 'untranslated_zh' }
|
|
586
387
|
}
|
|
587
|
-
if (!hasRu && hasEn) {
|
|
588
|
-
return { key: trimmed, value: trimmed, source: 'untranslated_en' }
|
|
388
|
+
if (!hasRu && hasEn) {return { key: trimmed, value: trimmed, source: 'untranslated_en' }
|
|
589
389
|
}
|
|
590
390
|
}
|
|
591
391
|
return null
|
|
592
392
|
}
|
|
593
|
-
const generateBugReportSnippet = (info = {}) => {
|
|
594
|
-
const { key, ns, original, current, override, pkgVersion = '0.3.1' } = info
|
|
393
|
+
const generateBugReportSnippet = (info = {}) => {const { key, ns, original, current, override, pkgVersion = '0.3.1' } = info
|
|
595
394
|
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)
|
|
395
|
+
'### 🌐 Неточность перевода / Пользовательское предложение','- **Пакет:** `@goodandready/dsh-russian-lang@v' + pkgVersion + '`',ns ? '- **Пространство имён:** `' + ns + '`' : null,key ? '- **Ключ:** `' + key + '`' : null,original ? '- **Оригинальный текст:** `' + original + '`' : null,current ? '- **Текущий перевод:** `' + current + '`' : null,override ? '- **Предлагаемый перевод:** `' + override + '`' : null,].filter(Boolean)
|
|
604
396
|
return lines.join('\n')
|
|
605
397
|
}
|
|
606
|
-
const bilingualCommandMatch = (query, command, options = {}) => {
|
|
607
|
-
if (!query || !command) return { matched: false, score: 0 }
|
|
398
|
+
const bilingualCommandMatch = (query, command, options = {}) => {if (!query || !command) return { matched: false, score: 0 }
|
|
608
399
|
const q = String(query).trim().toLowerCase()
|
|
609
400
|
if (!q) return { matched: false, score: 0 }
|
|
610
401
|
const cmdObj = typeof command === 'string' ? { title: command } : command
|
|
611
402
|
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 : [])
|
|
403
|
+
cmdObj.title,cmdObj.label,cmdObj.name,cmdObj.originalTitle,cmdObj.enTitle,cmdObj.id,cmdObj.description,cmdObj.category,...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
|
|
621
404
|
].filter((c) => typeof c === 'string' && c.trim())
|
|
622
405
|
if (!candidates.length) return { matched: false, score: 0 }
|
|
623
406
|
const queryVariants = [{ text: q, kind: 'direct' }]
|
|
@@ -626,155 +409,107 @@ if (lat2cyr !== q) queryVariants.push({ text: lat2cyr, kind: 'layout' })
|
|
|
626
409
|
const cyr2lat = translit(q, LAYOUT_CYR_TO_LAT)
|
|
627
410
|
if (cyr2lat !== q) queryVariants.push({ text: cyr2lat, kind: 'layout' })
|
|
628
411
|
const phonLat2Cyr = phoneticTranslit(q, 'lat2cyr')
|
|
629
|
-
if (phonLat2Cyr && phonLat2Cyr !== q && phonLat2Cyr !== lat2cyr) {
|
|
630
|
-
queryVariants.push({ text: phonLat2Cyr, kind: 'phonetic' })
|
|
412
|
+
if (phonLat2Cyr && phonLat2Cyr !== q && phonLat2Cyr !== lat2cyr) {queryVariants.push({ text: phonLat2Cyr, kind: 'phonetic' })
|
|
631
413
|
}
|
|
632
414
|
const phonCyr2Lat = phoneticTranslit(q, 'cyr2lat')
|
|
633
|
-
if (phonCyr2Lat && phonCyr2Lat !== q && phonCyr2Lat !== cyr2lat) {
|
|
634
|
-
queryVariants.push({ text: phonCyr2Lat, kind: 'phonetic' })
|
|
415
|
+
if (phonCyr2Lat && phonCyr2Lat !== q && phonCyr2Lat !== cyr2lat) {queryVariants.push({ text: phonCyr2Lat, kind: 'phonetic' })
|
|
635
416
|
}
|
|
636
417
|
let bestScore = 0
|
|
637
418
|
let bestVariant = null
|
|
638
419
|
let bestCandidate = null
|
|
639
|
-
for (const variant of queryVariants) {
|
|
640
|
-
|
|
641
|
-
const score = fuzzyMatchRu(variant.text, cand)
|
|
642
|
-
if (score > bestScore) {
|
|
643
|
-
bestScore = score
|
|
420
|
+
for (const variant of queryVariants) {for (const cand of candidates) {const score = fuzzyMatchRu(variant.text, cand)
|
|
421
|
+
if (score > bestScore) {bestScore = score
|
|
644
422
|
bestVariant = variant
|
|
645
423
|
bestCandidate = cand
|
|
646
424
|
}
|
|
647
425
|
}
|
|
648
426
|
}
|
|
649
427
|
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'
|
|
428
|
+
return {matched: bestScore >= threshold,score: bestScore,matchedText: bestCandidate,variant: bestVariant ? bestVariant.text : q,kind: bestVariant ? bestVariant.kind : 'none'
|
|
656
429
|
}
|
|
657
430
|
}
|
|
658
|
-
const filterCommands = (query, commands = [], options = {}) => {
|
|
659
|
-
if (!Array.isArray(commands)) return []
|
|
431
|
+
const filterCommands = (query, commands = [], options = {}) => {if (!Array.isArray(commands)) return []
|
|
660
432
|
if (!query || !String(query).trim()) return commands
|
|
661
433
|
const results = []
|
|
662
|
-
for (const cmd of commands) {
|
|
663
|
-
|
|
664
|
-
if (res.matched) {
|
|
665
|
-
results.push({ item: cmd, ...res })
|
|
434
|
+
for (const cmd of commands) {const res = bilingualCommandMatch(query, cmd, options)
|
|
435
|
+
if (res.matched) {results.push({ item: cmd, ...res })
|
|
666
436
|
}
|
|
667
437
|
}
|
|
668
438
|
return results.sort((a, b) => b.score - a.score).map((r) => r.item)
|
|
669
439
|
}
|
|
670
|
-
|
|
671
|
-
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
|
-
|
|
440
|
+
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.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":"Магазин плагинов"}}
|
|
673
441
|
const ZH_RU = {}
|
|
674
|
-
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":"
|
|
442
|
+
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
443
|
const SETTINGS_NS_NAME = 'russian-lang'
|
|
676
444
|
const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
|
|
677
|
-
function apply(ctx) {
|
|
678
|
-
const
|
|
679
|
-
const resolveScope = (c) => {
|
|
680
|
-
try {
|
|
681
|
-
if (c && c.configForms) return c.configForms.get(SETTINGS_NS_NAME)
|
|
445
|
+
function apply(ctx) {const runtime = ctx.locale
|
|
446
|
+
const resolveScope = (c) => {try {if (c && c.configForms) return c.configForms.get(SETTINGS_NS_NAME)
|
|
682
447
|
if (c && c.settingsScope) return c.settingsScope.bind({ namespace: SETTINGS_NS_NAME })
|
|
683
448
|
} catch (err) { void err }
|
|
684
449
|
return null
|
|
685
450
|
}
|
|
686
451
|
const rawScope = resolveScope(ctx)
|
|
687
|
-
const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {
|
|
688
|
-
|
|
689
|
-
|
|
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]) }
|
|
452
|
+
const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
|
|
453
|
+
}
|
|
454
|
+
for (const ns of Object.keys(RU)) {ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', RU[ns]) }
|
|
695
455
|
catch (err) { return () => {} }
|
|
696
456
|
}, 'dsh-russian-lang: ' + ns)
|
|
697
457
|
}
|
|
698
|
-
const registerDictMap = (dictMap) => {
|
|
699
|
-
|
|
700
|
-
for (const ns of Object.keys(dictMap)) {
|
|
701
|
-
if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
|
|
458
|
+
const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
|
|
459
|
+
for (const ns of Object.keys(dictMap)) {if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
|
|
702
460
|
const dict = dictMap[ns]
|
|
703
461
|
if (typeof dict !== 'object' || !dict) continue
|
|
704
|
-
if (!RU[ns]) {
|
|
705
|
-
|
|
706
|
-
ctx.effect(() => {
|
|
707
|
-
try { return ctx.locale.register(ns, 'ru', dict) }
|
|
462
|
+
if (!RU[ns]) {RU[ns] = dict
|
|
463
|
+
ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) }
|
|
708
464
|
catch (err) { return () => {} }
|
|
709
465
|
}, 'dsh-russian-lang: ' + ns)
|
|
710
|
-
} else {
|
|
711
|
-
Object.assign(RU[ns], dict)
|
|
466
|
+
} else {Object.assign(RU[ns], dict)
|
|
712
467
|
}
|
|
713
468
|
}
|
|
714
469
|
}
|
|
715
470
|
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
|
|
471
|
+
const loadLocalDict = (key) => {try {const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
|
|
719
472
|
return raw ? JSON.parse(raw) : null
|
|
720
473
|
} catch (_) { return null }
|
|
721
474
|
}
|
|
722
|
-
const saveLocalDict = (key, etag, data) => {
|
|
723
|
-
try {
|
|
724
|
-
if (typeof localStorage !== 'undefined') {
|
|
725
|
-
localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
|
|
475
|
+
const saveLocalDict = (key, etag, data) => {try {if (typeof localStorage !== 'undefined') {localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
|
|
726
476
|
}
|
|
727
477
|
} catch (_e) { void _e }
|
|
728
478
|
}
|
|
729
|
-
try {
|
|
730
|
-
|
|
731
|
-
if (cachedCore &&
|
|
732
|
-
registerDictMap(cachedCore.data.core)
|
|
733
|
-
if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {
|
|
734
|
-
Object.assign(ZH_RU, cachedCore.data.zhRu)
|
|
479
|
+
try {const cachedCore = loadLocalDict('core')
|
|
480
|
+
if (cachedCore && cachedCore.data && cachedCore.data.core) {registerDictMap(cachedCore.data.core)
|
|
481
|
+
if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, cachedCore.data.zhRu)
|
|
735
482
|
}
|
|
736
483
|
}
|
|
737
484
|
} catch (_e) { void _e }
|
|
738
|
-
const fetchCachedResource = async (url, cacheKey, onData) => {
|
|
739
|
-
let etag = null
|
|
485
|
+
const fetchCachedResource = async (url, cacheKey, onData) => {let etag = null
|
|
740
486
|
let delivered = false
|
|
741
|
-
if (typeof caches !== 'undefined') {
|
|
742
|
-
try {
|
|
743
|
-
const cache = await caches.open(CACHE_NAME)
|
|
487
|
+
if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
|
|
744
488
|
const matched = await cache.match(url)
|
|
745
|
-
if (matched) {
|
|
746
|
-
etag = matched.headers.get('etag')
|
|
489
|
+
if (matched) {etag = matched.headers.get('etag')
|
|
747
490
|
const parsed = await matched.json()
|
|
748
|
-
if (parsed) {
|
|
749
|
-
delivered = true
|
|
491
|
+
if (parsed) {delivered = true
|
|
750
492
|
onData(parsed)
|
|
751
493
|
}
|
|
752
494
|
}
|
|
753
495
|
} catch (_e) { void _e }
|
|
754
496
|
}
|
|
755
|
-
if (!delivered && cacheKey) {
|
|
756
|
-
|
|
757
|
-
if (local && local.data) {
|
|
758
|
-
etag = local.etag
|
|
497
|
+
if (!delivered && cacheKey) {const local = loadLocalDict(cacheKey)
|
|
498
|
+
if (local && local.data) {etag = local.etag
|
|
759
499
|
delivered = true
|
|
760
500
|
onData(local.data)
|
|
761
501
|
}
|
|
762
502
|
}
|
|
763
503
|
if (typeof fetch !== 'function') return
|
|
764
|
-
try {
|
|
765
|
-
const headers = { 'Accept': 'application/json' }
|
|
504
|
+
try {const headers = { 'Accept': 'application/json' }
|
|
766
505
|
if (etag) headers['If-None-Match'] = etag
|
|
767
506
|
const res = await fetch(url, { headers })
|
|
768
507
|
if (res.status === 304) return
|
|
769
|
-
if (res.ok) {
|
|
770
|
-
const newEtag = res.headers.get('etag')
|
|
508
|
+
if (res.ok) {const newEtag = res.headers.get('etag')
|
|
771
509
|
const cloned = res.clone()
|
|
772
510
|
const data = await res.json()
|
|
773
|
-
if (data) {
|
|
774
|
-
|
|
775
|
-
if (typeof caches !== 'undefined') {
|
|
776
|
-
try {
|
|
777
|
-
const cache = await caches.open(CACHE_NAME)
|
|
511
|
+
if (data) {onData(data)
|
|
512
|
+
if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
|
|
778
513
|
await cache.put(url, cloned)
|
|
779
514
|
} catch (_e) { void _e }
|
|
780
515
|
}
|
|
@@ -786,11 +521,9 @@ if (cacheKey) saveLocalDict(cacheKey, newEtag, data)
|
|
|
786
521
|
const loadedPluginNames = new Set()
|
|
787
522
|
const pendingPluginLoads = new Set()
|
|
788
523
|
let pluginBatchTimer = null
|
|
789
|
-
const loadPluginDictionaries = (names) => {
|
|
790
|
-
if (typeof fetch !== 'function') return
|
|
524
|
+
const loadPluginDictionaries = (names) => {if (typeof fetch !== 'function') return
|
|
791
525
|
const toLoad = []
|
|
792
|
-
for (const raw of names) {
|
|
793
|
-
const n = String(raw).trim()
|
|
526
|
+
for (const raw of names) {const n = String(raw).trim()
|
|
794
527
|
if (!n || loadedPluginNames.has(n) || pendingPluginLoads.has(n)) continue
|
|
795
528
|
toLoad.push(n)
|
|
796
529
|
pendingPluginLoads.add(n)
|
|
@@ -798,42 +531,31 @@ pendingPluginLoads.add(n)
|
|
|
798
531
|
const qs = toLoad.length > 0 ? ('?names=' + encodeURIComponent(toLoad.join(','))) : ''
|
|
799
532
|
const url = '/api/dsh-russian-lang/dict/plugins' + qs
|
|
800
533
|
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)
|
|
534
|
+
fetchCachedResource(url, cacheKey, (data) => {if (data && data.plugins) {registerDictMap(data.plugins)
|
|
804
535
|
for (const n of toLoad) loadedPluginNames.add(n)
|
|
805
536
|
}
|
|
806
|
-
}).catch(() => {}).finally(() => {
|
|
807
|
-
for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
537
|
+
}).catch(() => {}).finally(() => {for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
808
538
|
})
|
|
809
539
|
}
|
|
810
|
-
const schedulePluginLoad = (name) => {
|
|
811
|
-
if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
|
|
540
|
+
const schedulePluginLoad = (name) => {if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
|
|
812
541
|
pendingPluginLoads.add(name)
|
|
813
542
|
if (pluginBatchTimer) clearTimeout(pluginBatchTimer)
|
|
814
|
-
pluginBatchTimer = setTimeout(() => {
|
|
815
|
-
const batch = Array.from(pendingPluginLoads).filter((n) => !loadedPluginNames.has(n))
|
|
543
|
+
pluginBatchTimer = setTimeout(() => {const batch = Array.from(pendingPluginLoads).filter((n) => !loadedPluginNames.has(n))
|
|
816
544
|
if (batch.length > 0) loadPluginDictionaries(batch)
|
|
817
545
|
}, 50)
|
|
818
546
|
}
|
|
819
|
-
fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {
|
|
820
|
-
if (!data) return
|
|
547
|
+
fetchCachedResource('/api/dsh-russian-lang/dict/core', 'core', (data) => {if (!data) return
|
|
821
548
|
if (data.core) registerDictMap(data.core)
|
|
822
|
-
if (data.zhRu && typeof ZH_RU === 'object') {
|
|
823
|
-
Object.assign(ZH_RU, data.zhRu)
|
|
549
|
+
if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
|
|
824
550
|
if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
825
551
|
}
|
|
826
552
|
if (typeof syncZhDom === 'function') syncZhDom()
|
|
827
553
|
setTimeout(() => { loadPluginDictionaries([]) }, 100)
|
|
828
|
-
}).catch(() => {
|
|
829
|
-
if (typeof fetch === 'function') {
|
|
830
|
-
fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
|
|
554
|
+
}).catch(() => {if (typeof fetch === 'function') {fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
|
|
831
555
|
.then((res) => res.ok ? res.json() : null)
|
|
832
|
-
.then((data) => {
|
|
833
|
-
if (!data) return
|
|
556
|
+
.then((data) => {if (!data) return
|
|
834
557
|
const allDicts = Object.assign({}, data.core || {}, data.plugins || {}, data)
|
|
835
|
-
if (data.zhRu && typeof ZH_RU === 'object') {
|
|
836
|
-
Object.assign(ZH_RU, data.zhRu)
|
|
558
|
+
if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
|
|
837
559
|
if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
838
560
|
}
|
|
839
561
|
registerDictMap(allDicts)
|
|
@@ -843,15 +565,12 @@ if (typeof syncZhDom === 'function') syncZhDom()
|
|
|
843
565
|
}
|
|
844
566
|
})
|
|
845
567
|
const origTranslate = runtime.translate.bind(runtime)
|
|
846
|
-
const getOverrides = () => {
|
|
847
|
-
try {
|
|
848
|
-
const value = scope.getSnapshot().value
|
|
568
|
+
const getOverrides = () => {try {const value = scope.getSnapshot().value
|
|
849
569
|
return value && value.overrides ? value.overrides : {}
|
|
850
570
|
} catch (err) { return {} }
|
|
851
571
|
}
|
|
852
572
|
const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
|
|
853
|
-
try {
|
|
854
|
-
runtime.formatNumber = formatNumber
|
|
573
|
+
try {runtime.formatNumber = formatNumber
|
|
855
574
|
runtime.formatCompactNumber = formatCompactNumber
|
|
856
575
|
runtime.formatDate = formatDate
|
|
857
576
|
runtime.formatTokens = formatTokens
|
|
@@ -865,96 +584,67 @@ runtime.humanizeError = humanizeError
|
|
|
865
584
|
runtime.plural = plural
|
|
866
585
|
runtime.pluralForm = pluralForm
|
|
867
586
|
} catch (err) { }
|
|
868
|
-
const lookupChain = () => {
|
|
869
|
-
try {
|
|
870
|
-
const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
|
|
587
|
+
const lookupChain = () => {try {const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
|
|
871
588
|
if (Array.isArray(chain) && chain.length) return chain
|
|
872
589
|
} catch (err) { }
|
|
873
590
|
return [runtime.getLocale().active]
|
|
874
591
|
}
|
|
875
592
|
const translationRegistry = new Map()
|
|
876
|
-
const lookup = (ns, key) => {
|
|
877
|
-
const chain = lookupChain()
|
|
593
|
+
const lookup = (ns, key) => {const chain = lookupChain()
|
|
878
594
|
return runtime.lookup.length >= 3 ? runtime.lookup(ns, key, chain) : runtime.lookup(ns, key)
|
|
879
595
|
}
|
|
880
|
-
runtime.translate = function (ns, key, params) {
|
|
881
|
-
if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
|
|
596
|
+
runtime.translate = function (ns, key, params) {if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
|
|
882
597
|
const overrides = getOverrides()
|
|
883
|
-
if (overrides[key] !== undefined) {
|
|
884
|
-
return params ? fill(overrides[key], params) : overrides[key]
|
|
598
|
+
if (overrides[key] !== undefined) {return params ? fill(overrides[key], params) : overrides[key]
|
|
885
599
|
}
|
|
886
|
-
if (runtime.getLocale().active === 'ru' && params) {
|
|
887
|
-
|
|
888
|
-
if (typeof n === 'number') {
|
|
889
|
-
const form = pluralForm(n)
|
|
600
|
+
if (runtime.getLocale().active === 'ru' && params) {const n = params.n ?? params.count
|
|
601
|
+
if (typeof n === 'number') {const form = pluralForm(n)
|
|
890
602
|
const m = /^(.*)[.](one|other)$/.exec(key)
|
|
891
|
-
if (m) {
|
|
892
|
-
if (form === 'few' || form === 'many') {
|
|
893
|
-
const pluralKey = m[1] + '.' + form
|
|
603
|
+
if (m) {if (form === 'few' || form === 'many') {const pluralKey = m[1] + '.' + form
|
|
894
604
|
const template = lookup(ns, pluralKey) ?? lookup('common', pluralKey)
|
|
895
|
-
if (template !== undefined) {
|
|
896
|
-
return fill(template, params)
|
|
605
|
+
if (template !== undefined) {return fill(template, params)
|
|
897
606
|
}
|
|
898
607
|
}
|
|
899
|
-
} else if (form !== 'other' && !/[.](one|other|few|many)$/.test(key)) {
|
|
900
|
-
const pluralKey = key + '.' + form
|
|
608
|
+
} else if (form !== 'other' && !/[.](one|other|few|many)$/.test(key)) {const pluralKey = key + '.' + form
|
|
901
609
|
const template = lookup(ns, pluralKey) ?? lookup('common', pluralKey)
|
|
902
|
-
if (template !== undefined) {
|
|
903
|
-
return fill(template, params)
|
|
610
|
+
if (template !== undefined) {return fill(template, params)
|
|
904
611
|
}
|
|
905
612
|
}
|
|
906
613
|
}
|
|
907
614
|
}
|
|
908
615
|
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 })
|
|
616
|
+
if (typeof res === 'string' && res && res.length < 300) {translationRegistry.set(res.trim(), { ns, key, params, value: res })
|
|
911
617
|
}
|
|
912
618
|
return res
|
|
913
619
|
}
|
|
914
|
-
const syncLang = () => {
|
|
915
|
-
|
|
916
|
-
if (typeof document !== 'undefined' && document.documentElement
|
|
917
|
-
&& runtime.getLocale().active === 'ru') {
|
|
918
|
-
document.documentElement.lang = 'ru-RU'
|
|
620
|
+
const syncLang = () => {try {if (typeof document !== 'undefined' && document.documentElement
|
|
621
|
+
&& runtime.getLocale().active === 'ru') {document.documentElement.lang = 'ru-RU'
|
|
919
622
|
}
|
|
920
623
|
} catch (err) { }
|
|
921
624
|
}
|
|
922
625
|
const native = runtime.getLocale().locales.some((l) => l.id === 'ru')
|
|
923
|
-
if (!native) {
|
|
924
|
-
runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
|
|
626
|
+
if (!native) {runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
|
|
925
627
|
syncLang()
|
|
926
628
|
}
|
|
927
|
-
const syncFlag = () => {
|
|
928
|
-
try {
|
|
929
|
-
const wantRu = runtime.getLocale().active === 'ru'
|
|
629
|
+
const syncFlag = () => {try {const wantRu = runtime.getLocale().active === 'ru'
|
|
930
630
|
const value = scope.getSnapshot().value || {}
|
|
931
|
-
if (!!value.enabled !== wantRu) {
|
|
932
|
-
|
|
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)
|
|
631
|
+
if (!!value.enabled !== wantRu) {const r = scope.set('enabled', wantRu)
|
|
632
|
+
if (r && typeof r.catch === 'function') {r.catch((err) => {console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
|
|
936
633
|
})
|
|
937
634
|
}
|
|
938
635
|
}
|
|
939
|
-
} catch (err) {
|
|
940
|
-
|
|
941
|
-
}
|
|
636
|
+
} catch (err) {}
|
|
942
637
|
}
|
|
943
|
-
ctx.effect(() => {
|
|
944
|
-
try { return runtime.subscribe(syncFlag) }
|
|
638
|
+
ctx.effect(() => {try { return runtime.subscribe(syncFlag) }
|
|
945
639
|
catch (err) { return undefined }
|
|
946
640
|
}, 'dsh-russian-lang: sync-flag')
|
|
947
|
-
const activate = () => {
|
|
948
|
-
try {
|
|
949
|
-
if (runtime.getLocale().active === 'ru') return
|
|
641
|
+
const activate = () => {try {if (runtime.getLocale().active === 'ru') return
|
|
950
642
|
runtime.setLocale('ru')
|
|
951
643
|
} catch (err) { console.warn('dsh-russian-lang: activate failed', err) }
|
|
952
644
|
}
|
|
953
645
|
let booted = false
|
|
954
|
-
const tryBoot = () => {
|
|
955
|
-
|
|
956
|
-
try {
|
|
957
|
-
const value = scope.getSnapshot().value
|
|
646
|
+
const tryBoot = () => {if (booted) return
|
|
647
|
+
try {const value = scope.getSnapshot().value
|
|
958
648
|
if (value && value.enabled === true) { booted = true; activate() }
|
|
959
649
|
} catch (err) { }
|
|
960
650
|
}
|
|
@@ -968,20 +658,17 @@ const SPELL_WAS = 'data-russian-lang-spell-was'
|
|
|
968
658
|
const LANG_WAS = 'data-russian-lang-lang-was'
|
|
969
659
|
const EDITABLE = 'textarea, input[type=text], input[type=search], [contenteditable=""], [contenteditable="true"]'
|
|
970
660
|
const MONO_RE = /mono|consol|courier/i
|
|
971
|
-
const isMonoField = (el) => {
|
|
972
|
-
try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
|
|
661
|
+
const isMonoField = (el) => {try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
|
|
973
662
|
catch (err) { return false }
|
|
974
663
|
}
|
|
975
|
-
const spellOn = (el) => {
|
|
976
|
-
if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
|
|
664
|
+
const spellOn = (el) => {if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
|
|
977
665
|
el.setAttribute(SPELL_ON, '1')
|
|
978
666
|
el.setAttribute(SPELL_WAS, el.getAttribute('spellcheck') ?? '')
|
|
979
667
|
el.setAttribute(LANG_WAS, el.getAttribute('lang') ?? '')
|
|
980
668
|
el.setAttribute('spellcheck', 'true')
|
|
981
669
|
el.setAttribute('lang', 'ru-RU')
|
|
982
670
|
}
|
|
983
|
-
const spellOff = (el) => {
|
|
984
|
-
if (!el.hasAttribute(SPELL_ON)) return
|
|
671
|
+
const spellOff = (el) => {if (!el.hasAttribute(SPELL_ON)) return
|
|
985
672
|
const was = el.getAttribute(SPELL_WAS)
|
|
986
673
|
if (was === '') el.removeAttribute('spellcheck')
|
|
987
674
|
else el.setAttribute('spellcheck', was)
|
|
@@ -993,21 +680,15 @@ el.removeAttribute(SPELL_WAS)
|
|
|
993
680
|
el.removeAttribute(LANG_WAS)
|
|
994
681
|
}
|
|
995
682
|
let spellObserver = null
|
|
996
|
-
const syncSpell = () => {
|
|
997
|
-
try {
|
|
998
|
-
if (typeof document === 'undefined') return
|
|
683
|
+
const syncSpell = () => {try {if (typeof document === 'undefined') return
|
|
999
684
|
const ru = runtime.getLocale().active === 'ru'
|
|
1000
|
-
if (!ru) {
|
|
1001
|
-
if (spellObserver) { spellObserver.disconnect(); spellObserver = null }
|
|
685
|
+
if (!ru) {if (spellObserver) { spellObserver.disconnect(); spellObserver = null }
|
|
1002
686
|
document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff)
|
|
1003
687
|
return
|
|
1004
688
|
}
|
|
1005
689
|
document.querySelectorAll(EDITABLE).forEach(spellOn)
|
|
1006
690
|
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
|
|
691
|
+
spellObserver = new MutationObserver((records) => {for (const record of records) {for (const node of record.addedNodes) {if (node.nodeType !== 1) continue
|
|
1011
692
|
if (node.matches(EDITABLE)) spellOn(node)
|
|
1012
693
|
node.querySelectorAll ? node.querySelectorAll(EDITABLE).forEach(spellOn) : null
|
|
1013
694
|
}
|
|
@@ -1017,9 +698,7 @@ spellObserver.observe(document.body, { childList: true, subtree: true })
|
|
|
1017
698
|
} catch (err) { }
|
|
1018
699
|
}
|
|
1019
700
|
const unsubscribeSpell = runtime.subscribe(syncSpell)
|
|
1020
|
-
ctx.effect(() => {
|
|
1021
|
-
return () => {
|
|
1022
|
-
unsubscribeSpell()
|
|
701
|
+
ctx.effect(() => {return () => {unsubscribeSpell()
|
|
1023
702
|
if (spellObserver) spellObserver.disconnect()
|
|
1024
703
|
try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }
|
|
1025
704
|
}
|
|
@@ -1029,326 +708,85 @@ const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
|
|
|
1029
708
|
const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
1030
709
|
const ZH_EXACT = new Map()
|
|
1031
710
|
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
|
-
'测试通道': 'Тест канала'
|
|
711
|
+
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
712
|
}
|
|
1081
713
|
for (const [k, v] of Object.entries(CORE_ZH_PRESETS)) ZH_EXACT.set(k, v)
|
|
1082
714
|
const zhSortedExact = []
|
|
1083
|
-
const rebuildZhSorted = () => {
|
|
1084
|
-
zhSortedExact.length = 0
|
|
715
|
+
const rebuildZhSorted = () => {zhSortedExact.length = 0
|
|
1085
716
|
for (const [k, v] of ZH_EXACT.entries()) zhSortedExact.push([k, v])
|
|
1086
717
|
zhSortedExact.sort((a, b) => b[0].length - a[0].length)
|
|
1087
718
|
}
|
|
1088
719
|
rebuildZhSorted()
|
|
1089
|
-
const updateZhRu = (entries) => {
|
|
1090
|
-
|
|
1091
|
-
|
|
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)
|
|
720
|
+
const updateZhRu = (entries) => {if (!entries || typeof entries !== 'object') return
|
|
721
|
+
for (const [zhText, ruText] of Object.entries(entries)) {if (typeof zhText !== 'string' || !ZH_CJK.test(zhText) || !ruText) continue
|
|
722
|
+
if (/\{[a-zA-Z_]\w*\}/.test(zhText)) {const parts = zhText.split(/\{[a-zA-Z_]\w*\}/g)
|
|
1095
723
|
if (parts.some((p) => p.length === 0)) continue
|
|
1096
724
|
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)
|
|
725
|
+
} else {ZH_EXACT.set(zhText, ruText)
|
|
1099
726
|
}
|
|
1100
727
|
}
|
|
1101
728
|
ZH_PATTERNS.sort((a, b) => b.re.source.length - a.re.source.length)
|
|
1102
729
|
rebuildZhSorted()
|
|
1103
730
|
}
|
|
1104
731
|
updateZhRu(ZH_RU)
|
|
1105
|
-
const zhTranslateText = (text) => {
|
|
1106
|
-
const overrides = getOverrides()
|
|
732
|
+
const zhTranslateText = (text) => {const overrides = getOverrides()
|
|
1107
733
|
if (overrides[text] !== undefined) return overrides[text]
|
|
1108
734
|
const trimmed = text.trim()
|
|
1109
735
|
if (overrides[trimmed] !== undefined) return text.replace(trimmed, overrides[trimmed])
|
|
1110
736
|
if (!ZH_CJK.test(text)) return null
|
|
1111
737
|
const exact = ZH_EXACT.get(text)
|
|
1112
738
|
if (exact !== undefined) return exact
|
|
1113
|
-
if (trimmed !== text) {
|
|
1114
|
-
const exactTrimmed = ZH_EXACT.get(trimmed)
|
|
739
|
+
if (trimmed !== text) {const exactTrimmed = ZH_EXACT.get(trimmed)
|
|
1115
740
|
if (exactTrimmed !== undefined) return text.replace(trimmed, exactTrimmed)
|
|
1116
741
|
}
|
|
1117
|
-
for (const p of ZH_PATTERNS) {
|
|
1118
|
-
|
|
1119
|
-
if (m && m[0] === trimmed) {
|
|
1120
|
-
let out = p.ruParts[0]
|
|
742
|
+
for (const p of ZH_PATTERNS) {const m = p.re.exec(trimmed)
|
|
743
|
+
if (m && m[0] === trimmed) {let out = p.ruParts[0]
|
|
1121
744
|
for (let i = 1; i < p.ruParts.length; i++) out += m[i] + p.ruParts[i]
|
|
1122
745
|
return text.replace(trimmed, out)
|
|
1123
746
|
}
|
|
1124
747
|
}
|
|
1125
748
|
let replaced = text
|
|
1126
749
|
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)
|
|
750
|
+
for (const [zhPhrase, ruPhrase] of zhSortedExact) {if (zhPhrase.length >= 2 && replaced.includes(zhPhrase)) {replaced = replaced.split(zhPhrase).join(ruPhrase)
|
|
1130
751
|
changed = true
|
|
1131
752
|
}
|
|
1132
753
|
}
|
|
1133
754
|
if (changed) return replaced
|
|
1134
755
|
return null
|
|
1135
756
|
}
|
|
1136
|
-
const DOM_EN_ATTRS = {
|
|
1137
|
-
'
|
|
1138
|
-
|
|
1139
|
-
|
|
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)
|
|
757
|
+
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...': 'Конкретные инструкции, вопросы или требования для данного специалиста…',}
|
|
758
|
+
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...': 'Загрузка списка и статуса...'
|
|
759
|
+
}
|
|
760
|
+
const ZH_WALKER = (root) => {try {const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
|
1308
761
|
const hits = []
|
|
1309
762
|
const curOverrides = getOverrides()
|
|
1310
|
-
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
1311
|
-
const val = node.nodeValue
|
|
763
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {const val = node.nodeValue
|
|
1312
764
|
if (!val) continue
|
|
1313
765
|
const trimmed = val.trim()
|
|
1314
|
-
if (curOverrides[trimmed] !== undefined) {
|
|
1315
|
-
node.nodeValue = val.replace(trimmed, curOverrides[trimmed])
|
|
766
|
+
if (curOverrides[trimmed] !== undefined) {node.nodeValue = val.replace(trimmed, curOverrides[trimmed])
|
|
1316
767
|
continue
|
|
1317
768
|
}
|
|
1318
|
-
if (trimmed.length >= 1 && ZH_CJK.test(val)) {
|
|
1319
|
-
|
|
1320
|
-
|
|
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])
|
|
769
|
+
if (trimmed.length >= 1 && ZH_CJK.test(val)) {hits.push(node)
|
|
770
|
+
} else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {const p = node.parentElement
|
|
771
|
+
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
772
|
}
|
|
1325
773
|
}
|
|
1326
774
|
}
|
|
1327
|
-
for (const node of hits) {
|
|
1328
|
-
const next = zhTranslateText(node.nodeValue)
|
|
775
|
+
for (const node of hits) {const next = zhTranslateText(node.nodeValue)
|
|
1329
776
|
if (next && next !== node.nodeValue) node.nodeValue = next
|
|
1330
777
|
}
|
|
1331
|
-
for (const el of root.querySelectorAll ? root.querySelectorAll('[title],[placeholder],[aria-label]') : []) {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
if (
|
|
1335
|
-
const
|
|
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)
|
|
778
|
+
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)
|
|
779
|
+
if (v) {const tr = v.trim()
|
|
780
|
+
if (DOM_EN_TEXT[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
|
|
781
|
+
} else if (DOM_EN_ATTRS[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
|
|
782
|
+
} else if (ZH_CJK.test(v)) {const next = zhTranslateText(v)
|
|
1342
783
|
if (next) el.setAttribute(attr, next)
|
|
1343
784
|
}
|
|
1344
785
|
}
|
|
1345
786
|
}
|
|
1346
787
|
}
|
|
1347
|
-
if (root.getAttribute) {
|
|
1348
|
-
|
|
1349
|
-
const v = root.getAttribute(attr)
|
|
1350
|
-
if (v) {
|
|
1351
|
-
const tr = v.trim()
|
|
788
|
+
if (root.getAttribute) {for (const attr of ['title', 'placeholder', 'aria-label']) {const v = root.getAttribute(attr)
|
|
789
|
+
if (v) {const tr = v.trim()
|
|
1352
790
|
if (DOM_EN_TEXT[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
|
|
1353
791
|
else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
|
|
1354
792
|
}
|
|
@@ -1359,35 +797,22 @@ else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr
|
|
|
1359
797
|
let zhObserver = null
|
|
1360
798
|
let zhDebounceTimer = null
|
|
1361
799
|
let zhWalking = false
|
|
1362
|
-
const queueZhWalk = (target) => {
|
|
1363
|
-
|
|
1364
|
-
zhDebounceTimer = setTimeout(() => {
|
|
1365
|
-
zhDebounceTimer = null
|
|
800
|
+
const queueZhWalk = (target) => {if (zhDebounceTimer) return
|
|
801
|
+
zhDebounceTimer = setTimeout(() => {zhDebounceTimer = null
|
|
1366
802
|
if (zhWalking) return
|
|
1367
803
|
zhWalking = true
|
|
1368
|
-
try {
|
|
1369
|
-
|
|
1370
|
-
} finally {
|
|
1371
|
-
zhWalking = false
|
|
804
|
+
try {ZH_WALKER(target || document.body)
|
|
805
|
+
} finally {zhWalking = false
|
|
1372
806
|
}
|
|
1373
807
|
}, 120)
|
|
1374
808
|
}
|
|
1375
|
-
const syncZhDom = () => {
|
|
1376
|
-
try {
|
|
1377
|
-
if (typeof document === 'undefined') return
|
|
809
|
+
const syncZhDom = () => {try {if (typeof document === 'undefined') return
|
|
1378
810
|
const ru = runtime.getLocale().active === 'ru'
|
|
1379
|
-
if (!ru) {
|
|
1380
|
-
if (zhObserver) { zhObserver.disconnect(); zhObserver = null }
|
|
811
|
+
if (!ru) {if (zhObserver) { zhObserver.disconnect(); zhObserver = null }
|
|
1381
812
|
if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
|
|
1382
813
|
return
|
|
1383
814
|
}
|
|
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
|
|
815
|
+
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
816
|
queueZhWalk(node)
|
|
1392
817
|
return
|
|
1393
818
|
}
|
|
@@ -1401,9 +826,7 @@ queueZhWalk(document.body)
|
|
|
1401
826
|
} catch (err) { }
|
|
1402
827
|
}
|
|
1403
828
|
const unsubscribeZh = runtime.subscribe(syncZhDom)
|
|
1404
|
-
ctx.effect(() => {
|
|
1405
|
-
return () => {
|
|
1406
|
-
unsubscribeZh()
|
|
829
|
+
ctx.effect(() => {return () => {unsubscribeZh()
|
|
1407
830
|
if (zhObserver) zhObserver.disconnect()
|
|
1408
831
|
if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
|
|
1409
832
|
}
|
|
@@ -1411,28 +834,21 @@ if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
|
|
|
1411
834
|
syncZhDom()
|
|
1412
835
|
const TYPO_YO_PAIRS = [["еще","ещё"],["ее","её"],["черный","чёрный"],["черная","чёрная"],["черные","чёрные"],["зеленый","зелёный"],["желтый","жёлтый"],["легкий","лёгкий"],["тяжелый","тяжёлый"],["надежный","надёжный"],["дешевый","дешёвый"],["идет","идёт"],["дает","даёт"],["ведет","ведёт"],["несет","несёт"],["живет","живёт"],["привел","привёл"],["шел","шёл"],["нее","неё"],["мое","моё"],["серьезно","серьёзно"],["придется","придётся"],["насчет","насчёт"],["твое","твоё"],["свое","своё"],["пойдем","пойдём"],["пришел","пришёл"],["нашел","нашёл"],["идем","идём"],["вперед","вперёд"],["пошел","пошёл"],["ребенка","ребёнка"],["ребенок","ребёнок"],["счет","счёт"],["своем","своём"],["придет","придёт"],["ушел","ушёл"],["ждет","ждёт"],["мертв","мёртв"],["твоем","твоём"],["вернется","вернётся"],["днем","днём"],["найдем","найдём"],["пойдет","пойдёт"],["начнем","начнём"],["вернемся","вернёмся"],["остается","остаётся"],["принес","принёс"],["идешь","идёшь"],["трех","трёх"],["ребенком","ребёнком"],["самолет","самолёт"],["определенно","определённо"],["пойдешь","пойдёшь"],["умрет","умрёт"],["прием","приём"],["прошел","прошёл"],["убьет","убьёт"],["тетя","тётя"],["провел","провёл"],["произойдет","произойдёт"],["семьей","семьёй"],["пойдемте","пойдёмте"],["пройдет","пройдёт"],["возьмем","возьмём"],["вернешься","вернёшься"],["найдешь","найдёшь"],["живешь","живёшь"],["отчет","отчёт"],["займет","займёт"],["ждем","ждём"],["сошел","сошёл"],["вдвоем","вдвоём"],["найдет","найдёт"],["вел","вёл"],["живем","живём"],["зашел","зашёл"],["начнется","начнётся"],["путем","путём"],["подойдет","подойдёт"],["режиссер","режиссёр"],["уйдет","уйдёт"],["придешь","придёшь"],["идемте","идёмте"],["ведешь","ведёшь"],["пес","пёс"],["умрешь","умрёшь"],["ждешь","ждёшь"],["найдете","найдёте"],["найдется","найдётся"],["клево","клёво"],["поймешь","поймёшь"],["разберемся","разберёмся"],["времен","времён"],["введен","введён"],["включен","включён"],["включенных","включённых"],["возьмется","возьмётся"],["завершен","завершён"],["завершенного","завершённого"],["задает","задаёт"],["задается","задаётся"],["заменен","заменён"],["звезд","звёзд"],["звезды","звёзды"],["изменен","изменён"],["истек","истёк"],["незавершенная","незавершённая"],["неподтвержденные","неподтверждённые"],["несохраненные","несохранённые"],["обновлен","обновлён"],["обновленные","обновлённые"],["определен","определён"],["отклонен","отклонён"],["отменен","отменён"],["очередность","очерёдность"],["поврежден","повреждён"],["повторен","повторён"],["подключен","подключён"],["подтвержденные","подтверждённые"],["приемки","приёмки"],["раздает","раздаёт"],["разрешенная","разрешённая"],["разрешенные","разрешённые"],["создаем","создаём"],["сойдется","сойдётся"],["сохранен","сохранён"],["сохраненный","сохранённый"],["счету","счёту"],["тяжелые","тяжёлые"],["темная","тёмная"],["уберет","уберёт"],["удален","удалён"],["учетные","учётные"],["учетных","учётных"],["емкости","ёмкости"],["емкость","ёмкость"]]
|
|
1413
836
|
const typoYo = makeTypoYo(TYPO_YO_PAIRS)
|
|
1414
|
-
const getTypoConf = () => {
|
|
1415
|
-
try {
|
|
1416
|
-
const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
|
|
837
|
+
const getTypoConf = () => {try {const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
|
|
1417
838
|
if (!t || t.enabled === false) return null
|
|
1418
839
|
return { yo: t.yo === true }
|
|
1419
840
|
} catch (err) { return null }
|
|
1420
841
|
}
|
|
1421
|
-
const typoNode = (node, conf) => {
|
|
1422
|
-
const before = node.nodeValue
|
|
842
|
+
const typoNode = (node, conf) => {const before = node.nodeValue
|
|
1423
843
|
if (!before || !before.match || (before.match(/[\u0400-\u04FF]/g) || []).length < 3) return
|
|
1424
844
|
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
845
|
if (node.parentElement && node.parentElement.closest('.katex, [data-latex], math')) return
|
|
1426
846
|
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
|
|
847
|
+
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
848
|
}
|
|
1430
849
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1431
|
-
if (sel && !sel.isCollapsed && sel.rangeCount > 0) {
|
|
1432
|
-
|
|
1433
|
-
const range = sel.getRangeAt(0)
|
|
1434
|
-
if (range.intersectsNode ? range.intersectsNode(node) : (sel.containsNode && sel.containsNode(node, true))) {
|
|
1435
|
-
return
|
|
850
|
+
if (sel && !sel.isCollapsed && sel.rangeCount > 0) {try {const range = sel.getRangeAt(0)
|
|
851
|
+
if (range.intersectsNode ? range.intersectsNode(node) : (sel.containsNode && sel.containsNode(node, true))) {return
|
|
1436
852
|
}
|
|
1437
853
|
} catch (e) { void e; }
|
|
1438
854
|
}
|
|
@@ -1443,44 +859,32 @@ after = typoNbsp(after)
|
|
|
1443
859
|
if (conf.yo) after = typoYo(after)
|
|
1444
860
|
if (after !== before) node.nodeValue = after
|
|
1445
861
|
}
|
|
1446
|
-
const typoWalk = (root, conf) => {
|
|
1447
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
|
862
|
+
const typoWalk = (root, conf) => {const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
|
1448
863
|
for (let node = walker.nextNode(); node; node = walker.nextNode()) typoNode(node, conf)
|
|
1449
864
|
}
|
|
1450
865
|
let typoObserver = null
|
|
1451
866
|
let typoQueued = null
|
|
1452
|
-
const flushTypo = () => {
|
|
1453
|
-
|
|
1454
|
-
try {
|
|
1455
|
-
const conf = getTypoConf()
|
|
867
|
+
const flushTypo = () => {typoQueued = null
|
|
868
|
+
try {const conf = getTypoConf()
|
|
1456
869
|
if (!conf || !typoPending.size) return
|
|
1457
|
-
for (const root of typoPending) {
|
|
1458
|
-
if (root.nodeType === 3) typoNode(root, conf)
|
|
870
|
+
for (const root of typoPending) {if (root.nodeType === 3) typoNode(root, conf)
|
|
1459
871
|
else typoWalk(root, conf)
|
|
1460
872
|
}
|
|
1461
873
|
typoPending.clear()
|
|
1462
874
|
} catch (err) { }
|
|
1463
875
|
}
|
|
1464
876
|
const typoPending = new Set()
|
|
1465
|
-
const queueTypo = (roots) => {
|
|
1466
|
-
for (const r of roots) typoPending.add(r)
|
|
877
|
+
const queueTypo = (roots) => {for (const r of roots) typoPending.add(r)
|
|
1467
878
|
if (!typoQueued) typoQueued = requestAnimationFrame(flushTypo)
|
|
1468
879
|
}
|
|
1469
|
-
const syncTypo = () => {
|
|
1470
|
-
|
|
1471
|
-
if (typeof document === 'undefined') return
|
|
1472
|
-
if (runtime.getLocale().active !== 'ru' || !getTypoConf()) {
|
|
1473
|
-
if (typoObserver) { typoObserver.disconnect(); typoObserver = null }
|
|
880
|
+
const syncTypo = () => {try {if (typeof document === 'undefined') return
|
|
881
|
+
if (runtime.getLocale().active !== 'ru' || !getTypoConf()) {if (typoObserver) { typoObserver.disconnect(); typoObserver = null }
|
|
1474
882
|
return
|
|
1475
883
|
}
|
|
1476
884
|
if (typoObserver) return
|
|
1477
885
|
typoWalk(document.body, getTypoConf())
|
|
1478
|
-
typoObserver = new MutationObserver((records) => {
|
|
1479
|
-
const
|
|
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)
|
|
886
|
+
typoObserver = new MutationObserver((records) => {const roots = []
|
|
887
|
+
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
888
|
}
|
|
1485
889
|
}
|
|
1486
890
|
}
|
|
@@ -1490,9 +894,7 @@ typoObserver.observe(document.body, { childList: true, subtree: true })
|
|
|
1490
894
|
} catch (err) { }
|
|
1491
895
|
}
|
|
1492
896
|
const unsubscribeTypo = runtime.subscribe(syncTypo)
|
|
1493
|
-
ctx.effect(() => {
|
|
1494
|
-
return () => {
|
|
1495
|
-
unsubscribeTypo()
|
|
897
|
+
ctx.effect(() => {return () => {unsubscribeTypo()
|
|
1496
898
|
if (typoObserver) typoObserver.disconnect()
|
|
1497
899
|
}
|
|
1498
900
|
}, 'dsh-russian-lang: typography')
|
|
@@ -1500,51 +902,37 @@ syncTypo()
|
|
|
1500
902
|
const layout = makeLayout(new Set(["не","что","ты","это","на","он","мы","как","вы","да","мне","нет","меня","так","но","его","все","она","тебя","если","за","бы","тебе","они","чтобы","же","есть","просто","из","для"]), new Set())
|
|
1501
903
|
const layoutFixCandidate = layout.candidate
|
|
1502
904
|
const learnWords = layout.learnWords
|
|
1503
|
-
function setNativeInputValue(el, value, cursorStart, cursorEnd) {
|
|
1504
|
-
if (!el) return
|
|
905
|
+
function setNativeInputValue(el, value, cursorStart, cursorEnd) {if (!el) return
|
|
1505
906
|
const oldVal = el.value || ''
|
|
1506
907
|
if (oldVal === value) return
|
|
1507
908
|
const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype
|
|
1508
909
|
const desc = Object.getOwnPropertyDescriptor(proto, 'value')
|
|
1509
|
-
if (desc && desc.set) {
|
|
1510
|
-
|
|
1511
|
-
} else {
|
|
1512
|
-
el.value = value
|
|
910
|
+
if (desc && desc.set) {desc.set.call(el, value)
|
|
911
|
+
} else {el.value = value
|
|
1513
912
|
}
|
|
1514
|
-
if (el._valueTracker) {
|
|
1515
|
-
el._valueTracker.setValue(value === '' ? '__force__' : '')
|
|
913
|
+
if (el._valueTracker) {el._valueTracker.setValue(value === '' ? '__force__' : '')
|
|
1516
914
|
}
|
|
1517
|
-
try {
|
|
1518
|
-
|
|
1519
|
-
if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {
|
|
1520
|
-
el[propsKey].onChange({ target: el, currentTarget: el })
|
|
915
|
+
try {const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$') || k.startsWith('__reactEventHandlers$'))
|
|
916
|
+
if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {el[propsKey].onChange({ target: el, currentTarget: el })
|
|
1521
917
|
}
|
|
1522
918
|
} catch (e) { void e; }
|
|
1523
|
-
try {
|
|
1524
|
-
el.dispatchEvent(new
|
|
1525
|
-
} catch (e) {
|
|
1526
|
-
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
919
|
+
try {el.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
|
|
920
|
+
} catch (e) {el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1527
921
|
}
|
|
1528
922
|
el.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1529
|
-
if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {
|
|
1530
|
-
const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
|
|
923
|
+
if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
|
|
1531
924
|
try { el.setSelectionRange(cursorStart, end) } catch (e) { void e; }
|
|
1532
925
|
}
|
|
1533
926
|
}
|
|
1534
927
|
let layoutHintEl = null
|
|
1535
|
-
const layoutCurrentInput = (ev) => {
|
|
1536
|
-
if (
|
|
1537
|
-
const t = ev.target
|
|
1538
|
-
if (t.tagName === 'TEXTAREA' || (t.tagName === 'INPUT' && (t.type === 'text' || !t.type))) {
|
|
1539
|
-
return t
|
|
928
|
+
const layoutCurrentInput = (ev) => {if (ev && ev.target) {const t = ev.target
|
|
929
|
+
if (t.tagName === 'TEXTAREA' || (t.tagName === 'INPUT' && (t.type === 'text' || !t.type))) {return t
|
|
1540
930
|
}
|
|
1541
931
|
const c = t.closest ? t.closest('[data-composer-input], [contenteditable="true"], [role="textbox"]') : null
|
|
1542
932
|
if (c) return c
|
|
1543
933
|
}
|
|
1544
934
|
const el = document.activeElement
|
|
1545
|
-
if (el) {
|
|
1546
|
-
if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && (el.type === 'text' || !el.type))) {
|
|
1547
|
-
return el
|
|
935
|
+
if (el) {if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && (el.type === 'text' || !el.type))) {return el
|
|
1548
936
|
}
|
|
1549
937
|
const c = el.closest ? el.closest('[data-composer-input], [contenteditable="true"], [role="textbox"]') : null
|
|
1550
938
|
if (c) return c
|
|
@@ -1553,43 +941,35 @@ const focused = document.querySelector('[data-composer-input], [contenteditable=
|
|
|
1553
941
|
if (focused) return focused
|
|
1554
942
|
return null
|
|
1555
943
|
}
|
|
1556
|
-
function getCaretCharacterOffset(root) {
|
|
1557
|
-
if (!root) return -1
|
|
944
|
+
function getCaretCharacterOffset(root) {if (!root) return -1
|
|
1558
945
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1559
946
|
if (!sel || !sel.rangeCount) return -1
|
|
1560
|
-
try {
|
|
1561
|
-
const range = sel.getRangeAt(0)
|
|
947
|
+
try {const range = sel.getRangeAt(0)
|
|
1562
948
|
if (!root.contains(range.startContainer)) return -1
|
|
1563
949
|
const preCaretRange = range.cloneRange()
|
|
1564
950
|
preCaretRange.selectNodeContents(root)
|
|
1565
951
|
preCaretRange.setEnd(range.startContainer, range.startOffset)
|
|
1566
952
|
return preCaretRange.toString().length
|
|
1567
|
-
} catch (e) {
|
|
1568
|
-
return -1
|
|
953
|
+
} catch (e) {return -1
|
|
1569
954
|
}
|
|
1570
955
|
}
|
|
1571
|
-
function setCaretCharacterOffset(root, offset) {
|
|
1572
|
-
if (!root || offset < 0) return
|
|
956
|
+
function setCaretCharacterOffset(root, offset) {if (!root || offset < 0) return
|
|
1573
957
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1574
958
|
if (!sel) return
|
|
1575
|
-
try {
|
|
1576
|
-
let current = 0
|
|
959
|
+
try {let current = 0
|
|
1577
960
|
let targetNode = null
|
|
1578
961
|
let targetOffset = 0
|
|
1579
962
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null)
|
|
1580
963
|
let node = walker.nextNode()
|
|
1581
|
-
while (node) {
|
|
1582
|
-
|
|
1583
|
-
if (current + len >= offset) {
|
|
1584
|
-
targetNode = node
|
|
964
|
+
while (node) {const len = node.nodeValue.length
|
|
965
|
+
if (current + len >= offset) {targetNode = node
|
|
1585
966
|
targetOffset = Math.max(0, offset - current)
|
|
1586
967
|
break
|
|
1587
968
|
}
|
|
1588
969
|
current += len
|
|
1589
970
|
node = walker.nextNode()
|
|
1590
971
|
}
|
|
1591
|
-
if (targetNode) {
|
|
1592
|
-
const range = document.createRange()
|
|
972
|
+
if (targetNode) {const range = document.createRange()
|
|
1593
973
|
range.setStart(targetNode, targetOffset)
|
|
1594
974
|
range.collapse(true)
|
|
1595
975
|
sel.removeAllRanges()
|
|
@@ -1597,12 +977,10 @@ sel.addRange(range)
|
|
|
1597
977
|
}
|
|
1598
978
|
} catch (e) { void e; }
|
|
1599
979
|
}
|
|
1600
|
-
const isCaretInCode = (el, value, caretOffset) => {
|
|
1601
|
-
if (!el) return false
|
|
980
|
+
const isCaretInCode = (el, value, caretOffset) => {if (!el) return false
|
|
1602
981
|
if (caretOffset == null || caretOffset < 0) caretOffset = (value || '').length
|
|
1603
982
|
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
|
|
983
|
+
if (sel && sel.anchorNode && sel.anchorNode.parentElement) {if (sel.anchorNode.parentElement.closest('code, pre, .katex, [data-latex], math')) return true
|
|
1606
984
|
}
|
|
1607
985
|
const textBefore = (value || '').slice(0, caretOffset)
|
|
1608
986
|
const triple = textBefore.match(/```/g)
|
|
@@ -1612,28 +990,22 @@ const single = lastLine.match(/`/g)
|
|
|
1612
990
|
if (single && single.length % 2 === 1) return true
|
|
1613
991
|
return false
|
|
1614
992
|
}
|
|
1615
|
-
const getComposerText = (el) => {
|
|
1616
|
-
if (
|
|
1617
|
-
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
1618
|
-
return el.value || ''
|
|
993
|
+
const getComposerText = (el) => {if (!el) return ''
|
|
994
|
+
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {return el.value || ''
|
|
1619
995
|
}
|
|
1620
996
|
const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
|
|
1621
997
|
const raw = host.innerText !== undefined ? host.innerText : (host.textContent || '')
|
|
1622
998
|
return raw.replace(/\r/g, '').replace(/[\u200B\uFEFF]/g, '').replace(/\n+$/, '')
|
|
1623
999
|
}
|
|
1624
|
-
const setComposerText = (el, value, cursorStart, cursorEnd) => {
|
|
1625
|
-
if (
|
|
1626
|
-
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
1627
|
-
setNativeInputValue(el, value, cursorStart, cursorEnd)
|
|
1000
|
+
const setComposerText = (el, value, cursorStart, cursorEnd) => {if (!el) return
|
|
1001
|
+
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {setNativeInputValue(el, value, cursorStart, cursorEnd)
|
|
1628
1002
|
return
|
|
1629
1003
|
}
|
|
1630
1004
|
const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
|
|
1631
1005
|
const initialOffset = typeof cursorStart === 'number' ? cursorStart : getCaretCharacterOffset(host)
|
|
1632
|
-
try {
|
|
1633
|
-
host.focus()
|
|
1006
|
+
try {host.focus()
|
|
1634
1007
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1635
|
-
if (sel) {
|
|
1636
|
-
const range = document.createRange()
|
|
1008
|
+
if (sel) {const range = document.createRange()
|
|
1637
1009
|
range.selectNodeContents(host)
|
|
1638
1010
|
sel.removeAllRanges()
|
|
1639
1011
|
sel.addRange(range)
|
|
@@ -1643,25 +1015,18 @@ const targetOffset = typeof cursorStart === 'number' ? cursorStart : (initialOff
|
|
|
1643
1015
|
setCaretCharacterOffset(host, targetOffset)
|
|
1644
1016
|
} catch (e) { void e; }
|
|
1645
1017
|
}
|
|
1646
|
-
const layoutDismiss = () => {
|
|
1647
|
-
if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
|
|
1018
|
+
const layoutDismiss = () => {if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
|
|
1648
1019
|
}
|
|
1649
|
-
const layoutShowHint = (inputEl, converted, direction) => {
|
|
1650
|
-
layoutDismiss()
|
|
1020
|
+
const layoutShowHint = (inputEl, converted, direction) => {layoutDismiss()
|
|
1651
1021
|
layoutHintEl = document.createElement('div')
|
|
1652
1022
|
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'
|
|
1023
|
+
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
1024
|
})
|
|
1659
1025
|
const label = direction === 'cyr2lat' ? 'Команда, не та раскладка' : 'Не та раскладка'
|
|
1660
1026
|
layoutHintEl.textContent = label + ': ' + converted
|
|
1661
|
-
layoutHintEl.addEventListener('mousedown', (ev) => {
|
|
1662
|
-
ev.preventDefault()
|
|
1027
|
+
layoutHintEl.addEventListener('mousedown', (ev) => {ev.preventDefault()
|
|
1663
1028
|
setComposerText(inputEl, converted)
|
|
1664
|
-
learnWords(converted)
|
|
1029
|
+
learnWords(converted)
|
|
1665
1030
|
layoutDismiss()
|
|
1666
1031
|
})
|
|
1667
1032
|
document.body.appendChild(layoutHintEl)
|
|
@@ -1670,30 +1035,23 @@ layoutHintEl.style.left = (r.left + 8) + 'px'
|
|
|
1670
1035
|
layoutHintEl.style.bottom = (window.innerHeight - r.top + 6) + 'px'
|
|
1671
1036
|
}
|
|
1672
1037
|
let layoutBadgeEl = null
|
|
1673
|
-
const layoutBadge = (el) => {
|
|
1674
|
-
const
|
|
1038
|
+
const layoutBadge = (el) => {const value = getComposerText(el)
|
|
1039
|
+
const detected = typeof detectInputLayout === 'function' ? detectInputLayout(value) : null
|
|
1675
1040
|
const last = value.trim().slice(-1)
|
|
1676
1041
|
const isCyr = /[\u0430-\u044f\u0451]/.test(last)
|
|
1677
1042
|
const isLat = /[a-z]/i.test(last)
|
|
1678
|
-
const label = isCyr ? 'RU' : (isLat ? 'EN' : '')
|
|
1043
|
+
const label = detected || (isCyr ? 'RU' : (isLat ? 'EN' : ''))
|
|
1679
1044
|
if (!label) { layoutBadgeHide(); return }
|
|
1680
|
-
if (!layoutBadgeEl) {
|
|
1681
|
-
layoutBadgeEl = document.createElement('button')
|
|
1045
|
+
if (!layoutBadgeEl) {layoutBadgeEl = document.createElement('button')
|
|
1682
1046
|
layoutBadgeEl.type = 'button'
|
|
1683
1047
|
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'
|
|
1048
|
+
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
1049
|
})
|
|
1690
1050
|
layoutBadgeEl.title = 'Раскладка — клик: конвертировать (Alt+L)'
|
|
1691
|
-
layoutBadgeEl.addEventListener('mousedown', (ev) => {
|
|
1692
|
-
ev.preventDefault()
|
|
1051
|
+
layoutBadgeEl.addEventListener('mousedown', (ev) => {ev.preventDefault()
|
|
1693
1052
|
const v = getComposerText(el)
|
|
1694
1053
|
const c = layoutFixCandidate(v, 'lat2cyr') || layoutFixCandidate(v, 'cyr2lat')
|
|
1695
|
-
if (c) {
|
|
1696
|
-
setComposerText(el, c.converted)
|
|
1054
|
+
if (c) {setComposerText(el, c.converted)
|
|
1697
1055
|
learnWords(c.converted)
|
|
1698
1056
|
}
|
|
1699
1057
|
})
|
|
@@ -1704,138 +1062,108 @@ const r = el.getBoundingClientRect()
|
|
|
1704
1062
|
layoutBadgeEl.style.left = (r.right - 36) + 'px'
|
|
1705
1063
|
layoutBadgeEl.style.top = (r.top - 22) + 'px'
|
|
1706
1064
|
}
|
|
1707
|
-
const layoutBadgeHide = () => {
|
|
1708
|
-
if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
|
|
1065
|
+
const layoutBadgeHide = () => {if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
|
|
1709
1066
|
}
|
|
1710
1067
|
let isFormatting = false
|
|
1711
|
-
const layoutOnInput = (ev) => {
|
|
1712
|
-
|
|
1713
|
-
try {
|
|
1714
|
-
const el = layoutCurrentInput(ev)
|
|
1068
|
+
const layoutOnInput = (ev) => {if (isFormatting) return
|
|
1069
|
+
try {const el = layoutCurrentInput(ev)
|
|
1715
1070
|
if (!el) { layoutDismiss(); layoutBadgeHide(); return }
|
|
1716
|
-
layoutBadge(el)
|
|
1071
|
+
layoutBadge(el)
|
|
1717
1072
|
const value = getComposerText(el)
|
|
1718
1073
|
if (value.trim().length < 4) { layoutDismiss(); return }
|
|
1719
1074
|
const latCount = (value.match(/[a-z]/g) || []).length
|
|
1720
1075
|
const cyrCount = (value.match(/[\u0430-\u044f\u0451]/g) || []).length
|
|
1721
|
-
if (latCount > cyrCount && cyrCount === 0) {
|
|
1722
|
-
const c = layoutFixCandidate(value, 'lat2cyr')
|
|
1076
|
+
if (latCount > cyrCount && cyrCount === 0) {const c = layoutFixCandidate(value, 'lat2cyr')
|
|
1723
1077
|
if (c) { layoutShowHint(el, c.converted, 'ru'); return }
|
|
1724
1078
|
}
|
|
1725
|
-
if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {
|
|
1726
|
-
const c = layoutFixCandidate(value, 'cyr2lat')
|
|
1079
|
+
if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {const c = layoutFixCandidate(value, 'cyr2lat')
|
|
1727
1080
|
if (c) { layoutShowHint(el, c.converted, 'cmd'); return }
|
|
1728
1081
|
}
|
|
1729
1082
|
layoutDismiss()
|
|
1730
1083
|
} catch (err) { }
|
|
1731
1084
|
}
|
|
1732
1085
|
const unsubscribeLayout = runtime.subscribe(layoutOnInput)
|
|
1733
|
-
const layoutOnKeydown = (ev) => {
|
|
1734
|
-
const el = layoutCurrentInput(ev)
|
|
1086
|
+
const layoutOnKeydown = (ev) => {const el = layoutCurrentInput(ev)
|
|
1735
1087
|
if (!el) return
|
|
1736
1088
|
const value = getComposerText(el)
|
|
1737
1089
|
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()
|
|
1090
|
+
if (ev.altKey && !ev.ctrlKey && !ev.metaKey && isL) {const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
1740
1091
|
const selectedText = (sel && !sel.isCollapsed) ? sel.toString() : ''
|
|
1741
|
-
if (selectedText) {
|
|
1742
|
-
|
|
1743
|
-
if (c) {
|
|
1744
|
-
ev.preventDefault()
|
|
1092
|
+
if (selectedText) {const c = layoutFixCandidate(selectedText, 'lat2cyr') || layoutFixCandidate(selectedText, 'cyr2lat')
|
|
1093
|
+
if (c) {ev.preventDefault()
|
|
1745
1094
|
isFormatting = true
|
|
1746
|
-
try {
|
|
1747
|
-
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
1748
|
-
const sStart = el.selectionStart || 0
|
|
1095
|
+
try {if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {const sStart = el.selectionStart || 0
|
|
1749
1096
|
const sEnd = el.selectionEnd || sStart
|
|
1750
1097
|
const nextVal = value.slice(0, sStart) + c.converted + value.slice(sEnd)
|
|
1751
1098
|
setNativeInputValue(el, nextVal, sStart, sStart + c.converted.length)
|
|
1752
|
-
} else {
|
|
1753
|
-
document.execCommand('insertText', false, c.converted)
|
|
1099
|
+
} else {document.execCommand('insertText', false, c.converted)
|
|
1754
1100
|
}
|
|
1755
1101
|
learnWords(c.converted)
|
|
1756
|
-
} finally {
|
|
1757
|
-
isFormatting = false
|
|
1102
|
+
} finally {isFormatting = false
|
|
1758
1103
|
}
|
|
1759
1104
|
return
|
|
1760
1105
|
}
|
|
1761
1106
|
}
|
|
1762
1107
|
const c = layoutFixCandidate(value, 'lat2cyr') || layoutFixCandidate(value, 'cyr2lat')
|
|
1763
|
-
if (c) {
|
|
1764
|
-
ev.preventDefault()
|
|
1108
|
+
if (c) {ev.preventDefault()
|
|
1765
1109
|
isFormatting = true
|
|
1766
|
-
try {
|
|
1767
|
-
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
1768
|
-
const sStart = el.selectionStart || 0
|
|
1110
|
+
try {if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {const sStart = el.selectionStart || 0
|
|
1769
1111
|
const sEnd = el.selectionEnd || sStart
|
|
1770
1112
|
setNativeInputValue(el, c.converted, sStart, sEnd)
|
|
1771
|
-
} else {
|
|
1772
|
-
const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
|
|
1113
|
+
} else {const host = (el.closest && el.closest('[data-composer-input], [contenteditable="true"]')) || el
|
|
1773
1114
|
const offset = getCaretCharacterOffset(host)
|
|
1774
1115
|
setComposerText(el, c.converted, offset, offset)
|
|
1775
1116
|
}
|
|
1776
|
-
learnWords(c.converted)
|
|
1777
|
-
} finally {
|
|
1778
|
-
isFormatting = false
|
|
1117
|
+
learnWords(c.converted)
|
|
1118
|
+
} finally {isFormatting = false
|
|
1779
1119
|
}
|
|
1780
1120
|
}
|
|
1781
1121
|
return
|
|
1782
1122
|
}
|
|
1783
|
-
if (ev.key === ' ' || ev.key === 'Enter') {
|
|
1784
|
-
if (
|
|
1785
|
-
const expanded = expandSlashAlias(value)
|
|
1786
|
-
if (expanded !== value) {
|
|
1787
|
-
if (ev.key === ' ') {
|
|
1788
|
-
ev.preventDefault()
|
|
1123
|
+
if (ev.key === ' ' || ev.key === 'Enter') {if (value && value.startsWith('/') && typeof expandSlashAlias === 'function') {const expanded = expandSlashAlias(value)
|
|
1124
|
+
if (expanded !== value) {if (ev.key === ' ') {ev.preventDefault()
|
|
1789
1125
|
isFormatting = true
|
|
1790
|
-
try {
|
|
1791
|
-
const nextVal = expanded + ' '
|
|
1126
|
+
try {const nextVal = expanded + ' '
|
|
1792
1127
|
setComposerText(el, nextVal, nextVal.length, nextVal.length)
|
|
1793
|
-
} finally {
|
|
1794
|
-
isFormatting = false
|
|
1128
|
+
} finally {isFormatting = false
|
|
1795
1129
|
}
|
|
1796
1130
|
return
|
|
1797
|
-
} else if (ev.key === 'Enter') {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
} finally {
|
|
1802
|
-
isFormatting = false
|
|
1131
|
+
} else if (ev.key === 'Enter') {isFormatting = true
|
|
1132
|
+
try {setComposerText(el, expanded, expanded.length, expanded.length)
|
|
1133
|
+
} finally {isFormatting = false
|
|
1134
|
+
}
|
|
1803
1135
|
}
|
|
1804
1136
|
}
|
|
1805
1137
|
}
|
|
1806
1138
|
}
|
|
1139
|
+
if (ev.key === 'Enter' && !ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1140
|
+
const typoLive = snapVal.typography ? snapVal.typography.liveInput === true : false
|
|
1141
|
+
if (typoLive && typeof formatInputLive === 'function' && (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT')) {const formatted = formatInputLive(value)
|
|
1142
|
+
if (formatted && formatted !== value) {setNativeInputValue(el, formatted, formatted.length, formatted.length)
|
|
1807
1143
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
|
|
1813
|
-
if (isTextarea) {
|
|
1814
|
-
|
|
1815
|
-
if (
|
|
1816
|
-
const textBefore = caretPos >= 0 ? value.slice(0, caretPos) : value
|
|
1817
|
-
if (ev.key === '-' && textBefore.endsWith('-')) {
|
|
1818
|
-
ev.preventDefault()
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
try {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1147
|
+
const typoLive = snapVal.typography ? snapVal.typography.liveInput === true : false
|
|
1148
|
+
if (typoLive && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
|
|
1149
|
+
if (isTextarea) {const caretPos = el.selectionStart || 0
|
|
1150
|
+
if (!isCaretInCode(el, value, caretPos)) {const textBefore = caretPos >= 0 ? value.slice(0, caretPos) : value
|
|
1151
|
+
if (ev.key === '-' && textBefore.endsWith('-')) {ev.preventDefault()
|
|
1819
1152
|
isFormatting = true
|
|
1820
|
-
try {
|
|
1821
|
-
const nextVal = value.slice(0, caretPos - 1) + '—' + value.slice(el.selectionEnd || caretPos)
|
|
1153
|
+
try {const nextVal = value.slice(0, caretPos - 1) + '—' + value.slice(el.selectionEnd || caretPos)
|
|
1822
1154
|
setNativeInputValue(el, nextVal, caretPos, caretPos)
|
|
1823
|
-
} finally {
|
|
1824
|
-
isFormatting = false
|
|
1155
|
+
} finally {isFormatting = false
|
|
1825
1156
|
}
|
|
1826
1157
|
return
|
|
1827
1158
|
}
|
|
1828
|
-
if (ev.key === '"') {
|
|
1829
|
-
ev.preventDefault()
|
|
1159
|
+
if (ev.key === '"') {ev.preventDefault()
|
|
1830
1160
|
isFormatting = true
|
|
1831
|
-
try {
|
|
1832
|
-
const prevChar = textBefore.slice(-1)
|
|
1161
|
+
try {const prevChar = textBefore.slice(-1)
|
|
1833
1162
|
const isOpening = !textBefore || /[\s([{-]/.test(prevChar)
|
|
1834
1163
|
const quoteChar = isOpening ? '«' : '»'
|
|
1835
1164
|
const nextVal = value.slice(0, caretPos) + quoteChar + value.slice(el.selectionEnd || caretPos)
|
|
1836
1165
|
setNativeInputValue(el, nextVal, caretPos + 1, caretPos + 1)
|
|
1837
|
-
} finally {
|
|
1838
|
-
isFormatting = false
|
|
1166
|
+
} finally {isFormatting = false
|
|
1839
1167
|
}
|
|
1840
1168
|
return
|
|
1841
1169
|
}
|
|
@@ -1844,13 +1172,8 @@ return
|
|
|
1844
1172
|
}
|
|
1845
1173
|
} catch (e) { void e; }
|
|
1846
1174
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
let pillEl = null
|
|
1850
|
-
const getHoverBox = () => {
|
|
1851
|
-
if (typeof document === 'undefined') return null
|
|
1852
|
-
if (!hoverBoxEl) {
|
|
1853
|
-
hoverBoxEl = document.createElement('div')
|
|
1175
|
+
const getHoverBox = () => {if (typeof document === 'undefined') return null
|
|
1176
|
+
if (!hoverBoxEl) {hoverBoxEl = document.createElement('div')
|
|
1854
1177
|
hoverBoxEl.id = 'dsh-ru-inspector-hover'
|
|
1855
1178
|
hoverBoxEl.className = 'rl-inspector-hover'
|
|
1856
1179
|
hoverBoxEl.innerHTML = '<span class="rl-inspector-badge"></span>'
|
|
@@ -1858,16 +1181,13 @@ document.body.appendChild(hoverBoxEl)
|
|
|
1858
1181
|
}
|
|
1859
1182
|
return hoverBoxEl
|
|
1860
1183
|
}
|
|
1861
|
-
const updateInspectorPill = () => {
|
|
1862
|
-
if (
|
|
1863
|
-
if (!pillEl) {
|
|
1864
|
-
pillEl = document.createElement('div')
|
|
1184
|
+
const updateInspectorPill = () => {if (typeof document === 'undefined') return
|
|
1185
|
+
if (!pillEl) {pillEl = document.createElement('div')
|
|
1865
1186
|
pillEl.id = 'dsh-ru-inspector-pill'
|
|
1866
1187
|
pillEl.className = 'rl-inspector-pill'
|
|
1867
1188
|
pillEl.innerHTML = '<span>🔍 Инспектор</span>' +
|
|
1868
1189
|
'<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
|
|
1190
|
+
pillEl.querySelector('.rl-pill-exit').onclick = () => {isInspectorActive = false
|
|
1871
1191
|
updateInspectorPill()
|
|
1872
1192
|
if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1873
1193
|
}
|
|
@@ -1876,8 +1196,7 @@ document.body.appendChild(pillEl)
|
|
|
1876
1196
|
pillEl.style.display = isInspectorActive ? 'flex' : 'none'
|
|
1877
1197
|
if (isInspectorActive) pillEl.classList.add('rl-inspector-pill-active')
|
|
1878
1198
|
}
|
|
1879
|
-
const openInspectorModal = (meta, rawText, targetElement) => {
|
|
1880
|
-
if (typeof document === 'undefined') return
|
|
1199
|
+
const openInspectorModal = (meta, rawText, targetElement) => {if (typeof document === 'undefined') return
|
|
1881
1200
|
if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1882
1201
|
const old = document.getElementById('dsh-ru-inspector-modal')
|
|
1883
1202
|
if (old) old.remove()
|
|
@@ -1888,8 +1207,7 @@ const k = meta ? (meta.key || (meta.ns ? meta.ns + '.' + meta.key : '')) : ''
|
|
|
1888
1207
|
const ns = meta && meta.ns ? meta.ns : ''
|
|
1889
1208
|
const origText = meta ? (meta.zh || meta.en || '') : ''
|
|
1890
1209
|
let srcLabel = 'Текст'
|
|
1891
|
-
if (meta) {
|
|
1892
|
-
if (meta.source === 'override') srcLabel = '✍️ Оверрайд'
|
|
1210
|
+
if (meta) {if (meta.source === 'override') srcLabel = '✍️ Оверрайд'
|
|
1893
1211
|
else if (meta.source === 'dictionary') srcLabel = '🟢 Словарь' + (ns ? ' ' + ns : '')
|
|
1894
1212
|
else if (meta.source === 'dom_zh') srcLabel = '🟡 DOM ZH'
|
|
1895
1213
|
else if (meta.source === 'dom_zh_original') srcLabel = '🟡 DOM ZH (Оригинал)'
|
|
@@ -1903,7 +1221,7 @@ modal.innerHTML = '<div class="rl-modal-box">' +
|
|
|
1903
1221
|
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
|
|
1904
1222
|
'<div style="font-weight:600;font-size:14px;display:flex;align-items:center;gap:6px;">' +
|
|
1905
1223
|
'<span>🔍 Инспектор перевода</span>' +
|
|
1906
|
-
'<span class="rl-badge rl-badge-dim" style="font-size:11px;"
|
|
1224
|
+
'<span class="rl-badge rl-badge-dim" style="font-size:11px;"></span>' +
|
|
1907
1225
|
'</div>' +
|
|
1908
1226
|
'<button type="button" class="rl-btn-close" style="background:none;border:none;color:var(--dsw-alias-label-secondary);font-size:18px;cursor:pointer;">×</button>' +
|
|
1909
1227
|
'</div>' +
|
|
@@ -1911,11 +1229,11 @@ modal.innerHTML = '<div class="rl-modal-box">' +
|
|
|
1911
1229
|
'<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;">' +
|
|
1912
1230
|
'<span style="color:var(--dsw-alias-label-secondary);">Ключ:</span>' +
|
|
1913
1231
|
'<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;"
|
|
1232
|
+
'<code class="rl-modal-key" style="color:var(--dsw-alias-state-brand-primary);font-weight:600;font-size:12px;"></code>' +
|
|
1915
1233
|
'<button type="button" class="rl-btn rl-copy-key-btn" style="padding:1px 6px;height:20px;font-size:10px;" title="Скопировать ключ">📋</button>' +
|
|
1916
1234
|
'</div>' +
|
|
1917
1235
|
'</div>' +
|
|
1918
|
-
|
|
1236
|
+
'<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
1237
|
'<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
1238
|
'</div>' +
|
|
1921
1239
|
'<div style="display:flex;flex-direction:column;gap:6px;">' +
|
|
@@ -1931,6 +1249,13 @@ modal.innerHTML = '<div class="rl-modal-box">' +
|
|
|
1931
1249
|
'</div>' +
|
|
1932
1250
|
'</div>' +
|
|
1933
1251
|
'</div>'
|
|
1252
|
+
modal.querySelector('.rl-badge-dim').textContent = srcLabel
|
|
1253
|
+
modal.querySelector('.rl-modal-key').textContent = k || '(текстовый узел)'
|
|
1254
|
+
if (origText) {const row = modal.querySelector('.rl-modal-orig-row')
|
|
1255
|
+
if (row) row.style.display = 'block'
|
|
1256
|
+
const el = modal.querySelector('.rl-orig-text')
|
|
1257
|
+
if (el) el.textContent = origText
|
|
1258
|
+
}
|
|
1934
1259
|
modal.querySelector('.rl-raw-text').textContent = rawText
|
|
1935
1260
|
const textarea = modal.querySelector('.rl-edit-val')
|
|
1936
1261
|
textarea.value = currentTranslation
|
|
@@ -1938,18 +1263,14 @@ modal.querySelector('.rl-btn-close').onclick = () => modal.remove()
|
|
|
1938
1263
|
modal.querySelector('.rl-cancel-btn').onclick = () => modal.remove()
|
|
1939
1264
|
modal.onclick = (e) => { if (e.target === modal) modal.remove() }
|
|
1940
1265
|
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; }
|
|
1266
|
+
if (copyKeyBtn) {copyKeyBtn.onclick = () => {try { navigator.clipboard.writeText(k || rawText) } catch (err) { void err; }
|
|
1944
1267
|
copyKeyBtn.textContent = '✓'
|
|
1945
1268
|
setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
|
|
1946
1269
|
}
|
|
1947
1270
|
}
|
|
1948
1271
|
const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
|
|
1949
|
-
if (copyReportBtn) {
|
|
1950
|
-
|
|
1951
|
-
const snippet = typeof generateBugReportSnippet === 'function'
|
|
1952
|
-
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.7' })
|
|
1272
|
+
if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
|
|
1273
|
+
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.9' })
|
|
1953
1274
|
: ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
|
|
1954
1275
|
try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
|
|
1955
1276
|
copyReportBtn.textContent = '✓ Скопировано'
|
|
@@ -1957,9 +1278,7 @@ setTimeout(() => { copyReportBtn.textContent = '📋 Скопировать ре
|
|
|
1957
1278
|
}
|
|
1958
1279
|
}
|
|
1959
1280
|
const resetBtn = modal.querySelector('.rl-reset-override-btn')
|
|
1960
|
-
if (resetBtn) {
|
|
1961
|
-
resetBtn.onclick = () => {
|
|
1962
|
-
const targetKey = k || rawText
|
|
1281
|
+
if (resetBtn) {resetBtn.onclick = () => {const targetKey = k || rawText
|
|
1963
1282
|
const cur = getOverrides()
|
|
1964
1283
|
const updated = Object.assign({}, cur)
|
|
1965
1284
|
delete updated[targetKey]
|
|
@@ -1969,19 +1288,14 @@ if (typeof queueZhWalk === 'function') queueZhWalk()
|
|
|
1969
1288
|
}
|
|
1970
1289
|
}
|
|
1971
1290
|
const saveBtn = modal.querySelector('.rl-save-btn')
|
|
1972
|
-
saveBtn.onclick = () => {
|
|
1973
|
-
const val = textarea.value.trim()
|
|
1291
|
+
saveBtn.onclick = () => {const val = textarea.value.trim()
|
|
1974
1292
|
const targetKey = k || rawText
|
|
1975
1293
|
if (!targetKey || !val) return
|
|
1976
1294
|
const cur = getOverrides()
|
|
1977
1295
|
const updated = Object.assign({}, cur, { [targetKey]: val })
|
|
1978
1296
|
try { scope.set('overrides', updated) } catch (_e) { void _e }
|
|
1979
|
-
if (targetElement) {
|
|
1980
|
-
|
|
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
|
|
1297
|
+
if (targetElement) {try {if (targetElement.childNodes && targetElement.childNodes.length === 1 && targetElement.childNodes[0].nodeType === 3) {targetElement.childNodes[0].nodeValue = val
|
|
1298
|
+
} else {targetElement.textContent = val
|
|
1985
1299
|
}
|
|
1986
1300
|
} catch (err) { void err; }
|
|
1987
1301
|
}
|
|
@@ -1993,8 +1307,7 @@ setTimeout(() => modal.remove(), 500)
|
|
|
1993
1307
|
document.body.appendChild(modal)
|
|
1994
1308
|
setTimeout(() => textarea.focus(), 50)
|
|
1995
1309
|
}
|
|
1996
|
-
const inspectorOnClick = (e) => {
|
|
1997
|
-
if (!e.altKey && !isInspectorActive) return
|
|
1310
|
+
const inspectorOnClick = (e) => {if (!e.altKey && !isInspectorActive) return
|
|
1998
1311
|
if (e.target && e.target.closest && (e.target.closest('#dsh-ru-inspector-modal') || e.target.closest('#dsh-ru-inspector-pill'))) return
|
|
1999
1312
|
const raw = (e.target && (e.target.innerText || e.target.textContent) || '').trim()
|
|
2000
1313
|
if (!raw) return
|
|
@@ -2003,15 +1316,12 @@ e.stopPropagation()
|
|
|
2003
1316
|
const meta = translationRegistry.get(raw) || (typeof findTranslationKey === 'function' ? findTranslationKey(raw, RU, getOverrides(), ZH_RU, DOM_EN_TEXT, { detectUntranslated: true }) : null)
|
|
2004
1317
|
openInspectorModal(meta, raw, e.target)
|
|
2005
1318
|
}
|
|
2006
|
-
const inspectorOnMouseMove = (e) => {
|
|
2007
|
-
if (!isInspectorActive && !e.altKey) {
|
|
2008
|
-
if (hoverBoxEl && hoverBoxEl.style.display !== 'none') hoverBoxEl.style.display = 'none'
|
|
1319
|
+
const inspectorOnMouseMove = (e) => {if (!isInspectorActive && !e.altKey) {if (hoverBoxEl && hoverBoxEl.style.display !== 'none') hoverBoxEl.style.display = 'none'
|
|
2009
1320
|
return
|
|
2010
1321
|
}
|
|
2011
1322
|
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
1323
|
const raw = (e.target.innerText || e.target.textContent || '').trim()
|
|
2013
|
-
if (!raw || raw.length > 200) {
|
|
2014
|
-
if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1324
|
+
if (!raw || raw.length > 200) {if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
2015
1325
|
return
|
|
2016
1326
|
}
|
|
2017
1327
|
const box = getHoverBox()
|
|
@@ -2023,11 +1333,9 @@ box.style.width = rect.width + 'px'
|
|
|
2023
1333
|
box.style.height = rect.height + 'px'
|
|
2024
1334
|
box.style.display = 'block'
|
|
2025
1335
|
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)
|
|
1336
|
+
if (badge) {const meta = translationRegistry.get(raw) || (typeof findTranslationKey === 'function' ? findTranslationKey(raw, RU, getOverrides(), ZH_RU, DOM_EN_TEXT, { detectUntranslated: true }) : null)
|
|
2028
1337
|
let title = '🔍 Клик: инспекция'
|
|
2029
|
-
if (meta) {
|
|
2030
|
-
if (meta.source === 'override') title = '✍️ ' + meta.key
|
|
1338
|
+
if (meta) {if (meta.source === 'override') title = '✍️ ' + meta.key
|
|
2031
1339
|
else if (meta.source === 'dictionary') title = '🟢 ' + (meta.ns ? meta.ns + '.' : '') + meta.key
|
|
2032
1340
|
else if (meta.source === 'dom_zh') title = '🟡 ' + meta.zh
|
|
2033
1341
|
else if (meta.source === 'dom_en') title = '🟡 ' + meta.en
|
|
@@ -2037,26 +1345,20 @@ else if (meta.source === 'untranslated_en') title = '🔴 EN'
|
|
|
2037
1345
|
badge.textContent = title
|
|
2038
1346
|
}
|
|
2039
1347
|
}
|
|
2040
|
-
const inspectorOnKeyDown = (e) => {
|
|
2041
|
-
if (e.altKey && (e.key === 'i' || e.key === 'I' || e.code === 'KeyI')) {
|
|
2042
|
-
e.preventDefault()
|
|
1348
|
+
const inspectorOnKeyDown = (e) => {if (e.altKey && (e.key === 'i' || e.key === 'I' || e.code === 'KeyI')) {e.preventDefault()
|
|
2043
1349
|
isInspectorActive = !isInspectorActive
|
|
2044
1350
|
updateInspectorPill()
|
|
2045
1351
|
if (!isInspectorActive && hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
2046
1352
|
}
|
|
2047
1353
|
}
|
|
2048
|
-
const inspectorOnKeyUp = (e) => {
|
|
2049
|
-
if (e.key === 'Alt' && !isInspectorActive) {
|
|
2050
|
-
if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1354
|
+
const inspectorOnKeyUp = (e) => {if (e.key === 'Alt' && !isInspectorActive) {if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
2051
1355
|
}
|
|
2052
1356
|
}
|
|
2053
|
-
ctx.effect(() => {
|
|
2054
|
-
document.addEventListener('click', inspectorOnClick, true)
|
|
1357
|
+
ctx.effect(() => {document.addEventListener('click', inspectorOnClick, true)
|
|
2055
1358
|
document.addEventListener('mousemove', inspectorOnMouseMove, true)
|
|
2056
1359
|
document.addEventListener('keydown', inspectorOnKeyDown, true)
|
|
2057
1360
|
document.addEventListener('keyup', inspectorOnKeyUp, true)
|
|
2058
|
-
return () => {
|
|
2059
|
-
document.removeEventListener('click', inspectorOnClick, true)
|
|
1361
|
+
return () => {document.removeEventListener('click', inspectorOnClick, true)
|
|
2060
1362
|
document.removeEventListener('mousemove', inspectorOnMouseMove, true)
|
|
2061
1363
|
document.removeEventListener('keydown', inspectorOnKeyDown, true)
|
|
2062
1364
|
document.removeEventListener('keyup', inspectorOnKeyUp, true)
|
|
@@ -2073,16 +1375,10 @@ runtime.bilingualCommandMatch = (query, cmd, opts) => (typeof bilingualCommandMa
|
|
|
2073
1375
|
runtime.filterCommands = (query, cmds, opts) => (typeof filterCommands === 'function' ? filterCommands(query, cmds, opts) : cmds)
|
|
2074
1376
|
runtime.humanizeError = (err) => (typeof humanizeError === 'function' ? humanizeError(err) : null)
|
|
2075
1377
|
runtime.formatErrorToast = (err) => (typeof formatErrorToast === 'function' ? formatErrorToast(err) : null)
|
|
2076
|
-
const toastObserver = typeof MutationObserver !== 'undefined' ? new MutationObserver((mutations) => {
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
if (
|
|
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
|
|
1378
|
+
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
|
|
1379
|
+
if (node.matches && (node.matches('.dsw-toast, [role="alert"], .toast, .ant-message-notice'))) {const text = (node.innerText || node.textContent || '').trim()
|
|
1380
|
+
if (/failed to fetch|network error|econnrefused|etimedout/i.test(text)) {const h = typeof humanizeError === 'function' ? humanizeError(text) : null
|
|
1381
|
+
if (h && h.title) {const targetEl = node.querySelector('.toast-body, .ant-message-custom-content') || node
|
|
2086
1382
|
targetEl.textContent = h.title + ': ' + h.message
|
|
2087
1383
|
}
|
|
2088
1384
|
}
|
|
@@ -2090,17 +1386,13 @@ targetEl.textContent = h.title + ': ' + h.message
|
|
|
2090
1386
|
}
|
|
2091
1387
|
}
|
|
2092
1388
|
}) : null
|
|
2093
|
-
ctx.effect(() => {
|
|
2094
|
-
if (toastObserver && typeof document !== 'undefined' && document.body) {
|
|
2095
|
-
toastObserver.observe(document.body, { childList: true, subtree: true })
|
|
1389
|
+
ctx.effect(() => {if (toastObserver && typeof document !== 'undefined' && document.body) {toastObserver.observe(document.body, { childList: true, subtree: true })
|
|
2096
1390
|
}
|
|
2097
1391
|
return () => { if (toastObserver) toastObserver.disconnect() }
|
|
2098
1392
|
}, 'dsh-russian-lang: toast-humanizer')
|
|
2099
|
-
ctx.effect(() => {
|
|
2100
|
-
document.addEventListener('input', layoutOnInput, true)
|
|
1393
|
+
ctx.effect(() => {document.addEventListener('input', layoutOnInput, true)
|
|
2101
1394
|
document.addEventListener('keydown', layoutOnKeydown, true)
|
|
2102
|
-
return () => {
|
|
2103
|
-
unsubscribeLayout()
|
|
1395
|
+
return () => {unsubscribeLayout()
|
|
2104
1396
|
document.removeEventListener('input', layoutOnInput, true)
|
|
2105
1397
|
document.removeEventListener('keydown', layoutOnKeydown, true)
|
|
2106
1398
|
layoutDismiss()
|
|
@@ -2108,125 +1400,61 @@ layoutBadgeHide()
|
|
|
2108
1400
|
}
|
|
2109
1401
|
}, 'dsh-russian-lang: layout')
|
|
2110
1402
|
if (!ctx.slots || !React) return
|
|
2111
|
-
const toggleRu = (wantRu) => {
|
|
2112
|
-
try {
|
|
2113
|
-
if (runtime.getLocale().active === wantRu) return
|
|
1403
|
+
const toggleRu = (wantRu) => {try {if (runtime.getLocale().active === wantRu) return
|
|
2114
1404
|
runtime.setLocale(wantRu ? 'ru' : 'en')
|
|
2115
1405
|
} catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }
|
|
2116
1406
|
}
|
|
2117
|
-
try {
|
|
2118
|
-
ctx.slots.
|
|
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
|
-
)
|
|
1407
|
+
try {ctx.slots.inject('plugins.item', () =>
|
|
1408
|
+
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
1409
|
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
|
-
)
|
|
1410
|
+
ctx.slots.register({name: 'plugins.row.config',key: ROW_CONFIG_KEY,locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
|
|
2136
1411
|
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
|
-
)
|
|
1412
|
+
ctx.slots.register({name: 'settings.plugin.item',key: SETTINGS_NS_NAME,locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
|
|
2144
1413
|
} catch (err) { console.warn('dsh-russian-lang: settings slot unavailable', err) }
|
|
2145
|
-
try {
|
|
2146
|
-
ctx.slots.
|
|
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
|
-
)
|
|
1414
|
+
try {ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1415
|
+
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
1416
|
} catch (err) { }
|
|
2156
|
-
try {
|
|
2157
|
-
ctx.slots.
|
|
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
|
-
)
|
|
1417
|
+
try {ctx.slots.inject('conversation.chat.assistant-actions', () =>
|
|
1418
|
+
ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian-lang-translate-action',order: 50,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, TranslateTurnAction),)
|
|
2166
1419
|
} catch (err) { }
|
|
2167
|
-
try {
|
|
2168
|
-
ctx.slots.
|
|
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
|
-
)
|
|
1420
|
+
try {ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1421
|
+
ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
|
|
2177
1422
|
} catch (err) { }
|
|
2178
1423
|
}
|
|
2179
|
-
function QuickLangSwitch(props) {
|
|
2180
|
-
const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
|
|
1424
|
+
function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
|
|
2181
1425
|
const runtime = props.runtime || inj.runtime
|
|
2182
1426
|
const toggleRu = props.toggleRu || inj.toggleRu
|
|
2183
1427
|
const [locale, setLocaleState] = React.useState(runtime ? (runtime.getLocale().active || 'en') : 'ru')
|
|
2184
|
-
React.useEffect(() => {
|
|
2185
|
-
|
|
2186
|
-
return runtime.subscribe(() => {
|
|
2187
|
-
try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }
|
|
1428
|
+
React.useEffect(() => {if (!runtime) return
|
|
1429
|
+
return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }
|
|
2188
1430
|
})
|
|
2189
1431
|
}, [runtime])
|
|
2190
1432
|
const isRu = locale === 'ru'
|
|
2191
|
-
return React.createElement('button', {
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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"]')
|
|
1433
|
+
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'))
|
|
1434
|
+
}
|
|
1435
|
+
function ExportMarkdownButton(props) {const [done, setDone] = React.useState(false)
|
|
1436
|
+
const onExport = () => {try {const titleEl = document.querySelector('.dsw-session-title, [data-session-title], header h1, header h2, [class*="title"]')
|
|
2203
1437
|
const rawTitle = (titleEl && titleEl.textContent.trim()) || document.title || 'Диалог DSH'
|
|
2204
1438
|
const title = rawTitle.replace(/\s*—\s*DeepSeek Harness\s*$/, '').trim() || 'Диалог DSH'
|
|
2205
1439
|
const flow = document.querySelector('[data-chat-flow]') || document.querySelector('[data-chat-flow-scroll]') || document.body
|
|
2206
1440
|
const flowItems = Array.from(flow.querySelectorAll('[data-chat-flow-kind]'))
|
|
2207
1441
|
const messages = []
|
|
2208
|
-
if (flowItems.length > 0) {
|
|
2209
|
-
|
|
2210
|
-
const kind = node.getAttribute('data-chat-flow-kind')
|
|
2211
|
-
if (kind === 'user' || kind === 'steering') {
|
|
2212
|
-
const bubble = node.querySelector('[class*="bubble"]') || node
|
|
1442
|
+
if (flowItems.length > 0) {flowItems.forEach((node) => {const kind = node.getAttribute('data-chat-flow-kind')
|
|
1443
|
+
if (kind === 'user' || kind === 'steering') {const bubble = node.querySelector('[class*="bubble"]') || node
|
|
2213
1444
|
const clone = bubble.cloneNode(true)
|
|
2214
1445
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
|
|
2215
1446
|
const text = clone.innerText.trim()
|
|
2216
1447
|
if (text) messages.push({ role: 'user', content: text })
|
|
2217
|
-
} else if (kind === 'assistant-step') {
|
|
2218
|
-
const clone = node.cloneNode(true)
|
|
1448
|
+
} else if (kind === 'assistant-step') {const clone = node.cloneNode(true)
|
|
2219
1449
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"], .rl-turn-translation').forEach((b) => b.remove())
|
|
2220
1450
|
const text = clone.innerText.trim()
|
|
2221
1451
|
if (text) messages.push({ role: 'assistant', content: text })
|
|
2222
1452
|
}
|
|
2223
1453
|
})
|
|
2224
1454
|
}
|
|
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]'))
|
|
1455
|
+
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
1456
|
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"]')
|
|
1457
|
+
allElements.forEach((node) => {const isUser = node.matches('[class*="userRow"], [class*="UserRow"], [data-role="user"]') || !!node.querySelector('[data-role="user"]')
|
|
2230
1458
|
const role = isUser ? 'user' : 'assistant'
|
|
2231
1459
|
const clone = node.cloneNode(true)
|
|
2232
1460
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"], .rl-turn-translation, [data-turn-tail]').forEach((b) => b.remove())
|
|
@@ -2236,10 +1464,7 @@ seen.add(text)
|
|
|
2236
1464
|
messages.push({ role, content: text })
|
|
2237
1465
|
})
|
|
2238
1466
|
}
|
|
2239
|
-
const session = {
|
|
2240
|
-
title,
|
|
2241
|
-
createdAt: new Date().toISOString(),
|
|
2242
|
-
messages
|
|
1467
|
+
const session = {title,createdAt: new Date().toISOString(),messages
|
|
2243
1468
|
}
|
|
2244
1469
|
const md = exportSessionToMarkdown(session)
|
|
2245
1470
|
const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' })
|
|
@@ -2252,175 +1477,65 @@ a.download = safeTitle + '-' + new Date().toISOString().slice(0, 10) + '.md'
|
|
|
2252
1477
|
document.body.appendChild(a)
|
|
2253
1478
|
a.click()
|
|
2254
1479
|
setDone(true)
|
|
2255
|
-
setTimeout(() => {
|
|
2256
|
-
try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }
|
|
1480
|
+
setTimeout(() => {try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }
|
|
2257
1481
|
}, 30000)
|
|
2258
1482
|
setTimeout(() => setDone(false), 2000)
|
|
2259
|
-
} catch (err) {
|
|
2260
|
-
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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 })
|
|
1483
|
+
} catch (err) {console.warn('dsh-russian-lang: export md failed', err)
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
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'))
|
|
1487
|
+
}
|
|
1488
|
+
async function translateTurnContent(text) {if (!text || typeof text !== 'string') return { error: 'Текст для перевода пуст.' }
|
|
1489
|
+
try {const res = await fetch('/api/dsh-russian-lang/translate', {method: 'POST',headers: { 'content-type': 'application/json', 'x-dsh-translator': '1' },body: JSON.stringify({ text })
|
|
2277
1490
|
})
|
|
2278
1491
|
const data = await res.json().catch(() => ({}))
|
|
2279
|
-
if (!res.ok) {
|
|
2280
|
-
return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
|
|
1492
|
+
if (!res.ok) {return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
|
|
2281
1493
|
}
|
|
2282
1494
|
return { translatedText: data.translatedText || text }
|
|
2283
|
-
} catch (err) {
|
|
2284
|
-
return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
|
|
1495
|
+
} catch (err) {return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
|
|
2285
1496
|
}
|
|
2286
1497
|
}
|
|
2287
|
-
function TranslateTurnAction(props) {
|
|
2288
|
-
const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1498
|
+
function TranslateTurnAction(props) {const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
2289
1499
|
const [loading, setLoading] = React.useState(false)
|
|
2290
1500
|
const [open, setOpen] = React.useState(false)
|
|
2291
|
-
|
|
2292
|
-
currentVersion: '0.3.7',
|
|
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()
|
|
1501
|
+
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
1502
|
const btn = ev.currentTarget
|
|
2376
1503
|
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
1504
|
if (!tailFlowItem) return
|
|
2378
1505
|
const parentContainer = tailFlowItem.parentElement
|
|
2379
1506
|
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')
|
|
1507
|
+
if (!box) {box = tailFlowItem.querySelector('.rl-turn-translation')
|
|
2382
1508
|
}
|
|
2383
|
-
if (box) {
|
|
2384
|
-
box.style.display = box.style.display === 'none' ? 'block' : 'none'
|
|
1509
|
+
if (box) {box.style.display = box.style.display === 'none' ? 'block' : 'none'
|
|
2385
1510
|
setOpen(box.style.display !== 'none')
|
|
2386
1511
|
return
|
|
2387
1512
|
}
|
|
2388
1513
|
setLoading(true)
|
|
2389
|
-
try {
|
|
2390
|
-
const assistantNodes = []
|
|
1514
|
+
try {const assistantNodes = []
|
|
2391
1515
|
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)
|
|
1516
|
+
while (prev && prev.getAttribute('data-chat-flow-kind') !== 'user') {if (prev.getAttribute('data-chat-flow-kind') === 'assistant-step') {assistantNodes.unshift(prev)
|
|
2395
1517
|
}
|
|
2396
1518
|
prev = prev.previousElementSibling
|
|
2397
1519
|
}
|
|
2398
1520
|
let rawText = ''
|
|
2399
|
-
if (assistantNodes.length > 0) {
|
|
2400
|
-
rawText = assistantNodes.map((node) => {
|
|
2401
|
-
const clone = node.cloneNode(true)
|
|
1521
|
+
if (assistantNodes.length > 0) {rawText = assistantNodes.map((node) => {const clone = node.cloneNode(true)
|
|
2402
1522
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
|
|
2403
1523
|
return clone.innerText.trim()
|
|
2404
1524
|
}).filter(Boolean).join('\n\n')
|
|
2405
|
-
} else if (parentContainer) {
|
|
2406
|
-
|
|
2407
|
-
if (allSteps.length > 0) {
|
|
2408
|
-
const beforeTail = allSteps.filter((s) => (s.compareDocumentPosition(tailFlowItem) & Node.DOCUMENT_POSITION_FOLLOWING))
|
|
1525
|
+
} else if (parentContainer) {const allSteps = Array.from(parentContainer.querySelectorAll('[data-chat-flow-kind="assistant-step"]'))
|
|
1526
|
+
if (allSteps.length > 0) {const beforeTail = allSteps.filter((s) => (s.compareDocumentPosition(tailFlowItem) & Node.DOCUMENT_POSITION_FOLLOWING))
|
|
2409
1527
|
const targetSteps = beforeTail.length > 0 ? [beforeTail[beforeTail.length - 1]] : [allSteps[allSteps.length - 1]]
|
|
2410
|
-
rawText = targetSteps.map((s) => {
|
|
2411
|
-
const clone = s.cloneNode(true)
|
|
1528
|
+
rawText = targetSteps.map((s) => {const clone = s.cloneNode(true)
|
|
2412
1529
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
|
|
2413
1530
|
return clone.innerText.trim()
|
|
2414
1531
|
}).filter(Boolean).join('\n\n')
|
|
2415
1532
|
}
|
|
2416
1533
|
}
|
|
2417
|
-
if (!rawText) {
|
|
2418
|
-
const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
|
|
1534
|
+
if (!rawText) {const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
|
|
2419
1535
|
if (prose) rawText = prose.innerText.trim()
|
|
2420
1536
|
}
|
|
2421
1537
|
let transResult = { error: 'Не удалось обнаружить текст сообщения ассистента для перевода.' }
|
|
2422
|
-
if (rawText) {
|
|
2423
|
-
transResult = await translateTurnContent(rawText)
|
|
1538
|
+
if (rawText) {transResult = await translateTurnContent(rawText)
|
|
2424
1539
|
}
|
|
2425
1540
|
const isErr = !!transResult.error
|
|
2426
1541
|
const displayText = transResult.translatedText || transResult.error || ''
|
|
@@ -2438,36 +1553,27 @@ box.innerHTML = '<div class="rl-trans-head">' +
|
|
|
2438
1553
|
'<div class="rl-trans-body"' + (isErr ? ' style="color: var(--dsw-alias-state-warning-primary); font-size: 13px;"' : '') + '></div>'
|
|
2439
1554
|
box.querySelector('.rl-trans-body').textContent = displayText
|
|
2440
1555
|
const copyBtn = box.querySelector('.rl-btn-copy')
|
|
2441
|
-
if (copyBtn) {
|
|
2442
|
-
|
|
2443
|
-
e.stopPropagation()
|
|
2444
|
-
try { navigator.clipboard.writeText(displayText) } catch (err) { void e; }
|
|
1556
|
+
if (copyBtn) {copyBtn.addEventListener('click', (e) => {e.stopPropagation()
|
|
1557
|
+
try { navigator.clipboard.writeText(displayText) } catch (err) { void err; }
|
|
2445
1558
|
copyBtn.textContent = '✓ Скопировано'
|
|
2446
1559
|
setTimeout(() => { copyBtn.textContent = '📋 Копировать' }, 2000)
|
|
2447
1560
|
})
|
|
2448
1561
|
}
|
|
2449
1562
|
const closeBtn = box.querySelector('.rl-btn-close')
|
|
2450
|
-
closeBtn.addEventListener('click', (e) => {
|
|
2451
|
-
e.stopPropagation()
|
|
1563
|
+
closeBtn.addEventListener('click', (e) => {e.stopPropagation()
|
|
2452
1564
|
box.style.display = 'none'
|
|
2453
1565
|
setOpen(false)
|
|
2454
1566
|
})
|
|
2455
|
-
if (parentContainer) {
|
|
2456
|
-
|
|
2457
|
-
} else {
|
|
2458
|
-
tailFlowItem.appendChild(box)
|
|
1567
|
+
if (parentContainer) {parentContainer.insertBefore(box, tailFlowItem)
|
|
1568
|
+
} else {tailFlowItem.appendChild(box)
|
|
2459
1569
|
}
|
|
2460
1570
|
setOpen(true)
|
|
2461
|
-
} catch (err) {
|
|
2462
|
-
|
|
2463
|
-
} finally {
|
|
2464
|
-
setLoading(false)
|
|
1571
|
+
} catch (err) {console.warn('dsh-russian-lang: translate turn failed', err)
|
|
1572
|
+
} finally {setLoading(false)
|
|
2465
1573
|
}
|
|
2466
|
-
},
|
|
2467
|
-
}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
1574
|
+
},}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
2468
1575
|
}
|
|
2469
|
-
function SettingsCard(props) {
|
|
2470
|
-
const inj = typeof props.inject === 'function'
|
|
1576
|
+
function SettingsCard(props) {const inj = typeof props.inject === 'function'
|
|
2471
1577
|
? (props.inject() || {})
|
|
2472
1578
|
: (props.inject || {})
|
|
2473
1579
|
const scope = props.scope || inj.scope
|
|
@@ -2481,20 +1587,73 @@ const [ruActive, setRuActive] = React.useState(
|
|
|
2481
1587
|
() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
|
|
2482
1588
|
const [typo, setTypoState] = React.useState(() =>
|
|
2483
1589
|
(snap.value && snap.value.typography) || {})
|
|
2484
|
-
React.
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
const
|
|
1590
|
+
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.9',latestVersion: undefined,updateAvailable: false
|
|
1591
|
+
})
|
|
1592
|
+
const [upLoading, setUpLoading] = React.useState(false)
|
|
1593
|
+
const [upMsg, setUpMsg] = React.useState(null)
|
|
1594
|
+
const checkUpdate = () => {setUpLoading(true)
|
|
1595
|
+
setUpMsg(null)
|
|
1596
|
+
fetch('/api/dsh-russian-lang/update', {headers: { 'x-dsh-plugin-update': '1' }
|
|
1597
|
+
})
|
|
1598
|
+
.then((r) => r.json())
|
|
1599
|
+
.then((data) => {setUpStatus(data)
|
|
1600
|
+
setUpLoading(false)
|
|
1601
|
+
})
|
|
1602
|
+
.catch(() => {setUpLoading(false)
|
|
1603
|
+
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1604
|
+
})
|
|
1605
|
+
}
|
|
1606
|
+
const triggerUpdate = () => {setUpLoading(true)
|
|
1607
|
+
setUpMsg(null)
|
|
1608
|
+
fetch('/api/dsh-russian-lang/update', {method: 'POST',headers: { 'x-dsh-plugin-update': '1' }
|
|
1609
|
+
})
|
|
1610
|
+
.then((r) => r.json())
|
|
1611
|
+
.then((data) => {setUpLoading(false)
|
|
1612
|
+
if (data.restartRequired || data.updatedVersion) {setUpStatus(data)
|
|
1613
|
+
setUpMsg({ type: 'ok', text: t('updaterSuccess') })
|
|
1614
|
+
} else if (data.error) {setUpMsg({ type: 'err', text: data.error })
|
|
1615
|
+
}
|
|
1616
|
+
})
|
|
1617
|
+
.catch(() => {setUpLoading(false)
|
|
1618
|
+
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1619
|
+
})
|
|
1620
|
+
}
|
|
1621
|
+
const [transStatus, setTransStatus] = React.useState(null)
|
|
1622
|
+
const [transStarting, setTransStarting] = React.useState(false)
|
|
1623
|
+
const [transMsg, setTransMsg] = React.useState(null)
|
|
1624
|
+
const fetchTransStatus = () => {fetch('/api/dsh-russian-lang/translator/status')
|
|
1625
|
+
.then((r) => r.json())
|
|
1626
|
+
.then((data) => setTransStatus(data))
|
|
1627
|
+
.catch(() => setTransStatus(null))
|
|
1628
|
+
}
|
|
1629
|
+
const startLibreTranslate = () => {setTransStarting(true)
|
|
1630
|
+
setTransMsg(null)
|
|
1631
|
+
fetch('/api/dsh-russian-lang/translator/setup', {method: 'POST',headers: { 'x-dsh-translator': '1' }
|
|
1632
|
+
})
|
|
1633
|
+
.then((r) => r.json())
|
|
1634
|
+
.then((data) => {setTransStarting(false)
|
|
1635
|
+
if (data.ok) {setTransMsg({ type: 'ok', text: data.message || 'Контейнер LibreTranslate запущен.' })
|
|
1636
|
+
fetchTransStatus()
|
|
1637
|
+
} else {setTransMsg({ type: 'err', text: data.error || 'Ошибка запуска контейнера.' })
|
|
1638
|
+
}
|
|
1639
|
+
})
|
|
1640
|
+
.catch((err) => {setTransStarting(false)
|
|
1641
|
+
setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
|
|
1642
|
+
})
|
|
1643
|
+
}
|
|
1644
|
+
React.useEffect(() => {if (open || (props && props.view === 'page')) {checkUpdate()
|
|
1645
|
+
fetchTransStatus()
|
|
1646
|
+
}
|
|
1647
|
+
}, [open, props])
|
|
1648
|
+
React.useEffect(() => {if (!scope || !scope.subscribe) return undefined
|
|
1649
|
+
const un = scope.subscribe(() => {const s = scope.getSnapshot()
|
|
2488
1650
|
setSnap(s)
|
|
2489
1651
|
if (s.value && s.value.typography) setTypoState(s.value.typography)
|
|
2490
1652
|
})
|
|
2491
1653
|
setSnap(scope.getSnapshot())
|
|
2492
1654
|
return un
|
|
2493
1655
|
}, [scope])
|
|
2494
|
-
React.useEffect(() => {
|
|
2495
|
-
try {
|
|
2496
|
-
const un = runtime.subscribe(() => {
|
|
2497
|
-
try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }
|
|
1656
|
+
React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }
|
|
2498
1657
|
})
|
|
2499
1658
|
return un
|
|
2500
1659
|
} catch (e) { return undefined }
|
|
@@ -2506,428 +1665,146 @@ const overrides = value.overrides || {}
|
|
|
2506
1665
|
const overridesCount = Object.keys(overrides).length
|
|
2507
1666
|
const [newKey, setNewKey] = React.useState('')
|
|
2508
1667
|
const [newVal, setNewVal] = React.useState('')
|
|
2509
|
-
const setTypo = (patch) => {
|
|
2510
|
-
const next = Object.assign({}, typo, patch)
|
|
1668
|
+
const setTypo = (patch) => {const next = Object.assign({}, typo, patch)
|
|
2511
1669
|
setTypoState(next)
|
|
2512
|
-
try {
|
|
2513
|
-
|
|
2514
|
-
if (r && typeof r.catch === 'function') r.catch((err) => {
|
|
2515
|
-
console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
|
|
1670
|
+
try {const r = scope.set('typography', next)
|
|
1671
|
+
if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
|
|
2516
1672
|
})
|
|
2517
|
-
} catch (err) {
|
|
2518
|
-
console.warn('dsh-russian-lang: scope.set typography sync threw', err && err.message || err)
|
|
1673
|
+
} catch (err) {console.warn('dsh-russian-lang: scope.set typography sync threw', err && err.message || err)
|
|
2519
1674
|
}
|
|
2520
1675
|
}
|
|
2521
1676
|
const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
|
|
2522
1677
|
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
|
-
})
|
|
1678
|
+
React.createElement('input', {type: 'checkbox', checked: !!checked, disabled: !!disabled,className: 'rl-check', onChange: (ev) => onChange(ev),})
|
|
2527
1679
|
const disabled = status !== 'ready'
|
|
2528
1680
|
const statusLine = status === 'ready'
|
|
2529
1681
|
? ''
|
|
2530
1682
|
: (status === 'unavailable' ? t('statusUnavailable') : t('statusLoading'))
|
|
2531
1683
|
let ChevronIcon = null
|
|
2532
|
-
try {
|
|
2533
|
-
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
1684
|
+
try {const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
2534
1685
|
ChevronIcon = primitives && primitives.IconChevronDownOutline14
|
|
2535
1686
|
} 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
|
-
}))
|
|
1687
|
+
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
1688
|
const Chevron = ChevronIcon || FallbackChevron
|
|
2544
1689
|
const presetKey = value.agentPromptPreset || 'technical_expert'
|
|
2545
1690
|
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'))
|
|
1691
|
+
if (props && props.view === 'summary') {return React.createElement('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
|
|
2548
1692
|
}
|
|
2549
1693
|
const page = !!(props && props.view === 'page')
|
|
2550
|
-
return React.createElement('div', { className: page ? 'rl-page-seat' : 'rl-card' },
|
|
2551
|
-
React.createElement('
|
|
2552
|
-
|
|
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')
|
|
1694
|
+
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'))
|
|
1695
|
+
),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'))
|
|
1696
|
+
),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
1697
|
)
|
|
2585
|
-
),
|
|
2586
|
-
React.createElement('div', { className: 'rl-
|
|
2587
|
-
),
|
|
2588
|
-
React.createElement('div', { className: 'rl-
|
|
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')
|
|
1698
|
+
),React.createElement('div', { className: 'rl-item-desc' }, t('enabledDesc'))
|
|
1699
|
+
),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
|
|
1700
|
+
),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'))
|
|
1701
|
+
),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
1702
|
)
|
|
2606
|
-
),
|
|
2607
|
-
React.createElement('div', { className: 'rl-item-
|
|
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')
|
|
1703
|
+
),React.createElement('div', { className: 'rl-item-desc' }, t('typographyDesc'))
|
|
1704
|
+
),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
1705
|
)
|
|
2616
|
-
),
|
|
2617
|
-
React.createElement('div', { className: 'rl-item-
|
|
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')
|
|
1706
|
+
),React.createElement('div', { className: 'rl-item-desc' }, t('liveInputDesc'))
|
|
1707
|
+
),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
1708
|
)
|
|
2626
|
-
),
|
|
2627
|
-
React.createElement('div', { className: 'rl-item-
|
|
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')
|
|
1709
|
+
),React.createElement('div', { className: 'rl-item-desc' }, t('yoDesc'))
|
|
1710
|
+
),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 err; }
|
|
1711
|
+
}, !ruActive || disabled),t('slashAliases')
|
|
2637
1712
|
)
|
|
2638
|
-
),
|
|
2639
|
-
React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))
|
|
1713
|
+
),React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))
|
|
2640
1714
|
)
|
|
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'))
|
|
1715
|
+
),React.createElement('div', { className: 'rl-hotkey-box' },React.createElement('span', { className: 'rl-hotkey-tag' }, '⌨️ Alt+L'),React.createElement('span', null, t('altLHintText'))
|
|
2645
1716
|
)
|
|
2646
|
-
),
|
|
2647
|
-
React.createElement('div', { className: 'rl-section-card' },
|
|
2648
|
-
|
|
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)
|
|
1717
|
+
),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 ? 'Активен' : 'Выключен')
|
|
1718
|
+
),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)
|
|
1719
|
+
if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
|
|
2663
1720
|
})
|
|
2664
|
-
} catch (err) {
|
|
2665
|
-
console.warn('dsh-russian-lang: scope.set agentPrompt sync threw', err && err.message || err)
|
|
1721
|
+
} catch (err) {console.warn('dsh-russian-lang: scope.set agentPrompt sync threw', err && err.message || err)
|
|
2666
1722
|
}
|
|
2667
|
-
}, false),
|
|
2668
|
-
t('agentPrompt')
|
|
1723
|
+
}, false),t('agentPrompt')
|
|
2669
1724
|
)
|
|
2670
|
-
),
|
|
2671
|
-
React.createElement('div', { className: 'rl-item-
|
|
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
|
|
1725
|
+
),React.createElement('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))
|
|
1726
|
+
),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
1727
|
scope.set('agentPromptPreset', val)
|
|
2683
1728
|
} catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }
|
|
2684
1729
|
}
|
|
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'))
|
|
1730
|
+
},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
1731
|
)
|
|
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
|
|
1732
|
+
),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
1733
|
) : null
|
|
2698
1734
|
) : null
|
|
2699
|
-
),
|
|
2700
|
-
React.createElement('div', { className: 'rl-section-card' },
|
|
2701
|
-
React.createElement('div', {
|
|
2702
|
-
React.createElement('
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
)
|
|
2706
|
-
React.createElement('div', {
|
|
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.7')),
|
|
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
|
|
1735
|
+
),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'))
|
|
1736
|
+
),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.9')),upStatus.updateAvailable
|
|
1737
|
+
? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
|
|
1738
|
+
: React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))
|
|
1739
|
+
),upStatus.updateAvailable
|
|
1740
|
+
? React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
|
|
1741
|
+
: React.createElement('button', {type: 'button',className: 'rl-btn',disabled: upLoading,onClick: checkUpdate,}, upLoading ? t('updaterChecking') : t('updaterCheckBtn'))
|
|
1742
|
+
),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
1743
|
}
|
|
2738
1744
|
}, upMsg.text) : null
|
|
2739
1745
|
)
|
|
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'))
|
|
1746
|
+
),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
1747
|
}, 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
|
|
1748
|
+
),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
1749
|
scope.set('translateEngine', val)
|
|
2759
1750
|
if (val === 'local') fetchTransStatus()
|
|
2760
1751
|
} catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }
|
|
2761
1752
|
}
|
|
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'))
|
|
1753
|
+
},React.createElement('option', { value: 'off' }, t('engineOff')),React.createElement('option', { value: 'local' }, t('engineLocal')),React.createElement('option', { value: 'google' }, t('engineGoogle'))
|
|
2766
1754
|
)
|
|
2767
|
-
),
|
|
2768
|
-
|
|
2769
|
-
style: {
|
|
2770
|
-
|
|
2771
|
-
|
|
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
|
|
1755
|
+
),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
|
|
1756
|
+
}
|
|
1757
|
+
}, t('googleWarn')) : null,value.translateEngine === 'local' ? React.createElement('div', {style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }
|
|
1758
|
+
},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')
|
|
1759
|
+
}, 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
1760
|
}, 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
|
|
1761
|
+
),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
1762
|
}
|
|
2796
1763
|
}, transMsg.text) : null
|
|
2797
1764
|
) : null
|
|
2798
1765
|
)
|
|
2799
|
-
),
|
|
2800
|
-
React.createElement('div', { className: 'rl-section-
|
|
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()
|
|
1766
|
+
),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, ['запись', 'записи', 'записей']) : 'записей'))
|
|
1767
|
+
),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
1768
|
const v = newVal.trim()
|
|
2829
1769
|
if (!k || !v) return
|
|
2830
1770
|
const next = Object.assign({}, overrides, { [k]: v })
|
|
2831
|
-
try { scope.set('overrides',
|
|
1771
|
+
try { scope.set('overrides', next) } catch (_e) { void _e }
|
|
2832
1772
|
setNewKey('')
|
|
2833
1773
|
setNewVal('')
|
|
2834
|
-
},
|
|
2835
|
-
|
|
2836
|
-
),
|
|
2837
|
-
overridesCount === 0
|
|
1774
|
+
},}, t('overrideAddBtn'))
|
|
1775
|
+
),overridesCount === 0
|
|
2838
1776
|
? 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
|
-
|
|
2841
|
-
React.createElement('
|
|
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)
|
|
1777
|
+
: React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },Object.keys(overrides).map((k) =>
|
|
1778
|
+
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])
|
|
1779
|
+
),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
1780
|
delete next[k]
|
|
2857
|
-
try { scope.set('overrides',
|
|
2858
|
-
},
|
|
2859
|
-
}, t('overrideDelete'))
|
|
1781
|
+
try { scope.set('overrides', next) } catch (_e) { void _e }
|
|
1782
|
+
},}, t('overrideDelete'))
|
|
2860
1783
|
)
|
|
2861
1784
|
)
|
|
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
|
|
1785
|
+
),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
1786
|
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))
|
|
1787
|
+
},}, 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
1788
|
alert('Оверрайды скопированы в буфер обмена как JSON!')
|
|
2881
1789
|
} catch (err) { void err; }
|
|
2882
1790
|
}
|
|
2883
|
-
}, '📤 Экспорт JSON'),
|
|
2884
|
-
React.createElement('button', {
|
|
2885
|
-
type: 'button',
|
|
2886
|
-
className: 'rl-btn',
|
|
2887
|
-
style: { fontSize: '11px' },
|
|
2888
|
-
onClick: () => {
|
|
2889
|
-
const input = prompt('Вставьте JSON с оверрайдами:')
|
|
1791
|
+
}, '📤 Экспорт JSON'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = prompt('Вставьте JSON с оверрайдами:')
|
|
2890
1792
|
if (!input) return
|
|
2891
|
-
try {
|
|
2892
|
-
|
|
2893
|
-
if (parsed && typeof parsed === 'object') {
|
|
2894
|
-
const merged = Object.assign({}, overrides, parsed)
|
|
1793
|
+
try {const parsed = JSON.parse(input)
|
|
1794
|
+
if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
|
|
2895
1795
|
scope.set('overrides', merged)
|
|
2896
1796
|
}
|
|
2897
1797
|
} catch (err) { alert('Ошибка разбора JSON: ' + (err && err.message || err)) }
|
|
2898
1798
|
}
|
|
2899
1799
|
}, '📥 Импорт JSON')
|
|
2900
1800
|
)
|
|
2901
|
-
),
|
|
2902
|
-
React.createElement('div', { className: 'rl-section-
|
|
2903
|
-
React.createElement('div', { className: 'rl-
|
|
2904
|
-
React.createElement('
|
|
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'))
|
|
1801
|
+
),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%')
|
|
1802
|
+
),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'))
|
|
1803
|
+
),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'))
|
|
1804
|
+
),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
1805
|
)
|
|
2921
|
-
),
|
|
2922
|
-
React.createElement('div', { className: 'rl-
|
|
2923
|
-
React.createElement('a', {
|
|
2924
|
-
href: makeIssueUrl({}, '0.3.7'),
|
|
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')
|
|
1806
|
+
),React.createElement('div', { className: 'rl-actions-row' },React.createElement('a', {href: makeIssueUrl({}, '0.3.9'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
|
|
1807
|
+
}, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')
|
|
2931
1808
|
)
|
|
2932
1809
|
)
|
|
2933
1810
|
)
|
|
@@ -2936,69 +1813,9 @@ t('exportMdHint')
|
|
|
2936
1813
|
)
|
|
2937
1814
|
}
|
|
2938
1815
|
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('')
|
|
1816
|
+
'.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
1817
|
const STYLE_ID = 'dsh-russian-lang-styles'
|
|
3000
|
-
if (typeof document !== 'undefined' && (document.getElementById && !document.getElementById(STYLE_ID))) {
|
|
3001
|
-
const tag = document.createElement('style')
|
|
1818
|
+
if (typeof document !== 'undefined' && (document.getElementById && !document.getElementById(STYLE_ID))) {const tag = document.createElement('style')
|
|
3002
1819
|
tag.id = STYLE_ID
|
|
3003
1820
|
tag.dataset.dshPlugin = 'dsh-russian-lang'
|
|
3004
1821
|
tag.dataset.plugin = '@goodandready/dsh-russian-lang'
|
|
@@ -3008,5 +1825,4 @@ document.head.appendChild(tag)
|
|
|
3008
1825
|
}
|
|
3009
1826
|
module.exports = { apply, inject: ['slots', 'locale', 'configForms'] }
|
|
3010
1827
|
return module.exports
|
|
3011
|
-
},
|
|
3012
|
-
})
|
|
1828
|
+
},})
|