@goodandready/dsh-russian-lang 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +186 -4
- package/lib/index.js +5 -1
- package/package.json +2 -2
- package/ru/03-models-workspace.json +2 -0
- package/ru-plugins/04-context-doctor.json +23 -14
package/lib/client.js
CHANGED
|
@@ -296,6 +296,9 @@ window.__ModuleLoader__.load({
|
|
|
296
296
|
"context-doctor": {
|
|
297
297
|
"cd.attention": "Стоит посмотреть",
|
|
298
298
|
"cd.catalog": "{n} скиллов",
|
|
299
|
+
"cd.catalog.few": "{n} скилла",
|
|
300
|
+
"cd.catalog.many": "{n} скиллов",
|
|
301
|
+
"cd.catalog.one": "{n} скилл",
|
|
299
302
|
"cd.empty": "Данных пока нет",
|
|
300
303
|
"cd.error": "Проверка не удалась",
|
|
301
304
|
"cd.files": "файлов",
|
|
@@ -307,12 +310,18 @@ window.__ModuleLoader__.load({
|
|
|
307
310
|
"cd.loading": "Проверка…",
|
|
308
311
|
"cd.mcp": "Инструменты MCP",
|
|
309
312
|
"cd.mcpTools": "{n} инструментов",
|
|
313
|
+
"cd.mcpTools.few": "{n} инструмента",
|
|
314
|
+
"cd.mcpTools.many": "{n} инструментов",
|
|
315
|
+
"cd.mcpTools.one": "{n} инструмент",
|
|
310
316
|
"cd.refresh": "Обновить",
|
|
311
317
|
"cd.residentTokens": "Проверка бюджета контекста",
|
|
312
318
|
"cd.review": "Стоит посмотреть",
|
|
313
319
|
"cd.reviewHint": "Некоторые части контекста стоит пересмотреть, пока они не стали дорогими.",
|
|
314
320
|
"cd.skills": "Каталог скиллов",
|
|
315
321
|
"cd.suggestions": "{n} рекомендаций",
|
|
322
|
+
"cd.suggestions.few": "{n} рекомендации",
|
|
323
|
+
"cd.suggestions.many": "{n} рекомендаций",
|
|
324
|
+
"cd.suggestions.one": "{n} рекомендация",
|
|
316
325
|
"cd.title": "Аудит контекста",
|
|
317
326
|
"cd.tools": "Схемы инструментов",
|
|
318
327
|
"cd.toolsCount": "инструментов",
|
|
@@ -1758,6 +1767,8 @@ window.__ModuleLoader__.load({
|
|
|
1758
1767
|
"rename.workspace.title": "Переименовать рабочую папку",
|
|
1759
1768
|
"search.clear": "Очистить поиск",
|
|
1760
1769
|
"search.hasMore": "Показаны первые {n} результатов. Уточните запрос.",
|
|
1770
|
+
"search.hasMore.few": "Показаны первые {n} результата. Уточните запрос.",
|
|
1771
|
+
"search.hasMore.one": "Найден один подходящий результат.",
|
|
1761
1772
|
"search.noMatches": "Подходящих сессий нет",
|
|
1762
1773
|
"search.pending": "Поиск по истории сессий…",
|
|
1763
1774
|
"search.placeholder": "Поиск по сессиям...",
|
|
@@ -1830,13 +1841,24 @@ window.__ModuleLoader__.load({
|
|
|
1830
1841
|
return params ? fill(overrides[key], params) : overrides[key]
|
|
1831
1842
|
}
|
|
1832
1843
|
// 2. Плюрализация для русского.
|
|
1833
|
-
|
|
1834
|
-
if (m && runtime.getLocale().active === 'ru' && params) {
|
|
1844
|
+
if (runtime.getLocale().active === 'ru' && params) {
|
|
1835
1845
|
const n = params.n ?? params.count
|
|
1836
1846
|
if (typeof n === 'number') {
|
|
1837
1847
|
const form = pluralRules.select(n)
|
|
1838
|
-
|
|
1839
|
-
|
|
1848
|
+
const m = /^(.*)[.](one|other)$/.exec(key)
|
|
1849
|
+
if (m) {
|
|
1850
|
+
// Ядро выбирает .one/.other по n===1; русскому нужны few/many.
|
|
1851
|
+
if (form === 'few' || form === 'many') {
|
|
1852
|
+
const pluralKey = m[1] + '.' + form
|
|
1853
|
+
const template = this.lookup(ns, pluralKey) ?? this.lookup('common', pluralKey)
|
|
1854
|
+
if (template !== undefined) {
|
|
1855
|
+
return fill(template, params)
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
} else if (form !== 'other' && !/[.](one|other|few|many)$/.test(key)) {
|
|
1859
|
+
// Счётный ключ без суффикса: t('X', {n}). Если словарь даёт формы
|
|
1860
|
+
// X.one / X.few / X.many - берём подходящую, иначе как раньше.
|
|
1861
|
+
const pluralKey = key + '.' + form
|
|
1840
1862
|
const template = this.lookup(ns, pluralKey) ?? this.lookup('common', pluralKey)
|
|
1841
1863
|
if (template !== undefined) {
|
|
1842
1864
|
return fill(template, params)
|
|
@@ -1947,6 +1969,166 @@ window.__ModuleLoader__.load({
|
|
|
1947
1969
|
const unsubscribeLang = runtime.subscribe(syncLang)
|
|
1948
1970
|
ctx.effect(() => unsubscribeLang, 'dsh-russian-lang: html-lang')
|
|
1949
1971
|
syncLang()
|
|
1972
|
+
|
|
1973
|
+
// 5. Орфография (russian-lang.spellcheck): при активном русском включаем
|
|
1974
|
+
// браузерный спелчек на текстовых полях. Код-редакторы и поля команд не
|
|
1975
|
+
// трогаем - отличаем их по моноширинному шрифту. Исходные значения
|
|
1976
|
+
// сохраняем в data-атрибутах и возвращаем при уходе с русского.
|
|
1977
|
+
const SPELL_ON = 'data-russian-lang-spell-on'
|
|
1978
|
+
const SPELL_WAS = 'data-russian-lang-spell-was'
|
|
1979
|
+
const LANG_WAS = 'data-russian-lang-lang-was'
|
|
1980
|
+
const EDITABLE = 'textarea, input[type=text], input[type=search], [contenteditable=""], [contenteditable="true"]'
|
|
1981
|
+
const MONO_RE = /mono|consol|courier/i
|
|
1982
|
+
const isMonoField = (el) => {
|
|
1983
|
+
try { return MONO_RE.test(getComputedStyle(el).fontFamily || '') }
|
|
1984
|
+
catch (err) { return false }
|
|
1985
|
+
}
|
|
1986
|
+
const spellOn = (el) => {
|
|
1987
|
+
if (el.hasAttribute(SPELL_ON) || isMonoField(el)) return
|
|
1988
|
+
el.setAttribute(SPELL_ON, '1')
|
|
1989
|
+
el.setAttribute(SPELL_WAS, el.getAttribute('spellcheck') ?? '')
|
|
1990
|
+
el.setAttribute(LANG_WAS, el.getAttribute('lang') ?? '')
|
|
1991
|
+
el.setAttribute('spellcheck', 'true')
|
|
1992
|
+
el.setAttribute('lang', 'ru-RU')
|
|
1993
|
+
}
|
|
1994
|
+
const spellOff = (el) => {
|
|
1995
|
+
if (!el.hasAttribute(SPELL_ON)) return
|
|
1996
|
+
const was = el.getAttribute(SPELL_WAS)
|
|
1997
|
+
if (was === '') el.removeAttribute('spellcheck')
|
|
1998
|
+
else el.setAttribute('spellcheck', was)
|
|
1999
|
+
const lang = el.getAttribute(LANG_WAS)
|
|
2000
|
+
if (lang === '') el.removeAttribute('lang')
|
|
2001
|
+
else el.setAttribute('lang', lang)
|
|
2002
|
+
el.removeAttribute(SPELL_ON)
|
|
2003
|
+
el.removeAttribute(SPELL_WAS)
|
|
2004
|
+
el.removeAttribute(LANG_WAS)
|
|
2005
|
+
}
|
|
2006
|
+
let spellObserver = null
|
|
2007
|
+
const syncSpell = () => {
|
|
2008
|
+
try {
|
|
2009
|
+
if (typeof document === 'undefined') return
|
|
2010
|
+
const ru = runtime.getLocale().active === 'ru'
|
|
2011
|
+
if (!ru) {
|
|
2012
|
+
if (spellObserver) { spellObserver.disconnect(); spellObserver = null }
|
|
2013
|
+
document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff)
|
|
2014
|
+
return
|
|
2015
|
+
}
|
|
2016
|
+
document.querySelectorAll(EDITABLE).forEach(spellOn)
|
|
2017
|
+
if (spellObserver) return
|
|
2018
|
+
// ponytail: реагируем только на добавленные узлы; полям, сменившим
|
|
2019
|
+
// шрифт на месте, поможет следующая перезагрузка страницы.
|
|
2020
|
+
spellObserver = new MutationObserver((records) => {
|
|
2021
|
+
for (const record of records) {
|
|
2022
|
+
for (const node of record.addedNodes) {
|
|
2023
|
+
if (node.nodeType !== 1) continue
|
|
2024
|
+
if (node.matches(EDITABLE)) spellOn(node)
|
|
2025
|
+
node.querySelectorAll ? node.querySelectorAll(EDITABLE).forEach(spellOn) : null
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
})
|
|
2029
|
+
spellObserver.observe(document.body, { childList: true, subtree: true })
|
|
2030
|
+
} catch (err) { /* ignore */ }
|
|
2031
|
+
}
|
|
2032
|
+
const unsubscribeSpell = runtime.subscribe(syncSpell)
|
|
2033
|
+
ctx.effect(() => {
|
|
2034
|
+
unsubscribeSpell()
|
|
2035
|
+
if (spellObserver) spellObserver.disconnect()
|
|
2036
|
+
try { document.querySelectorAll('[' + SPELL_ON + ']').forEach(spellOff) } catch (err) { /* ignore */ }
|
|
2037
|
+
}, 'dsh-russian-lang: spellcheck')
|
|
2038
|
+
syncSpell()
|
|
2039
|
+
|
|
2040
|
+
// 6. Типографика (russian-lang.typography { enabled, yo }): постпроцессор
|
|
2041
|
+
// текстовых узлов при активном русском - ёлочки, тире, неразрывные
|
|
2042
|
+
// пробелы перед короткими словами, опционально ё (безопасный список).
|
|
2043
|
+
// Код, ссылки, кнопки и поля ввода не трогаем. Правила идемпотентны,
|
|
2044
|
+
// повторный проход по своим же правкам ничего не меняет.
|
|
2045
|
+
const TYPO_SKIP = new Set(['CODE', 'PRE', 'A', 'SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'SELECT', 'OPTION', 'KBD', 'SAMP', 'BUTTON'])
|
|
2046
|
+
const typoQuotes = (text) => text.replace(/"([^"\n]{1,200})"/g, '\u00AB$1\u00BB')
|
|
2047
|
+
const typoDash = (text) => text
|
|
2048
|
+
.replace(/(^|[\s(\[\u00AB])--(?=\s|$)/g, '$1\u2014')
|
|
2049
|
+
.replace(/(^|[\s(\[\u00AB])-(?=\s)/g, '$1\u2014')
|
|
2050
|
+
const TYPO_SHORT = new Set(['в', 'с', 'к', 'о', 'у', 'а', 'и', 'но', 'не', 'ни', 'на', 'по', 'до', 'из', 'за', 'от', 'об'])
|
|
2051
|
+
const typoNbsp = (text) => text.replace(/(^|[\s(\[\u00AB])([а-яё]{1,2})(\s+)/g, (match, lead, word) => (
|
|
2052
|
+
TYPO_SHORT.has(word) ? lead + word + '\u00A0' : match
|
|
2053
|
+
))
|
|
2054
|
+
const TYPO_YO = [
|
|
2055
|
+
[/еще/g, 'ещё'], [/Еще/g, 'Ещё'], [/ЕЩЕ/g, 'ЕЩЁ'],
|
|
2056
|
+
[/\bее\b/g, 'её'], [/\bЕе\b/g, 'Её'],
|
|
2057
|
+
[/\bчерный\b/g, 'чёрный'], [/\bчерная\b/g, 'чёрная'], [/\bчерные\b/g, 'чёрные'],
|
|
2058
|
+
[/\bзеленый\b/g, 'зелёный'], [/\bжелтый\b/g, 'жёлтый'],
|
|
2059
|
+
[/\bлегкий\b/g, 'лёгкий'], [/\bтяжелый\b/g, 'тяжёлый'],
|
|
2060
|
+
[/\bнадежный\b/g, 'надёжный'], [/\bдешевый\b/g, 'дешёвый'],
|
|
2061
|
+
[/\bидет\b/g, 'идёт'], [/\bдает\b/g, 'даёт'], [/\bберет\b/g, 'берёт'],
|
|
2062
|
+
[/\bведет\b/g, 'ведёт'], [/\bнесет\b/g, 'несёт'], [/\bживет\b/g, 'живёт'],
|
|
2063
|
+
[/\bпривел\b/g, 'привёл'], [/\bшел\b/g, 'шёл']
|
|
2064
|
+
]
|
|
2065
|
+
const typoYo = (text) => {
|
|
2066
|
+
for (const pair of TYPO_YO) text = text.replace(pair[0], pair[1])
|
|
2067
|
+
return text
|
|
2068
|
+
}
|
|
2069
|
+
const getTypoConf = () => {
|
|
2070
|
+
try {
|
|
2071
|
+
const t = scope.getSnapshot().value && scope.getSnapshot().value.typography
|
|
2072
|
+
if (!t || t.enabled === false) return null
|
|
2073
|
+
return { yo: t.yo === true }
|
|
2074
|
+
} catch (err) { return null }
|
|
2075
|
+
}
|
|
2076
|
+
const typoNode = (node, conf) => {
|
|
2077
|
+
const before = node.nodeValue
|
|
2078
|
+
if (!before || !before.match || (before.match(/[\u0400-\u04FF]/g) || []).length < 3) return
|
|
2079
|
+
if (node.parentElement && node.parentElement.closest('code, pre, a, script, style, textarea, input, select, button, kbd, samp')) return
|
|
2080
|
+
let after = typoQuotes(before)
|
|
2081
|
+
after = typoDash(after)
|
|
2082
|
+
after = typoNbsp(after)
|
|
2083
|
+
if (conf.yo) after = typoYo(after)
|
|
2084
|
+
if (after !== before) node.nodeValue = after
|
|
2085
|
+
}
|
|
2086
|
+
const typoWalk = (root, conf) => {
|
|
2087
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
|
2088
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) typoNode(node, conf)
|
|
2089
|
+
}
|
|
2090
|
+
let typoObserver = null
|
|
2091
|
+
let typoQueued = null
|
|
2092
|
+
const flushTypo = () => {
|
|
2093
|
+
typoQueued = null
|
|
2094
|
+
try {
|
|
2095
|
+
const conf = getTypoConf()
|
|
2096
|
+
if (!conf || !typoPending.size) return
|
|
2097
|
+
for (const root of typoPending) {
|
|
2098
|
+
if (root.nodeType === 3) typoNode(root, conf)
|
|
2099
|
+
else typoWalk(root, conf)
|
|
2100
|
+
}
|
|
2101
|
+
typoPending.clear()
|
|
2102
|
+
} catch (err) { /* ignore */ }
|
|
2103
|
+
}
|
|
2104
|
+
const typoPending = new Set()
|
|
2105
|
+
const queueTypo = (roots) => {
|
|
2106
|
+
for (const r of roots) typoPending.add(r)
|
|
2107
|
+
if (!typoQueued) typoQueued = requestAnimationFrame(flushTypo)
|
|
2108
|
+
}
|
|
2109
|
+
const syncTypo = () => {
|
|
2110
|
+
try {
|
|
2111
|
+
if (typeof document === 'undefined') return
|
|
2112
|
+
if (runtime.getLocale().active !== 'ru' || !getTypoConf()) {
|
|
2113
|
+
if (typoObserver) { typoObserver.disconnect(); typoObserver = null }
|
|
2114
|
+
return
|
|
2115
|
+
}
|
|
2116
|
+
if (typoObserver) return
|
|
2117
|
+
typoWalk(document.body, getTypoConf())
|
|
2118
|
+
typoObserver = new MutationObserver((records) => {
|
|
2119
|
+
const roots = []
|
|
2120
|
+
for (const record of records) roots.push(record.nodeType ? record.target : record)
|
|
2121
|
+
queueTypo(roots)
|
|
2122
|
+
})
|
|
2123
|
+
typoObserver.observe(document.body, { childList: true, characterData: true, subtree: true })
|
|
2124
|
+
} catch (err) { /* ignore */ }
|
|
2125
|
+
}
|
|
2126
|
+
const unsubscribeTypo = runtime.subscribe(syncTypo)
|
|
2127
|
+
ctx.effect(() => {
|
|
2128
|
+
unsubscribeTypo()
|
|
2129
|
+
if (typoObserver) typoObserver.disconnect()
|
|
2130
|
+
}, 'dsh-russian-lang: typography')
|
|
2131
|
+
syncTypo()
|
|
1950
2132
|
}
|
|
1951
2133
|
|
|
1952
2134
|
module.exports = { apply, inject: ['locale', 'connection', 'remote', 'settingsScope'] }
|
package/lib/index.js
CHANGED
|
@@ -11,7 +11,11 @@ export const name = '@goodandready/dsh-russian-lang'
|
|
|
11
11
|
|
|
12
12
|
const RussianLangSettingsSchema = z.object({
|
|
13
13
|
enabled: z.boolean().required(false),
|
|
14
|
-
overrides: z.dict(z.string()).required(false)
|
|
14
|
+
overrides: z.dict(z.string()).required(false),
|
|
15
|
+
typography: z.object({
|
|
16
|
+
enabled: z.boolean().required(false),
|
|
17
|
+
yo: z.boolean().required(false)
|
|
18
|
+
}).required(false)
|
|
15
19
|
})
|
|
16
20
|
|
|
17
21
|
export function apply(ctx) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-russian-lang",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Russian localization for the DeepSeek Harness web UI: adds Русский as the third option in the native Settings - General - Language menu. Translates
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Russian localization for the DeepSeek Harness web UI: adds Русский as the third option in the native Settings - General - Language menu. Translates the full core surface (100% coverage of DSH 0.1.1-rc.2) plus 9 community plugin namespaces - 39 namespaces, 1709 strings, with Russian plural forms, count-key plurals and an optional typography pass (guillemets, em dash, nbsp). The locale runtime snapshot is extended at runtime on unpatched cores; no files in the DSH installation are modified. The choice is persisted through the plugin-owned russian-lang namespace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -126,6 +126,8 @@
|
|
|
126
126
|
"rename.workspace.title": "Переименовать рабочую папку",
|
|
127
127
|
"search.clear": "Очистить поиск",
|
|
128
128
|
"search.hasMore": "Показаны первые {n} результатов. Уточните запрос.",
|
|
129
|
+
"search.hasMore.one": "Найден один подходящий результат.",
|
|
130
|
+
"search.hasMore.few": "Показаны первые {n} результата. Уточните запрос.",
|
|
129
131
|
"search.noMatches": "Подходящих сессий нет",
|
|
130
132
|
"search.pending": "Поиск по истории сессий…",
|
|
131
133
|
"search.placeholder": "Поиск по сессиям...",
|
|
@@ -6,20 +6,29 @@
|
|
|
6
6
|
"cd.skills": "Каталог скиллов",
|
|
7
7
|
"cd.tools": "Схемы инструментов",
|
|
8
8
|
"cd.mcp": "Инструменты MCP",
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
"cd.suggestions": "{n} рекомендаций",
|
|
10
|
+
"cd.suggestions.one": "{n} рекомендация",
|
|
11
|
+
"cd.suggestions.few": "{n} рекомендации",
|
|
12
|
+
"cd.suggestions.many": "{n} рекомендаций",
|
|
13
|
+
"cd.refresh": "Обновить",
|
|
14
|
+
"cd.loading": "Проверка…",
|
|
15
|
+
"cd.error": "Проверка не удалась",
|
|
16
|
+
"cd.empty": "Данных пока нет",
|
|
17
|
+
"cd.healthy": "В норме",
|
|
18
|
+
"cd.attention": "Стоит посмотреть",
|
|
19
|
+
"cd.review": "Стоит посмотреть",
|
|
20
|
+
"cd.healthyHint": "Контекст расходуется экономно и укладывается в рекомендованный бюджет.",
|
|
21
|
+
"cd.reviewHint": "Некоторые части контекста стоит пересмотреть, пока они не стали дорогими.",
|
|
22
|
+
"cd.guideline": "из 50k",
|
|
23
|
+
"cd.updated": "Обновлено",
|
|
24
|
+
"cd.catalog": "{n} скиллов",
|
|
25
|
+
"cd.catalog.one": "{n} скилл",
|
|
26
|
+
"cd.catalog.few": "{n} скилла",
|
|
27
|
+
"cd.catalog.many": "{n} скиллов",
|
|
28
|
+
"cd.mcpTools": "{n} инструментов",
|
|
29
|
+
"cd.mcpTools.one": "{n} инструмент",
|
|
30
|
+
"cd.mcpTools.few": "{n} инструмента",
|
|
31
|
+
"cd.mcpTools.many": "{n} инструментов",
|
|
23
32
|
"cd.files": "файлов",
|
|
24
33
|
"cd.toolsCount": "инструментов",
|
|
25
34
|
"cd.hint": "Открыть аудит контекста"
|