@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 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.27-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
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 [Слой перевода и адаптации — 8576 ключей / 100% вычитано]
56
- Loader --> CoreDict[58 Пространств имён ядра DSH / 2442 ключа]
57
- Loader --> PluginDict[64 Плагина экосистемы DSH / 6134 ключа]
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
- typography:
183
- enabled: true # Ёлочки, длинные тире, неразрывные пробелы
184
- yo: false # Восстанавливать «ё» (по умолчанию выключено)
185
- agentPrompt: false # Просить агента отвечать по-русски
186
- overrides: {} # Свои значения для любых ключей перевода
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` в клоне репозитория проверяет синтаксис бандла через `node --check`.
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
- const tMatches = code.matchAll(/\bt\(\s*['"`]([a-zA-Z0-9_.-]+)['"`]\s*[,)]/g)
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
- const gPath = glossaryPath || path.join(ROOT, 'glossary.json')
106
- if (fs.existsSync(gPath)) {
204
+ if (glossaryPath) {
205
+ if (!fs.existsSync(glossaryPath)) {
206
+ console.error(`Ошибка: Файл глоссария не найден: ${glossaryPath}`)
207
+ return false
208
+ }
107
209
  try {
108
- glossary = JSON.parse(fs.readFileSync(gPath, 'utf8')).terms || {}
109
- } catch (_) {}
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
- console.log(`✓ Извлечено ключей: ${Object.keys(extracted[ns] || {}).length} -> ${out}`)
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.27' })
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
- try { scope.set('overrides', updated) } catch (err) { void err; }
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.27',latestVersion: undefined,updateAvailable: false
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.27')),upStatus.updateAvailable
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: () => {const input = typeof window !== 'undefined' && typeof window.prompt === 'function' ? window.prompt(t('overridesImportPrompt')) : null
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
- }} 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.27'),target: '_blank',rel: 'noopener noreferrer',className: 'rl-btn rl-btn-primary'
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":"В папке нет задач ","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":"Логин сервисного аккаунта"}}
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.27",
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",