@goodandready/dsh-russian-lang 0.3.9 → 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 +1 -1
- package/lib/client.js +178 -456
- package/lib/client.template.js +50 -145
- package/lib/translator.js +46 -13
- 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,16 +228,13 @@ 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)
|
|
@@ -280,11 +263,9 @@ if (RUSSIAN_SLASH_ALIASES[lowerCmd]) {return RUSSIAN_SLASH_ALIASES[lowerCmd] + r
|
|
|
280
263
|
}
|
|
281
264
|
return input
|
|
282
265
|
}
|
|
283
|
-
const PHONETIC_LAT_TO_CYR = [
|
|
284
|
-
['shch', 'щ'], ['yo', 'ё'], ['zh', 'ж'], ['ch', 'ч'], ['sh', 'ш'],['yu', 'ю'], ['ya', 'я'], ['ts', 'ц'],['a', 'а'], ['b', 'б'], ['v', 'в'], ['g', 'г'], ['d', 'д'], ['e', 'е'],['z', 'з'], ['i', 'и'], ['j', 'й'], ['k', 'к'], ['l', 'л'], ['m', 'м'],['n', 'н'], ['o', 'о'], ['p', 'п'], ['r', 'р'], ['s', 'с'], ['t', 'т'],['u', 'у'], ['f', 'ф'], ['h', 'х'], ['c', 'ц'], ['y', 'ы'], ['x', 'кс']
|
|
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', 'кс']
|
|
285
267
|
]
|
|
286
|
-
const PHONETIC_CYR_TO_LAT = [
|
|
287
|
-
['щ', 'shch'], ['ё', 'yo'], ['ж', 'zh'], ['ч', 'ch'], ['ш', 'sh'],['ю', 'yu'], ['я', 'ya'], ['ц', 'ts'],['а', 'a'], ['б', 'b'], ['в', 'v'], ['г', 'g'], ['д', 'd'], ['е', 'e'],['з', 'z'], ['и', 'i'], ['й', 'j'], ['к', 'k'], ['л', 'l'], ['м', 'm'],['н', 'n'], ['о', 'o'], ['п', 'p'], ['р', 'r'], ['с', 's'], ['т', 't'],['у', 'u'], ['ф', 'f'], ['х', 'h'], ['ы', 'y'], ['э', 'e'], ['ъ', ''], ['ь', '']
|
|
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'], ['ъ', ''], ['ь', '']
|
|
288
269
|
]
|
|
289
270
|
const phoneticTranslit = (text, direction = 'lat2cyr') => {if (!text || typeof text !== 'string') return text
|
|
290
271
|
let res = text
|
|
@@ -298,8 +279,7 @@ res = res.replaceAll(fromTitle, toTitle)
|
|
|
298
279
|
}
|
|
299
280
|
res = res.replaceAll(from, to)
|
|
300
281
|
if (from.length === 1) {res = res.replaceAll(fromUpper, toUpper)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
282
|
+
}}
|
|
303
283
|
return res
|
|
304
284
|
}
|
|
305
285
|
const detectInputLayout = (text) => {if (!text || typeof text !== 'string') return null
|
|
@@ -320,8 +300,7 @@ const SYSTEM_PROMPT_PRESETS = {technical_expert: {id: 'technical_expert',label:
|
|
|
320
300
|
},code_reviewer: {id: 'code_reviewer',label: 'Код-ревьюер',desc: 'Поиск багов, безопасность и архитектурный аудит',text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
|
|
321
301
|
},architect: {id: 'architect',label: 'Системный архитектор',desc: 'Контракты API/CLI, модульность и надёжность',text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
|
|
322
302
|
},tutor: {id: 'tutor',label: 'Наставник (ментор)',desc: 'Понятные объяснения сложных концепций и пошаговый разбор',text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
|
|
323
|
-
}
|
|
324
|
-
}
|
|
303
|
+
}}
|
|
325
304
|
const exportSessionToMarkdown = (session, options = {}) => {if (!session) return ''
|
|
326
305
|
const title = session.title || session.name || 'Диалог DSH'
|
|
327
306
|
const date = session.createdAt ? new Date(session.createdAt) : new Date()
|
|
@@ -354,53 +333,31 @@ return md
|
|
|
354
333
|
const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}, domEn = {}, options = {}) => {if (!text || typeof text !== 'string') return null
|
|
355
334
|
const trimmed = text.trim()
|
|
356
335
|
if (!trimmed) return null
|
|
357
|
-
if (overrides && typeof overrides === 'object') {if (overrides[trimmed] !== undefined) {return { key: trimmed, value: overrides[trimmed], source: 'override' }
|
|
358
|
-
}
|
|
359
|
-
for (const [k, v] of Object.entries(overrides)) {if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {return { key: k, value: v, source: 'override' }
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
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' }}}}
|
|
363
338
|
if (dicts && typeof dicts === 'object') {for (const [ns, map] of Object.entries(dicts)) {if (!map || typeof map !== 'object') continue
|
|
364
|
-
if (map[trimmed] !== undefined) {return { ns, key: trimmed, value: map[trimmed], source: 'dictionary' }
|
|
365
|
-
}
|
|
366
|
-
for (const [
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (zhRu && typeof zhRu === 'object') {for (const [zh, ru] of Object.entries(zhRu)) {if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {return { zh, key: zh, value: ru, source: 'dom_zh' }
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (zhRu[trimmed] !== undefined) {return { zh: trimmed, key: trimmed, value: zhRu[trimmed], source: 'dom_zh_original' }
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (domEn && typeof domEn === 'object') {for (const [en, ru] of Object.entries(domEn)) {if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {return { en, key: en, value: ru, source: 'dom_en' }
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (domEn[trimmed] !== undefined) {return { en: trimmed, key: trimmed, value: domEn[trimmed], source: 'dom_en_original' }
|
|
381
|
-
}
|
|
382
|
-
}
|
|
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' }}}
|
|
383
345
|
if (options && options.detectUntranslated) {const hasRu = /[а-яё]/i.test(trimmed)
|
|
384
346
|
const hasZh = /[㐀-鿿豈-]/.test(trimmed)
|
|
385
347
|
const hasEn = /[a-z]/i.test(trimmed)
|
|
386
|
-
if (!hasRu && hasZh) {return { key: trimmed, value: trimmed, source: 'untranslated_zh' }
|
|
387
|
-
}
|
|
388
|
-
if (!hasRu && hasEn) {return { key: trimmed, value: trimmed, source: 'untranslated_en' }
|
|
389
|
-
}
|
|
390
|
-
}
|
|
348
|
+
if (!hasRu && hasZh) {return { key: trimmed, value: trimmed, source: 'untranslated_zh' }}
|
|
349
|
+
if (!hasRu && hasEn) {return { key: trimmed, value: trimmed, source: 'untranslated_en' }}}
|
|
391
350
|
return null
|
|
392
351
|
}
|
|
393
352
|
const generateBugReportSnippet = (info = {}) => {const { key, ns, original, current, override, pkgVersion = '0.3.1' } = info
|
|
394
|
-
const lines = [
|
|
395
|
-
'### 🌐 Неточность перевода / Пользовательское предложение','- **Пакет:** `@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)
|
|
396
354
|
return lines.join('\n')
|
|
397
355
|
}
|
|
398
356
|
const bilingualCommandMatch = (query, command, options = {}) => {if (!query || !command) return { matched: false, score: 0 }
|
|
399
357
|
const q = String(query).trim().toLowerCase()
|
|
400
358
|
if (!q) return { matched: false, score: 0 }
|
|
401
359
|
const cmdObj = typeof command === 'string' ? { title: command } : command
|
|
402
|
-
const candidates = [
|
|
403
|
-
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 : [])
|
|
404
361
|
].filter((c) => typeof c === 'string' && c.trim())
|
|
405
362
|
if (!candidates.length) return { matched: false, score: 0 }
|
|
406
363
|
const queryVariants = [{ text: q, kind: 'direct' }]
|
|
@@ -421,20 +378,16 @@ for (const variant of queryVariants) {for (const cand of candidates) {const scor
|
|
|
421
378
|
if (score > bestScore) {bestScore = score
|
|
422
379
|
bestVariant = variant
|
|
423
380
|
bestCandidate = cand
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
381
|
+
}}}
|
|
427
382
|
const threshold = options.threshold ?? 50
|
|
428
383
|
return {matched: bestScore >= threshold,score: bestScore,matchedText: bestCandidate,variant: bestVariant ? bestVariant.text : q,kind: bestVariant ? bestVariant.kind : 'none'
|
|
429
|
-
}
|
|
430
|
-
}
|
|
384
|
+
}}
|
|
431
385
|
const filterCommands = (query, commands = [], options = {}) => {if (!Array.isArray(commands)) return []
|
|
432
386
|
if (!query || !String(query).trim()) return commands
|
|
433
387
|
const results = []
|
|
434
388
|
for (const cmd of commands) {const res = bilingualCommandMatch(query, cmd, options)
|
|
435
389
|
if (res.matched) {results.push({ item: cmd, ...res })
|
|
436
|
-
}
|
|
437
|
-
}
|
|
390
|
+
}}
|
|
438
391
|
return results.sort((a, b) => b.score - a.score).map((r) => r.item)
|
|
439
392
|
}
|
|
440
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":"Магазин плагинов"}}
|
|
@@ -451,37 +404,26 @@ return null
|
|
|
451
404
|
const rawScope = resolveScope(ctx)
|
|
452
405
|
const scope = (rawScope && typeof rawScope.getSnapshot === 'function') ? rawScope : {getSnapshot: () => ({ status: 'ready', value: {} }),subscribe: () => () => {},set: () => Promise.resolve()
|
|
453
406
|
}
|
|
454
|
-
for (const ns of Object.keys(RU)) {ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', RU[ns]) }
|
|
455
|
-
catch (err) { return () => {} }
|
|
456
|
-
}, '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)
|
|
457
408
|
}
|
|
458
409
|
const registerDictMap = (dictMap) => {if (!dictMap || typeof dictMap !== 'object') return
|
|
459
410
|
for (const ns of Object.keys(dictMap)) {if (ns === 'zhRu' || ns === 'plugins' || ns === 'core') continue
|
|
460
411
|
const dict = dictMap[ns]
|
|
461
412
|
if (typeof dict !== 'object' || !dict) continue
|
|
462
413
|
if (!RU[ns]) {RU[ns] = dict
|
|
463
|
-
ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) }
|
|
464
|
-
catch (err) { return () => {} }
|
|
465
|
-
}, 'dsh-russian-lang: ' + ns)
|
|
414
|
+
ctx.effect(() => {try { return ctx.locale.register(ns, 'ru', dict) } catch (err) { return () => {} }}, 'dsh-russian-lang: ' + ns)
|
|
466
415
|
} else {Object.assign(RU[ns], dict)
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
416
|
+
}}}
|
|
470
417
|
const CACHE_NAME = 'dsh-ru-cache-v1'
|
|
471
418
|
const loadLocalDict = (key) => {try {const raw = typeof localStorage !== 'undefined' ? localStorage.getItem('dsh_ru_' + key) : null
|
|
472
419
|
return raw ? JSON.parse(raw) : null
|
|
473
|
-
} catch (_) { return null }
|
|
474
|
-
}
|
|
420
|
+
} catch (_) { return null }}
|
|
475
421
|
const saveLocalDict = (key, etag, data) => {try {if (typeof localStorage !== 'undefined') {localStorage.setItem('dsh_ru_' + key, JSON.stringify({ etag, data }))
|
|
476
|
-
}
|
|
477
|
-
} catch (_e) { void _e }
|
|
478
|
-
}
|
|
422
|
+
}} catch (_e) { void _e }}
|
|
479
423
|
try {const cachedCore = loadLocalDict('core')
|
|
480
424
|
if (cachedCore && cachedCore.data && cachedCore.data.core) {registerDictMap(cachedCore.data.core)
|
|
481
425
|
if (cachedCore.data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, cachedCore.data.zhRu)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
} catch (_e) { void _e }
|
|
426
|
+
}}} catch (_e) { void _e }
|
|
485
427
|
const fetchCachedResource = async (url, cacheKey, onData) => {let etag = null
|
|
486
428
|
let delivered = false
|
|
487
429
|
if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
|
|
@@ -490,16 +432,12 @@ if (matched) {etag = matched.headers.get('etag')
|
|
|
490
432
|
const parsed = await matched.json()
|
|
491
433
|
if (parsed) {delivered = true
|
|
492
434
|
onData(parsed)
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
} catch (_e) { void _e }
|
|
496
|
-
}
|
|
435
|
+
}}} catch (_e) { void _e }}
|
|
497
436
|
if (!delivered && cacheKey) {const local = loadLocalDict(cacheKey)
|
|
498
437
|
if (local && local.data) {etag = local.etag
|
|
499
438
|
delivered = true
|
|
500
439
|
onData(local.data)
|
|
501
|
-
}
|
|
502
|
-
}
|
|
440
|
+
}}
|
|
503
441
|
if (typeof fetch !== 'function') return
|
|
504
442
|
try {const headers = { 'Accept': 'application/json' }
|
|
505
443
|
if (etag) headers['If-None-Match'] = etag
|
|
@@ -511,13 +449,9 @@ const data = await res.json()
|
|
|
511
449
|
if (data) {onData(data)
|
|
512
450
|
if (typeof caches !== 'undefined') {try {const cache = await caches.open(CACHE_NAME)
|
|
513
451
|
await cache.put(url, cloned)
|
|
514
|
-
} catch (_e) { void _e }
|
|
515
|
-
}
|
|
452
|
+
} catch (_e) { void _e }}
|
|
516
453
|
if (cacheKey) saveLocalDict(cacheKey, newEtag, data)
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
} catch (_e) { void _e }
|
|
520
|
-
}
|
|
454
|
+
}}} catch (_e) { void _e }}
|
|
521
455
|
const loadedPluginNames = new Set()
|
|
522
456
|
const pendingPluginLoads = new Set()
|
|
523
457
|
let pluginBatchTimer = null
|
|
@@ -533,8 +467,7 @@ const url = '/api/dsh-russian-lang/dict/plugins' + qs
|
|
|
533
467
|
const cacheKey = toLoad.length > 0 ? ('plugins_' + toLoad.sort().join('_')) : 'plugins_all'
|
|
534
468
|
fetchCachedResource(url, cacheKey, (data) => {if (data && data.plugins) {registerDictMap(data.plugins)
|
|
535
469
|
for (const n of toLoad) loadedPluginNames.add(n)
|
|
536
|
-
}
|
|
537
|
-
}).catch(() => {}).finally(() => {for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
470
|
+
}}).catch(() => {}).finally(() => {for (const n of toLoad) pendingPluginLoads.delete(n)
|
|
538
471
|
})
|
|
539
472
|
}
|
|
540
473
|
const schedulePluginLoad = (name) => {if (!name || loadedPluginNames.has(name) || pendingPluginLoads.has(name)) return
|
|
@@ -551,103 +484,76 @@ if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
|
551
484
|
}
|
|
552
485
|
if (typeof syncZhDom === 'function') syncZhDom()
|
|
553
486
|
setTimeout(() => { loadPluginDictionaries([]) }, 100)
|
|
554
|
-
}).catch(() => {if (typeof fetch === 'function') {fetch('/api/dsh-russian-lang/dict/all', { headers: { 'Accept': 'application/json' } })
|
|
555
|
-
.then((res) => res.ok ? res.json() : null)
|
|
556
|
-
.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
|
|
557
488
|
const allDicts = Object.assign({}, data.core || {}, data.plugins || {}, data)
|
|
558
489
|
if (data.zhRu && typeof ZH_RU === 'object') {Object.assign(ZH_RU, data.zhRu)
|
|
559
490
|
if (typeof updateZhRu === 'function') updateZhRu(data.zhRu)
|
|
560
491
|
}
|
|
561
492
|
registerDictMap(allDicts)
|
|
562
493
|
if (typeof syncZhDom === 'function') syncZhDom()
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
})
|
|
567
|
-
const origTranslate = runtime.translate.bind(runtime)
|
|
494
|
+
}).catch(() => {})
|
|
495
|
+
}})
|
|
568
496
|
const getOverrides = () => {try {const value = scope.getSnapshot().value
|
|
569
|
-
return value && value.overrides ? value.overrides : {}
|
|
570
|
-
} catch (err) { return {} }
|
|
571
|
-
}
|
|
497
|
+
return value && value.overrides ? value.overrides : {}} catch (err) { return {} }}
|
|
572
498
|
const getPluginLocalizationStatus = makePluginLocalizationStatus(RU)
|
|
573
|
-
try {runtime.formatNumber = formatNumber
|
|
574
|
-
runtime.formatCompactNumber = formatCompactNumber
|
|
575
|
-
runtime.formatDate = formatDate
|
|
576
|
-
runtime.formatTokens = formatTokens
|
|
577
|
-
runtime.formatRelativeTime = formatRelativeTime
|
|
578
|
-
runtime.formatCurrency = formatCurrency
|
|
579
|
-
runtime.inflect = inflect
|
|
580
|
-
runtime.getPluginLocalizationStatus = getPluginLocalizationStatus
|
|
581
|
-
runtime.stemRussian = stemRussian
|
|
582
|
-
runtime.fuzzyMatchRu = fuzzyMatchRu
|
|
583
|
-
runtime.humanizeError = humanizeError
|
|
584
|
-
runtime.plural = plural
|
|
585
|
-
runtime.pluralForm = pluralForm
|
|
586
|
-
} catch (err) { }
|
|
587
499
|
const lookupChain = () => {try {const chain = runtime.fallbackChain && runtime.fallbackChain(runtime.getLocale().active)
|
|
588
500
|
if (Array.isArray(chain) && chain.length) return chain
|
|
589
501
|
} catch (err) { }
|
|
590
502
|
return [runtime.getLocale().active]
|
|
591
503
|
}
|
|
592
504
|
const translationRegistry = new Map()
|
|
593
|
-
const lookup = (ns, key) =>
|
|
594
|
-
|
|
595
|
-
}
|
|
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) : (() => '')
|
|
596
519
|
runtime.translate = function (ns, key, params) {if (ns && !RU[ns] && !loadedPluginNames.has(ns)) schedulePluginLoad(ns)
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
599
|
-
}
|
|
520
|
+
const ov = getOverrides()
|
|
521
|
+
if (ov[key] !== undefined) return params ? fill(ov[key], params) : ov[key]
|
|
600
522
|
if (runtime.getLocale().active === 'ru' && params) {const n = params.n ?? params.count
|
|
601
523
|
if (typeof n === 'number') {const form = pluralForm(n)
|
|
602
524
|
const m = /^(.*)[.](one|other)$/.exec(key)
|
|
603
|
-
if (m) {if (form === 'few' || form === 'many') {const
|
|
604
|
-
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if (template !== undefined) {return fill(template, params)
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
const res = origTranslate(ns, key, params)
|
|
616
|
-
if (typeof res === 'string' && res && res.length < 300) {translationRegistry.set(res.trim(), { ns, key, params, value: res })
|
|
617
|
-
}
|
|
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 })
|
|
618
532
|
return res
|
|
619
533
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
} catch (err) { }
|
|
624
|
-
}
|
|
625
|
-
const native = runtime.getLocale().locales.some((l) => l.id === 'ru')
|
|
626
|
-
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' })
|
|
627
536
|
syncLang()
|
|
628
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')
|
|
629
543
|
const syncFlag = () => {try {const wantRu = runtime.getLocale().active === 'ru'
|
|
630
544
|
const value = scope.getSnapshot().value || {}
|
|
631
545
|
if (!!value.enabled !== wantRu) {const r = scope.set('enabled', wantRu)
|
|
632
546
|
if (r && typeof r.catch === 'function') {r.catch((err) => {console.warn('dsh-russian-lang: scope.set enabled failed', err && err.message || err)
|
|
633
547
|
})
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
} catch (err) {}
|
|
637
|
-
}
|
|
638
|
-
ctx.effect(() => {try { return runtime.subscribe(syncFlag) }
|
|
639
|
-
catch (err) { return undefined }
|
|
640
|
-
}, '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')
|
|
641
550
|
const activate = () => {try {if (runtime.getLocale().active === 'ru') return
|
|
642
551
|
runtime.setLocale('ru')
|
|
643
|
-
} catch (err) { console.warn('dsh-russian-lang: activate failed', err) }
|
|
644
|
-
}
|
|
552
|
+
} catch (err) { console.warn('dsh-russian-lang: activate failed', err) }}
|
|
645
553
|
let booted = false
|
|
646
554
|
const tryBoot = () => {if (booted) return
|
|
647
555
|
try {const value = scope.getSnapshot().value
|
|
648
|
-
if (value && value.enabled === true) { booted = true; activate() }
|
|
649
|
-
} catch (err) { }
|
|
650
|
-
}
|
|
556
|
+
if (value && value.enabled === true) { booted = true; activate() }} catch (err) { }}
|
|
651
557
|
ctx.effect(() => scope.subscribe(tryBoot), 'dsh-russian-lang: boot')
|
|
652
558
|
tryBoot()
|
|
653
559
|
const unsubscribeLang = runtime.subscribe(syncLang)
|
|
@@ -658,9 +564,7 @@ const SPELL_WAS = 'data-russian-lang-spell-was'
|
|
|
658
564
|
const LANG_WAS = 'data-russian-lang-lang-was'
|
|
659
565
|
const EDITABLE = 'textarea, input[type=text], input[type=search], [contenteditable=""], [contenteditable="true"]'
|
|
660
566
|
const MONO_RE = /mono|consol|courier/i
|
|
661
|
-
const isMonoField = (el) => {try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
|
|
662
|
-
catch (err) { return false }
|
|
663
|
-
}
|
|
567
|
+
const isMonoField = (el) => {try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') } catch (err) { return false }}
|
|
664
568
|
const spellOn = (el) => {if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
|
|
665
569
|
el.setAttribute(SPELL_ON, '1')
|
|
666
570
|
el.setAttribute(SPELL_WAS, el.getAttribute('spellcheck') ?? '')
|
|
@@ -691,18 +595,13 @@ if (spellObserver) return
|
|
|
691
595
|
spellObserver = new MutationObserver((records) => {for (const record of records) {for (const node of record.addedNodes) {if (node.nodeType !== 1) continue
|
|
692
596
|
if (node.matches(EDITABLE)) spellOn(node)
|
|
693
597
|
node.querySelectorAll ? node.querySelectorAll(EDITABLE).forEach(spellOn) : null
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
})
|
|
598
|
+
}}})
|
|
697
599
|
spellObserver.observe(document.body, { childList: true, subtree: true })
|
|
698
|
-
} catch (err) { }
|
|
699
|
-
}
|
|
600
|
+
} catch (err) { }}
|
|
700
601
|
const unsubscribeSpell = runtime.subscribe(syncSpell)
|
|
701
602
|
ctx.effect(() => {return () => {unsubscribeSpell()
|
|
702
603
|
if (spellObserver) spellObserver.disconnect()
|
|
703
|
-
try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }
|
|
704
|
-
}
|
|
705
|
-
}, 'dsh-russian-lang: spellcheck')
|
|
604
|
+
try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { }}}, 'dsh-russian-lang: spellcheck')
|
|
706
605
|
syncSpell()
|
|
707
606
|
const ZH_CJK = /[\u3400-\u9fff\uf900-\ufaff]/
|
|
708
607
|
const ZH_RE_ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
@@ -723,8 +622,7 @@ if (/\{[a-zA-Z_]\w*\}/.test(zhText)) {const parts = zhText.split(/\{[a-zA-Z_]\w*
|
|
|
723
622
|
if (parts.some((p) => p.length === 0)) continue
|
|
724
623
|
ZH_PATTERNS.push({ re: new RegExp(parts.map(ZH_RE_ESC).join('([\\s\\S]*?)')), ruParts: ruText.split(/\{[a-zA-Z_]\w*\}/g) })
|
|
725
624
|
} else {ZH_EXACT.set(zhText, ruText)
|
|
726
|
-
}
|
|
727
|
-
}
|
|
625
|
+
}}
|
|
728
626
|
ZH_PATTERNS.sort((a, b) => b.re.source.length - a.re.source.length)
|
|
729
627
|
rebuildZhSorted()
|
|
730
628
|
}
|
|
@@ -743,14 +641,12 @@ for (const p of ZH_PATTERNS) {const m = p.re.exec(trimmed)
|
|
|
743
641
|
if (m && m[0] === trimmed) {let out = p.ruParts[0]
|
|
744
642
|
for (let i = 1; i < p.ruParts.length; i++) out += m[i] + p.ruParts[i]
|
|
745
643
|
return text.replace(trimmed, out)
|
|
746
|
-
}
|
|
747
|
-
}
|
|
644
|
+
}}
|
|
748
645
|
let replaced = text
|
|
749
646
|
let changed = false
|
|
750
647
|
for (const [zhPhrase, ruPhrase] of zhSortedExact) {if (zhPhrase.length >= 2 && replaced.includes(zhPhrase)) {replaced = replaced.split(zhPhrase).join(ruPhrase)
|
|
751
648
|
changed = true
|
|
752
|
-
}
|
|
753
|
-
}
|
|
649
|
+
}}
|
|
754
650
|
if (changed) return replaced
|
|
755
651
|
return null
|
|
756
652
|
}
|
|
@@ -769,9 +665,7 @@ continue
|
|
|
769
665
|
if (trimmed.length >= 1 && ZH_CJK.test(val)) {hits.push(node)
|
|
770
666
|
} else if (trimmed.length >= 1 && DOM_EN_TEXT[trimmed]) {const p = node.parentElement
|
|
771
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])
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
668
|
+
}}}
|
|
775
669
|
for (const node of hits) {const next = zhTranslateText(node.nodeValue)
|
|
776
670
|
if (next && next !== node.nodeValue) node.nodeValue = next
|
|
777
671
|
}
|
|
@@ -781,19 +675,12 @@ if (DOM_EN_TEXT[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
|
|
|
781
675
|
} else if (DOM_EN_ATTRS[tr]) {el.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
|
|
782
676
|
} else if (ZH_CJK.test(v)) {const next = zhTranslateText(v)
|
|
783
677
|
if (next) el.setAttribute(attr, next)
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
678
|
+
}}}}
|
|
788
679
|
if (root.getAttribute) {for (const attr of ['title', 'placeholder', 'aria-label']) {const v = root.getAttribute(attr)
|
|
789
680
|
if (v) {const tr = v.trim()
|
|
790
681
|
if (DOM_EN_TEXT[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_TEXT[tr]))
|
|
791
682
|
else if (DOM_EN_ATTRS[tr]) root.setAttribute(attr, v.replace(tr, DOM_EN_ATTRS[tr]))
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
} catch (err) { }
|
|
796
|
-
}
|
|
683
|
+
}}}} catch (err) { }}
|
|
797
684
|
let zhObserver = null
|
|
798
685
|
let zhDebounceTimer = null
|
|
799
686
|
let zhWalking = false
|
|
@@ -803,8 +690,7 @@ if (zhWalking) return
|
|
|
803
690
|
zhWalking = true
|
|
804
691
|
try {ZH_WALKER(target || document.body)
|
|
805
692
|
} finally {zhWalking = false
|
|
806
|
-
}
|
|
807
|
-
}, 120)
|
|
693
|
+
}}, 120)
|
|
808
694
|
}
|
|
809
695
|
const syncZhDom = () => {try {if (typeof document === 'undefined') return
|
|
810
696
|
const ru = runtime.getLocale().active === 'ru'
|
|
@@ -815,30 +701,21 @@ return
|
|
|
815
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
|
|
816
702
|
queueZhWalk(node)
|
|
817
703
|
return
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
})
|
|
704
|
+
}}}}})
|
|
823
705
|
zhObserver.observe(document.body, { childList: true, subtree: true })
|
|
824
706
|
}
|
|
825
707
|
queueZhWalk(document.body)
|
|
826
|
-
} catch (err) { }
|
|
827
|
-
}
|
|
708
|
+
} catch (err) { }}
|
|
828
709
|
const unsubscribeZh = runtime.subscribe(syncZhDom)
|
|
829
710
|
ctx.effect(() => {return () => {unsubscribeZh()
|
|
830
711
|
if (zhObserver) zhObserver.disconnect()
|
|
831
|
-
if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }
|
|
832
|
-
}
|
|
833
|
-
}, 'dsh-russian-lang: zh-dom')
|
|
712
|
+
if (zhDebounceTimer) { clearTimeout(zhDebounceTimer); zhDebounceTimer = null }}}, 'dsh-russian-lang: zh-dom')
|
|
834
713
|
syncZhDom()
|
|
835
714
|
const TYPO_YO_PAIRS = [["еще","ещё"],["ее","её"],["черный","чёрный"],["черная","чёрная"],["черные","чёрные"],["зеленый","зелёный"],["желтый","жёлтый"],["легкий","лёгкий"],["тяжелый","тяжёлый"],["надежный","надёжный"],["дешевый","дешёвый"],["идет","идёт"],["дает","даёт"],["ведет","ведёт"],["несет","несёт"],["живет","живёт"],["привел","привёл"],["шел","шёл"],["нее","неё"],["мое","моё"],["серьезно","серьёзно"],["придется","придётся"],["насчет","насчёт"],["твое","твоё"],["свое","своё"],["пойдем","пойдём"],["пришел","пришёл"],["нашел","нашёл"],["идем","идём"],["вперед","вперёд"],["пошел","пошёл"],["ребенка","ребёнка"],["ребенок","ребёнок"],["счет","счёт"],["своем","своём"],["придет","придёт"],["ушел","ушёл"],["ждет","ждёт"],["мертв","мёртв"],["твоем","твоём"],["вернется","вернётся"],["днем","днём"],["найдем","найдём"],["пойдет","пойдёт"],["начнем","начнём"],["вернемся","вернёмся"],["остается","остаётся"],["принес","принёс"],["идешь","идёшь"],["трех","трёх"],["ребенком","ребёнком"],["самолет","самолёт"],["определенно","определённо"],["пойдешь","пойдёшь"],["умрет","умрёт"],["прием","приём"],["прошел","прошёл"],["убьет","убьёт"],["тетя","тётя"],["провел","провёл"],["произойдет","произойдёт"],["семьей","семьёй"],["пойдемте","пойдёмте"],["пройдет","пройдёт"],["возьмем","возьмём"],["вернешься","вернёшься"],["найдешь","найдёшь"],["живешь","живёшь"],["отчет","отчёт"],["займет","займёт"],["ждем","ждём"],["сошел","сошёл"],["вдвоем","вдвоём"],["найдет","найдёт"],["вел","вёл"],["живем","живём"],["зашел","зашёл"],["начнется","начнётся"],["путем","путём"],["подойдет","подойдёт"],["режиссер","режиссёр"],["уйдет","уйдёт"],["придешь","придёшь"],["идемте","идёмте"],["ведешь","ведёшь"],["пес","пёс"],["умрешь","умрёшь"],["ждешь","ждёшь"],["найдете","найдёте"],["найдется","найдётся"],["клево","клёво"],["поймешь","поймёшь"],["разберемся","разберёмся"],["времен","времён"],["введен","введён"],["включен","включён"],["включенных","включённых"],["возьмется","возьмётся"],["завершен","завершён"],["завершенного","завершённого"],["задает","задаёт"],["задается","задаётся"],["заменен","заменён"],["звезд","звёзд"],["звезды","звёзды"],["изменен","изменён"],["истек","истёк"],["незавершенная","незавершённая"],["неподтвержденные","неподтверждённые"],["несохраненные","несохранённые"],["обновлен","обновлён"],["обновленные","обновлённые"],["определен","определён"],["отклонен","отклонён"],["отменен","отменён"],["очередность","очерёдность"],["поврежден","повреждён"],["повторен","повторён"],["подключен","подключён"],["подтвержденные","подтверждённые"],["приемки","приёмки"],["раздает","раздаёт"],["разрешенная","разрешённая"],["разрешенные","разрешённые"],["создаем","создаём"],["сойдется","сойдётся"],["сохранен","сохранён"],["сохраненный","сохранённый"],["счету","счёту"],["тяжелые","тяжёлые"],["темная","тёмная"],["уберет","уберёт"],["удален","удалён"],["учетные","учётные"],["учетных","учётных"],["емкости","ёмкости"],["емкость","ёмкость"]]
|
|
836
715
|
const typoYo = makeTypoYo(TYPO_YO_PAIRS)
|
|
837
716
|
const getTypoConf = () => {try {const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
|
|
838
717
|
if (!t || t.enabled === false) return null
|
|
839
|
-
return { yo: t.yo === true }
|
|
840
|
-
} catch (err) { return null }
|
|
841
|
-
}
|
|
718
|
+
return { yo: t.yo === true }} catch (err) { return null }}
|
|
842
719
|
const typoNode = (node, conf) => {const before = node.nodeValue
|
|
843
720
|
if (!before || !before.match || (before.match(/[\u0400-\u04FF]/g) || []).length < 3) return
|
|
844
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
|
|
@@ -849,9 +726,7 @@ if (node.parentElement && node.parentElement.closest('[data-chat-flow-status="ru
|
|
|
849
726
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
850
727
|
if (sel && !sel.isCollapsed && sel.rangeCount > 0) {try {const range = sel.getRangeAt(0)
|
|
851
728
|
if (range.intersectsNode ? range.intersectsNode(node) : (sel.containsNode && sel.containsNode(node, true))) {return
|
|
852
|
-
}
|
|
853
|
-
} catch (e) { void e; }
|
|
854
|
-
}
|
|
729
|
+
}} catch (e) { void e; }}
|
|
855
730
|
let after = typoQuotes(before)
|
|
856
731
|
after = typoDash(after)
|
|
857
732
|
after = typoPunct(after)
|
|
@@ -871,8 +746,7 @@ for (const root of typoPending) {if (root.nodeType === 3) typoNode(root, conf)
|
|
|
871
746
|
else typoWalk(root, conf)
|
|
872
747
|
}
|
|
873
748
|
typoPending.clear()
|
|
874
|
-
} catch (err) { }
|
|
875
|
-
}
|
|
749
|
+
} catch (err) { }}
|
|
876
750
|
const typoPending = new Set()
|
|
877
751
|
const queueTypo = (roots) => {for (const r of roots) typoPending.add(r)
|
|
878
752
|
if (!typoQueued) typoQueued = requestAnimationFrame(flushTypo)
|
|
@@ -885,19 +759,15 @@ if (typoObserver) return
|
|
|
885
759
|
typoWalk(document.body, getTypoConf())
|
|
886
760
|
typoObserver = new MutationObserver((records) => {const roots = []
|
|
887
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)
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
762
|
+
}}}
|
|
891
763
|
if (roots.length > 0) queueTypo(roots)
|
|
892
764
|
})
|
|
893
765
|
typoObserver.observe(document.body, { childList: true, subtree: true })
|
|
894
|
-
} catch (err) { }
|
|
895
|
-
}
|
|
766
|
+
} catch (err) { }}
|
|
896
767
|
const unsubscribeTypo = runtime.subscribe(syncTypo)
|
|
897
768
|
ctx.effect(() => {return () => {unsubscribeTypo()
|
|
898
769
|
if (typoObserver) typoObserver.disconnect()
|
|
899
|
-
}
|
|
900
|
-
}, 'dsh-russian-lang: typography')
|
|
770
|
+
}}, 'dsh-russian-lang: typography')
|
|
901
771
|
syncTypo()
|
|
902
772
|
const layout = makeLayout(new Set(["не","что","ты","это","на","он","мы","как","вы","да","мне","нет","меня","так","но","его","все","она","тебя","если","за","бы","тебе","они","чтобы","же","есть","просто","из","для"]), new Set())
|
|
903
773
|
const layoutFixCandidate = layout.candidate
|
|
@@ -914,16 +784,13 @@ if (el._valueTracker) {el._valueTracker.setValue(value === '' ? '__force__' : ''
|
|
|
914
784
|
}
|
|
915
785
|
try {const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$') || k.startsWith('__reactEventHandlers$'))
|
|
916
786
|
if (propsKey && el[propsKey] && typeof el[propsKey].onChange === 'function') {el[propsKey].onChange({ target: el, currentTarget: el })
|
|
917
|
-
}
|
|
918
|
-
} catch (e) { void e; }
|
|
787
|
+
}} catch (e) { void e; }
|
|
919
788
|
try {el.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
|
|
920
789
|
} catch (e) {el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
921
790
|
}
|
|
922
791
|
el.dispatchEvent(new Event('change', { bubbles: true }))
|
|
923
792
|
if (typeof cursorStart === 'number' && typeof el.setSelectionRange === 'function') {const end = typeof cursorEnd === 'number' ? cursorEnd : cursorStart
|
|
924
|
-
try { el.setSelectionRange(cursorStart, end) } catch (e) { void e; }
|
|
925
|
-
}
|
|
926
|
-
}
|
|
793
|
+
try { el.setSelectionRange(cursorStart, end) } catch (e) { void e; }}}
|
|
927
794
|
let layoutHintEl = null
|
|
928
795
|
const layoutCurrentInput = (ev) => {if (ev && ev.target) {const t = ev.target
|
|
929
796
|
if (t.tagName === 'TEXTAREA' || (t.tagName === 'INPUT' && (t.type === 'text' || !t.type))) {return t
|
|
@@ -951,8 +818,7 @@ preCaretRange.selectNodeContents(root)
|
|
|
951
818
|
preCaretRange.setEnd(range.startContainer, range.startOffset)
|
|
952
819
|
return preCaretRange.toString().length
|
|
953
820
|
} catch (e) {return -1
|
|
954
|
-
}
|
|
955
|
-
}
|
|
821
|
+
}}
|
|
956
822
|
function setCaretCharacterOffset(root, offset) {if (!root || offset < 0) return
|
|
957
823
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
958
824
|
if (!sel) return
|
|
@@ -974,9 +840,7 @@ range.setStart(targetNode, targetOffset)
|
|
|
974
840
|
range.collapse(true)
|
|
975
841
|
sel.removeAllRanges()
|
|
976
842
|
sel.addRange(range)
|
|
977
|
-
}
|
|
978
|
-
} catch (e) { void e; }
|
|
979
|
-
}
|
|
843
|
+
}} catch (e) { void e; }}
|
|
980
844
|
const isCaretInCode = (el, value, caretOffset) => {if (!el) return false
|
|
981
845
|
if (caretOffset == null || caretOffset < 0) caretOffset = (value || '').length
|
|
982
846
|
const sel = typeof window !== 'undefined' && window.getSelection && window.getSelection()
|
|
@@ -1013,10 +877,8 @@ sel.addRange(range)
|
|
|
1013
877
|
document.execCommand('insertText', false, value)
|
|
1014
878
|
const targetOffset = typeof cursorStart === 'number' ? cursorStart : (initialOffset >= 0 ? Math.min(initialOffset, value.length) : value.length)
|
|
1015
879
|
setCaretCharacterOffset(host, targetOffset)
|
|
1016
|
-
} catch (e) { void e; }
|
|
1017
|
-
}
|
|
1018
|
-
const layoutDismiss = () => {if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }
|
|
1019
|
-
}
|
|
880
|
+
} catch (e) { void e; }}
|
|
881
|
+
const layoutDismiss = () => {if (layoutHintEl) { layoutHintEl.remove(); layoutHintEl = null }}
|
|
1020
882
|
const layoutShowHint = (inputEl, converted, direction) => {layoutDismiss()
|
|
1021
883
|
layoutHintEl = document.createElement('div')
|
|
1022
884
|
layoutHintEl.dataset.russianLangLayout = '1'
|
|
@@ -1053,8 +915,7 @@ const v = getComposerText(el)
|
|
|
1053
915
|
const c = layoutFixCandidate(v, 'lat2cyr') || layoutFixCandidate(v, 'cyr2lat')
|
|
1054
916
|
if (c) {setComposerText(el, c.converted)
|
|
1055
917
|
learnWords(c.converted)
|
|
1056
|
-
}
|
|
1057
|
-
})
|
|
918
|
+
}})
|
|
1058
919
|
document.body.appendChild(layoutBadgeEl)
|
|
1059
920
|
}
|
|
1060
921
|
layoutBadgeEl.textContent = label
|
|
@@ -1062,8 +923,7 @@ const r = el.getBoundingClientRect()
|
|
|
1062
923
|
layoutBadgeEl.style.left = (r.right - 36) + 'px'
|
|
1063
924
|
layoutBadgeEl.style.top = (r.top - 22) + 'px'
|
|
1064
925
|
}
|
|
1065
|
-
const layoutBadgeHide = () => {if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }
|
|
1066
|
-
}
|
|
926
|
+
const layoutBadgeHide = () => {if (layoutBadgeEl) { layoutBadgeEl.remove(); layoutBadgeEl = null }}
|
|
1067
927
|
let isFormatting = false
|
|
1068
928
|
const layoutOnInput = (ev) => {if (isFormatting) return
|
|
1069
929
|
try {const el = layoutCurrentInput(ev)
|
|
@@ -1074,14 +934,11 @@ if (value.trim().length < 4) { layoutDismiss(); return }
|
|
|
1074
934
|
const latCount = (value.match(/[a-z]/g) || []).length
|
|
1075
935
|
const cyrCount = (value.match(/[\u0430-\u044f\u0451]/g) || []).length
|
|
1076
936
|
if (latCount > cyrCount && cyrCount === 0) {const c = layoutFixCandidate(value, 'lat2cyr')
|
|
1077
|
-
if (c) { layoutShowHint(el, c.converted, 'ru'); return }
|
|
1078
|
-
}
|
|
937
|
+
if (c) { layoutShowHint(el, c.converted, 'ru'); return }}
|
|
1079
938
|
if (cyrCount > 0 && latCount === 0 && value.trim().startsWith('/')) {const c = layoutFixCandidate(value, 'cyr2lat')
|
|
1080
|
-
if (c) { layoutShowHint(el, c.converted, 'cmd'); return }
|
|
1081
|
-
}
|
|
939
|
+
if (c) { layoutShowHint(el, c.converted, 'cmd'); return }}
|
|
1082
940
|
layoutDismiss()
|
|
1083
|
-
} catch (err) { }
|
|
1084
|
-
}
|
|
941
|
+
} catch (err) { }}
|
|
1085
942
|
const unsubscribeLayout = runtime.subscribe(layoutOnInput)
|
|
1086
943
|
const layoutOnKeydown = (ev) => {const el = layoutCurrentInput(ev)
|
|
1087
944
|
if (!el) return
|
|
@@ -1102,8 +959,7 @@ learnWords(c.converted)
|
|
|
1102
959
|
} finally {isFormatting = false
|
|
1103
960
|
}
|
|
1104
961
|
return
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
962
|
+
}}
|
|
1107
963
|
const c = layoutFixCandidate(value, 'lat2cyr') || layoutFixCandidate(value, 'cyr2lat')
|
|
1108
964
|
if (c) {ev.preventDefault()
|
|
1109
965
|
isFormatting = true
|
|
@@ -1116,8 +972,7 @@ setComposerText(el, c.converted, offset, offset)
|
|
|
1116
972
|
}
|
|
1117
973
|
learnWords(c.converted)
|
|
1118
974
|
} finally {isFormatting = false
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
975
|
+
}}
|
|
1121
976
|
return
|
|
1122
977
|
}
|
|
1123
978
|
if (ev.key === ' ' || ev.key === 'Enter') {if (value && value.startsWith('/') && typeof expandSlashAlias === 'function') {const expanded = expandSlashAlias(value)
|
|
@@ -1131,18 +986,12 @@ return
|
|
|
1131
986
|
} else if (ev.key === 'Enter') {isFormatting = true
|
|
1132
987
|
try {setComposerText(el, expanded, expanded.length, expanded.length)
|
|
1133
988
|
} finally {isFormatting = false
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
989
|
+
}}}}}
|
|
1139
990
|
if (ev.key === 'Enter' && !ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1140
991
|
const typoLive = snapVal.typography ? snapVal.typography.liveInput === true : false
|
|
1141
992
|
if (typoLive && typeof formatInputLive === 'function' && (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT')) {const formatted = formatInputLive(value)
|
|
1142
993
|
if (formatted && formatted !== value) {setNativeInputValue(el, formatted, formatted.length, formatted.length)
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
994
|
+
}}}
|
|
1146
995
|
try {const snapVal = scope ? (scope.getSnapshot().value || {}) : {}
|
|
1147
996
|
const typoLive = snapVal.typography ? snapVal.typography.liveInput === true : false
|
|
1148
997
|
if (typoLive && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {const isTextarea = el.tagName === 'TEXTAREA' || el.tagName === 'INPUT'
|
|
@@ -1166,12 +1015,7 @@ setNativeInputValue(el, nextVal, caretPos + 1, caretPos + 1)
|
|
|
1166
1015
|
} finally {isFormatting = false
|
|
1167
1016
|
}
|
|
1168
1017
|
return
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
} catch (e) { void e; }
|
|
1174
|
-
}
|
|
1018
|
+
}}}}} catch (e) { void e; }}
|
|
1175
1019
|
const getHoverBox = () => {if (typeof document === 'undefined') return null
|
|
1176
1020
|
if (!hoverBoxEl) {hoverBoxEl = document.createElement('div')
|
|
1177
1021
|
hoverBoxEl.id = 'dsh-ru-inspector-hover'
|
|
@@ -1266,17 +1110,15 @@ const copyKeyBtn = modal.querySelector('.rl-copy-key-btn')
|
|
|
1266
1110
|
if (copyKeyBtn) {copyKeyBtn.onclick = () => {try { navigator.clipboard.writeText(k || rawText) } catch (err) { void err; }
|
|
1267
1111
|
copyKeyBtn.textContent = '✓'
|
|
1268
1112
|
setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1113
|
+
}}
|
|
1271
1114
|
const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
|
|
1272
1115
|
if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
|
|
1273
|
-
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.
|
|
1116
|
+
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.10' })
|
|
1274
1117
|
: ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
|
|
1275
1118
|
try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
|
|
1276
1119
|
copyReportBtn.textContent = '✓ Скопировано'
|
|
1277
1120
|
setTimeout(() => { copyReportBtn.textContent = '📋 Скопировать репорт' }, 2000)
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1121
|
+
}}
|
|
1280
1122
|
const resetBtn = modal.querySelector('.rl-reset-override-btn')
|
|
1281
1123
|
if (resetBtn) {resetBtn.onclick = () => {const targetKey = k || rawText
|
|
1282
1124
|
const cur = getOverrides()
|
|
@@ -1285,8 +1127,7 @@ delete updated[targetKey]
|
|
|
1285
1127
|
try { scope.set('overrides', updated) } catch (err) { void err; }
|
|
1286
1128
|
modal.remove()
|
|
1287
1129
|
if (typeof queueZhWalk === 'function') queueZhWalk()
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1130
|
+
}}
|
|
1290
1131
|
const saveBtn = modal.querySelector('.rl-save-btn')
|
|
1291
1132
|
saveBtn.onclick = () => {const val = textarea.value.trim()
|
|
1292
1133
|
const targetKey = k || rawText
|
|
@@ -1296,9 +1137,7 @@ const updated = Object.assign({}, cur, { [targetKey]: val })
|
|
|
1296
1137
|
try { scope.set('overrides', updated) } catch (_e) { void _e }
|
|
1297
1138
|
if (targetElement) {try {if (targetElement.childNodes && targetElement.childNodes.length === 1 && targetElement.childNodes[0].nodeType === 3) {targetElement.childNodes[0].nodeValue = val
|
|
1298
1139
|
} else {targetElement.textContent = val
|
|
1299
|
-
}
|
|
1300
|
-
} catch (err) { void err; }
|
|
1301
|
-
}
|
|
1140
|
+
}} catch (err) { void err; }}
|
|
1302
1141
|
saveBtn.textContent = '✓ Применено'
|
|
1303
1142
|
saveBtn.disabled = true
|
|
1304
1143
|
if (typeof queueZhWalk === 'function') queueZhWalk()
|
|
@@ -1343,17 +1182,14 @@ else if (meta.source === 'untranslated_zh') title = '🔴 ZH'
|
|
|
1343
1182
|
else if (meta.source === 'untranslated_en') title = '🔴 EN'
|
|
1344
1183
|
}
|
|
1345
1184
|
badge.textContent = title
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1185
|
+
}}
|
|
1348
1186
|
const inspectorOnKeyDown = (e) => {if (e.altKey && (e.key === 'i' || e.key === 'I' || e.code === 'KeyI')) {e.preventDefault()
|
|
1349
1187
|
isInspectorActive = !isInspectorActive
|
|
1350
1188
|
updateInspectorPill()
|
|
1351
1189
|
if (!isInspectorActive && hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1190
|
+
}}
|
|
1354
1191
|
const inspectorOnKeyUp = (e) => {if (e.key === 'Alt' && !isInspectorActive) {if (hoverBoxEl) hoverBoxEl.style.display = 'none'
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1192
|
+
}}
|
|
1357
1193
|
ctx.effect(() => {document.addEventListener('click', inspectorOnClick, true)
|
|
1358
1194
|
document.addEventListener('mousemove', inspectorOnMouseMove, true)
|
|
1359
1195
|
document.addEventListener('keydown', inspectorOnKeyDown, true)
|
|
@@ -1365,9 +1201,7 @@ document.removeEventListener('keyup', inspectorOnKeyUp, true)
|
|
|
1365
1201
|
const m = document.getElementById('dsh-ru-inspector-modal')
|
|
1366
1202
|
if (m) m.remove()
|
|
1367
1203
|
if (hoverBoxEl) { hoverBoxEl.remove(); hoverBoxEl = null }
|
|
1368
|
-
if (pillEl) { pillEl.remove(); pillEl = null }
|
|
1369
|
-
}
|
|
1370
|
-
}, 'dsh-russian-lang: inspector')
|
|
1204
|
+
if (pillEl) { pillEl.remove(); pillEl = null }}}, 'dsh-russian-lang: inspector')
|
|
1371
1205
|
runtime.findTranslationKey = (text) => (typeof findTranslationKey === 'function' ? findTranslationKey(text, RU, getOverrides(), ZH_RU) : null)
|
|
1372
1206
|
runtime.openInspector = openInspectorModal
|
|
1373
1207
|
runtime.translationRegistry = translationRegistry
|
|
@@ -1380,16 +1214,10 @@ if (node.matches && (node.matches('.dsw-toast, [role="alert"], .toast, .ant-mess
|
|
|
1380
1214
|
if (/failed to fetch|network error|econnrefused|etimedout/i.test(text)) {const h = typeof humanizeError === 'function' ? humanizeError(text) : null
|
|
1381
1215
|
if (h && h.title) {const targetEl = node.querySelector('.toast-body, .ant-message-custom-content') || node
|
|
1382
1216
|
targetEl.textContent = h.title + ': ' + h.message
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
}) : null
|
|
1217
|
+
}}}}}}) : null
|
|
1389
1218
|
ctx.effect(() => {if (toastObserver && typeof document !== 'undefined' && document.body) {toastObserver.observe(document.body, { childList: true, subtree: true })
|
|
1390
1219
|
}
|
|
1391
|
-
return () => { if (toastObserver) toastObserver.disconnect() }
|
|
1392
|
-
}, 'dsh-russian-lang: toast-humanizer')
|
|
1220
|
+
return () => { if (toastObserver) toastObserver.disconnect() }}, 'dsh-russian-lang: toast-humanizer')
|
|
1393
1221
|
ctx.effect(() => {document.addEventListener('input', layoutOnInput, true)
|
|
1394
1222
|
document.addEventListener('keydown', layoutOnKeydown, true)
|
|
1395
1223
|
return () => {unsubscribeLayout()
|
|
@@ -1397,13 +1225,11 @@ document.removeEventListener('input', layoutOnInput, true)
|
|
|
1397
1225
|
document.removeEventListener('keydown', layoutOnKeydown, true)
|
|
1398
1226
|
layoutDismiss()
|
|
1399
1227
|
layoutBadgeHide()
|
|
1400
|
-
}
|
|
1401
|
-
}, 'dsh-russian-lang: layout')
|
|
1228
|
+
}}, 'dsh-russian-lang: layout')
|
|
1402
1229
|
if (!ctx.slots || !React) return
|
|
1403
1230
|
const toggleRu = (wantRu) => {try {if (runtime.getLocale().active === wantRu) return
|
|
1404
1231
|
runtime.setLocale(wantRu ? 'ru' : 'en')
|
|
1405
|
-
} catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }
|
|
1406
|
-
}
|
|
1232
|
+
} catch (err) { console.warn('dsh-russian-lang: toggle failed', err) }}
|
|
1407
1233
|
try {ctx.slots.inject('plugins.item', () =>
|
|
1408
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),)
|
|
1409
1235
|
ctx.slots.inject('plugins.row.config', () =>
|
|
@@ -1419,15 +1245,13 @@ ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian
|
|
|
1419
1245
|
} catch (err) { }
|
|
1420
1246
|
try {ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1421
1247
|
ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
|
|
1422
|
-
} catch (err) { }
|
|
1423
|
-
}
|
|
1248
|
+
} catch (err) { }}
|
|
1424
1249
|
function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
|
|
1425
1250
|
const runtime = props.runtime || inj.runtime
|
|
1426
1251
|
const toggleRu = props.toggleRu || inj.toggleRu
|
|
1427
1252
|
const [locale, setLocaleState] = React.useState(runtime ? (runtime.getLocale().active || 'en') : 'ru')
|
|
1428
1253
|
React.useEffect(() => {if (!runtime) return
|
|
1429
|
-
return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }
|
|
1430
|
-
})
|
|
1254
|
+
return runtime.subscribe(() => {try { setLocaleState(runtime.getLocale().active || 'en') } catch (e) { }})
|
|
1431
1255
|
}, [runtime])
|
|
1432
1256
|
const isRu = locale === 'ru'
|
|
1433
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'))
|
|
@@ -1449,8 +1273,7 @@ if (text) messages.push({ role: 'user', content: text })
|
|
|
1449
1273
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"], .rl-turn-translation').forEach((b) => b.remove())
|
|
1450
1274
|
const text = clone.innerText.trim()
|
|
1451
1275
|
if (text) messages.push({ role: 'assistant', content: text })
|
|
1452
|
-
}
|
|
1453
|
-
})
|
|
1276
|
+
}})
|
|
1454
1277
|
}
|
|
1455
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]'))
|
|
1456
1279
|
const seen = new Set()
|
|
@@ -1477,24 +1300,18 @@ a.download = safeTitle + '-' + new Date().toISOString().slice(0, 10) + '.md'
|
|
|
1477
1300
|
document.body.appendChild(a)
|
|
1478
1301
|
a.click()
|
|
1479
1302
|
setDone(true)
|
|
1480
|
-
setTimeout(() => {try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }
|
|
1481
|
-
}, 30000)
|
|
1303
|
+
setTimeout(() => {try { document.body.removeChild(a); URL.revokeObjectURL(url) } catch (e) { void e; }}, 30000)
|
|
1482
1304
|
setTimeout(() => setDone(false), 2000)
|
|
1483
1305
|
} catch (err) {console.warn('dsh-russian-lang: export md failed', err)
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1306
|
+
}}
|
|
1486
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'))
|
|
1487
1308
|
}
|
|
1488
1309
|
async function translateTurnContent(text) {if (!text || typeof text !== 'string') return { error: 'Текст для перевода пуст.' }
|
|
1489
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 })
|
|
1490
1311
|
})
|
|
1491
1312
|
const data = await res.json().catch(() => ({}))
|
|
1492
|
-
if (!res.ok) {return { error: data.error || ('Ошибка перевода (HTTP ' + res.status + ')') }
|
|
1493
|
-
}
|
|
1494
|
-
return { translatedText: data.translatedText || text }
|
|
1495
|
-
} catch (err) {return { error: 'Не удалось связаться с хостом DSH: ' + (err.message || err) }
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
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) }}}
|
|
1498
1315
|
function TranslateTurnAction(props) {const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1499
1316
|
const [loading, setLoading] = React.useState(false)
|
|
1500
1317
|
const [open, setOpen] = React.useState(false)
|
|
@@ -1529,8 +1346,7 @@ rawText = targetSteps.map((s) => {const clone = s.cloneNode(true)
|
|
|
1529
1346
|
clone.querySelectorAll('button, svg, [class*="actions"], [class*="Actions"]').forEach((b) => b.remove())
|
|
1530
1347
|
return clone.innerText.trim()
|
|
1531
1348
|
}).filter(Boolean).join('\n\n')
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1349
|
+
}}
|
|
1534
1350
|
if (!rawText) {const prose = tailFlowItem.querySelector('.dsw-prose, [data-block-kind="text"], .dsw-markdown-view, p')
|
|
1535
1351
|
if (prose) rawText = prose.innerText.trim()
|
|
1536
1352
|
}
|
|
@@ -1570,8 +1386,7 @@ if (parentContainer) {parentContainer.insertBefore(box, tailFlowItem)
|
|
|
1570
1386
|
setOpen(true)
|
|
1571
1387
|
} catch (err) {console.warn('dsh-russian-lang: translate turn failed', err)
|
|
1572
1388
|
} finally {setLoading(false)
|
|
1573
|
-
}
|
|
1574
|
-
},}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
1389
|
+
}},}, React.createElement('span', null, loading ? '...' : (open ? 'RU ✓' : 'RU ↗')))
|
|
1575
1390
|
}
|
|
1576
1391
|
function SettingsCard(props) {const inj = typeof props.inject === 'function'
|
|
1577
1392
|
? (props.inject() || {})
|
|
@@ -1581,70 +1396,50 @@ const runtime = props.runtime || inj.runtime
|
|
|
1581
1396
|
const toggleRu = props.toggleRu || inj.toggleRu
|
|
1582
1397
|
const t = typeof props.t === 'function' ? props.t : ((k) => k)
|
|
1583
1398
|
const [open, setOpen] = React.useState(false)
|
|
1584
|
-
const [snap, setSnap] = React.useState(
|
|
1585
|
-
() =>
|
|
1586
|
-
const [ruActive, setRuActive] = React.useState(
|
|
1587
|
-
() => { 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 } })
|
|
1588
1401
|
const [typo, setTypoState] = React.useState(() =>
|
|
1589
1402
|
(snap.value && snap.value.typography) || {})
|
|
1590
|
-
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.
|
|
1403
|
+
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.10',latestVersion: undefined,updateAvailable: false
|
|
1591
1404
|
})
|
|
1592
1405
|
const [upLoading, setUpLoading] = React.useState(false)
|
|
1593
1406
|
const [upMsg, setUpMsg] = React.useState(null)
|
|
1594
1407
|
const checkUpdate = () => {setUpLoading(true)
|
|
1595
1408
|
setUpMsg(null)
|
|
1596
|
-
fetch('/api/dsh-russian-lang/update', {headers: { 'x-dsh-plugin-update': '1' }
|
|
1597
|
-
})
|
|
1598
|
-
.then((r) => r.json())
|
|
1599
|
-
.then((data) => {setUpStatus(data)
|
|
1409
|
+
fetch('/api/dsh-russian-lang/update', {headers: { 'x-dsh-plugin-update': '1' }}).then((r) => r.json()).then((data) => {setUpStatus(data)
|
|
1600
1410
|
setUpLoading(false)
|
|
1601
|
-
})
|
|
1602
|
-
.catch(() => {setUpLoading(false)
|
|
1411
|
+
}).catch(() => {setUpLoading(false)
|
|
1603
1412
|
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1604
1413
|
})
|
|
1605
1414
|
}
|
|
1606
1415
|
const triggerUpdate = () => {setUpLoading(true)
|
|
1607
1416
|
setUpMsg(null)
|
|
1608
|
-
fetch('/api/dsh-russian-lang/update', {method: 'POST',headers: { 'x-dsh-plugin-update': '1' }
|
|
1609
|
-
})
|
|
1610
|
-
.then((r) => r.json())
|
|
1611
|
-
.then((data) => {setUpLoading(false)
|
|
1417
|
+
fetch('/api/dsh-russian-lang/update', {method: 'POST',headers: { 'x-dsh-plugin-update': '1' }}).then((r) => r.json()).then((data) => {setUpLoading(false)
|
|
1612
1418
|
if (data.restartRequired || data.updatedVersion) {setUpStatus(data)
|
|
1613
1419
|
setUpMsg({ type: 'ok', text: t('updaterSuccess') })
|
|
1614
1420
|
} else if (data.error) {setUpMsg({ type: 'err', text: data.error })
|
|
1615
|
-
}
|
|
1616
|
-
})
|
|
1617
|
-
.catch(() => {setUpLoading(false)
|
|
1421
|
+
}}).catch(() => {setUpLoading(false)
|
|
1618
1422
|
setUpMsg({ type: 'err', text: t('updaterFailed') })
|
|
1619
1423
|
})
|
|
1620
1424
|
}
|
|
1621
1425
|
const [transStatus, setTransStatus] = React.useState(null)
|
|
1622
1426
|
const [transStarting, setTransStarting] = React.useState(false)
|
|
1623
1427
|
const [transMsg, setTransMsg] = React.useState(null)
|
|
1624
|
-
const fetchTransStatus = () => {fetch('/api/dsh-russian-lang/translator/status')
|
|
1625
|
-
.then((r) => r.json())
|
|
1626
|
-
.then((data) => setTransStatus(data))
|
|
1627
|
-
.catch(() => setTransStatus(null))
|
|
1428
|
+
const fetchTransStatus = () => {fetch('/api/dsh-russian-lang/translator/status').then((r) => r.json()).then((data) => setTransStatus(data)).catch(() => setTransStatus(null))
|
|
1628
1429
|
}
|
|
1629
1430
|
const startLibreTranslate = () => {setTransStarting(true)
|
|
1630
1431
|
setTransMsg(null)
|
|
1631
|
-
fetch('/api/dsh-russian-lang/translator/setup', {method: 'POST',headers: { 'x-dsh-translator': '1' }
|
|
1632
|
-
})
|
|
1633
|
-
.then((r) => r.json())
|
|
1634
|
-
.then((data) => {setTransStarting(false)
|
|
1432
|
+
fetch('/api/dsh-russian-lang/translator/setup', {method: 'POST',headers: { 'x-dsh-translator': '1' }}).then((r) => r.json()).then((data) => {setTransStarting(false)
|
|
1635
1433
|
if (data.ok) {setTransMsg({ type: 'ok', text: data.message || 'Контейнер LibreTranslate запущен.' })
|
|
1636
1434
|
fetchTransStatus()
|
|
1637
1435
|
} else {setTransMsg({ type: 'err', text: data.error || 'Ошибка запуска контейнера.' })
|
|
1638
|
-
}
|
|
1639
|
-
})
|
|
1640
|
-
.catch((err) => {setTransStarting(false)
|
|
1436
|
+
}}).catch((err) => {setTransStarting(false)
|
|
1641
1437
|
setTransMsg({ type: 'err', text: 'Ошибка: ' + (err.message || err) })
|
|
1642
1438
|
})
|
|
1643
1439
|
}
|
|
1644
1440
|
React.useEffect(() => {if (open || (props && props.view === 'page')) {checkUpdate()
|
|
1645
1441
|
fetchTransStatus()
|
|
1646
|
-
}
|
|
1647
|
-
}, [open, props])
|
|
1442
|
+
}}, [open, props])
|
|
1648
1443
|
React.useEffect(() => {if (!scope || !scope.subscribe) return undefined
|
|
1649
1444
|
const un = scope.subscribe(() => {const s = scope.getSnapshot()
|
|
1650
1445
|
setSnap(s)
|
|
@@ -1653,11 +1448,9 @@ if (s.value && s.value.typography) setTypoState(s.value.typography)
|
|
|
1653
1448
|
setSnap(scope.getSnapshot())
|
|
1654
1449
|
return un
|
|
1655
1450
|
}, [scope])
|
|
1656
|
-
React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }
|
|
1657
|
-
})
|
|
1451
|
+
React.useEffect(() => {try {const un = runtime.subscribe(() => {try { setRuActive(runtime.getLocale().active === 'ru') } catch (e) { }})
|
|
1658
1452
|
return un
|
|
1659
|
-
} catch (e) { return undefined }
|
|
1660
|
-
}, [runtime])
|
|
1453
|
+
} catch (e) { return undefined }}, [runtime])
|
|
1661
1454
|
const status = snap.status || 'loading'
|
|
1662
1455
|
const value = snap.value || {}
|
|
1663
1456
|
const typography = typo
|
|
@@ -1671,8 +1464,7 @@ try {const r = scope.set('typography', next)
|
|
|
1671
1464
|
if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set typography failed', err && err.message || err)
|
|
1672
1465
|
})
|
|
1673
1466
|
} catch (err) {console.warn('dsh-russian-lang: scope.set typography sync threw', err && err.message || err)
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1467
|
+
}}
|
|
1676
1468
|
const onEnabled = (ev) => { if (toggleRu) toggleRu(ev.target.checked) }
|
|
1677
1469
|
const checkbox = (checked, onChange, disabled) =>
|
|
1678
1470
|
React.createElement('input', {type: 'checkbox', checked: !!checked, disabled: !!disabled,className: 'rl-check', onChange: (ev) => onChange(ev),})
|
|
@@ -1691,129 +1483,59 @@ const presetInfo = typeof SYSTEM_PROMPT_PRESETS !== 'undefined' ? SYSTEM_PROMPT_
|
|
|
1691
1483
|
if (props && props.view === 'summary') {return React.createElement('div', { className: 'rl-sub' }, statusLine || t('cardSub'))
|
|
1692
1484
|
}
|
|
1693
1485
|
const page = !!(props && props.view === 'page')
|
|
1694
|
-
return React.createElement('div', { className: page ? 'rl-page-seat' : 'rl-card' },React.createElement('button', {type: 'button',className: 'rl-head',style: page ? { display: 'none' } : undefined,'aria-expanded': page ? 'true' : String(open),onClick: () => setOpen(!open),},React.createElement('span', { className: 'rl-head-main' },React.createElement('div', { className: 'rl-title' },'🇷🇺 ' + t('cardTitle'),React.createElement('span', { className: 'rl-badge ' + (ruActive ? 'rl-badge-ok' : 'rl-badge-warn') },ruActive ? t('badgeRu') : t('badgeEn')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, t('badgeCoverage'))
|
|
1695
|
-
),React.createElement('div', { className: 'rl-
|
|
1696
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secLanguageDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(ruActive, onEnabled, disabled),t('enabled')
|
|
1697
|
-
)
|
|
1698
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('enabledDesc'))
|
|
1699
|
-
),React.createElement('div', { className: 'rl-hint-text' }, t('quickSwitchNote')),overridesCount > 0 ? React.createElement('div', { className: 'rl-badge rl-badge-dim', style: { alignSelf: 'flex-start' } },t('overridesCount') + ': ' + overridesCount) : null
|
|
1700
|
-
),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secTypography')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, t('badgeSmartUx'))
|
|
1701
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secTypographyDesc')),React.createElement('div', { className: 'rl-grid-2' },React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(typography.enabled !== false && ruActive,(ev) => setTypo({ enabled: ev.target.checked }), !ruActive || disabled),t('typography')
|
|
1702
|
-
)
|
|
1703
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('typographyDesc'))
|
|
1704
|
-
),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(typography.liveInput !== false && ruActive,(ev) => setTypo({ liveInput: ev.target.checked }), !ruActive || disabled),t('liveInput')
|
|
1705
|
-
)
|
|
1706
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('liveInputDesc'))
|
|
1707
|
-
),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(typography.yo === true && ruActive,(ev) => setTypo({ yo: ev.target.checked }), !ruActive || disabled),t('yo')
|
|
1708
|
-
)
|
|
1709
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('yoDesc'))
|
|
1710
|
-
),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(value.slashAliases !== false && ruActive,(ev) => {try { scope.set('slashAliases', ev.target.checked) } catch (err) { void err; }
|
|
1711
|
-
}, !ruActive || disabled),t('slashAliases')
|
|
1712
|
-
)
|
|
1713
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('slashAliasesDesc'))
|
|
1714
|
-
)
|
|
1715
|
-
),React.createElement('div', { className: 'rl-hotkey-box' },React.createElement('span', { className: 'rl-hotkey-tag' }, '⌨️ Alt+L'),React.createElement('span', null, t('altLHintText'))
|
|
1716
|
-
)
|
|
1717
|
-
),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secAgentPrompt')),React.createElement('span', { className: 'rl-badge ' + (value.agentPrompt ? 'rl-badge-ok' : 'rl-badge-dim') },value.agentPrompt ? 'Активен' : 'Выключен')
|
|
1718
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secAgentPromptDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { className: 'rl-item-head' },React.createElement('label', { className: 'rl-item-label' },checkbox(value.agentPrompt === true,(ev) => {try {const r = scope.set('agentPrompt', ev.target.checked)
|
|
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)
|
|
1719
1488
|
if (r && typeof r.catch === 'function') r.catch((err) => {console.warn('dsh-russian-lang: scope.set agentPrompt failed', err && err.message || err)
|
|
1720
1489
|
})
|
|
1721
1490
|
} catch (err) {console.warn('dsh-russian-lang: scope.set agentPrompt sync threw', err && err.message || err)
|
|
1722
|
-
}
|
|
1723
|
-
}, false),t('agentPrompt')
|
|
1724
|
-
)
|
|
1725
|
-
),React.createElement('div', { className: 'rl-item-desc' }, t('agentPromptDesc'))
|
|
1726
|
-
),value.agentPrompt ? React.createElement(React.Fragment, null,React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('agentPromptPreset')),React.createElement('select', {className: 'rl-select',value: value.agentPromptPreset || 'technical_expert',onChange: (ev) => {try {const val = ev.target.value
|
|
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
|
|
1727
1492
|
scope.set('agentPromptPreset', val)
|
|
1728
|
-
} catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }
|
|
1729
|
-
}
|
|
1730
|
-
},React.createElement('option', { value: 'technical_expert' }, t('presetExpert')),React.createElement('option', { value: 'tech_writer' }, t('presetWriter')),React.createElement('option', { value: 'concise' }, t('presetConcise')),React.createElement('option', { value: 'code_reviewer' }, t('presetReviewer')),React.createElement('option', { value: 'architect' }, t('presetArchitect')),React.createElement('option', { value: 'tutor' }, t('presetTutor'))
|
|
1731
|
-
)
|
|
1732
|
-
),presetInfo ? React.createElement('div', { className: 'rl-preview-box' },React.createElement('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),presetInfo.text
|
|
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
|
|
1733
1494
|
) : null
|
|
1734
1495
|
) : null
|
|
1735
|
-
),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secUpdater')),React.createElement('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))
|
|
1736
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },React.createElement('div', null,React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.9')),upStatus.updateAvailable
|
|
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
|
|
1737
1497
|
? React.createElement('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
|
|
1738
|
-
: React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))
|
|
1739
|
-
),upStatus.updateAvailable
|
|
1498
|
+
: React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
|
|
1740
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 || ''))
|
|
1741
|
-
: React.createElement('button', {type: 'button',className: 'rl-btn',disabled: upLoading,onClick: checkUpdate,}, upLoading ? t('updaterChecking') : t('updaterCheckBtn'))
|
|
1742
|
-
|
|
1743
|
-
}
|
|
1744
|
-
},
|
|
1745
|
-
)
|
|
1746
|
-
),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secTranslator')),React.createElement('span', {className: 'rl-badge ' + (value.translateEngine === 'local' ? 'rl-badge-ok' : (value.translateEngine === 'google' ? 'rl-badge-warn' : 'rl-badge-dim'))
|
|
1747
|
-
}, value.translateEngine === 'local' ? 'Локально' : (value.translateEngine === 'google' ? 'Google' : 'Выключен'))
|
|
1748
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secTranslatorDesc')),React.createElement('div', { className: 'rl-item-card' },React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px' } },React.createElement('label', { className: 'rl-item-label', style: { fontWeight: 500 } }, t('translateEngine')),React.createElement('select', {className: 'rl-select',value: value.translateEngine || 'off',onChange: (ev) => {try {const val = ev.target.value
|
|
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
|
|
1749
1504
|
scope.set('translateEngine', val)
|
|
1750
1505
|
if (val === 'local') fetchTransStatus()
|
|
1751
|
-
} catch (err) { console.warn('dsh-russian-lang: set translateEngine failed', err) }
|
|
1752
|
-
}
|
|
1753
|
-
},React.createElement('option', { value: 'off' }, t('engineOff')),React.createElement('option', { value: 'local' }, t('engineLocal')),React.createElement('option', { value: 'google' }, t('engineGoogle'))
|
|
1754
|
-
)
|
|
1755
|
-
),value.translateEngine === 'google' ? React.createElement('div', {style: {marginTop: '10px', padding: '8px 12px', borderRadius: '8px',background: 'color-mix(in srgb, var(--dsw-alias-state-warning-primary) 12%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-state-warning-primary) 30%, transparent)',color: 'var(--dsw-alias-state-warning-primary)', fontSize: '12px', fontWeight: 500, lineHeight: 1.4
|
|
1756
|
-
}
|
|
1757
|
-
}, t('googleWarn')) : null,value.translateEngine === 'local' ? React.createElement('div', {style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }
|
|
1758
|
-
},React.createElement('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, t('localInfo')),React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },React.createElement('span', {className: 'rl-badge ' + (transStatus && transStatus.running ? 'rl-badge-ok' : 'rl-badge-dim')
|
|
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')
|
|
1759
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
|
|
1760
1509
|
}, transStarting ? t('localStarting') : t('localStartBtn')) : null
|
|
1761
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
|
|
1762
|
-
}
|
|
1763
|
-
}, transMsg.text) : null
|
|
1511
|
+
}}, transMsg.text) : null
|
|
1764
1512
|
) : null
|
|
1765
|
-
)
|
|
1766
|
-
),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secOverrides')),React.createElement('span', { className: 'rl-badge rl-badge-dim' },String(overridesCount) + ' ' + (typeof plural === 'function' ? plural(overridesCount, ['запись', 'записи', 'записей']) : 'записей'))
|
|
1767
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secOverridesDesc')),React.createElement('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },React.createElement('input', {className: 'rl-select',style: { flex: '1 1 180px', maxWidth: 'none' },placeholder: t('overrideKeyPlaceholder'),value: newKey,onChange: (e) => setNewKey(e.target.value),}),React.createElement('input', {className: 'rl-select',style: { flex: '2 1 240px', maxWidth: 'none' },placeholder: t('overrideValuePlaceholder'),value: newVal,onChange: (e) => setNewVal(e.target.value),}),React.createElement('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: !newKey.trim() || !newVal.trim(),onClick: () => {const k = newKey.trim()
|
|
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()
|
|
1768
1514
|
const v = newVal.trim()
|
|
1769
1515
|
if (!k || !v) return
|
|
1770
1516
|
const next = Object.assign({}, overrides, { [k]: v })
|
|
1771
1517
|
try { scope.set('overrides', next) } catch (_e) { void _e }
|
|
1772
1518
|
setNewKey('')
|
|
1773
1519
|
setNewVal('')
|
|
1774
|
-
},}, t('overrideAddBtn'))
|
|
1775
|
-
),overridesCount === 0
|
|
1520
|
+
},}, t('overrideAddBtn'))),overridesCount === 0
|
|
1776
1521
|
? React.createElement('div', { className: 'rl-hint-text', style: { fontStyle: 'italic', padding: '4px 0' } }, t('overrideEmpty'))
|
|
1777
1522
|
: React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '6px', marginTop: '6px' } },Object.keys(overrides).map((k) =>
|
|
1778
|
-
React.createElement('div', {key: k,style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 10px', background: 'var(--dsw-alias-bg-layer-3)', borderRadius: '6px', border: '1px solid var(--dsw-alias-border-l2)', fontSize: '12px' },},React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', overflow: 'hidden' } },React.createElement('code', { style: { color: 'var(--dsw-alias-state-brand-primary)', fontWeight: 600 } }, k),React.createElement('span', { style: { color: 'var(--dsw-alias-label-secondary)' } }, '→'),React.createElement('span', { style: { color: 'var(--dsw-alias-label-primary)' } }, overrides[k])
|
|
1779
|
-
),React.createElement('button', {type: 'button',className: 'rl-btn',style: { padding: '2px 8px', height: '24px', fontSize: '11px', color: 'var(--dsw-alias-state-error-primary)' },onClick: () => {const next = Object.assign({}, overrides)
|
|
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)
|
|
1780
1524
|
delete next[k]
|
|
1781
|
-
try { scope.set('overrides', next) } catch (_e) { void _e }
|
|
1782
|
-
},}, t('overrideDelete'))
|
|
1783
|
-
)
|
|
1784
|
-
)
|
|
1785
|
-
),React.createElement('div', { className: 'rl-hint-text', style: { marginTop: '8px' } }, t('overrideTip')),React.createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', marginTop: '10px' } },React.createElement('button', {type: 'button',className: 'rl-btn' + (isInspectorActive ? ' rl-btn-primary' : ''),onClick: () => {isInspectorActive = !isInspectorActive
|
|
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
|
|
1786
1526
|
updateInspectorPill()
|
|
1787
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))
|
|
1788
1528
|
alert('Оверрайды скопированы в буфер обмена как JSON!')
|
|
1789
|
-
} catch (err) { void err; }
|
|
1790
|
-
}
|
|
1791
|
-
}, '📤 Экспорт 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 с оверрайдами:')
|
|
1792
1530
|
if (!input) return
|
|
1793
1531
|
try {const parsed = JSON.parse(input)
|
|
1794
1532
|
if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
|
|
1795
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')))))))
|
|
1796
1536
|
}
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
}, '📥 Импорт JSON')
|
|
1800
|
-
)
|
|
1801
|
-
),React.createElement('div', { className: 'rl-section-card' },React.createElement('div', { className: 'rl-section-title' },React.createElement('span', null, t('secSupport')),React.createElement('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')
|
|
1802
|
-
),React.createElement('div', { className: 'rl-section-desc' }, t('secSupportDesc')),React.createElement('div', { className: 'rl-grid-3' },React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '113'),React.createElement('div', { className: 'rl-stat-label' }, t('statNamespaces'))
|
|
1803
|
-
),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '1 225'),React.createElement('div', { className: 'rl-stat-label' }, t('statCoreKeys'))
|
|
1804
|
-
),React.createElement('div', { className: 'rl-stat-box' },React.createElement('div', { className: 'rl-stat-val' }, '5 924'),React.createElement('div', { className: 'rl-stat-label' }, t('statPluginKeys'))
|
|
1805
|
-
)
|
|
1806
|
-
),React.createElement('div', { className: 'rl-actions-row' },React.createElement('a', {href: makeIssueUrl({}, '0.3.9'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
|
|
1807
|
-
}, '💬 ' + t('reportIssue')),React.createElement('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')
|
|
1808
|
-
)
|
|
1809
|
-
)
|
|
1810
|
-
)
|
|
1811
|
-
)
|
|
1812
|
-
)
|
|
1813
|
-
)
|
|
1814
|
-
}
|
|
1815
|
-
const RL_CSS = [
|
|
1816
|
-
'.rl-modal-mask{position:fixed;top:0;left:0;width:100vw;height:100vh;background:var(--dsw-alias-bg-mask);display:flex;align-items:center;justify-content:center;z-index:99999;backdrop-filter:blur(2px)}','.rl-modal-box{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;width:calc(100vw - 40px);max-width:500px;box-shadow:var(--dsw-alias-shadow-l3);display:flex;flex-direction:column;gap:12px;padding:18px}','.rl-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;overflow:hidden;transition:border-color .15s ease}','.rl-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 18px}','.rl-head:hover{background:var(--dsw-alias-bg-layer-2)}','.rl-head-main{flex:1;display:flex;flex-direction:column;gap:4px}','.rl-title{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4;display:flex;align-items:center;flex-wrap:wrap;gap:8px}','.rl-sub{color:var(--dsw-alias-label-secondary);font-size:13px}','.rl-chev{margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary);display:inline-flex;transition:transform .16s}','.rl-chev-open{transform:rotate(180deg)}','.rl-body{border-top:1px solid var(--dsw-alias-border-l2);padding:18px}','.rl-page{display:flex;flex-direction:column;gap:16px;max-width:960px}','.rl-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}','.rl-section-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;gap:8px}','.rl-section-desc{font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:-4px;line-height:1.4}','.rl-grid-2{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:10px}','.rl-grid-3{display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:10px}','.rl-badge{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:4px;font-weight:500}','.rl-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}','.rl-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}','.rl-badge-dim{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}','.rl-item-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}','.rl-item-head{display:flex;align-items:center;justify-content:space-between;gap:8px}','.rl-item-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px;cursor:pointer}','.rl-item-desc{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}','.rl-stat-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}','.rl-stat-val{font-size:16px;font-weight:700;color:var(--dsw-alias-label-primary)}','.rl-stat-label{font-size:11px;color:var(--dsw-alias-label-secondary);letter-spacing:0.3px}','.rl-hotkey-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4;display:flex;align-items:flex-start;gap:10px}','.rl-hotkey-tag{font-size:11px;font-weight:700;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);white-space:nowrap}','.rl-preview-box{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}','.rl-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 12px;font-size:12px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease;text-decoration:none}','.rl-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-secondary)}','.rl-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}','.rl-btn-primary:hover:not(:disabled){opacity:0.9}','.rl-select{height:32px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;outline:none;width:100%;max-width:380px}','.rl-select:focus{border-color:var(--dsw-alias-state-brand-primary)}','.rl-check{width:16px;height:16px;accent-color:var(--dsw-alias-label-primary);cursor:pointer;flex-shrink:0}','.rl-hint-text{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.4}','.rl-actions-row{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding-top:4px}','.rl-lang-chip{appearance:none;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:700;padding:2px 7px;border-radius:6px;display:inline-flex;align-items:center;transition:all .15s;margin:0 4px}','.rl-lang-chip:hover{border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-label-primary)}','.rl-lang-chip-active{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}','.rl-action-btn{appearance:none;background:0 0;border:1px solid transparent;border-radius:4px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;padding:2px 5px;display:inline-flex;align-items:center;transition:all .15s}','.rl-action-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3)}','.rl-action-btn-active{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}','.rl-turn-translation{margin:8px 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:8px;font-size:13px;line-height:1.5}','.rl-trans-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px}','.rl-trans-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:0.5px}','.rl-trans-tools{display:flex;gap:4px}','.rl-trans-btn{appearance:none;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;padding:2px 7px;border-radius:4px;font-size:11px}','.rl-trans-btn:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}','.rl-trans-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;user-select:text}','.rl-export-md-btn{appearance:none;border:1px solid var(--dsw-alias-border-l2);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:transparent;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:500;display:inline-flex;white-space:nowrap;margin-left:6px;transition:all .15s ease}','.rl-export-md-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}','.rl-inspector-hover{position:absolute;pointer-events:none;border:2px solid var(--dsw-alias-state-brand-primary);border-radius:4px;background:color-mix(in srgb,var(--dsw-alias-state-brand-primary) 12%,transparent);box-shadow:0 0 10px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 30%,transparent);z-index:99998;transition:all .06s ease;display:none}','.rl-inspector-badge{position:absolute;bottom:calc(100% + 4px);left:0;background:var(--dsw-alias-state-brand-primary);color:var(--dsw-alias-label-inverse);font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;white-space:nowrap;box-shadow:var(--dsw-alias-shadow-l2);pointer-events:none}','.rl-inspector-pill{position:fixed;bottom:18px;right:18px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l1);border-radius:20px;padding:6px 12px;box-shadow:var(--dsw-alias-shadow-l3);display:none;align-items:center;gap:8px;font-size:12px;z-index:99990;user-select:none}','.rl-inspector-pill-active{border-color:var(--dsw-alias-state-brand-primary);box-shadow:0 0 12px color-mix(in srgb,var(--dsw-alias-state-brand-primary) 35%,transparent)}',].join('')
|
|
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('')
|
|
1817
1539
|
const STYLE_ID = 'dsh-russian-lang-styles'
|
|
1818
1540
|
if (typeof document !== 'undefined' && (document.getElementById && !document.getElementById(STYLE_ID))) {const tag = document.createElement('style')
|
|
1819
1541
|
tag.id = STYLE_ID
|