@goodandready/dsh-russian-lang 0.2.26 → 0.3.1

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.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/bin/dsh-i18n.mjs +247 -0
  3. package/lib/client.js +785 -410
  4. package/lib/index.js +69 -13
  5. package/lib/locales/plugins/01-token-dashboard.json +1 -1
  6. package/lib/locales/plugins/10-kanban.json +1 -1
  7. package/lib/locales/plugins/13-agent-teams.json +1 -1
  8. package/lib/locales/plugins/16-usage-stats.json +1 -1
  9. package/lib/locales/plugins/23-moa.json +1 -1
  10. package/lib/locales/plugins/24-personal-directive.json +1 -1
  11. package/lib/locales/plugins/33-messenger-gateway.json +1 -1
  12. package/lib/locales/plugins/35-gitea.json +1 -1
  13. package/lib/locales/plugins/36-cost-meter.json +1 -1
  14. package/lib/locales/plugins/37-time-machine.json +1 -1
  15. package/lib/locales/plugins/38-context-lens.json +1 -1
  16. package/lib/locales/plugins/39-shadow-auditor.json +1 -1
  17. package/lib/locales/plugins/40-clinebot.json +1 -1
  18. package/lib/locales/plugins/41-lanmode.json +1 -1
  19. package/lib/locales/plugins/42-plugin-console.json +1 -1
  20. package/lib/locales/plugins/46-memory-brain.json +1 -1
  21. package/lib/locales/plugins/48-task-tracker.json +1 -1
  22. package/lib/locales/plugins/49-dsml-guard.json +1 -1
  23. package/lib/locales/plugins/49-usage-guard.json +1 -1
  24. package/lib/locales/plugins/50-automode.json +1 -1
  25. package/lib/locales/plugins/54-workshop-market.json +1 -1
  26. package/lib/locales/plugins/56-cron.json +1 -1
  27. package/lib/locales/plugins/57-live-canvas.json +1 -1
  28. package/lib/locales/plugins/58-grok-xsearch.json +1 -1
  29. package/lib/locales/plugins/59-goal.json +1 -1
  30. package/lib/locales/plugins/60-model-sync.json +1 -1
  31. package/lib/locales/plugins/61-issue-reporter.json +1 -1
  32. package/lib/locales/plugins/62-model-search.json +1 -1
  33. package/lib/locales/plugins/63-agent-orchestrator.json +1 -1
  34. package/lib/locales/plugins/64-key-limits.json +1 -1
  35. package/lib/locales/plugins/65-subscriptions.json +1 -1
  36. package/lib/locales/plugins/66-tts.json +1 -1
  37. package/lib/locales/plugins/67-voice.json +1 -1
  38. package/lib/locales/plugins/68-image-gen.json +1 -1
  39. package/lib/locales/plugins/69-plugin-notify.json +1 -1
  40. package/lib/locales/plugins/70-hypotheses.json +1 -1
  41. package/lib/locales/plugins/71-memory-meter.json +1 -1
  42. package/lib/locales/plugins/72-server-monitor.json +1 -70
  43. package/lib/locales/plugins/73-agentrouter.json +1 -1
  44. package/lib/locales/plugins/75-test-pilot.json +1 -0
  45. package/lib/locales/plugins/76-visualize.json +1 -0
  46. package/lib/locales/plugins/77-github-ops.json +1 -0
  47. package/lib/locales/plugins/78-web-gateway.json +1 -0
  48. package/lib/locales/plugins/79-approval-gate.json +1 -0
  49. package/lib/locales.js +50 -12
  50. package/lib/pure.js +251 -13
  51. package/package.json +7 -3
  52. package/lib/locales/plugins/44-goal.json +0 -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.2.25-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.1-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>
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+ const ROOT = path.resolve(__dirname, '..')
8
+
9
+ function getPkgVersion() {
10
+ try {
11
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'))
12
+ return pkg.version || '0.0.0'
13
+ } catch (_) {
14
+ return '0.0.0'
15
+ }
16
+ }
17
+
18
+ function printHelp() {
19
+ console.log(`
20
+ dsh-i18n (v${getPkgVersion()}) — Инструмент локализации для плагинов DeepSeek Harness (DSH)
21
+
22
+ Использование:
23
+ dsh-i18n <команда> [опции]
24
+
25
+ Команды:
26
+ extract <каталог> Извлечь ключи локализации (t('...'), translate('...')) из исходников
27
+ validate <файл.json> Проверить файл словаря на синтаксис, плейсхолдеры и глоссарий
28
+ scaffold <имя-плагина> Создать структуру и стартовый шаблон локализации для плагина
29
+
30
+ Опции:
31
+ -o, --out <путь> Путь для сохранения результата
32
+ -n, --namespace <имя> Пространство имён по умолчанию (для extract)
33
+ -g, --glossary <путь> Пользовательский файл глоссария (для validate)
34
+ -h, --help Справка по командам
35
+ -v, --version Версия утилиты
36
+
37
+ Примеры:
38
+ dsh-i18n extract src/ -n my-plugin -o ru/my-plugin.json
39
+ dsh-i18n validate ru/my-plugin.json
40
+ dsh-i18n scaffold dsh-my-plugin --out ru/
41
+ `)
42
+ }
43
+
44
+ function extractKeys(targetDir, defaultNs = 'common') {
45
+ const result = { [defaultNs]: {} }
46
+ const extensions = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx'])
47
+
48
+ function walk(dir) {
49
+ if (!fs.existsSync(dir)) return
50
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
51
+ for (const entry of entries) {
52
+ const fullPath = path.join(dir, entry.name)
53
+ if (entry.isDirectory()) {
54
+ if (['node_modules', '.git', 'dist', 'build', '.worktrees'].includes(entry.name)) continue
55
+ walk(fullPath)
56
+ } else if (entry.isFile() && extensions.has(path.extname(entry.name))) {
57
+ const code = fs.readFileSync(fullPath, 'utf8')
58
+ const tMatches = code.matchAll(/\bt\(\s*['"`]([a-zA-Z0-9_.-]+)['"`]\s*\)/g)
59
+ for (const m of tMatches) {
60
+ result[defaultNs][m[1]] = result[defaultNs][m[1]] || ''
61
+ }
62
+ const transMatches = code.matchAll(/\btranslate\(\s*['"`]([a-zA-Z0-9_.-]+)['"`]\s*,\s*['"`]([a-zA-Z0-9_.-]+)['"`]/g)
63
+ for (const m of transMatches) {
64
+ const ns = m[1]
65
+ const key = m[2]
66
+ if (!result[ns]) result[ns] = {}
67
+ result[ns][key] = result[ns][key] || ''
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ walk(targetDir)
74
+ return result
75
+ }
76
+
77
+ function validateDict(filePath, glossaryPath) {
78
+ if (!fs.existsSync(filePath)) {
79
+ console.error(`Ошибка: Файл не найден: ${filePath}`)
80
+ return false
81
+ }
82
+
83
+ let data
84
+ try {
85
+ data = JSON.parse(fs.readFileSync(filePath, 'utf8'))
86
+ } catch (err) {
87
+ console.error(`Ошибка синтаксиса JSON в ${filePath}: ${err.message}`)
88
+ return false
89
+ }
90
+
91
+ let errors = []
92
+ let warnings = []
93
+ let totalKeys = 0
94
+
95
+ let glossary = null
96
+ const gPath = glossaryPath || path.join(ROOT, 'glossary.json')
97
+ if (fs.existsSync(gPath)) {
98
+ try {
99
+ glossary = JSON.parse(fs.readFileSync(gPath, 'utf8')).terms || {}
100
+ } catch (_) {}
101
+ }
102
+
103
+ function checkString(ns, key, val) {
104
+ totalKeys++
105
+ if (typeof val !== 'string') {
106
+ errors.push(`[${ns}:${key}] Значение должно быть строкой, получено: ${typeof val}`)
107
+ return
108
+ }
109
+
110
+ if (/\{[\s]*\}/.test(val)) {
111
+ errors.push(`[${ns}:${key}] Обнаружен пустой плейсхолдер: "{}"`)
112
+ }
113
+ const openCount = (val.match(/\{/g) || []).length
114
+ const closeCount = (val.match(/\}/g) || []).length
115
+ if (openCount !== closeCount) {
116
+ errors.push(`[${ns}:${key}] Несовпадение количества открывающих и закрывающих фигурных скобок ({: ${openCount}, }: ${closeCount})`)
117
+ }
118
+
119
+ if (glossary) {
120
+ const lower = val.toLowerCase()
121
+ for (const [term, info] of Object.entries(glossary)) {
122
+ if (Array.isArray(info.forbidden)) {
123
+ for (const forbidden of info.forbidden) {
124
+ const fLow = forbidden.toLowerCase()
125
+ const re = new RegExp(`\\b${fLow}\\b`, 'i')
126
+ if (re.test(lower)) {
127
+ warnings.push(`[${ns}:${key}] Запрещённый термин по глоссарию "${forbidden}" (рекомендуется: "${info.canonical}")`)
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ for (const ns of Object.keys(data)) {
136
+ const section = data[ns]
137
+ if (typeof section === 'object' && section !== null) {
138
+ for (const [k, v] of Object.entries(section)) {
139
+ checkString(ns, k, v)
140
+ }
141
+ }
142
+ }
143
+
144
+ console.log(`Проверено ключей: ${totalKeys}`)
145
+ if (warnings.length) {
146
+ console.warn(`Предупреждения (${warnings.length}):`)
147
+ for (const w of warnings) console.warn(` ⚠️ ${w}`)
148
+ }
149
+ if (errors.length) {
150
+ console.error(`Ошибки (${errors.length}):`)
151
+ for (const e of errors) console.error(` ❌ ${e}`)
152
+ return false
153
+ }
154
+
155
+ console.log(`✓ Файл ${filePath} прошёл проверку валидности!`)
156
+ return true
157
+ }
158
+
159
+ function scaffoldPlugin(pluginName, outDir = '.') {
160
+ const cleanName = pluginName.replace(/^@goodandready\//, '').replace(/^dsh-/, '')
161
+ const ns = cleanName
162
+ const template = {
163
+ [ns]: {
164
+ title: 'Название плагина',
165
+ description: 'Описание назначения и возможностей плагина',
166
+ settings: 'Настройки',
167
+ status: 'Статус',
168
+ save: 'Сохранить',
169
+ cancel: 'Отмена'
170
+ }
171
+ }
172
+
173
+ const dir = path.resolve(outDir)
174
+ fs.mkdirSync(dir, { recursive: true })
175
+ const targetFile = path.join(dir, `${cleanName}.json`)
176
+
177
+ fs.writeFileSync(targetFile, JSON.stringify(template, null, 2) + '\n', 'utf8')
178
+ console.log(`✓ Шаблон локализации создан: ${targetFile}`)
179
+ console.log(`
180
+ Пример подключения в коде плагина (Cordis / DSH):
181
+
182
+ import dictRu from './ru/${cleanName}.json' assert { type: 'json' }
183
+
184
+ export function apply(ctx) {
185
+ ctx.effect(() => {
186
+ return ctx.locale.register('${ns}', 'ru', dictRu['${ns}'])
187
+ })
188
+ }
189
+ `)
190
+ }
191
+
192
+ const args = process.argv.slice(2)
193
+ if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
194
+ printHelp()
195
+ process.exit(0)
196
+ }
197
+
198
+ if (args.includes('-v') || args.includes('--version')) {
199
+ console.log(getPkgVersion())
200
+ process.exit(0)
201
+ }
202
+
203
+ const command = args[0]
204
+ if (command === 'extract') {
205
+ const target = args[1] || '.'
206
+ let ns = 'common'
207
+ let out = null
208
+ for (let i = 2; i < args.length; i++) {
209
+ if ((args[i] === '-n' || args[i] === '--namespace') && args[i + 1]) ns = args[++i]
210
+ if ((args[i] === '-o' || args[i] === '--out') && args[i + 1]) out = args[++i]
211
+ }
212
+ const extracted = extractKeys(path.resolve(target), ns)
213
+ const jsonStr = JSON.stringify(extracted, null, 2)
214
+ if (out) {
215
+ fs.mkdirSync(path.dirname(path.resolve(out)), { recursive: true })
216
+ fs.writeFileSync(path.resolve(out), jsonStr + '\n', 'utf8')
217
+ console.log(`✓ Извлечено ключей: ${Object.keys(extracted[ns] || {}).length} -> ${out}`)
218
+ } else {
219
+ console.log(jsonStr)
220
+ }
221
+ } else if (command === 'validate') {
222
+ const file = args[1]
223
+ if (!file) {
224
+ console.error('Ошибка: Укажите путь к JSON файлу словаря (dsh-i18n validate <file.json>)')
225
+ process.exit(1)
226
+ }
227
+ let glossary = null
228
+ for (let i = 2; i < args.length; i++) {
229
+ if ((args[i] === '-g' || args[i] === '--glossary') && args[i + 1]) glossary = args[++i]
230
+ }
231
+ const ok = validateDict(path.resolve(file), glossary ? path.resolve(glossary) : null)
232
+ process.exit(ok ? 0 : 1)
233
+ } else if (command === 'scaffold') {
234
+ const name = args[1]
235
+ if (!name) {
236
+ console.error('Ошибка: Укажите имя плагина (dsh-i18n scaffold <plugin-name>)')
237
+ process.exit(1)
238
+ }
239
+ let out = '.'
240
+ for (let i = 2; i < args.length; i++) {
241
+ if ((args[i] === '-o' || args[i] === '--out') && args[i + 1]) out = args[++i]
242
+ }
243
+ scaffoldPlugin(name, out)
244
+ } else {
245
+ console.error(`Неизвестная команда: "${command}". Запустите "dsh-i18n --help" для справки.`)
246
+ process.exit(1)
247
+ }