@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.
- package/README.md +1 -1
- package/bin/dsh-i18n.mjs +247 -0
- package/lib/client.js +785 -410
- package/lib/index.js +69 -13
- package/lib/locales/plugins/01-token-dashboard.json +1 -1
- package/lib/locales/plugins/10-kanban.json +1 -1
- package/lib/locales/plugins/13-agent-teams.json +1 -1
- package/lib/locales/plugins/16-usage-stats.json +1 -1
- package/lib/locales/plugins/23-moa.json +1 -1
- package/lib/locales/plugins/24-personal-directive.json +1 -1
- package/lib/locales/plugins/33-messenger-gateway.json +1 -1
- package/lib/locales/plugins/35-gitea.json +1 -1
- package/lib/locales/plugins/36-cost-meter.json +1 -1
- package/lib/locales/plugins/37-time-machine.json +1 -1
- package/lib/locales/plugins/38-context-lens.json +1 -1
- package/lib/locales/plugins/39-shadow-auditor.json +1 -1
- package/lib/locales/plugins/40-clinebot.json +1 -1
- package/lib/locales/plugins/41-lanmode.json +1 -1
- package/lib/locales/plugins/42-plugin-console.json +1 -1
- package/lib/locales/plugins/46-memory-brain.json +1 -1
- package/lib/locales/plugins/48-task-tracker.json +1 -1
- package/lib/locales/plugins/49-dsml-guard.json +1 -1
- package/lib/locales/plugins/49-usage-guard.json +1 -1
- package/lib/locales/plugins/50-automode.json +1 -1
- package/lib/locales/plugins/54-workshop-market.json +1 -1
- package/lib/locales/plugins/56-cron.json +1 -1
- package/lib/locales/plugins/57-live-canvas.json +1 -1
- package/lib/locales/plugins/58-grok-xsearch.json +1 -1
- package/lib/locales/plugins/59-goal.json +1 -1
- package/lib/locales/plugins/60-model-sync.json +1 -1
- package/lib/locales/plugins/61-issue-reporter.json +1 -1
- package/lib/locales/plugins/62-model-search.json +1 -1
- package/lib/locales/plugins/63-agent-orchestrator.json +1 -1
- package/lib/locales/plugins/64-key-limits.json +1 -1
- package/lib/locales/plugins/65-subscriptions.json +1 -1
- package/lib/locales/plugins/66-tts.json +1 -1
- package/lib/locales/plugins/67-voice.json +1 -1
- package/lib/locales/plugins/68-image-gen.json +1 -1
- package/lib/locales/plugins/69-plugin-notify.json +1 -1
- package/lib/locales/plugins/70-hypotheses.json +1 -1
- package/lib/locales/plugins/71-memory-meter.json +1 -1
- package/lib/locales/plugins/72-server-monitor.json +1 -70
- package/lib/locales/plugins/73-agentrouter.json +1 -1
- package/lib/locales/plugins/75-test-pilot.json +1 -0
- package/lib/locales/plugins/76-visualize.json +1 -0
- package/lib/locales/plugins/77-github-ops.json +1 -0
- package/lib/locales/plugins/78-web-gateway.json +1 -0
- package/lib/locales/plugins/79-approval-gate.json +1 -0
- package/lib/locales.js +50 -12
- package/lib/pure.js +251 -13
- package/package.json +7 -3
- 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.
|
|
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>
|
package/bin/dsh-i18n.mjs
ADDED
|
@@ -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
|
+
}
|