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