@goodandready/dsh-russian-lang 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/lib/client.js +190 -464
- package/lib/client.template.js +56 -152
- package/lib/locales/core.json +1 -1
- package/lib/pure.js +6 -11
- package/lib/translator.js +51 -20
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -15,8 +15,7 @@ if (typeof style === 'string' && datePresetFormats[style]) {return datePresetFor
|
|
|
15
15
|
}
|
|
16
16
|
if (typeof style === 'object' && style !== null) {try {return new Intl.DateTimeFormat('ru-RU', style).format(d)
|
|
17
17
|
} catch (_) {return datePresetFormats.short.format(d)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
18
|
+
}}
|
|
20
19
|
return datePresetFormats.short.format(d)
|
|
21
20
|
}
|
|
22
21
|
const formatCompactNumber = (val) => {if (val === null || val === undefined || val === '') return ''
|
|
@@ -35,8 +34,7 @@ const currencyFormats = new Map()
|
|
|
35
34
|
const getCurrencyFormat = (cur) => {const c = (cur || 'RUB').toUpperCase()
|
|
36
35
|
if (!currencyFormats.has(c)) {try {currencyFormats.set(c, new Intl.NumberFormat('ru-RU', { style: 'currency', currency: c }))
|
|
37
36
|
} catch (e) {currencyFormats.set(c, numberFormat)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
37
|
+
}}
|
|
40
38
|
return currencyFormats.get(c)
|
|
41
39
|
}
|
|
42
40
|
const formatNumber = (val) => {if (val === null || val === undefined || val === '') return ''
|
|
@@ -62,8 +60,7 @@ if (absSec < 2592000) return relativeTimeFormat.format(Math.round(diffSec / 8640
|
|
|
62
60
|
if (absSec < 31536000) return relativeTimeFormat.format(Math.round(diffSec / 2592000), 'month')
|
|
63
61
|
return relativeTimeFormat.format(Math.round(diffSec / 31536000), 'year')
|
|
64
62
|
}
|
|
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: 'папке' }
|
|
66
|
-
}
|
|
63
|
+
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: 'папке' }}
|
|
67
64
|
const keepCase = (src, out) => (src[0] === src[0].toUpperCase() ? out[0].toUpperCase() + out.slice(1) : out)
|
|
68
65
|
const inflectWord = (word, cName) => {if (!word || typeof word !== 'string') return word
|
|
69
66
|
const lower = word.toLowerCase()
|
|
@@ -72,10 +69,8 @@ if (INFLECT_CUSTOM[lower] && INFLECT_CUSTOM[lower][cName]) {return keepCase(word
|
|
|
72
69
|
if (/[a-zA-Z0-9_-]/.test(word) || /^[А-ЯЁ]{2,}$/.test(word)) return word
|
|
73
70
|
if (/[оеиую]$/i.test(word) && !/(ко|ло|но|то|во|ро|до|по|со|мо|го)$/i.test(word)) return word
|
|
74
71
|
const w = lower
|
|
75
|
-
const endings = [
|
|
76
|
-
['
|
|
77
|
-
return { gen: s + genEnd, 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 + 'е' })]
|
|
72
|
+
const endings = [['ия', 2, (s) => ({ gen: s + 'ии', dat: s + 'ии', acc: s + 'ию', ins: s + 'ией', pre: s + 'ии' })],['а', 1, (s) => {const genEnd = /[гкхжшчщ]/.test(s.slice(-1)) ? 'и' : 'ы'
|
|
73
|
+
return { gen: s + genEnd, 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 + 'и' })],['й', 1, (s) => ({ gen: s + 'я', dat: s + 'ю', acc: s + 'я', ins: s + 'ем', pre: s + 'е' })]
|
|
79
74
|
]
|
|
80
75
|
for (const [suffix, cut, build] of endings) {if (w.endsWith(suffix)) return keepCase(word, build(w.slice(0, -cut))[cName] || w)
|
|
81
76
|
}
|
|
@@ -156,8 +151,7 @@ if (matched === qStems.length && qStems.length > 0) return 80
|
|
|
156
151
|
if (matched > 0) return 50
|
|
157
152
|
return 0
|
|
158
153
|
}
|
|
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
|
-
}
|
|
154
|
+
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: 'Проверьте идентификатор модели в настройках.' }}
|
|
161
155
|
const fromMap = (key, rawMsg) => ({code: String(key), title: ERROR_MAP[key].title, message: ERROR_MAP[key].message, hint: ERROR_MAP[key].hint, raw: rawMsg
|
|
162
156
|
})
|
|
163
157
|
const humanizeError = (err) => {if (!err) return null
|
|
@@ -174,19 +168,16 @@ if (/websocket.*(?:closed|failed)/i.test(rawMsg)) return fromMap('WS_CLOSED', ra
|
|
|
174
168
|
if (/insufficient_quota|quota\s*exceeded/i.test(rawMsg)) return fromMap('QUOTA_EXCEEDED', rawMsg)
|
|
175
169
|
if (/context.*(?:exceeded|overflow|length)/i.test(rawMsg)) return fromMap('CONTEXT_OVERFLOW', rawMsg)
|
|
176
170
|
if (/model.*not.*found|model.*does not exist/i.test(rawMsg)) return fromMap('MODEL_NOT_FOUND', rawMsg)
|
|
177
|
-
return { code: 'UNKNOWN', title: 'Ошибка операции', message: rawMsg, hint: 'Проверьте параметры операции и логи.', raw: rawMsg }
|
|
178
|
-
}
|
|
171
|
+
return { code: 'UNKNOWN', title: 'Ошибка операции', message: rawMsg, hint: 'Проверьте параметры операции и логи.', raw: rawMsg }}
|
|
179
172
|
const formatErrorToast = (err) => {const h = humanizeError(err)
|
|
180
173
|
if (!h) return null
|
|
181
174
|
return {type: 'error',title: h.title,description: h.message + (h.hint ? ' ' + h.hint : '')
|
|
182
|
-
}
|
|
183
|
-
}
|
|
175
|
+
}}
|
|
184
176
|
const makeIssueUrl = (opts = {}, version = '') => {const repo = 'GooDAnDReaDY/dsh-russian-lang'
|
|
185
177
|
const title = opts.title || (opts.plugin
|
|
186
178
|
? `[Перевод] Запрос локализации для плагина ${opts.plugin}`
|
|
187
179
|
: '[Ошибка перевода] Неточный перевод фразы')
|
|
188
|
-
const bodyLines = [
|
|
189
|
-
'### Описание проблемы',opts.description || (opts.plugin
|
|
180
|
+
const bodyLines = ['### Описание проблемы',opts.description || (opts.plugin
|
|
190
181
|
? `Просьба добавить русскую локализацию для плагина \`${opts.plugin}\`.`
|
|
191
182
|
: 'Обнаружена неточность в переводе интерфейса.'),'','### Технический контекст',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 || '_Опишите ваш вариант перевода..._'
|
|
192
183
|
].filter(Boolean)
|
|
@@ -201,16 +192,12 @@ const count = Object.keys((dicts && dicts[ns]) || {}).length
|
|
|
201
192
|
return count > 0 ? { status: 'full', count, label: `RU: ${count} строк` } : none
|
|
202
193
|
}
|
|
203
194
|
const typoQuotes = (text) => text
|
|
204
|
-
.replace(/"([^"\n]{1,200})"/g, '«$1»')
|
|
205
|
-
.replace(/[“„]([^“”\n]{1,200})[”"]/g, '«$1»')
|
|
206
|
-
.replace(/[『「]([^』」\n]{1,200})[』」]/g, '«$1»')
|
|
195
|
+
.replace(/"([^"\n]{1,200})"/g, '«$1»').replace(/[“„]([^“”\n]{1,200})[”"]/g, '«$1»').replace(/[『「]([^』」\n]{1,200})[』」]/g, '«$1»')
|
|
207
196
|
const typoDash = (text) => text
|
|
208
|
-
.replace(/(^|[\s(\[«])--(?=\s|$)/g, '$1—')
|
|
209
|
-
.replace(/(^|[\s(\[«])-(?=\s)/g, '$1—')
|
|
197
|
+
.replace(/(^|[\s(\[«])--(?=\s|$)/g, '$1—').replace(/(^|[\s(\[«])-(?=\s)/g, '$1—')
|
|
210
198
|
const typoPunct = (text) => text.replace(/\s+([,.:;!?])(?=\s|$)/g, '$1')
|
|
211
199
|
const TYPO_SHORT = new Set(['в', 'с', 'к', 'о', 'у', 'а', 'и', 'но', 'не', 'ни', 'на', 'по', 'до', 'из', 'за', 'от', 'об'])
|
|
212
|
-
const typoNbsp = (text) => text.replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (
|
|
213
|
-
TYPO_SHORT.has(word) ? lead + word + ' ' : match
|
|
200
|
+
const typoNbsp = (text) => text.replace(/(^|[\s(\[«])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (TYPO_SHORT.has(word) ? lead + word + ' ' : match
|
|
214
201
|
))
|
|
215
202
|
const YO_EDGE_L = '(?<![а-яёА-ЯЁ])'
|
|
216
203
|
const YO_EDGE_R = '(?![а-яёА-ЯЁ])'
|
|
@@ -224,8 +211,7 @@ const makeTypoYo = (pairs) => {const list = typeof pairs === 'string'
|
|
|
224
211
|
const compiled = list.map((p) => [new RegExp(YO_EDGE_L + p[0] + YO_EDGE_R, 'gi'), p[1]])
|
|
225
212
|
return (text) => {for (const [re, repl] of compiled) text = text.replace(re, (match) => yoCase(match, repl))
|
|
226
213
|
return text
|
|
227
|
-
}
|
|
228
|
-
}
|
|
214
|
+
}}
|
|
229
215
|
const LAYOUT_LAT_TO_CYR = { ...EN_RU_KEYS, '/': '.', '`': 'ё' }
|
|
230
216
|
const LAYOUT_CYR_TO_LAT = (() => {const out = {}
|
|
231
217
|
for (const k in LAYOUT_LAT_TO_CYR) out[LAYOUT_LAT_TO_CYR[k]] = k
|
|
@@ -242,25 +228,26 @@ return words.filter((w) => freq.has(w) || localDict.has(w)).length / words.lengt
|
|
|
242
228
|
const candidate = (value, direction) => {if (direction === 'lat2cyr') {const converted = translit(value, LAYOUT_LAT_TO_CYR)
|
|
243
229
|
if (!/[а-яё]{2}/.test(converted)) return null
|
|
244
230
|
if (ruWordFraction(converted) < 0.7) return null
|
|
245
|
-
return { converted }
|
|
246
|
-
}
|
|
231
|
+
return { converted }}
|
|
247
232
|
const converted = translit(value, LAYOUT_CYR_TO_LAT)
|
|
248
233
|
return converted.startsWith('/') ? { converted } : null
|
|
249
234
|
}
|
|
250
235
|
const learnWords = (text) => {for (const w of text.toLowerCase().split(/[^а-яё]+/).filter(Boolean)) {if (w.length >= 3) localDict.add(w)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return { ruWordFraction, candidate, learnWords, localDict }
|
|
254
|
-
}
|
|
236
|
+
}}
|
|
237
|
+
return { ruWordFraction, candidate, learnWords, localDict }}
|
|
255
238
|
const SHORT_WORDS_RE = /(^|[\s(«])([вВнНсСпПоОкКуУиИаА]|из|от|до|за|по|со|во|ко|об|на|под|над|при|про|без|для|не|ни|но)( )/g
|
|
256
239
|
const formatInputLive = (text) => {if (!text || typeof text !== 'string') return text
|
|
257
240
|
const parts = text.split(/(```[\s\S]*?```|`[^`\n]*`)/g)
|
|
258
241
|
for (let i = 0; i < parts.length; i += 2) {let s = parts[i]
|
|
259
242
|
if (!s) continue
|
|
243
|
+
const lq = [], cf = []
|
|
244
|
+
s = s.replace(/"([^"\n]*[a-zA-Z][^"\n]*)"/g, (m) => `\x01L${lq.push(m) - 1}\x01`)
|
|
245
|
+
s = s.replace(/(^|\s)(--[a-zA-Z0-9_\u0400-\u04FF-]+)/g, (m, p1, p2) => `${p1}\x01C${cf.push(p2) - 1}\x01`)
|
|
246
|
+
s = s.replace(/(^|\s)(--)(?=\s|$)/g, (m, p1, p2) => /[a-zA-Z]/.test(text) ? `${p1}\x01C${cf.push(p2) - 1}\x01` : m)
|
|
260
247
|
s = s.replace(/--/g, '—')
|
|
261
|
-
s = s.replace(/(^|[\s([{-])"/g, '$1«')
|
|
262
|
-
s = s.replace(/"/g, '»')
|
|
248
|
+
s = s.replace(/(^|[\s([{-])"/g, '$1«').replace(/"/g, '»')
|
|
263
249
|
s = s.replace(SHORT_WORDS_RE, '$1$2\u00A0')
|
|
250
|
+
s = s.replace(/\x01C(\d+)\x01/g, (_, j) => cf[+j]).replace(/\x01L(\d+)\x01/g, (_, j) => lq[+j])
|
|
264
251
|
parts[i] = s
|
|
265
252
|
}
|
|
266
253
|
return parts.join('')
|
|
@@ -276,11 +263,9 @@ if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {return RUSSIAN_SLASH_ALIASES[lowerCmd] + r
|
|
|
276
263
|
}
|
|
277
264
|
return input
|
|
278
265
|
}
|
|
279
|
-
const PHONETIC_LAT_TO_CYR = [
|
|
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', 'кс']
|
|
266
|
+
const PHONETIC_LAT_TO_CYR = [['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', 'кс']
|
|
281
267
|
]
|
|
282
|
-
const PHONETIC_CYR_TO_LAT = [
|
|
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'], ['ъ', ''], ['ь', '']
|
|
268
|
+
const PHONETIC_CYR_TO_LAT = [['щ', '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'], ['ъ', ''], ['ь', '']
|
|
284
269
|
]
|
|
285
270
|
const phoneticTranslit = (text, direction = 'lat2cyr') => {if (!text || typeof text !== 'string') return text
|
|
286
271
|
let res = text
|
|
@@ -294,8 +279,7 @@ res = res.replaceAll(fromTitle, toTitle)
|
|
|
294
279
|
}
|
|
295
280
|
res = res.replaceAll(from, to)
|
|
296
281
|
if (from.length === 1) {res = res.replaceAll(fromUpper, toUpper)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
282
|
+
}}
|
|
299
283
|
return res
|
|
300
284
|
}
|
|
301
285
|
const detectInputLayout = (text) => {if (!text || typeof text !== 'string') return null
|
|
@@ -316,8 +300,7 @@ const SYSTEM_PROMPT_PRESETS = {technical_expert: {id: 'technical_expert',label:
|
|
|
316
300
|
},code_reviewer: {id: 'code_reviewer',label: 'Код-ревьюер',desc: 'Поиск багов, безопасность и архитектурный аудит',text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
|
|
317
301
|
},architect: {id: 'architect',label: 'Системный архитектор',desc: 'Контракты API/CLI, модульность и надёжность',text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
|
|
318
302
|
},tutor: {id: 'tutor',label: 'Наставник (ментор)',desc: 'Понятные объяснения сложных концепций и пошаговый разбор',text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
|
|
319
|
-
}
|
|
320
|
-
}
|
|
303
|
+
}}
|
|
321
304
|
const exportSessionToMarkdown = (session, options = {}) => {if (!session) return ''
|
|
322
305
|
const title = session.title || session.name || 'Диалог DSH'
|
|
323
306
|
const date = session.createdAt ? new Date(session.createdAt) : new Date()
|
|
@@ -350,53 +333,31 @@ return md
|
|
|
350
333
|
const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}, domEn = {}, options = {}) => {if (!text || typeof text !== 'string') return null
|
|
351
334
|
const trimmed = text.trim()
|
|
352
335
|
if (!trimmed) return null
|
|
353
|
-
if (overrides && typeof overrides === 'object') {if (overrides[trimmed] !== undefined) {return { key: trimmed, value: overrides[trimmed], source: 'override' }
|
|
354
|
-
}
|
|
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' }
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
336
|
+
if (overrides && typeof overrides === 'object') {if (overrides[trimmed] !== undefined) {return { key: trimmed, value: overrides[trimmed], source: 'override' }}
|
|
337
|
+
for (const [k, v] of Object.entries(overrides)) {if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {return { key: k, value: v, source: 'override' }}}}
|
|
359
338
|
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' }
|
|
361
|
-
}
|
|
362
|
-
for (const [
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
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' }
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (zhRu[trimmed] !== undefined) {return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }
|
|
371
|
-
}
|
|
372
|
-
}
|
|
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' }
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
if (domEn[trimmed] !== undefined) {return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }
|
|
377
|
-
}
|
|
378
|
-
}
|
|
339
|
+
if (map[trimmed] !== undefined) {return { ns, key: trimmed, value: map[trimmed], source: 'dictionary' }}
|
|
340
|
+
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' }}}}}
|
|
341
|
+
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' }}}
|
|
342
|
+
if (zhRu[trimmed] !== undefined) {return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }}}
|
|
343
|
+
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' }}}
|
|
344
|
+
if (domEn[trimmed] !== undefined) {return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }}}
|
|
379
345
|
if (options && options.detectUntranslated) {const hasRu = /[а-яё]/i.test(trimmed)
|
|
380
346
|
const hasZh = /[㐀-鿿豈-]/.test(trimmed)
|
|
381
347
|
const hasEn = /[a-z]/i.test(trimmed)
|
|
382
|
-
if (!hasRu && hasZh) {return { key: trimmed, value: trimmed, source: 'untranslated_zh' }
|
|
383
|
-
}
|
|
384
|
-
if (!hasRu && hasEn) {return { key: trimmed, value: trimmed, source: 'untranslated_en' }
|
|
385
|
-
}
|
|
386
|
-
}
|
|
348
|
+
if (!hasRu && hasZh) {return { key: trimmed, value: trimmed, source: 'untranslated_zh' }}
|
|
349
|
+
if (!hasRu && hasEn) {return { key: trimmed, value: trimmed, source: 'untranslated_en' }}}
|
|
387
350
|
return null
|
|
388
351
|
}
|
|
389
352
|
const generateBugReportSnippet = (info = {}) => {const { key, ns, original, current, override, pkgVersion = '0.3.1' } = info
|
|
390
|
-
const lines = [
|
|
391
|
-
'### 🌐 Неточность перевода / Пользовательское предложение','- **Пакет:** `@goodandready/dsh-russian-lang@v' + pkgVersion + '`',ns ? '- **Пространство имён:** `' + ns + '`' : null,key ? '- **Ключ:** `' + key + '`' : null,original ? '- **Оригинальный текст:** `' + original + '`' : null,current ? '- **Текущий перевод:** `' + current + '`' : null,override ? '- **Предлагаемый перевод:** `' + override + '`' : null,].filter(Boolean)
|
|
353
|
+
const lines = ['### 🌐 Неточность перевода / Пользовательское предложение','- **Пакет:** `@goodandready/dsh-russian-lang@v' + pkgVersion + '`',ns ? '- **Пространство имён:** `' + ns + '`' : null,key ? '- **Ключ:** `' + key + '`' : null,original ? '- **Оригинальный текст:** `' + original + '`' : null,current ? '- **Текущий перевод:** `' + current + '`' : null,override ? '- **Предлагаемый перевод:** `' + override + '`' : null,].filter(Boolean)
|
|
392
354
|
return lines.join('\n')
|
|
393
355
|
}
|
|
394
356
|
const bilingualCommandMatch = (query, command, options = {}) => {if (!query || !command) return { matched: false, score: 0 }
|
|
395
357
|
const q = String(query).trim().toLowerCase()
|
|
396
358
|
if (!q) return { matched: false, score: 0 }
|
|
397
359
|
const cmdObj = typeof command === 'string' ? { title: command } : command
|
|
398
|
-
const candidates = [
|
|
399
|
-
cmdObj.title,cmdObj.label,cmdObj.name,cmdObj.originalTitle,cmdObj.enTitle,cmdObj.id,cmdObj.description,cmdObj.category,...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
|
|
360
|
+
const candidates = [cmdObj.title,cmdObj.label,cmdObj.name,cmdObj.originalTitle,cmdObj.enTitle,cmdObj.id,cmdObj.description,cmdObj.category,...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
|
|
400
361
|
].filter((c) => typeof c === 'string' && c.trim())
|
|
401
362
|
if (!candidates.length) return { matched: false, score: 0 }
|
|
402
363
|
const queryVariants = [{ text: q, kind: 'direct' }]
|
|
@@ -417,25 +378,21 @@ for (const variant of queryVariants) {for (const cand of candidates) {const scor
|
|
|
417
378
|
if (score > bestScore) {bestScore = score
|
|
418
379
|
bestVariant = variant
|
|
419
380
|
bestCandidate = cand
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
381
|
+
}}}
|
|
423
382
|
const threshold = options.threshold ?? 50
|
|
424
383
|
return {matched: bestScore >= threshold,score: bestScore,matchedText: bestCandidate,variant: bestVariant ? bestVariant.text : q,kind: bestVariant ? bestVariant.kind : 'none'
|
|
425
|
-
}
|
|
426
|
-
}
|
|
384
|
+
}}
|
|
427
385
|
const filterCommands = (query, commands = [], options = {}) => {if (!Array.isArray(commands)) return []
|
|
428
386
|
if (!query || !String(query).trim()) return commands
|
|
429
387
|
const results = []
|
|
430
388
|
for (const cmd of commands) {const res = bilingualCommandMatch(query, cmd, options)
|
|
431
389
|
if (res.matched) {results.push({ item: cmd, ...res })
|
|
432
|
-
}
|
|
433
|
-
}
|
|
390
|
+
}}
|
|
434
391
|
return results.sort((a, b) => b.score - a.score).map((r) => r.item)
|
|
435
392
|
}
|
|
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.
|
|
393
|
+
const RU = {"common":{"back":"Назад","brand.localBuild":"Локальная сборка DSH","cancel":"Отмена","close":"Закрыть","codeBlock.title":"Блок кода","codeBlock.unwrap":"Не переносить строки","codeBlock.wrap":"Переносить строки","collapse":"Свернуть","copied":"Скопировано","copy":"Копировать","copy.compactJson":"Копировать компактный JSON","copy.failed":"Не удалось скопировать","copy.json":"Копировать JSON","copy.optionsHint":"{action}; правый клик для вариантов копирования","copy.path":"Копировать путь к свойству","copy.prettyJson":"Копировать форматированный JSON","copy.value":"Копировать значение","delete":"Удалить","edit":"Изменить","expand":"Развернуть","json.label":"JSON","load.failed":"Не удалось загрузить","loading":"Загрузка…","markdown.footnotes":"Сноски","markdown.truncatedCharacters":"… обрезано на {total} символах","more":"Ещё","next":"Дальше","none":"Нет","number.million":"{value}M","number.thousand":"{value}K","ok":"ОК","previous":"Назад","retry":"Повторить","save":"Сохранить","search":"Поиск","skip":"Пропустить","submit":"Отправить","submitting":"Отправка…","truncated":"Обрезано","unknown":"Неизвестно"},"dsh-cron":{"sidebar.label":"Задачи по расписанию"},"dsh-usage-stats":{"footer.todayLabel":"Сегодня"},"pluginMarket":{"trigger":"Магазин плагинов"}}
|
|
437
394
|
const ZH_RU = {}
|
|
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":"
|
|
395
|
+
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 ↗ ] на блоках сообщений."}
|
|
439
396
|
const SETTINGS_NS_NAME = 'russian-lang'
|
|
440
397
|
const ROW_CONFIG_KEY = '@goodandready/dsh-russian-lang#russian-lang'
|
|
441
398
|
function apply(ctx) {const runtime = ctx.locale
|
|
@@ -447,37 +404,26 @@ return null
|
|
|
447
404
|
const rawScope = resolveScope(ctx)
|
|
448
405
|
const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
|
|
449
406
|
}
|
|
450
|
-
for (const ns of Object.keys(RU)) {ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', RU[ns]) }
|
|
451
|
-
catch (err) { return () => {} }
|
|
452
|
-
}, 'dsh-russian-lang: ' + ns)
|
|
407
|
+
for (const ns of Object.keys(RU)) {ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', RU[ns]) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
|
|
453
408
|
}
|
|
454
409
|
const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
|
|
455
410
|
for (const ns of Object.keys(dictMap)) {if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
|
|
456
411
|
const dict = dictMap[ns]
|
|
457
412
|
if (typeof dict !== 'object' || !dict) continue
|
|
458
413
|
if (!RU[ns]) {RU[ns] = dict
|
|
459
|
-
ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) }
|
|
460
|
-
catch (err) { return () => {} }
|
|
461
|
-
}, 'dsh-russian-lang: ' + ns)
|
|
414
|
+
ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
|
|
462
415
|
} else {Object.assign(RU[ns], dict)
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
416
|
+
}}}
|
|
466
417
|
const CACHE_NAME = 'dsh-ru-cache-v1'
|
|
467
418
|
const loadLocalDict = (key) => {try {const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
|
|
468
419
|
return raw ? JSON.parse(raw) : null
|
|
469
|
-
} catch (_) { return null }
|
|
470
|
-
}
|
|
420
|
+
} catch (_) { return null }}
|
|
471
421
|
const saveLocalDict = (key, etag, data) => {try {if (typeof localStorage !== 'undefined') {localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
|
|
472
|
-
}
|
|
473
|
-
} catch (_e) { void _e }
|
|
474
|
-
}
|
|
422
|
+
}} catch (_e) { void _e }}
|
|
475
423
|
try {const cachedCore = loadLocalDict('core')
|
|
476
424
|
if (cachedCore && cachedCore.data && cachedCore.data.core) {registerDictMap(cachedCore.data.core)
|
|
477
425
|
if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, cachedCore.data.zhRu)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
} catch (_e) { void _e }
|
|
426
|
+
}}} catch (_e) { void _e }
|
|
481
427
|
const fetchCachedResource = async (url, cacheKey, onData) => {let etag = null
|
|
482
428
|
let delivered = false
|
|
483
429
|
if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
|
|
@@ -486,16 +432,12 @@ if (matched) {etag = matched.headers.get('etag')
|
|
|
486
432
|
const parsed = await matched.json()
|
|
487
433
|
if (parsed) {delivered = true
|
|
488
434
|
onData(parsed)
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
} catch (_e) { void _e }
|
|
492
|
-
}
|
|
435
|
+
}}} catch (_e) { void _e }}
|
|
493
436
|
if (!delivered && cacheKey) {const local = loadLocalDict(cacheKey)
|
|
494
437
|
if (local && local.data) {etag = local.etag
|
|
495
438
|
delivered = true
|
|
496
439
|
onData(local.data)
|
|
497
|
-
}
|
|
498
|
-
}
|
|
440
|
+
}}
|
|
499
441
|
if (typeof fetch !== 'function') return
|
|
500
442
|
try {const headers = { 'Accept': 'application/json' }
|
|
501
443
|
if (etag) headers['If-None-Match'] = etag
|
|
@@ -507,13 +449,9 @@ const data = await res.json()
|
|
|
507
449
|
if (data) {onData(data)
|
|
508
450
|
if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
|
|
509
451
|
await cache.put(url, cloned)
|
|
510
|
-
} catch (_e) { void _e }
|
|
511
|
-
}
|
|
452
|
+
} catch (_e) { void _e }}
|
|
512
453
|
if (cacheKey) saveLocalDict(cacheKey, newEtag, data)
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
} catch (_e) { void _e }
|
|
516
|
-
}
|
|
454
|
+
}}} catch (_e) { void _e }}
|
|
517
455
|
const loadedPluginNames = new Set()
|
|
518
456
|
const pendingPluginLoads = new Set()
|
|
519
457
|
let pluginBatchTimer = null
|
|
@@ -529,8 +467,7 @@ const url = '/api/dsh-russian-lang/dict/plugins' + qs
|
|
|
529
467
|
const cacheKey = toLoad.length > 0 ? ('plugins_' + toLoad.sort().join('_')) : 'plugins_all'
|
|
530
468
|
fetchCachedResource(url, cacheKey, (data) => {if (data && data.plugins) {registerDictMap(data.plugins)
|
|
531
469
|
for (const n of toLoad) loadedPluginNames.add(n)
|
|
532
|
-
}
|
|
533
|
-
}).catch(() => {}).finally(() => {for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
470
|
+
}}).catch(() => {}).finally(() => {for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
534
471
|
})
|
|
535
472
|
}
|
|
536
473
|
const schedulePluginLoad = (name) => {if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
|
|
@@ -547,103 +484,76 @@ if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
|
547
484
|
}
|
|
548
485
|
if (typeof syncZhDom === 'function') syncZhDom()
|
|
549
486
|
setTimeout(() => { loadPluginDictionaries([]) }, 100)
|
|
550
|
-
}).catch(() => {if (typeof fetch === 'function') {fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
|
|
551
|
-
.then((res) => res.ok ? res.json() : null)
|
|
552
|
-
.then((data) => {if (!data) return
|
|
487
|
+
}).catch(() => {if (typeof fetch === 'function') {fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } }).then((res) => res.ok ? res.json() : null).then((data) => {if (!data) return
|
|
553
488
|
const allDicts = Object.assign({}, data.core || {}, data.plugins || {}, data)
|
|
554
489
|
if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
|
|
555
490
|
if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
556
491
|
}
|
|
557
492
|
registerDictMap(allDicts)
|
|
558
493
|
if (typeof syncZhDom === 'function') syncZhDom()
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
})
|
|
563
|
-
const origTranslate = runtime.translate.bind(runtime)
|
|
494
|
+
}).catch(() => {})
|
|
495
|
+
}})
|
|
564
496
|
const getOverrides = () => {try {const value = scope.getSnapshot().value
|
|
565
|
-
return value && value.overrides ? value.overrides : {}
|
|
566
|
-
} catch (err) { return {} }
|
|
567
|
-
}
|
|
497
|
+
return value && value.overrides ? value.overrides : {}} catch (err) { return {} }}
|
|
568
498
|
const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
|
|
569
|
-
try {runtime.formatNumber = formatNumber
|
|
570
|
-
runtime.formatCompactNumber = formatCompactNumber
|
|
571
|
-
runtime.formatDate = formatDate
|
|
572
|
-
runtime.formatTokens = formatTokens
|
|
573
|
-
runtime.formatRelativeTime = formatRelativeTime
|
|
574
|
-
runtime.formatCurrency = formatCurrency
|
|
575
|
-
runtime.inflect = inflect
|
|
576
|
-
runtime.getPluginLocalizationStatus = getPluginLocalizationStatus
|
|
577
|
-
runtime.stemRussian = stemRussian
|
|
578
|
-
runtime.fuzzyMatchRu = fuzzyMatchRu
|
|
579
|
-
runtime.humanizeError = humanizeError
|
|
580
|
-
runtime.plural = plural
|
|
581
|
-
runtime.pluralForm = pluralForm
|
|
582
|
-
} catch (err) { }
|
|
583
499
|
const lookupChain = () => {try {const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
|
|
584
500
|
if (Array.isArray(chain) && chain.length) return chain
|
|
585
501
|
} catch (err) { }
|
|
586
502
|
return [runtime.getLocale().active]
|
|
587
503
|
}
|
|
588
504
|
const translationRegistry = new Map()
|
|
589
|
-
const lookup = (ns, key) =>
|
|
590
|
-
|
|
591
|
-
}
|
|
505
|
+
const lookup = (ns, key) => (runtime.lookup.length >= 3 ? runtime.lookup(ns, key, lookupChain()) : runtime.lookup(ns, key))
|
|
506
|
+
const syncLang = () => {try {if (typeof document !== 'undefined' && document.documentElement && runtime.getLocale().active === 'ru') document.documentElement.lang = 'ru-RU'
|
|
507
|
+
} catch (err) { }}
|
|
508
|
+
const RUNTIME_PROP_NAMES = ['plural','pluralForm','formatDate','formatCompactNumber','formatTokens','formatNumber','formatRelativeTime','formatCurrency','inflect','getPluginLocalizationStatus','stemRussian','fuzzyMatchRu','humanizeError']
|
|
509
|
+
ctx.effect(() => {const origTranslate = runtime.translate
|
|
510
|
+
const origProps = {}
|
|
511
|
+
for (const p of RUNTIME_PROP_NAMES) origProps[p] = runtime[p]
|
|
512
|
+
runtime.plural = plural
|
|
513
|
+
runtime.pluralForm = pluralForm
|
|
514
|
+
runtime.formatDate = formatDate
|
|
515
|
+
runtime.formatCompactNumber = formatCompactNumber
|
|
516
|
+
runtime.formatTokens = formatTokens
|
|
517
|
+
Object.assign(runtime, { formatNumber, formatRelativeTime, formatCurrency, inflect, getPluginLocalizationStatus, stemRussian, fuzzyMatchRu, humanizeError })
|
|
518
|
+
const boundOrig = typeof origTranslate === 'function' ? origTranslate.bind(runtime) : (() => '')
|
|
592
519
|
runtime.translate = function (ns, key, params) {if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
595
|
-
}
|
|
520
|
+
const ov = getOverrides()
|
|
521
|
+
if (ov[key] !== undefined) return params ? fill(ov[key], params) : ov[key]
|
|
596
522
|
if (runtime.getLocale().active === 'ru' && params) {const n = params.n ?? params.count
|
|
597
523
|
if (typeof n === 'number') {const form = pluralForm(n)
|
|
598
524
|
const m = /^(.*)[.](one|other)$/.exec(key)
|
|
599
|
-
if (m) {if (form === 'few' || form === 'many') {const
|
|
600
|
-
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (template !== undefined) {return fill(template, params)
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
const res = origTranslate(ns, key, params)
|
|
612
|
-
if (typeof res === 'string' && res && res.length < 300) {translationRegistry.set(res.trim(), { ns, key, params, value: res })
|
|
613
|
-
}
|
|
525
|
+
if (m) {if (form === 'few' || form === 'many') {const tmpl = lookup(ns, m[1] + '.' + form) ?? lookup('common', m[1] + '.' + form)
|
|
526
|
+
if (tmpl !== undefined) return fill(tmpl, params)
|
|
527
|
+
}} else if (form !== 'other' && !/[.](one|other|few|many)$/.test(key)) {const tmpl = lookup(ns, key + '.' + form) ?? lookup('common', key + '.' + form)
|
|
528
|
+
if (tmpl !== undefined) return fill(tmpl, params)
|
|
529
|
+
}}}
|
|
530
|
+
const res = boundOrig(ns, key, params)
|
|
531
|
+
if (typeof res === 'string' && res && res.length < 300) translationRegistry.set(res.trim(), { ns, key, params, value: res })
|
|
614
532
|
return res
|
|
615
533
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
} catch (err) { }
|
|
620
|
-
}
|
|
621
|
-
const native = runtime.getLocale().locales.some((l) => l.id === 'ru')
|
|
622
|
-
if (!native) {runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
|
|
534
|
+
let removeLang = null
|
|
535
|
+
if (!runtime.getLocale().locales.some((l) => l.id === 'ru') && typeof runtime.addLanguage === 'function') {removeLang = runtime.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
|
|
623
536
|
syncLang()
|
|
624
537
|
}
|
|
538
|
+
return () => {runtime.translate = origTranslate
|
|
539
|
+
for (const p of RUNTIME_PROP_NAMES) {if (origProps[p] !== undefined) runtime[p] = origProps[p]
|
|
540
|
+
else delete runtime[p]
|
|
541
|
+
}
|
|
542
|
+
if (typeof removeLang === 'function') { try { removeLang() } catch (_) { } }}}, 'dsh-russian-lang: runtime-lifecycle')
|
|
625
543
|
const syncFlag = () => {try {const wantRu = runtime.getLocale().active === 'ru'
|
|
626
544
|
const value = scope.getSnapshot().value || {}
|
|
627
545
|
if (!!value.enabled !== wantRu) {const r = scope.set('enabled', wantRu)
|
|
628
546
|
if (r && typeof r.catch === 'function') {r.catch((err) => {console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
|
|
629
547
|
})
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
} catch (err) {}
|
|
633
|
-
}
|
|
634
|
-
ctx.effect(() => {try { return runtime.subscribe(syncFlag) }
|
|
635
|
-
catch (err) { return undefined }
|
|
636
|
-
}, 'dsh-russian-lang: sync-flag')
|
|
548
|
+
}}} catch (err) {}}
|
|
549
|
+
ctx.effect(() => {try { return runtime.subscribe(syncFlag) } catch (err) { return undefined }}, 'dsh-russian-lang: sync-flag')
|
|
637
550
|
const activate = () => {try {if (runtime.getLocale().active === 'ru') return
|
|
638
551
|
runtime.setLocale('ru')
|
|
639
|
-
} catch (err) { console.warn('dsh-russian-lang: activate failed', err) }
|
|
640
|
-
}
|
|
552
|
+
} catch (err) { console.warn('dsh-russian-lang: activate failed', err) }}
|
|
641
553
|
let booted = false
|
|
642
554
|
const tryBoot = () => {if (booted) return
|
|
643
555
|
try {const value = scope.getSnapshot().value
|
|
644
|
-
if (value && value.enabled === true) { booted = true; activate() }
|
|
645
|
-
} catch (err) { }
|
|
646
|
-
}
|
|
556
|
+
if (value && value.enabled === true) { booted = true; activate() }} catch (err) { }}
|
|
647
557
|
ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
|
|
648
558
|
tryBoot()
|
|
649
559
|
const unsubscribeLang = runtime.subscribe(syncLang)
|
|
@@ -654,9 +564,7 @@ const SPELL_WAS = 'data-russian-lang-spell-was'
|
|
|
654
564
|
const LANG_WAS = 'data-russian-lang-lang-was'
|
|
655
565
|
const EDITABLE = 'textarea, input[type=text], input[type=search], [contenteditable=""], [contenteditable="true"]'
|
|
656
566
|
const MONO_RE = /mono|consol|courier/i
|
|
657
|
-
const isMonoField = (el) => {try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
|
|
658
|
-
catch (err) { return false }
|
|
659
|
-
}
|
|
567
|
+
const isMonoField = (el) => {try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') } catch (err) { return false }}
|
|
660
568
|
const spellOn = (el) => {if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
|
|
661
569
|
el.setAttribute(SPELL_ON, '1')
|
|
662
570
|
el.setAttribute(SPELL_WAS, el.getAttribute('spellcheck') ?? '')
|
|
@@ -687,18 +595,13 @@ if (spellObserver) return
|
|
|
687
595
|
spellObserver = new MutationObserver((records) => {for (const record of records) {for (const node of record.addedNodes) {if (node.nodeType !== 1) continue
|
|
688
596
|
if (node.matches(EDITABLE)) spellOn(node)
|
|
689
597
|
node.querySelectorAll ? node.querySelectorAll(EDITABLE).forEach(spellOn) : null
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
})
|
|
598
|
+
}}})
|
|
693
599
|
spellObserver.observe(document.body, { childList: true, subtree: true })
|
|
694
|
-
} catch (err) { }
|
|
695
|
-
}
|
|
600
|
+
} catch (err) { }}
|
|
696
601
|
const unsubscribeSpell = runtime.subscribe(syncSpell)
|
|
697
602
|
ctx.effect(() => {return () => {unsubscribeSpell()
|
|
698
603
|
if (spellObserver) spellObserver.disconnect()
|
|
699
|
-
try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }
|
|
700
|
-
}
|
|
701
|
-
}, 'dsh-russian-lang: spellcheck')
|
|
604
|
+
try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }}}, 'dsh-russian-lang: spellcheck')
|
|
702
605
|
syncSpell()
|
|
703
606
|
const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
|
|
704
607
|
const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
@@ -719,8 +622,7 @@ if (/\{[a-zA-Z_]\w*\}/.test(zhText)) {const parts = zhText.split(/\{[a-zA-Z_]\w*
|
|
|
719
622
|
if (parts.some((p) => p.length === 0)) continue
|
|
720
623
|
ZH_PATTERNS.push({ re: new RegExp(parts.map(ZH_RE_ESC).join('([\\s\\S]*?)')), ruParts: ruText.split(/\{[a-zA-Z_]\w*\}/g) })
|
|
721
624
|
} else {ZH_EXACT.set(zhText, ruText)
|
|
722
|
-
}
|
|
723
|
-
}
|
|
625
|
+
}}
|
|
724
626
|
ZH_PATTERNS.sort((a, b) => b.re.source.length - a.re.source.length)
|
|
725
627
|
rebuildZhSorted()
|
|
726
628
|
}
|
|
@@ -739,14 +641,12 @@ for (const p of ZH_PATTERNS) {const m = p.re.exec(trimmed)
|
|
|
739
641
|
if (m && m[0] === trimmed) {let out = p.ruParts[0]
|
|
740
642
|
for (let i = 1; i < p.ruParts.length; i++) out += m[i] + p.ruParts[i]
|
|
741
643
|
return text.replace(trimmed, out)
|
|
742
|
-
}
|
|
743
|
-
}
|
|
644
|
+
}}
|
|
744
645
|
let replaced = text
|
|
745
646
|
let changed = false
|
|
746
647
|
for (const [zhPhrase, ruPhrase] of zhSortedExact) {if (zhPhrase.length >= 2 && replaced.includes(zhPhrase)) {replaced = replaced.split(zhPhrase).join(ruPhrase)
|
|
747
648
|
changed = true
|
|
748
|
-
}
|
|
749
|
-
}
|
|
649
|
+
}}
|
|
750
650
|
if (changed) return replaced
|
|
751
651
|
return null
|
|
752
652
|
}
|
|
@@ -765,9 +665,7 @@ continue
|
|
|
765
665
|
if (trimmed.length >= 1 && ZH_CJK.test(val)) {hits.push(node)
|
|
766
666
|
} else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {const p = node.parentElement
|
|
767
667
|
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])
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
668
|
+
}}}
|
|
771
669
|
for (const node of hits) {const next = zhTranslateText(node.nodeValue)
|
|
772
670
|
if (next && next !== node.nodeValue) node.nodeValue = next
|
|
773
671
|
}
|
|
@@ -777,19 +675,12 @@ if (DOM_EN_TEXT[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
|
|
|
777
675
|
} else if (DOM_EN_ATTRS[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
|
|
778
676
|
} else if (ZH_CJK.test(v)) {const next = zhTranslateText(v)
|
|
779
677
|
if (next) el.setAttribute(attr, next)
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
678
|
+
}}}}
|
|
784
679
|
if (root.getAttribute) {for (const attr of ['title', 'placeholder', 'aria-label']) {const v = root.getAttribute(attr)
|
|
785
680
|
if (v) {const tr = v.trim()
|
|
786
681
|
if (DOM_EN_TEXT[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
|
|
787
682
|
else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
} catch (err) { }
|
|
792
|
-
}
|
|
683
|
+
}}}} catch (err) { }}
|
|
793
684
|
let zhObserver = null
|
|
794
685
|
let zhDebounceTimer = null
|
|
795
686
|
let zhWalking = false
|
|
@@ -799,8 +690,7 @@ if (zhWalking) return
|
|
|
799
690
|
zhWalking = true
|
|
800
691
|
try {ZH_WALKER(target || document.body)
|
|
801
692
|
} finally {zhWalking = false
|
|
802
|
-
}
|
|
803
|
-
}, 120)
|
|
693
|
+
}}, 120)
|
|
804
694
|
}
|
|
805
695
|
const syncZhDom = () => {try {if (typeof document === 'undefined') return
|
|
806
696
|
const ru = runtime.getLocale().active === 'ru'
|
|
@@ -811,30 +701,21 @@ return
|
|
|
811
701
|
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
|
|
812
702
|
queueZhWalk(node)
|
|
813
703
|
return
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
})
|
|
704
|
+
}}}}})
|
|
819
705
|
zhObserver.observe(document.body, { childList: true, subtree: true })
|
|
820
706
|
}
|
|
821
707
|
queueZhWalk(document.body)
|
|
822
|
-
} catch (err) { }
|
|
823
|
-
}
|
|
708
|
+
} catch (err) { }}
|
|
824
709
|
const unsubscribeZh = runtime.subscribe(syncZhDom)
|
|
825
710
|
ctx.effect(() => {return () => {unsubscribeZh()
|
|
826
711
|
if (zhObserver) zhObserver.disconnect()
|
|
827
|
-
if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
|
|
828
|
-
}
|
|
829
|
-
}, 'dsh-russian-lang: zh-dom')
|
|
712
|
+
if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }}}, 'dsh-russian-lang: zh-dom')
|
|
830
713
|
syncZhDom()
|
|
831
714
|
const TYPO_YO_PAIRS = [["еще","ещё"],["ее","её"],["черный","чёрный"],["черная","чёрная"],["черные","чёрные"],["зеленый","зелёный"],["желтый","жёлтый"],["легкий","лёгкий"],["тяжелый","тяжёлый"],["надежный","надёжный"],["дешевый","дешёвый"],["идет","идёт"],["дает","даёт"],["ведет","ведёт"],["несет","несёт"],["живет","живёт"],["привел","привёл"],["шел","шёл"],["нее","неё"],["мое","моё"],["серьезно","серьёзно"],["придется","придётся"],["насчет","насчёт"],["твое","твоё"],["свое","своё"],["пойдем","пойдём"],["пришел","пришёл"],["нашел","нашёл"],["идем","идём"],["вперед","вперёд"],["пошел","пошёл"],["ребенка","ребёнка"],["ребенок","ребёнок"],["счет","счёт"],["своем","своём"],["придет","придёт"],["ушел","ушёл"],["ждет","ждёт"],["мертв","мёртв"],["твоем","твоём"],["вернется","вернётся"],["днем","днём"],["найдем","найдём"],["пойдет","пойдёт"],["начнем","начнём"],["вернемся","вернёмся"],["остается","остаётся"],["принес","принёс"],["идешь","идёшь"],["трех","трёх"],["ребенком","ребёнком"],["самолет","самолёт"],["определенно","определённо"],["пойдешь","пойдёшь"],["умрет","умрёт"],["прием","приём"],["прошел","прошёл"],["убьет","убьёт"],["тетя","тётя"],["провел","провёл"],["произойдет","произойдёт"],["семьей","семьёй"],["пойдемте","пойдёмте"],["пройдет","пройдёт"],["возьмем","возьмём"],["вернешься","вернёшься"],["найдешь","найдёшь"],["живешь","живёшь"],["отчет","отчёт"],["займет","займёт"],["ждем","ждём"],["сошел","сошёл"],["вдвоем","вдвоём"],["найдет","найдёт"],["вел","вёл"],["живем","живём"],["зашел","зашёл"],["начнется","начнётся"],["путем","путём"],["подойдет","подойдёт"],["режиссер","режиссёр"],["уйдет","уйдёт"],["придешь","придёшь"],["идемте","идёмте"],["ведешь","ведёшь"],["пес","пёс"],["умрешь","умрёшь"],["ждешь","ждёшь"],["найдете","найдёте"],["найдется","найдётся"],["клево","клёво"],["поймешь","поймёшь"],["разберемся","разберёмся"],["времен","времён"],["введен","введён"],["включен","включён"],["включенных","включённых"],["возьмется","возьмётся"],["завершен","завершён"],["завершенного","завершённого"],["задает","задаёт"],["задается","задаётся"],["заменен","заменён"],["звезд","звёзд"],["звезды","звёзды"],["изменен","изменён"],["истек","истёк"],["незавершенная","незавершённая"],["неподтвержденные","неподтверждённые"],["несохраненные","несохранённые"],["обновлен","обновлён"],["обновленные","обновлённые"],["определен","определён"],["отклонен","отклонён"],["отменен","отменён"],["очередность","очерёдность"],["поврежден","повреждён"],["повторен","повторён"],["подключен","подключён"],["подтвержденные","подтверждённые"],["приемки","приёмки"],["раздает","раздаёт"],["разрешенная","разрешённая"],["разрешенные","разрешённые"],["создаем","создаём"],["сойдется","сойдётся"],["сохранен","сохранён"],["сохраненный","сохранённый"],["счету","счёту"],["тяжелые","тяжёлые"],["темная","тёмная"],["уберет","уберёт"],["удален","удалён"],["учетные","учётные"],["учетных","учётных"],["емкости","ёмкости"],["емкость","ёмкость"]]
|
|
832
715
|
const typoYo = makeTypoYo(TYPO_YO_PAIRS)
|
|
833
716
|
const getTypoConf = () => {try {const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
|
|
834
717
|
if (!t || t.enabled === false) return null
|
|
835
|
-
return { yo: t.yo === true }
|
|
836
|
-
} catch (err) { return null }
|
|
837
|
-
}
|
|
718
|
+
return { yo: t.yo === true }} catch (err) { return null }}
|
|
838
719
|
const typoNode = (node, conf) => {const before = node.nodeValue
|
|
839
720
|
if (!before || !before.match || (before.match(/[\u0400-\u04FF]/g) || []).length < 3) return
|
|
840
721
|
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
|
|
@@ -845,9 +726,7 @@ if (node.parentElement && node.parentElement.closest('[data-chat-flow-status="ru
|
|
|
845
726
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
846
727
|
if (sel && !sel.isCollapsed && sel.rangeCount > 0) {try {const range = sel.getRangeAt(0)
|
|
847
728
|
if (range.intersectsNode ? range.intersectsNode(node) : (sel.containsNode && sel.containsNode(node, true))) {return
|
|
848
|
-
}
|
|
849
|
-
} catch (e) { void e; }
|
|
850
|
-
}
|
|
729
|
+
}} catch (e) { void e; }}
|
|
851
730
|
let after = typoQuotes(before)
|
|
852
731
|
after = typoDash(after)
|
|
853
732
|
after = typoPunct(after)
|
|
@@ -867,8 +746,7 @@ for (const root of typoPending) {if (root.nodeType === 3) typoNode(root, conf)
|
|
|
867
746
|
else typoWalk(root, conf)
|
|
868
747
|
}
|
|
869
748
|
typoPending.clear()
|
|
870
|
-
} catch (err) { }
|
|
871
|
-
}
|
|
749
|
+
} catch (err) { }}
|
|
872
750
|
const typoPending = new Set()
|
|
873
751
|
const queueTypo = (roots) => {for (const r of roots) typoPending.add(r)
|
|
874
752
|
if (!typoQueued) typoQueued = requestAnimationFrame(flushTypo)
|
|
@@ -881,19 +759,15 @@ if (typoObserver) return
|
|
|
881
759
|
typoWalk(document.body, getTypoConf())
|
|
882
760
|
typoObserver = new MutationObserver((records) => {const roots = []
|
|
883
761
|
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)
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
762
|
+
}}}
|
|
887
763
|
if (roots.length > 0) queueTypo(roots)
|
|
888
764
|
})
|
|
889
765
|
typoObserver.observe(document.body, { childList: true, subtree: true })
|
|
890
|
-
} catch (err) { }
|
|
891
|
-
}
|
|
766
|
+
} catch (err) { }}
|
|
892
767
|
const unsubscribeTypo = runtime.subscribe(syncTypo)
|
|
893
768
|
ctx.effect(() => {return () => {unsubscribeTypo()
|
|
894
769
|
if (typoObserver) typoObserver.disconnect()
|
|
895
|
-
}
|
|
896
|
-
}, 'dsh-russian-lang: typography')
|
|
770
|
+
}}, 'dsh-russian-lang: typography')
|
|
897
771
|
syncTypo()
|
|
898
772
|
const layout = makeLayout(new Set(["не","что","ты","это","на","он","мы","как","вы","да","мне","нет","меня","так","но","его","все","она","тебя","если","за","бы","тебе","они","чтобы","же","есть","просто","из","для"]), new Set())
|
|
899
773
|
const layoutFixCandidate = layout.candidate
|
|
@@ -910,16 +784,13 @@ if (el._valueTracker) {el._valueTracker.setValue(value === '' ? '__force__' : ''
|
|
|
910
784
|
}
|
|
911
785
|
try {const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$') || k.startsWith('__reactEventHandlers$'))
|
|
912
786
|
if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {el[propsKey].onChange({ target: el, currentTarget: el })
|
|
913
|
-
}
|
|
914
|
-
} catch (e) { void e; }
|
|
787
|
+
}} catch (e) { void e; }
|
|
915
788
|
try {el.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
|
|
916
789
|
} catch (e) {el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
917
790
|
}
|
|
918
791
|
el.dispatchEvent(new Event('change', { bubbles: true }))
|
|
919
792
|
if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
|
|
920
|
-
try { el.setSelectionRange(cursorStart, end) } catch (e) { void e; }
|
|
921
|
-
}
|
|
922
|
-
}
|
|
793
|
+
try { el.setSelectionRange(cursorStart, end) } catch (e) { void e; }}}
|
|
923
794
|
let layoutHintEl = null
|
|
924
795
|
const layoutCurrentInput = (ev) => {if (ev && ev.target) {const t = ev.target
|
|
925
796
|
if (t.tagName === 'TEXTAREA' || (t.tagName === 'INPUT' && (t.type === 'text' || !t.type))) {return t
|
|
@@ -947,8 +818,7 @@ preCaretRange.selectNodeContents(root)
|
|
|
947
818
|
preCaretRange.setEnd(range.startContainer, range.startOffset)
|
|
948
819
|
return preCaretRange.toString().length
|
|
949
820
|
} catch (e) {return -1
|
|
950
|
-
}
|
|
951
|
-
}
|
|
821
|
+
}}
|
|
952
822
|
function setCaretCharacterOffset(root, offset) {if (!root || offset < 0) return
|
|
953
823
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
954
824
|
if (!sel) return
|
|
@@ -970,9 +840,7 @@ range.setStart(targetNode, targetOffset)
|
|
|
970
840
|
range.collapse(true)
|
|
971
841
|
sel.removeAllRanges()
|
|
972
842
|
sel.addRange(range)
|
|
973
|
-
}
|
|
974
|
-
} catch (e) { void e; }
|
|
975
|
-
}
|
|
843
|
+
}} catch (e) { void e; }}
|
|
976
844
|
const isCaretInCode = (el, value, caretOffset) => {if (!el) return false
|
|
977
845
|
if (caretOffset == null || caretOffset < 0) caretOffset = (value || '').length
|
|
978
846
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
@@ -1009,10 +877,8 @@ sel.addRange(range)
|
|
|
1009
877
|
document.execCommand('insertText', false, value)
|
|
1010
878
|
const targetOffset = typeof cursorStart === 'number' ? cursorStart : (initialOffset >= 0 ? Math.min(initialOffset, value.length) : value.length)
|
|
1011
879
|
setCaretCharacterOffset(host, targetOffset)
|
|
1012
|
-
} catch (e) { void e; }
|
|
1013
|
-
}
|
|
1014
|
-
const layoutDismiss = () => {if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
|
|
1015
|
-
}
|
|
880
|
+
} catch (e) { void e; }}
|
|
881
|
+
const layoutDismiss = () => {if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }}
|
|
1016
882
|
const layoutShowHint = (inputEl, converted, direction) => {layoutDismiss()
|
|
1017
883
|
layoutHintEl = document.createElement('div')
|
|
1018
884
|
layoutHintEl.dataset.russianLangLayout = '1'
|
|
@@ -1049,8 +915,7 @@ const v = getComposerText(el)
|
|
|
1049
915
|
const c = layoutFixCandidate(v, 'lat2cyr') || layoutFixCandidate(v, 'cyr2lat')
|
|
1050
916
|
if (c) {setComposerText(el, c.converted)
|
|
1051
917
|
learnWords(c.converted)
|
|
1052
|
-
}
|
|
1053
|
-
})
|
|
918
|
+
}})
|
|
1054
919
|
document.body.appendChild(layoutBadgeEl)
|
|
1055
920
|
}
|
|
1056
921
|
layoutBadgeEl.textContent = label
|
|
@@ -1058,8 +923,7 @@ const r = el.getBoundingClientRect()
|
|
|
1058
923
|
layoutBadgeEl.style.left = (r.right - 36) + 'px'
|
|
1059
924
|
layoutBadgeEl.style.top = (r.top - 22) + 'px'
|
|
1060
925
|
}
|
|
1061
|
-
const layoutBadgeHide = () => {if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
|
|
1062
|
-
}
|
|
926
|
+
const layoutBadgeHide = () => {if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }}
|
|
1063
927
|
let isFormatting = false
|
|
1064
928
|
const layoutOnInput = (ev) => {if (isFormatting) return
|
|
1065
929
|
try {const el = layoutCurrentInput(ev)
|
|
@@ -1070,14 +934,11 @@ if (value.trim().length < 4) { layoutDismiss(); return }
|
|
|
1070
934
|
const latCount = (value.match(/[a-z]/g) || []).length
|
|
1071
935
|
const cyrCount = (value.match(/[\u0430-\u044f\u0451]/g) || []).length
|
|
1072
936
|
if (latCount > cyrCount && cyrCount === 0) {const c = layoutFixCandidate(value, 'lat2cyr')
|
|
1073
|
-
if (c) { layoutShowHint(el, c.converted, 'ru'); return }
|
|
1074
|
-
}
|
|
937
|
+
if (c) { layoutShowHint(el, c.converted, 'ru'); return }}
|
|
1075
938
|
if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {const c = layoutFixCandidate(value, 'cyr2lat')
|
|
1076
|
-
if (c) { layoutShowHint(el, c.converted, 'cmd'); return }
|
|
1077
|
-
}
|
|
939
|
+
if (c) { layoutShowHint(el, c.converted, 'cmd'); return }}
|
|
1078
940
|
layoutDismiss()
|
|
1079
|
-
} catch (err) { }
|
|
1080
|
-
}
|
|
941
|
+
} catch (err) { }}
|
|
1081
942
|
const unsubscribeLayout = runtime.subscribe(layoutOnInput)
|
|
1082
943
|
const layoutOnKeydown = (ev) => {const el = layoutCurrentInput(ev)
|
|
1083
944
|
if (!el) return
|
|
@@ -1098,8 +959,7 @@ learnWords(c.converted)
|
|
|
1098
959
|
} finally {isFormatting = false
|
|
1099
960
|
}
|
|
1100
961
|
return
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
962
|
+
}}
|
|
1103
963
|
const c = layoutFixCandidate(value, 'lat2cyr') || layoutFixCandidate(value, 'cyr2lat')
|
|
1104
964
|
if (c) {ev.preventDefault()
|
|
1105
965
|
isFormatting = true
|
|
@@ -1112,8 +972,7 @@ setComposerText(el, c.converted, offset, offset)
|
|
|
1112
972
|
}
|
|
1113
973
|
learnWords(c.converted)
|
|
1114
974
|
} finally {isFormatting = false
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
975
|
+
}}
|
|
1117
976
|
return
|
|
1118
977
|
}
|
|
1119
978
|
if (ev.key === ' ' || ev.key === 'Enter') {if (value && value.startsWith('/') && typeof expandSlashAlias === 'function') {const expanded = expandSlashAlias(value)
|
|
@@ -1127,20 +986,14 @@ return
|
|
|
1127
986
|
} else if (ev.key === 'Enter') {isFormatting = true
|
|
1128
987
|
try {setComposerText(el, expanded, expanded.length, expanded.length)
|
|
1129
988
|
} finally {isFormatting = false
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
989
|
+
}}}}}
|
|
1135
990
|
if (ev.key === 'Enter' && !ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1136
|
-
const typoLive = snapVal.typography ? snapVal.typography.liveInput
|
|
991
|
+
const typoLive = snapVal.typography ? snapVal.typography.liveInput === true : false
|
|
1137
992
|
if (typoLive && typeof formatInputLive === 'function' && (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT')) {const formatted = formatInputLive(value)
|
|
1138
993
|
if (formatted && formatted !== value) {setNativeInputValue(el, formatted, formatted.length, formatted.length)
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
994
|
+
}}}
|
|
1142
995
|
try {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1143
|
-
const typoLive = snapVal.typography ? snapVal.typography.liveInput
|
|
996
|
+
const typoLive = snapVal.typography ? snapVal.typography.liveInput === true : false
|
|
1144
997
|
if (typoLive && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
|
|
1145
998
|
if (isTextarea) {const caretPos = el.selectionStart || 0
|
|
1146
999
|
if (!isCaretInCode(el, value, caretPos)) {const textBefore = caretPos >= 0 ? value.slice(0, caretPos) : value
|
|
@@ -1162,12 +1015,7 @@ setNativeInputValue(el, nextVal, caretPos + 1, caretPos + 1)
|
|
|
1162
1015
|
} finally {isFormatting = false
|
|
1163
1016
|
}
|
|
1164
1017
|
return
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
} catch (e) { void e; }
|
|
1170
|
-
}
|
|
1018
|
+
}}}}} catch (e) { void e; }}
|
|
1171
1019
|
const getHoverBox = () => {if (typeof document === 'undefined') return null
|
|
1172
1020
|
if (!hoverBoxEl) {hoverBoxEl = document.createElement('div')
|
|
1173
1021
|
hoverBoxEl.id = 'dsh-ru-inspector-hover'
|
|
@@ -1262,17 +1110,15 @@ const copyKeyBtn = modal.querySelector('.rl-copy-key-btn')
|
|
|
1262
1110
|
if (copyKeyBtn) {copyKeyBtn.onclick = () => {try { navigator.clipboard.writeText(k || rawText) } catch (err) { void err; }
|
|
1263
1111
|
copyKeyBtn.textContent = '✓'
|
|
1264
1112
|
setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1113
|
+
}}
|
|
1267
1114
|
const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
|
|
1268
1115
|
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.
|
|
1116
|
+
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.10' })
|
|
1270
1117
|
: ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
|
|
1271
1118
|
try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
|
|
1272
1119
|
copyReportBtn.textContent = '✓ Скопировано'
|
|
1273
1120
|
setTimeout(() => { copyReportBtn.textContent = '📋 Скопировать репорт' }, 2000)
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1121
|
+
}}
|
|
1276
1122
|
const resetBtn = modal.querySelector('.rl-reset-override-btn')
|
|
1277
1123
|
if (resetBtn) {resetBtn.onclick = () => {const targetKey = k || rawText
|
|
1278
1124
|
const cur = getOverrides()
|
|
@@ -1281,8 +1127,7 @@ delete updated[targetKey]
|
|
|
1281
1127
|
try { scope.set('overrides', updated) } catch (err) { void err; }
|
|
1282
1128
|
modal.remove()
|
|
1283
1129
|
if (typeof queueZhWalk === 'function') queueZhWalk()
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1130
|
+
}}
|
|
1286
1131
|
const saveBtn = modal.querySelector('.rl-save-btn')
|
|
1287
1132
|
saveBtn.onclick = () => {const val = textarea.value.trim()
|
|
1288
1133
|
const targetKey = k || rawText
|
|
@@ -1292,9 +1137,7 @@ const updated = Object.assign({}, cur, { [targetKey]: val })
|
|
|
1292
1137
|
try { scope.set('overrides', updated) } catch (_e) { void _e }
|
|
1293
1138
|
if (targetElement) {try {if (targetElement.childNodes && targetElement.childNodes.length === 1 && targetElement.childNodes[0].nodeType === 3) {targetElement.childNodes[0].nodeValue = val
|
|
1294
1139
|
} else {targetElement.textContent = val
|
|
1295
|
-
}
|
|
1296
|
-
} catch (err) { void err; }
|
|
1297
|
-
}
|
|
1140
|
+
}} catch (err) { void err; }}
|
|
1298
1141
|
saveBtn.textContent = '✓ Применено'
|
|
1299
1142
|
saveBtn.disabled = true
|
|
1300
1143
|
if (typeof queueZhWalk === 'function') queueZhWalk()
|
|
@@ -1339,17 +1182,14 @@ else if (meta.source === 'untranslated_zh') title = '🔴 ZH'
|
|
|
1339
1182
|
else if (meta.source === 'untranslated_en') title = '🔴 EN'
|
|
1340
1183
|
}
|
|
1341
1184
|
badge.textContent = title
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1185
|
+
}}
|
|
1344
1186
|
const inspectorOnKeyDown = (e) => {if (e.altKey && (e.key === 'i' || e.key === 'I' || e.code === 'KeyI')) {e.preventDefault()
|
|
1345
1187
|
isInspectorActive = !isInspectorActive
|
|
1346
1188
|
updateInspectorPill()
|
|
1347
1189
|
if (!isInspectorActive && hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1190
|
+
}}
|
|
1350
1191
|
const inspectorOnKeyUp = (e) => {if (e.key === 'Alt' && !isInspectorActive) {if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1192
|
+
}}
|
|
1353
1193
|
ctx.effect(() => {document.addEventListener('click', inspectorOnClick, true)
|
|
1354
1194
|
document.addEventListener('mousemove', inspectorOnMouseMove, true)
|
|
1355
1195
|
document.addEventListener('keydown', inspectorOnKeyDown, true)
|
|
@@ -1361,9 +1201,7 @@ document.removeEventListener('keyup', inspectorOnKeyUp, true)
|
|
|
1361
1201
|
const m = document.getElementById('dsh-ru-inspector-modal')
|
|
1362
1202
|
if (m) m.remove()
|
|
1363
1203
|
if (hoverBoxEl) { hoverBoxEl.remove(); hoverBoxEl = null }
|
|
1364
|
-
if (pillEl) { pillEl.remove(); pillEl = null }
|
|
1365
|
-
}
|
|
1366
|
-
}, 'dsh-russian-lang: inspector')
|
|
1204
|
+
if (pillEl) { pillEl.remove(); pillEl = null }}}, 'dsh-russian-lang: inspector')
|
|
1367
1205
|
runtime.findTranslationKey = (text) => (typeof findTranslationKey === 'function' ? findTranslationKey(text, RU, getOverrides(), ZH_RU) : null)
|
|
1368
1206
|
runtime.openInspector = openInspectorModal
|
|
1369
1207
|
runtime.translationRegistry = translationRegistry
|
|
@@ -1376,16 +1214,10 @@ if (node.matches && (node.matches('.dsw-toast, [role="alert"], .toast, .ant-mess
|
|
|
1376
1214
|
if (/failed to fetch|network error|econnrefused|etimedout/i.test(text)) {const h = typeof humanizeError === 'function' ? humanizeError(text) : null
|
|
1377
1215
|
if (h && h.title) {const targetEl = node.querySelector('.toast-body, .ant-message-custom-content') || node
|
|
1378
1216
|
targetEl.textContent = h.title + ': ' + h.message
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
}) : null
|
|
1217
|
+
}}}}}}) : null
|
|
1385
1218
|
ctx.effect(() => {if (toastObserver && typeof document !== 'undefined' && document.body) {toastObserver.observe(document.body, { childList: true, subtree: true })
|
|
1386
1219
|
}
|
|
1387
|
-
return () => { if (toastObserver) toastObserver.disconnect() }
|
|
1388
|
-
}, 'dsh-russian-lang: toast-humanizer')
|
|
1220
|
+
return () => { if (toastObserver) toastObserver.disconnect() }}, 'dsh-russian-lang: toast-humanizer')
|
|
1389
1221
|
ctx.effect(() => {document.addEventListener('input', layoutOnInput, true)
|
|
1390
1222
|
document.addEventListener('keydown', layoutOnKeydown, true)
|
|
1391
1223
|
return () => {unsubscribeLayout()
|
|
@@ -1393,13 +1225,11 @@ document.removeEventListener('input', layoutOnInput, true)
|
|
|
1393
1225
|
document.removeEventListener('keydown', layoutOnKeydown, true)
|
|
1394
1226
|
layoutDismiss()
|
|
1395
1227
|
layoutBadgeHide()
|
|
1396
|
-
}
|
|
1397
|
-
}, 'dsh-russian-lang: layout')
|
|
1228
|
+
}}, 'dsh-russian-lang: layout')
|
|
1398
1229
|
if (!ctx.slots || !React) return
|
|
1399
1230
|
const toggleRu = (wantRu) => {try {if (runtime.getLocale().active === wantRu) return
|
|
1400
1231
|
runtime.setLocale(wantRu ? 'ru' : 'en')
|
|
1401
|
-
} catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }
|
|
1402
|
-
}
|
|
1232
|
+
} catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }}
|
|
1403
1233
|
try {ctx.slots.inject('plugins.item', () =>
|
|
1404
1234
|
ctx.slots.register({name: 'plugins.item',id: SETTINGS_NS_NAME,order: 60,label: () => 'Russian language',locale: SETTINGS_NS_NAME,inject: () => ({ scope, runtime, toggleRu }),}, SettingsCard),)
|
|
1405
1235
|
ctx.slots.inject('plugins.row.config', () =>
|
|
@@ -1415,15 +1245,13 @@ ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian
|
|
|
1415
1245
|
} catch (err) { }
|
|
1416
1246
|
try {ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1417
1247
|
ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
|
|
1418
|
-
} catch (err) { }
|
|
1419
|
-
}
|
|
1248
|
+
} catch (err) { }}
|
|
1420
1249
|
function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
|
|
1421
1250
|
const runtime = props.runtime || inj.runtime
|
|
1422
1251
|
const toggleRu = props.toggleRu || inj.toggleRu
|
|
1423
1252
|
const [locale, setLocaleState] = React.useState(runtime ? (runtime.getLocale().active || 'en') : 'ru')
|
|
1424
1253
|
React.useEffect(() => {if (!runtime) return
|
|
1425
|
-
return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }
|
|
1426
|
-
})
|
|
1254
|
+
return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }})
|
|
1427
1255
|
}, [runtime])
|
|
1428
1256
|
const isRu = locale === 'ru'
|
|
1429
1257
|
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'))
|
|
@@ -1445,8 +1273,7 @@ if (text) messages.push({ role: 'user', content: text })
|
|
|
1445
1273
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"], .rl-turn-translation').forEach((b) => b.remove())
|
|
1446
1274
|
const text = clone.innerText.trim()
|
|
1447
1275
|
if (text) messages.push({ role: 'assistant', content: text })
|
|
1448
|
-
}
|
|
1449
|
-
})
|
|
1276
|
+
}})
|
|
1450
1277
|
}
|
|
1451
1278
|
if (messages.length === 0) {const allElements = Array.from(document.querySelectorAll('[class*="userRow"], [class*="UserRow"], [class*="assistant-step"], [class*="AssistantMarkdown"], .dsw-turn-node, [data-role]'))
|
|
1452
1279
|
const seen = new Set()
|
|
@@ -1473,24 +1300,18 @@ a.download = safeTitle + '-' + new Date().toISOString().slice(0, 10) + '.md'
|
|
|
1473
1300
|
document.body.appendChild(a)
|
|
1474
1301
|
a.click()
|
|
1475
1302
|
setDone(true)
|
|
1476
|
-
setTimeout(() => {try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }
|
|
1477
|
-
}, 30000)
|
|
1303
|
+
setTimeout(() => {try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }}, 30000)
|
|
1478
1304
|
setTimeout(() => setDone(false), 2000)
|
|
1479
1305
|
} catch (err) {console.warn('dsh-russian-lang: export md failed', err)
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1306
|
+
}}
|
|
1482
1307
|
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
1308
|
}
|
|
1484
1309
|
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' },
|
|
1310
|
+
try {const res = await fetch('/api/dsh-russian-lang/translate', {method: 'POST',headers: { 'content-type': 'application/json', 'x-dsh-translator': '1' },body: JSON.stringify({ text })
|
|
1486
1311
|
})
|
|
1487
1312
|
const data = await res.json().catch(() => ({}))
|
|
1488
|
-
if (!res.ok) {return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
|
|
1489
|
-
}
|
|
1490
|
-
return { translatedText: data.translatedText || text }
|
|
1491
|
-
} catch (err) {return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1313
|
+
if (!res.ok) {return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }}
|
|
1314
|
+
return { translatedText: data.translatedText || text }} catch (err) {return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }}}
|
|
1494
1315
|
function TranslateTurnAction(props) {const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1495
1316
|
const [loading, setLoading] = React.useState(false)
|
|
1496
1317
|
const [open, setOpen] = React.useState(false)
|
|
@@ -1525,8 +1346,7 @@ rawText = targetSteps.map((s) => {const clone = s.cloneNode(true)
|
|
|
1525
1346
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
|
|
1526
1347
|
return clone.innerText.trim()
|
|
1527
1348
|
}).filter(Boolean).join('\n\n')
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1349
|
+
}}
|
|
1530
1350
|
if (!rawText) {const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
|
|
1531
1351
|
if (prose) rawText = prose.innerText.trim()
|
|
1532
1352
|
}
|
|
@@ -1566,8 +1386,7 @@ if (parentContainer) {parentContainer.insertBefore(box, tailFlowItem)
|
|
|
1566
1386
|
setOpen(true)
|
|
1567
1387
|
} catch (err) {console.warn('dsh-russian-lang: translate turn failed', err)
|
|
1568
1388
|
} finally {setLoading(false)
|
|
1569
|
-
}
|
|
1570
|
-
},}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
1389
|
+
}},}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
1571
1390
|
}
|
|
1572
1391
|
function SettingsCard(props) {const inj = typeof props.inject === 'function'
|
|
1573
1392
|
? (props.inject() || {})
|
|
@@ -1577,70 +1396,50 @@ const runtime = props.runtime || inj.runtime
|
|
|
1577
1396
|
const toggleRu = props.toggleRu || inj.toggleRu
|
|
1578
1397
|
const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1579
1398
|
const [open, setOpen] = React.useState(false)
|
|
1580
|
-
const [snap, setSnap] = React.useState(
|
|
1581
|
-
() =>
|
|
1582
|
-
const [ruActive, setRuActive] = React.useState(
|
|
1583
|
-
() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
|
|
1399
|
+
const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope.getSnapshot() : { status: 'loading', value: {} }))
|
|
1400
|
+
const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
|
|
1584
1401
|
const [typo, setTypoState] = React.useState(() =>
|
|
1585
1402
|
(snap.value && snap.value.typography) || {})
|
|
1586
|
-
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.
|
|
1403
|
+
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.10',latestVersion: undefined,updateAvailable: false
|
|
1587
1404
|
})
|
|
1588
1405
|
const [upLoading, setUpLoading] = React.useState(false)
|
|
1589
1406
|
const [upMsg, setUpMsg] = React.useState(null)
|
|
1590
1407
|
const checkUpdate = () => {setUpLoading(true)
|
|
1591
1408
|
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)
|
|
1409
|
+
fetch('/api/dsh-russian-lang/update', {headers: { 'x-dsh-plugin-update': '1' }}).then((r) => r.json()).then((data) => {setUpStatus(data)
|
|
1596
1410
|
setUpLoading(false)
|
|
1597
|
-
})
|
|
1598
|
-
.catch(() => {setUpLoading(false)
|
|
1411
|
+
}).catch(() => {setUpLoading(false)
|
|
1599
1412
|
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1600
1413
|
})
|
|
1601
1414
|
}
|
|
1602
1415
|
const triggerUpdate = () => {setUpLoading(true)
|
|
1603
1416
|
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)
|
|
1417
|
+
fetch('/api/dsh-russian-lang/update', {method: 'POST',headers: { 'x-dsh-plugin-update': '1' }}).then((r) => r.json()).then((data) => {setUpLoading(false)
|
|
1608
1418
|
if (data.restartRequired || data.updatedVersion) {setUpStatus(data)
|
|
1609
1419
|
setUpMsg({ type: 'ok', text: t('updaterSuccess') })
|
|
1610
1420
|
} else if (data.error) {setUpMsg({ type: 'err', text: data.error })
|
|
1611
|
-
}
|
|
1612
|
-
})
|
|
1613
|
-
.catch(() => {setUpLoading(false)
|
|
1421
|
+
}}).catch(() => {setUpLoading(false)
|
|
1614
1422
|
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1615
1423
|
})
|
|
1616
1424
|
}
|
|
1617
1425
|
const [transStatus, setTransStatus] = React.useState(null)
|
|
1618
1426
|
const [transStarting, setTransStarting] = React.useState(false)
|
|
1619
1427
|
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))
|
|
1428
|
+
const fetchTransStatus = () => {fetch('/api/dsh-russian-lang/translator/status').then((r) => r.json()).then((data) => setTransStatus(data)).catch(() => setTransStatus(null))
|
|
1624
1429
|
}
|
|
1625
1430
|
const startLibreTranslate = () => {setTransStarting(true)
|
|
1626
1431
|
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)
|
|
1432
|
+
fetch('/api/dsh-russian-lang/translator/setup', {method: 'POST',headers: { 'x-dsh-translator': '1' }}).then((r) => r.json()).then((data) => {setTransStarting(false)
|
|
1631
1433
|
if (data.ok) {setTransMsg({ type: 'ok', text: data.message || 'Контейнер LibreTranslate запущен.' })
|
|
1632
1434
|
fetchTransStatus()
|
|
1633
1435
|
} else {setTransMsg({ type: 'err', text: data.error || 'Ошибка запуска контейнера.' })
|
|
1634
|
-
}
|
|
1635
|
-
})
|
|
1636
|
-
.catch((err) => {setTransStarting(false)
|
|
1436
|
+
}}).catch((err) => {setTransStarting(false)
|
|
1637
1437
|
setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
|
|
1638
1438
|
})
|
|
1639
1439
|
}
|
|
1640
1440
|
React.useEffect(() => {if (open || (props && props.view === 'page')) {checkUpdate()
|
|
1641
1441
|
fetchTransStatus()
|
|
1642
|
-
}
|
|
1643
|
-
}, [open, props])
|
|
1442
|
+
}}, [open, props])
|
|
1644
1443
|
React.useEffect(() => {if (!scope || !scope.subscribe) return undefined
|
|
1645
1444
|
const un = scope.subscribe(() => {const s = scope.getSnapshot()
|
|
1646
1445
|
setSnap(s)
|
|
@@ -1649,11 +1448,9 @@ if (s.value && s.value.typography) setTypoState(s.value.typography)
|
|
|
1649
1448
|
setSnap(scope.getSnapshot())
|
|
1650
1449
|
return un
|
|
1651
1450
|
}, [scope])
|
|
1652
|
-
React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }
|
|
1653
|
-
})
|
|
1451
|
+
React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }})
|
|
1654
1452
|
return un
|
|
1655
|
-
} catch (e) { return undefined }
|
|
1656
|
-
}, [runtime])
|
|
1453
|
+
} catch (e) { return undefined }}, [runtime])
|
|
1657
1454
|
const status = snap.status || 'loading'
|
|
1658
1455
|
const value = snap.value || {}
|
|
1659
1456
|
const typography = typo
|
|
@@ -1667,8 +1464,7 @@ try {const r = scope.set('typography', next)
|
|
|
1667
1464
|
if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
|
|
1668
1465
|
})
|
|
1669
1466
|
} catch (err) {console.warn('dsh-russian-lang: scope.set typography sync threw', err && err.message || err)
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1467
|
+
}}
|
|
1672
1468
|
const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
|
|
1673
1469
|
const checkbox = (checked, onChange, disabled) =>
|
|
1674
1470
|
React.createElement('input', {type: 'checkbox', checked: !!checked, disabled: !!disabled,className: 'rl-check', onChange: (ev) => onChange(ev),})
|
|
@@ -1687,129 +1483,59 @@ const presetInfo = typeof SYSTEM_PROMPT_PRESETS !== 'undefined' ? SYSTEM_PROMPT_
|
|
|
1687
1483
|
if (props && props.view === 'summary') {return React.createElement('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
|
|
1688
1484
|
}
|
|
1689
1485
|
const page = !!(props && props.view === 'page')
|
|
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-
|
|
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')
|
|
1693
|
-
)
|
|
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')
|
|
1698
|
-
)
|
|
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')
|
|
1701
|
-
)
|
|
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')
|
|
1704
|
-
)
|
|
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')
|
|
1708
|
-
)
|
|
1709
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))
|
|
1710
|
-
)
|
|
1711
|
-
),React.createElement('div', { className: 'rl-hotkey-box' },React.createElement('span', { className: 'rl-hotkey-tag' }, '⌨️ Alt+L'),React.createElement('span', null, t('altLHintText'))
|
|
1712
|
-
)
|
|
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)
|
|
1486
|
+
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'))),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'))),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'))),React.createElement('div', { className: 'rl-item-desc' }, t('enabledDesc'))),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
|
|
1487
|
+
),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'))),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'))),React.createElement('div', { className: 'rl-item-desc' }, t('typographyDesc'))),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'))),React.createElement('div', { className: 'rl-item-desc' }, t('liveInputDesc'))),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'))),React.createElement('div', { className: 'rl-item-desc' }, t('yoDesc'))),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(value.slashAliases !== false && ruActive,(ev) => {try { scope.set('slashAliases', ev.target.checked) } catch (err) { void err; }}, !ruActive || disabled),t('slashAliases'))),React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc')))),React.createElement('div', { className: 'rl-hotkey-box' },React.createElement('span', { className: 'rl-hotkey-tag' }, '⌨️ Alt+L'),React.createElement('span', null, t('altLHintText')))),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 ? 'Активен' : 'Выключен')),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
1488
|
if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
|
|
1716
1489
|
})
|
|
1717
1490
|
} catch (err) {console.warn('dsh-russian-lang: scope.set agentPrompt sync threw', err && err.message || err)
|
|
1718
|
-
}
|
|
1719
|
-
}, false),t('agentPrompt')
|
|
1720
|
-
)
|
|
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
|
|
1491
|
+
}}, false),t('agentPrompt'))),React.createElement('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))),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
|
|
1723
1492
|
scope.set('agentPromptPreset', val)
|
|
1724
|
-
} catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }
|
|
1725
|
-
}
|
|
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'))
|
|
1727
|
-
)
|
|
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
|
|
1493
|
+
} catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }}},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')))),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
|
|
1729
1494
|
) : null
|
|
1730
1495
|
) : null
|
|
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
|
|
1496
|
+
),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'))),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.10')),upStatus.updateAvailable
|
|
1733
1497
|
? 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
|
|
1498
|
+
: React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
|
|
1736
1499
|
? 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
|
-
|
|
1739
|
-
}
|
|
1740
|
-
},
|
|
1741
|
-
)
|
|
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'))
|
|
1743
|
-
}, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))
|
|
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
|
|
1500
|
+
: React.createElement('button', {type: 'button',className: 'rl-btn',disabled: upLoading,onClick: checkUpdate,}, upLoading ? t('updaterChecking') : t('updaterCheckBtn'))),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
|
|
1501
|
+
}}, upMsg.text) : null
|
|
1502
|
+
)),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'))
|
|
1503
|
+
}, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))),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
|
|
1745
1504
|
scope.set('translateEngine', val)
|
|
1746
1505
|
if (val === 'local') fetchTransStatus()
|
|
1747
|
-
} catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }
|
|
1748
|
-
}
|
|
1749
|
-
},React.createElement('option', { value: 'off' }, t('engineOff')),React.createElement('option', { value: 'local' }, t('engineLocal')),React.createElement('option', { value: 'google' }, t('engineGoogle'))
|
|
1750
|
-
)
|
|
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')
|
|
1506
|
+
} catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }}},React.createElement('option', { value: 'off' }, t('engineOff')),React.createElement('option', { value: 'local' }, t('engineLocal')),React.createElement('option', { value: 'google' }, t('engineGoogle')))),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
|
|
1507
|
+
}}, t('googleWarn')) : null,value.translateEngine === 'local' ? React.createElement('div', {style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }},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
1508
|
}, 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
|
|
1756
1509
|
}, transStarting ? t('localStarting') : t('localStartBtn')) : null
|
|
1757
1510
|
),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
|
|
1758
|
-
}
|
|
1759
|
-
}, transMsg.text) : null
|
|
1511
|
+
}}, transMsg.text) : null
|
|
1760
1512
|
) : null
|
|
1761
|
-
)
|
|
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()
|
|
1513
|
+
)),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, ['запись', 'записи', 'записей']) : 'записей'))),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()
|
|
1764
1514
|
const v = newVal.trim()
|
|
1765
1515
|
if (!k || !v) return
|
|
1766
1516
|
const next = Object.assign({}, overrides, { [k]: v })
|
|
1767
|
-
try { scope.set('overrides',
|
|
1517
|
+
try { scope.set('overrides', next) } catch (_e) { void _e }
|
|
1768
1518
|
setNewKey('')
|
|
1769
1519
|
setNewVal('')
|
|
1770
|
-
},}, t('overrideAddBtn'))
|
|
1771
|
-
),overridesCount === 0
|
|
1520
|
+
},}, t('overrideAddBtn'))),overridesCount === 0
|
|
1772
1521
|
? React.createElement('div', { className: 'rl-hint-text', style: { fontStyle: 'italic', padding: '4px 0' } }, t('overrideEmpty'))
|
|
1773
1522
|
: 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)
|
|
1523
|
+
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])),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)
|
|
1776
1524
|
delete next[k]
|
|
1777
|
-
try { scope.set('overrides',
|
|
1778
|
-
},}, t('overrideDelete'))
|
|
1779
|
-
)
|
|
1780
|
-
)
|
|
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
|
|
1525
|
+
try { scope.set('overrides', next) } catch (_e) { void _e }},}, t('overrideDelete'))))),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
|
|
1782
1526
|
updateInspectorPill()
|
|
1783
1527
|
},}, isInspectorActive ? '🔍 Инспектор перевода (включён)' : '🔍 Включить инспектор перевода (Alt+I)'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {try {navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
|
|
1784
1528
|
alert('Оверрайды скопированы в буфер обмена как JSON!')
|
|
1785
|
-
} catch (err) { void err; }
|
|
1786
|
-
}
|
|
1787
|
-
}, '📤 Экспорт JSON'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = prompt('Вставьте JSON с оверрайдами:')
|
|
1529
|
+
} catch (err) { void err; }}}, '📤 Экспорт JSON'),React.createElement('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {const input = prompt('Вставьте JSON с оверрайдами:')
|
|
1788
1530
|
if (!input) return
|
|
1789
1531
|
try {const parsed = JSON.parse(input)
|
|
1790
1532
|
if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
|
|
1791
1533
|
scope.set('overrides', merged)
|
|
1534
|
+
}} catch (err) { alert('Ошибка разбора JSON: ' + (err && err.message || err)) }}}, '📥 Импорт JSON'))),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%')),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'))),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'))),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')))),React.createElement('div', { className: 'rl-actions-row' },React.createElement('a', {href: makeIssueUrl({}, '0.3.10'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
|
|
1535
|
+
}, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
|
|
1792
1536
|
}
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
}, '📥 Импорт JSON')
|
|
1796
|
-
)
|
|
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'))
|
|
1801
|
-
)
|
|
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')
|
|
1804
|
-
)
|
|
1805
|
-
)
|
|
1806
|
-
)
|
|
1807
|
-
)
|
|
1808
|
-
)
|
|
1809
|
-
)
|
|
1810
|
-
}
|
|
1811
|
-
const RL_CSS = [
|
|
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('')
|
|
1537
|
+
const RL_CSS = ['.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)}'
|
|
1538
|
+
].join('')
|
|
1813
1539
|
const STYLE_ID = 'dsh-russian-lang-styles'
|
|
1814
1540
|
if (typeof document !== 'undefined' && (document.getElementById && !document.getElementById(STYLE_ID))) {const tag = document.createElement('style')
|
|
1815
1541
|
tag.id = STYLE_ID
|