@goodandready/dsh-russian-lang 0.3.27 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -14
- package/bin/dsh-i18n.mjs +131 -16
- package/lib/client.js +25 -14
- package/lib/locales/plugins/48-task-tracker.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h3>Полная русская локализация, умная типографика и исправление раскладки клавиатуры для DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.3.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@goodandready/dsh-russian-lang"><img src="https://img.shields.io/badge/npm-v0.3.28-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-10b981.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
10
|
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
11
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
@@ -52,9 +52,9 @@ graph LR
|
|
|
52
52
|
DSH[Web UI Интерфейс] --> Loader[Загрузчик локализации dsh-russian-lang]
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
subgraph TranslationLayer [Слой перевода и адаптации —
|
|
56
|
-
Loader --> CoreDict[58 Пространств имён ядра DSH /
|
|
57
|
-
Loader --> PluginDict[
|
|
55
|
+
subgraph TranslationLayer [Слой перевода и адаптации — 8 584 ключа / 100% вычитано]
|
|
56
|
+
Loader --> CoreDict[58 Пространств имён ядра DSH / 2 442 ключа]
|
|
57
|
+
Loader --> PluginDict[43 Пространства имён плагинов DSH / 6 142 ключа]
|
|
58
58
|
Loader --> PluralEngine[Движок грамматических Plural-форм: 1/2/5]
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -178,12 +178,19 @@ dsh plugin --profile web add @goodandready/dsh-russian-lang
|
|
|
178
178
|
```yaml
|
|
179
179
|
# Актуальный формат (DSH v0.2.0-rc.2+ / v0.1.7+):
|
|
180
180
|
dsh-russian-lang:
|
|
181
|
-
enabled: true
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
enabled: true # Русский язык интерфейса (по умолчанию: true)
|
|
182
|
+
overrides: {} # Пользовательские оверрайды ключей (по умолчанию: {})
|
|
183
|
+
typography: # Интеллектуальная типографика (по умолчанию: все opt-in false)
|
|
184
|
+
enabled: false # Ёлочки, длинные тире, неразрывные пробелы
|
|
185
|
+
yo: false # Восстановление безопасной буквы «ё»
|
|
186
|
+
liveInput: false # Живая типографика при вводе в текстовых полях
|
|
187
|
+
agentPrompt: false # Системная инструкция агенту отвечать по-русски (по умолчанию: false)
|
|
188
|
+
agentPromptPreset: technical_expert # Пресет системного промпта (по умолчанию: technical_expert)
|
|
189
|
+
slashAliases: true # Русские алиасы слэш-команд (/помощь, /очистить) (по умолчанию: true)
|
|
190
|
+
layoutConversion: true # Детектор неверной раскладки и подсказка Alt+L (по умолчанию: true)
|
|
191
|
+
quickSwitch: true # Быстрый переключатель RU/EN в статусной строке (по умолчанию: true)
|
|
192
|
+
translateEngine: local # Движок перевода реплик: local (LibreTranslate) или cloud (Google) (по умолчанию: local)
|
|
193
|
+
localApiUrl: http://localhost:5000 # URL локального инстанса LibreTranslate (по умолчанию: http://localhost:5000)
|
|
187
194
|
|
|
188
195
|
# Легаси-формат (DSH <= 0.1.6, поддерживается автоматически как fallback):
|
|
189
196
|
# russian-lang:
|
|
@@ -193,16 +200,15 @@ dsh-russian-lang:
|
|
|
193
200
|
|
|
194
201
|
Удобнее всего настраивать параметры визуально в интерфейсе: **Настройки → Плагины → Русская локализация**.
|
|
195
202
|
|
|
196
|
-
Исправление раскладки (<kbd>Alt+L</kbd>) и
|
|
197
|
-
русским языком и отдельных ключей не имеют.
|
|
203
|
+
Исправление раскладки (<kbd>Alt+L</kbd>) и русские алиасы слэш-команд включены по умолчанию (`true`) и могут быть индивидуально отключены в настройках (`layoutConversion: false`, `slashAliases: false`). Браузерный спелчек (`lang="ru-RU"`) активируется автоматически при выборе русской локали.
|
|
198
204
|
|
|
199
205
|
---
|
|
200
206
|
|
|
201
207
|
## 🛠 Разработка и зеркало репозитория
|
|
202
208
|
|
|
203
|
-
Публичный репозиторий на GitHub является дистрибутивным зеркалом релизов `@goodandready/dsh-russian-lang`. Основная разработка, сборочный пайплайн и полный тестовый контур ведутся в
|
|
209
|
+
Публичный репозиторий на GitHub является дистрибутивным зеркалом релизов `@goodandready/dsh-russian-lang`. Основная разработка, сборочный пайплайн и полный тестовый контур ведутся в основном Gitea-репозитории проекта.
|
|
204
210
|
|
|
205
|
-
Pull Requests и Issues на GitHub приветствуются и обрабатываются в обычном порядке! Для внесения правок в логику клиента используйте `lib/client.template.js`. Запуск `npm test` в
|
|
211
|
+
Pull Requests и Issues на GitHub приветствуются и обрабатываются в обычном порядке! Для внесения правок в логику клиента используйте `lib/client.template.js`. Запуск `npm test` в репозитории выполняет полную проверку (270+ модульных тестов Node.js, тесты сборщика, линтеры переводов и глоссария).
|
|
206
212
|
|
|
207
213
|
---
|
|
208
214
|
|
package/bin/dsh-i18n.mjs
CHANGED
|
@@ -45,6 +45,115 @@ dsh-i18n (v${getPkgVersion()}) — Инструмент локализации
|
|
|
45
45
|
`)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function extractFromCode(code, result, defaultNs = 'common') {
|
|
49
|
+
let i = 0
|
|
50
|
+
const len = code.length
|
|
51
|
+
|
|
52
|
+
function skipWhitespace(idx) {
|
|
53
|
+
while (idx < len && /\s/.test(code[idx])) idx++
|
|
54
|
+
return idx
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readString(idx) {
|
|
58
|
+
const quote = code[idx]
|
|
59
|
+
if (quote !== "'" && quote !== '"' && quote !== '`') return null
|
|
60
|
+
let s = ''
|
|
61
|
+
idx++
|
|
62
|
+
while (idx < len) {
|
|
63
|
+
const ch = code[idx]
|
|
64
|
+
if (ch === '\\') {
|
|
65
|
+
if (idx + 1 < len) {
|
|
66
|
+
s += code[idx + 1]
|
|
67
|
+
idx += 2
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (ch === quote) {
|
|
72
|
+
return { val: s, next: idx + 1 }
|
|
73
|
+
}
|
|
74
|
+
s += ch
|
|
75
|
+
idx++
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
while (i < len) {
|
|
81
|
+
const ch = code[i]
|
|
82
|
+
|
|
83
|
+
// Single-line comment
|
|
84
|
+
if (ch === '/' && code[i + 1] === '/') {
|
|
85
|
+
i += 2
|
|
86
|
+
while (i < len && code[i] !== '\n') i++
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Multi-line comment
|
|
91
|
+
if (ch === '/' && code[i + 1] === '*') {
|
|
92
|
+
i += 2
|
|
93
|
+
while (i < len && !(code[i] === '*' && code[i + 1] === '/')) i++
|
|
94
|
+
i += 2
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// String literals in normal code
|
|
99
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
100
|
+
const strRes = readString(i)
|
|
101
|
+
if (strRes) {
|
|
102
|
+
i = strRes.next
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check for word boundary identifier
|
|
108
|
+
const prevChar = i > 0 ? code[i - 1] : ' '
|
|
109
|
+
const isWordStart = !/[a-zA-Z0-9_$.]/.test(prevChar)
|
|
110
|
+
|
|
111
|
+
if (isWordStart) {
|
|
112
|
+
if (code.startsWith('translate', i) && !/[a-zA-Z0-9_$]/.test(code[i + 9] || '')) {
|
|
113
|
+
let j = skipWhitespace(i + 9)
|
|
114
|
+
if (code[j] === '(') {
|
|
115
|
+
j = skipWhitespace(j + 1)
|
|
116
|
+
const nsStr = readString(j)
|
|
117
|
+
if (nsStr) {
|
|
118
|
+
j = skipWhitespace(nsStr.next)
|
|
119
|
+
if (code[j] === ',') {
|
|
120
|
+
j = skipWhitespace(j + 1)
|
|
121
|
+
const keyStr = readString(j)
|
|
122
|
+
if (keyStr) {
|
|
123
|
+
j = skipWhitespace(keyStr.next)
|
|
124
|
+
if (code[j] === ',' || code[j] === ')') {
|
|
125
|
+
const ns = nsStr.val
|
|
126
|
+
const key = keyStr.val
|
|
127
|
+
if (!result[ns]) result[ns] = {}
|
|
128
|
+
result[ns][key] = result[ns][key] || ''
|
|
129
|
+
i = j
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else if (code.startsWith('t', i) && !/[a-zA-Z0-9_$]/.test(code[i + 1] || '')) {
|
|
137
|
+
let j = skipWhitespace(i + 1)
|
|
138
|
+
if (code[j] === '(') {
|
|
139
|
+
j = skipWhitespace(j + 1)
|
|
140
|
+
const keyStr = readString(j)
|
|
141
|
+
if (keyStr) {
|
|
142
|
+
j = skipWhitespace(keyStr.next)
|
|
143
|
+
if (code[j] === ',' || code[j] === ')') {
|
|
144
|
+
result[defaultNs][keyStr.val] = result[defaultNs][keyStr.val] || ''
|
|
145
|
+
i = j
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
i++
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
48
157
|
function extractKeys(targetDir, defaultNs = 'common') {
|
|
49
158
|
const result = { [defaultNs]: {} }
|
|
50
159
|
const extensions = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx'])
|
|
@@ -59,17 +168,7 @@ function extractKeys(targetDir, defaultNs = 'common') {
|
|
|
59
168
|
walk(fullPath)
|
|
60
169
|
} else if (entry.isFile() && extensions.has(path.extname(entry.name))) {
|
|
61
170
|
const code = fs.readFileSync(fullPath, 'utf8')
|
|
62
|
-
|
|
63
|
-
for (const m of tMatches) {
|
|
64
|
-
result[defaultNs][m[1]] = result[defaultNs][m[1]] || ''
|
|
65
|
-
}
|
|
66
|
-
const transMatches = code.matchAll(/\btranslate\(\s*['"`]([a-zA-Z0-9_.-]+)['"`]\s*,\s*['"`]([a-zA-Z0-9_.-]+)['"`]/g)
|
|
67
|
-
for (const m of transMatches) {
|
|
68
|
-
const ns = m[1]
|
|
69
|
-
const key = m[2]
|
|
70
|
-
if (!result[ns]) result[ns] = {}
|
|
71
|
-
result[ns][key] = result[ns][key] || ''
|
|
72
|
-
}
|
|
171
|
+
extractFromCode(code, result, defaultNs)
|
|
73
172
|
}
|
|
74
173
|
}
|
|
75
174
|
}
|
|
@@ -102,11 +201,26 @@ function validateDict(filePath, glossaryPath) {
|
|
|
102
201
|
let totalKeys = 0
|
|
103
202
|
|
|
104
203
|
let glossary = null
|
|
105
|
-
|
|
106
|
-
|
|
204
|
+
if (glossaryPath) {
|
|
205
|
+
if (!fs.existsSync(glossaryPath)) {
|
|
206
|
+
console.error(`Ошибка: Файл глоссария не найден: ${glossaryPath}`)
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
107
209
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
210
|
+
const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'))
|
|
211
|
+
glossary = parsed.terms || parsed
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error(`Ошибка чтения глоссария ${glossaryPath}: ${err.message}`)
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
const defaultGPath = path.join(ROOT, 'glossary.json')
|
|
218
|
+
if (fs.existsSync(defaultGPath)) {
|
|
219
|
+
try {
|
|
220
|
+
const parsed = JSON.parse(fs.readFileSync(defaultGPath, 'utf8'))
|
|
221
|
+
glossary = parsed.terms || parsed
|
|
222
|
+
} catch (_) {}
|
|
223
|
+
}
|
|
110
224
|
}
|
|
111
225
|
|
|
112
226
|
function checkString(ns, key, val) {
|
|
@@ -224,7 +338,8 @@ if (command === 'extract') {
|
|
|
224
338
|
if (out) {
|
|
225
339
|
fs.mkdirSync(path.dirname(path.resolve(out)), { recursive: true })
|
|
226
340
|
fs.writeFileSync(path.resolve(out), jsonStr + '\n', 'utf8')
|
|
227
|
-
|
|
341
|
+
const totalCount = Object.values(extracted).reduce((sum, nObj) => sum + Object.keys(nObj || {}).length, 0)
|
|
342
|
+
console.log(`✓ Извлечено ключей: ${totalCount} -> ${out}`)
|
|
228
343
|
} else {
|
|
229
344
|
console.log(jsonStr)
|
|
230
345
|
}
|
package/lib/client.js
CHANGED
|
@@ -1199,27 +1199,33 @@ setTimeout(() => { copyKeyBtn.textContent = '📋' }, 1500)
|
|
|
1199
1199
|
}}
|
|
1200
1200
|
const copyReportBtn = modal.querySelector('.rl-copy-report-btn')
|
|
1201
1201
|
if (copyReportBtn) {copyReportBtn.onclick = () => {const snippet = typeof generateBugReportSnippet === 'function'
|
|
1202
|
-
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.
|
|
1202
|
+
? generateBugReportSnippet({ key: k, ns, original: origText || rawText, current: rawText, override: textarea.value.trim(), pkgVersion: '0.3.28' })
|
|
1203
1203
|
: ('### Репорт: ' + (k || rawText) + '\n- Исходный: ' + (origText || rawText) + '\n- Перевод: ' + textarea.value.trim())
|
|
1204
1204
|
try { navigator.clipboard.writeText(snippet) } catch (err) { void err; }
|
|
1205
1205
|
copyReportBtn.textContent = '✓ Скопировано'
|
|
1206
1206
|
setTimeout(() => { copyReportBtn.textContent = '📋 Скопировать репорт' }, 2000)
|
|
1207
1207
|
}}
|
|
1208
|
+
const saveBtn = modal.querySelector('.rl-save-btn')
|
|
1208
1209
|
const resetBtn = modal.querySelector('.rl-reset-override-btn')
|
|
1209
|
-
if (resetBtn) {resetBtn.onclick = () => {const targetKey = k || rawText
|
|
1210
|
+
if (resetBtn) {resetBtn.onclick = async () => {const targetKey = k || rawText
|
|
1210
1211
|
const cur = getOverrides()
|
|
1211
1212
|
const updated = Object.assign({}, cur)
|
|
1212
1213
|
delete updated[targetKey]
|
|
1213
|
-
|
|
1214
|
+
resetBtn.disabled = true
|
|
1215
|
+
try {const res = await Promise.resolve(scope.set('overrides', updated))
|
|
1216
|
+
if (res === false) throw new Error('Reset rejected')
|
|
1214
1217
|
modal.remove()
|
|
1215
1218
|
if (typeof queueZhWalk === 'function') queueZhWalk()
|
|
1216
|
-
}
|
|
1219
|
+
} catch (err) {resetBtn.disabled = false
|
|
1220
|
+
console.warn('dsh-russian-lang: reset override failed', err && err.message || err)
|
|
1221
|
+
}}}
|
|
1217
1222
|
const inspSnap = typeof scope?.getSnapshot === 'function' ? scope.getSnapshot() : { status: 'ready', writable: true }
|
|
1218
|
-
if (inspSnap.status !== 'ready' || inspSnap.writable === false) {saveBtn.disabled = true
|
|
1223
|
+
if (inspSnap.status !== 'ready' || inspSnap.writable === false) {if (saveBtn) {saveBtn.disabled = true
|
|
1219
1224
|
saveBtn.title = 'Настройки доступны только для чтения'
|
|
1225
|
+
}
|
|
1220
1226
|
if (resetBtn) resetBtn.disabled = true
|
|
1221
1227
|
}
|
|
1222
|
-
saveBtn.onclick = async () => {const val = textarea.value.trim()
|
|
1228
|
+
if (saveBtn) {saveBtn.onclick = async () => {const val = textarea.value.trim()
|
|
1223
1229
|
const targetKey = k || rawText
|
|
1224
1230
|
if (!targetKey || !val || saveBtn.disabled) return
|
|
1225
1231
|
const cur = getOverrides()
|
|
@@ -1237,7 +1243,7 @@ setTimeout(() => modal.remove(), 500)
|
|
|
1237
1243
|
} catch (err) {saveBtn.disabled = false
|
|
1238
1244
|
saveBtn.textContent = 'Ошибка сохранения'
|
|
1239
1245
|
console.warn('dsh-russian-lang: save override failed', err && err.message || err)
|
|
1240
|
-
}}
|
|
1246
|
+
}}}
|
|
1241
1247
|
document.body.appendChild(modal)
|
|
1242
1248
|
setTimeout(() => textarea.focus(), 50)
|
|
1243
1249
|
}
|
|
@@ -1336,7 +1342,10 @@ ctx.slots.register({name: 'conversation.chat.assistant-actions',id: 'dsh-russian
|
|
|
1336
1342
|
} catch (err) { void err; }
|
|
1337
1343
|
try {ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1338
1344
|
ctx.slots.register({name: 'conversation.session.header.utilities',id: 'dsh-russian-lang-md-export',order: 101,locale: SETTINGS_NS_NAME,inject: () => ({ runtime }),}, ExportMarkdownButton),)
|
|
1339
|
-
} catch (err) { void err; }
|
|
1345
|
+
} catch (err) { void err; }
|
|
1346
|
+
module.exports.isProtectedTextNode = isProtectedTextNode
|
|
1347
|
+
module.exports.ZH_WALKER = ZH_WALKER
|
|
1348
|
+
}
|
|
1340
1349
|
function QuickLangSwitch(props) {const inj = typeof props.inject === 'function' ? (props.inject() || {}) : (props.inject || {})
|
|
1341
1350
|
const scope = props.scope || inj.scope
|
|
1342
1351
|
const runtime = props.runtime || inj.runtime
|
|
@@ -1499,7 +1508,7 @@ const [snap, setSnap] = React.useState(() => (scope && scope.getSnapshot ? scope
|
|
|
1499
1508
|
const [ruActive, setRuActive] = React.useState(() => { try { return runtime.getLocale().active === 'ru' } catch (e) { return false } })
|
|
1500
1509
|
const [typo, setTypoState] = React.useState(() =>
|
|
1501
1510
|
(snap.value && snap.value.typography) || {})
|
|
1502
|
-
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.
|
|
1511
|
+
const [upStatus, setUpStatus] = React.useState({currentVersion: '0.3.28',latestVersion: undefined,updateAvailable: false
|
|
1503
1512
|
})
|
|
1504
1513
|
const [upLoading, setUpLoading] = React.useState(false)
|
|
1505
1514
|
const [upMsg, setUpMsg] = React.useState(null)
|
|
@@ -1594,7 +1603,7 @@ scope.set('agentPromptPreset', val)
|
|
|
1594
1603
|
} catch (err) { console.warn('dsh-russian-lang: set agentPromptPreset failed', err) }}},h('option', { value: 'technical_expert' }, t('presetExpert')),h('option', { value: 'tech_writer' }, t('presetWriter')),h('option', { value: 'concise' }, t('presetConcise')),h('option', { value: 'code_reviewer' }, t('presetReviewer')),h('option', { value: 'architect' }, t('presetArchitect')),h('option', { value: 'tutor' }, t('presetTutor')))),presetInfo ? h('div', { className: 'rl-preview-box' },h('div', { style: { fontWeight: 600, marginBottom: '4px', color: 'var(--dsw-alias-label-primary)' } }, '💬 ' + presetInfo.label + ':'),presetInfo.text
|
|
1595
1604
|
) : null
|
|
1596
1605
|
) : null
|
|
1597
|
-
),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secUpdater')),h('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))),h('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),h('div', { className: 'rl-item-card' },h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },h('div', null,h('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.
|
|
1606
|
+
),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secUpdater')),h('span', { className: 'rl-badge ' + (upStatus.updateAvailable ? 'rl-badge-warn' : 'rl-badge-ok') },upStatus.updateAvailable ? t('badgeUpdateAvailable') : t('badgeUpToDate'))),h('div', { className: 'rl-section-desc' }, t('secUpdaterDesc')),h('div', { className: 'rl-item-card' },h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '8px' } },h('div', null,h('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },t('updaterCurrent').replace('{version}', upStatus.currentVersion || '0.3.28')),upStatus.updateAvailable
|
|
1598
1607
|
? h('div', { style: { color: 'var(--dsw-alias-state-warning-primary)', marginTop: '2px', fontWeight: 500 } },t('updaterLatest').replace('{version}', upStatus.latestVersion || ''))
|
|
1599
1608
|
: h('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: '2px' } },t('updaterUpToDate'))),upStatus.updateAvailable
|
|
1600
1609
|
? h('button', {type: 'button',className: 'rl-btn rl-btn-primary',disabled: upLoading,onClick: triggerUpdate,}, upLoading ? t('updaterUpdating') : t('updaterBtn').replace('{version}', upStatus.latestVersion || ''))
|
|
@@ -1627,12 +1636,14 @@ try { scope.set('overrides', next) } catch (_e) { void _e }},}, t('overrideDelet
|
|
|
1627
1636
|
updateInspectorPill()
|
|
1628
1637
|
},}, isInspectorActive ? t('inspectorToggleOn') : t('inspectorToggleOff')),h('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {try {navigator.clipboard.writeText(JSON.stringify(overrides, null, 2))
|
|
1629
1638
|
alert(t('overridesCopied'))
|
|
1630
|
-
} catch (err) { void err; }}}, t('exportJsonBtn')),h('button', {type: 'button',className: 'rl-btn',style: { fontSize: '11px' },onClick: () => {
|
|
1639
|
+
} catch (err) { void err; }}}, t('exportJsonBtn')),h('button', {type: 'button',className: 'rl-btn',disabled: !!disabled,style: { fontSize: '11px' },onClick: async () => {if (disabled) return
|
|
1640
|
+
const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt(t('overridesImportPrompt')) : null
|
|
1631
1641
|
if (!input) return
|
|
1632
1642
|
try {const parsed = JSON.parse(input)
|
|
1633
1643
|
if (parsed && typeof parsed === 'object') {const merged = Object.assign({}, overrides, parsed)
|
|
1634
|
-
scope.set('overrides', merged)
|
|
1635
|
-
|
|
1644
|
+
const res = await Promise.resolve(scope.set('overrides', merged))
|
|
1645
|
+
if (res === false) throw new Error('Import rejected')
|
|
1646
|
+
}} catch (err) { alert(t('overridesImportError') + (err && err.message || err)) }}}, t('importJsonBtn')))),h('div', { className: 'rl-section-card' },h('div', { className: 'rl-section-title' },h('span', null, t('secSupport')),h('span', { className: 'rl-badge rl-badge-ok' }, '🟢 100.0%')),h('div', { className: 'rl-section-desc' }, t('secSupportDesc')),h('div', { className: 'rl-grid-3' },h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '101'),h('div', { className: 'rl-stat-label' }, t('statNamespaces'))),h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '2 442'),h('div', { className: 'rl-stat-label' }, t('statCoreKeys'))),h('div', { className: 'rl-stat-box' },h('div', { className: 'rl-stat-val' }, '6 142'),h('div', { className: 'rl-stat-label' }, t('statPluginKeys')))),h('div', { className: 'rl-actions-row' },h('a', {href: makeIssueUrl({}, '0.3.28'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
|
|
1636
1647
|
}, '💬 ' + t('reportIssue')),h('div', { className: 'rl-hint-text', style: { flex: 1 } },t('exportMdHint')))))))
|
|
1637
1648
|
}
|
|
1638
1649
|
const RL_CSS = [':root{--rl-sb:var(--dsw-alias-state-brand-primary);--rl-ls:var(--dsw-alias-label-secondary);--rl-lp:var(--dsw-alias-label-primary);--rl-b:var(--dsw-alias-border-l2);--rl-l3:var(--dsw-alias-bg-layer-3);--rl-l2:var(--dsw-alias-bg-layer-2);--rl-l1:var(--dsw-alias-bg-layer-1)}.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(--rl-l2);color:var(--rl-lp);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(--rl-b);background:var(--rl-l3);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(--rl-l2)}.rl-head-main{flex:1;display:flex;flex-direction:column;gap:4px}.rl-title{color:var(--rl-lp);font-size:16px;font-weight:600;line-height:1.4;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.rl-sub{color:var(--rl-ls);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(--rl-b);padding:18px}.rl-page{display:flex;flex-direction:column;gap:16px;max-width:960px}.rl-section-card{border:1px solid var(--rl-b);background:var(--rl-l2);border-radius:10px;padding:16px 18px;display:flex;flex-direction:column;gap:12px}.rl-section-title{font-size:14px;font-weight:600;color:var(--rl-lp);display:flex;align-items:center;justify-content:space-between;gap:8px}.rl-section-desc{font-size:12px;color:var(--rl-ls);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(--rl-b);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(--rl-b);color:var(--rl-ls);background:var(--rl-l3)}.rl-item-card{border:1px solid var(--rl-b);background:var(--rl-l3);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(--rl-lp);display:flex;align-items:center;gap:8px;cursor:pointer}.rl-item-desc{font-size:11px;color:var(--rl-ls);line-height:1.4}.rl-stat-box{border:1px solid var(--rl-b);background:var(--rl-l3);border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:2px}.rl-stat-val{font-size:16px;font-weight:700;color:var(--rl-lp)}.rl-stat-label{font-size:11px;color:var(--rl-ls);letter-spacing:0.3px}.rl-preview-box{border:1px solid var(--rl-b);background:var(--rl-l3);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--rl-ls);line-height:1.4}.rl-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--rl-b);border-radius:8px;padding:6px 12px;font-size:12px;background:var(--rl-l3);color:var(--rl-lp);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(--rl-l1);border-color:var(--rl-ls)}.rl-btn-primary{background:var(--rl-lp);color:var(--rl-l3);border-color:transparent}.rl-btn-primary:hover:not(:disabled){opacity:0.9}.rl-select{height:32px;border:1px solid var(--rl-b);background:var(--rl-l3);color:var(--rl-lp);border-radius:6px;padding:0 8px;font-size:12px;outline:none;width:100%;max-width:380px}.rl-select:focus{border-color:var(--rl-sb)}.rl-check{width:16px;height:16px;accent-color:var(--rl-lp);cursor:pointer;flex-shrink:0}.rl-hint-text{font-size:11px;color:var(--rl-ls);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(--rl-b);background:var(--rl-l3);color:var(--rl-ls);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(--rl-lp);color:var(--rl-lp)}.rl-lang-chip-active{color:var(--rl-lp);border-color:var(--rl-lp);background:var(--rl-l2)}.rl-action-btn{appearance:none;background:0 0;border:1px solid transparent;border-radius:4px;color:var(--rl-ls);cursor:pointer;font-size:11px;padding:2px 5px;display:inline-flex;align-items:center;transition:all .15s}.rl-action-btn:hover{color:var(--rl-lp);border-color:var(--rl-b);background:var(--rl-l3)}.rl-action-btn-active{color:var(--rl-lp);background:var(--rl-l2);border-color:var(--rl-b)}.rl-turn-translation{margin:8px 0;padding:10px 12px;border:1px solid var(--rl-b);background:var(--rl-l2);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(--rl-b);padding-bottom:4px}.rl-trans-title{font-size:11px;font-weight:600;color:var(--rl-ls);text-transform:uppercase;letter-spacing:0.5px}.rl-trans-tools{display:flex;gap:4px}.rl-trans-btn{appearance:none;background:var(--rl-l3);border:1px solid var(--rl-b);color:var(--rl-ls);cursor:pointer;padding:2px 7px;border-radius:4px;font-size:11px}.rl-trans-btn:hover{background:var(--rl-l1);color:var(--rl-lp)}.rl-trans-body{color:var(--rl-lp);white-space:pre-wrap;word-break:break-word;user-select:text}.rl-export-md-btn{appearance:none;border:1px solid var(--rl-b);height:32px;color:var(--rl-lp);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(--rl-sb);border-radius:4px;background:color-mix(in srgb,var(--rl-sb) 12%,transparent);box-shadow:0 0 10px color-mix(in srgb,var(--rl-sb) 30%,transparent);z-index:99998;transition:all .06s ease;display:none}.rl-inspector-badge{position:absolute;bottom:calc(100% + 4px);left:0;background:var(--rl-sb);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(--rl-l2);color:var(--rl-lp);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(--rl-sb);box-shadow:0 0 12px color-mix(in srgb,var(--rl-sb) 35%,transparent)}'
|
|
@@ -1646,6 +1657,6 @@ tag.dataset.pluginCss = 'rl-card'
|
|
|
1646
1657
|
tag.textContent = RL_CSS
|
|
1647
1658
|
document.head.appendChild(tag)
|
|
1648
1659
|
}
|
|
1649
|
-
module.exports = { apply, inject: ['slots', 'locale', 'configForms'] }
|
|
1660
|
+
module.exports = { apply, inject: ['slots', 'locale', 'configForms'], resolveTypography, SettingsCard }
|
|
1650
1661
|
return module.exports
|
|
1651
1662
|
},})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"dsh-task-tracker":{"board.back":"В чат","board.backHint":"Закрыть трекер задач и вернуться в чат","field.base":"URL сервера Task Tracker","field.filePath":"Пользовательский путь к файлу tasks.json (необязательно)","field.filePathPlaceholder":"~/.dsh/task-tracker/tasks.json","field.pass":"Пароль / Токен","field.passFile":"Путь к файлу пароля","field.user":"Пользователь","filePath":"Путь к файлу","loading":"Загрузка настроек трекера…","passHint":"Токен доступа к API Task Tracker","passPlaceholder":"Секретный токен","save":"Сохранить","saved":"Настройки сохранены","saving":"Сохранение…","sessions":"Сессии","sub":"Интеграция с внутренним трекером задач и тикетов","subtitle":"API входящих задач (базовый URL и Basic-аутентификация)","tasks.active":"Активные","tasks.add":"+ Добавить","tasks.addTask":"+ Добавить задачу","tasks.all":"Все","tasks.backToChat":"К чату","tasks.cancel":"Отмена","tasks.changeIcon":"Сменить иконку","tasks.chatWithAgent":"Чат с агентом","tasks.close":"Закрыть","tasks.completed":"Выполнено","tasks.confirmDelete":"Удалить эту задачу?","tasks.create":"+ Создать","tasks.createFolder":"Создать папку","tasks.delete":"Удалить","tasks.descPlaceholder":"Подробное описание задачи…","tasks.description":"Описание","tasks.empty":"Задачи не найдены","tasks.emptyInFolder":"В папке нет задач
|
|
1
|
+
{"dsh-task-tracker":{"board.back":"В чат","board.backHint":"Закрыть трекер задач и вернуться в чат","field.base":"URL сервера Task Tracker","field.filePath":"Пользовательский путь к файлу tasks.json (необязательно)","field.filePathPlaceholder":"~/.dsh/task-tracker/tasks.json","field.pass":"Пароль / Токен","field.passFile":"Путь к файлу пароля","field.user":"Пользователь","filePath":"Путь к файлу","loading":"Загрузка настроек трекера…","passHint":"Токен доступа к API Task Tracker","passPlaceholder":"Секретный токен","save":"Сохранить","saved":"Настройки сохранены","saving":"Сохранение…","sessions":"Сессии","sub":"Интеграция с внутренним трекером задач и тикетов","subtitle":"API входящих задач (базовый URL и Basic-аутентификация)","tasks.active":"Активные","tasks.add":"+ Добавить","tasks.addTask":"+ Добавить задачу","tasks.all":"Все","tasks.backToChat":"К чату","tasks.cancel":"Отмена","tasks.changeIcon":"Сменить иконку","tasks.chatWithAgent":"Чат с агентом","tasks.close":"Закрыть","tasks.completed":"Выполнено","tasks.confirmDelete":"Удалить эту задачу?","tasks.create":"+ Создать","tasks.createFolder":"Создать папку","tasks.delete":"Удалить","tasks.descPlaceholder":"Подробное описание задачи…","tasks.description":"Описание","tasks.empty":"Задачи не найдены","tasks.emptyInFolder":"В папке «%s» нет задач","tasks.folder":"Папка","tasks.folderBtn":"Папка","tasks.folders":"Папки","tasks.form.desc":"Описание","tasks.form.descPlaceholder":"Подробное описание задачи…","tasks.form.folder":"Папка","tasks.form.priority":"Приоритет","tasks.form.title":"Название","tasks.form.titlePlaceholder":"Название задачи","tasks.loadingTasks":"Загрузка задач…","tasks.markComplete":"Отметить как выполненную","tasks.markIncomplete":"Вернуть в работу","tasks.meta.author":"Автор:","tasks.meta.created":"Создано:","tasks.modal.title":"Задача","tasks.newFolderPrompt":"Название новой папки:","tasks.newPlaceholder":"Что нужно сделать?","tasks.newTask":"Новая задача","tasks.openAgentChat":"💬 Обсудить задачу с агентом","tasks.priority":"Приоритет","tasks.priority.high":"Высокий","tasks.priority.low":"Низкий","tasks.priority.medium":"Средний","tasks.priority.normal":"Обычный","tasks.priorityHigh":"Высокий","tasks.priorityLow":"Низкий","tasks.priorityMedium":"Средний","tasks.priorityNone":"Обычный","tasks.promptTemplate":"Шаблон промпта","tasks.quickAddPlaceholder":"Что нужно сделать? Нажмите Enter для добавления","tasks.refresh":"Обновить","tasks.search":"Поиск задач…","tasks.showAllFolders":"Показать все папки","tasks.startChatWithAgent":"💬 Начать чат с агентом","tasks.status":"Статус","tasks.status.completed":"Завершено","tasks.statusActive":"Активно","tasks.statusCompleted":"Выполнено","tasks.taskDetails":"Детали задачи","tasks.tasksCountSuffix":"задач","tasks.title":"Задачи","tasks.titleLabel":"Название *","title":"Трекер задач (Task Tracker)","unavailable":"Область настроек недоступна","updater.available":"Доступно обновление: v{v}","updater.checkFailed":"Не удалось проверить обновления","updater.checking":"Проверка обновлений…","updater.upToDate":"Актуальная версия","updater.updateFailed":"Ошибка при обновлении","updater.updateNow":"Обновить сейчас","updater.updated":"Успешно обновлено до v{v}. Перезапустите DSH для применения.","updater.updating":"Обновление…","updater.version":"Версия","userHint":"Логин сервисного аккаунта"}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-russian-lang",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.28",
|
|
4
4
|
"description": "Russian localization for DeepSeek Harness (DSH v0.2.0-rc.2+) web UI: adds Русский as native language option. 100.0% coverage across 8,584 UI keys (101 namespaces: Core 2,442 + Plugins 6,142). Features full DSH v0.2.0-rc.2 core support and complete GoodAndReady plugin catalog.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|