@goodandready/dsh-russian-lang 0.2.20 → 0.2.22
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 +13 -3
- package/README.ru.md +13 -3
- package/lib/client.js +198 -74
- package/lib/index.js +10 -2
- package/lib/locales/plugins/07-better-sidebar.json +1 -1
- package/lib/locales/plugins/25-hooks.json +1 -1
- package/lib/locales/plugins/32-vision-bridge.json +1 -1
- package/lib/locales/plugins/34-key-rotation.json +1 -1
- package/lib/locales/plugins/71-memory-meter.json +1 -0
- package/lib/locales/plugins/72-server-monitor.json +1 -0
- package/lib/locales/plugins/73-agentrouter.json +1 -0
- package/lib/translator.js +303 -0
- package/package.json +1 -2
- package/README.zh.md +0 -102
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host-side translation services & LibreTranslate integration
|
|
5
|
+
* for @goodandready/dsh-russian-lang.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
function execPromise(cmd, args, timeoutMs = 15000) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
execFile(cmd, args, { timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
11
|
+
if (err) return reject(new Error(stderr?.trim() || err.message))
|
|
12
|
+
resolve(stdout?.trim() || '')
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readJsonBody(request, limit = 500_000) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
let body = ''
|
|
20
|
+
request.on('data', (chunk) => {
|
|
21
|
+
body += chunk
|
|
22
|
+
if (body.length > limit) {
|
|
23
|
+
reject(new Error('Payload too large'))
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
request.on('end', () => {
|
|
27
|
+
try {
|
|
28
|
+
resolve(body ? JSON.parse(body) : {})
|
|
29
|
+
} catch (err) {
|
|
30
|
+
reject(err)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
request.on('error', reject)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getTranslatorStatus(settingsCtx) {
|
|
38
|
+
const currentSettings = settingsCtx?.settings?.get('russian-lang') || {}
|
|
39
|
+
const engine = currentSettings.translateEngine || 'off'
|
|
40
|
+
const localUrl = (currentSettings.localApiUrl || 'http://127.0.0.1:5000').replace(/\/+$/, '')
|
|
41
|
+
|
|
42
|
+
let dockerAvailable = false
|
|
43
|
+
let installed = false
|
|
44
|
+
let running = false
|
|
45
|
+
let responsive = false
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const statusOut = await execPromise('docker', [
|
|
49
|
+
'ps', '-a', '--filter', 'name=^libretranslate$', '--format', '{{.Status}}'
|
|
50
|
+
], 3000)
|
|
51
|
+
dockerAvailable = true
|
|
52
|
+
if (statusOut) {
|
|
53
|
+
installed = true
|
|
54
|
+
running = statusOut.toLowerCase().startsWith('up')
|
|
55
|
+
}
|
|
56
|
+
} catch (_) {
|
|
57
|
+
dockerAvailable = false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Ping HTTP endpoint if running or local
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(`${localUrl}/languages`, { signal: AbortSignal.timeout(1500) })
|
|
63
|
+
if (res.ok) responsive = true
|
|
64
|
+
} catch (_) {}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
engine,
|
|
68
|
+
localUrl,
|
|
69
|
+
dockerAvailable,
|
|
70
|
+
installed,
|
|
71
|
+
running,
|
|
72
|
+
responsive,
|
|
73
|
+
memory: '~500–700 МБ RAM',
|
|
74
|
+
recommended: 'libretranslate (CTranslate2 / int8)'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function setupLibreTranslateContainer() {
|
|
79
|
+
// Check if container exists
|
|
80
|
+
try {
|
|
81
|
+
const nameOut = await execPromise('docker', [
|
|
82
|
+
'ps', '-a', '--filter', 'name=^libretranslate$', '--format', '{{.Names}}'
|
|
83
|
+
], 3000)
|
|
84
|
+
if (nameOut && nameOut.trim() === 'libretranslate') {
|
|
85
|
+
// Start stopped container
|
|
86
|
+
await execPromise('docker', ['start', 'libretranslate'], 10000)
|
|
87
|
+
return { ok: true, action: 'started', message: 'Контейнер libretranslate запущен.' }
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
throw new Error('Docker недоступен или нет прав на выполнение: ' + err.message)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Run new container
|
|
94
|
+
try {
|
|
95
|
+
await execPromise('docker', [
|
|
96
|
+
'run', '-d',
|
|
97
|
+
'--name', 'libretranslate',
|
|
98
|
+
'--restart', 'unless-stopped',
|
|
99
|
+
'-p', '127.0.0.1:5000:5000',
|
|
100
|
+
'libretranslate/libretranslate',
|
|
101
|
+
'--load-only', 'en,ru,zh'
|
|
102
|
+
], 30000)
|
|
103
|
+
return { ok: true, action: 'created', message: 'Контейнер libretranslate создан и запускается (~600 МБ RAM).' }
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw new Error('Ошибка запуска контейнера libretranslate: ' + err.message)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function translateGoogle(text, targetLang = 'ru') {
|
|
110
|
+
const parts = text.split(/(```[\s\S]*?```)/g)
|
|
111
|
+
const resultParts = []
|
|
112
|
+
for (let i = 0; i < parts.length; i++) {
|
|
113
|
+
if (i % 2 === 1) {
|
|
114
|
+
resultParts.push(parts[i])
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
const chunk = parts[i]
|
|
118
|
+
if (!chunk.trim()) {
|
|
119
|
+
resultParts.push(chunk)
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
const paragraphs = chunk.split(/\n\n+/)
|
|
123
|
+
const transParas = []
|
|
124
|
+
for (const para of paragraphs) {
|
|
125
|
+
const pTrim = para.trim()
|
|
126
|
+
if (!pTrim) continue
|
|
127
|
+
const sentences = pTrim.match(/[^.!?\n]+[.!?\n]*/g) || [pTrim]
|
|
128
|
+
const transSents = []
|
|
129
|
+
for (const s of sentences) {
|
|
130
|
+
if (!s.trim()) continue
|
|
131
|
+
const gurl = 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=' + encodeURIComponent(targetLang) + '&dt=t&q=' + encodeURIComponent(s)
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch(gurl, { signal: AbortSignal.timeout(6000) })
|
|
134
|
+
if (res.ok) {
|
|
135
|
+
const data = await res.json()
|
|
136
|
+
if (Array.isArray(data) && Array.isArray(data[0])) {
|
|
137
|
+
transSents.push(data[0].map((it) => it[0]).join(''))
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (_) {}
|
|
142
|
+
transSents.push(s)
|
|
143
|
+
}
|
|
144
|
+
transParas.push(transSents.join(''))
|
|
145
|
+
}
|
|
146
|
+
resultParts.push(transParas.join('\n\n'))
|
|
147
|
+
}
|
|
148
|
+
return resultParts.join('')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function translateLibre(text, targetLang = 'ru', localUrl = 'http://127.0.0.1:5000') {
|
|
152
|
+
const endpoint = localUrl.replace(/\/+$/, '') + '/translate'
|
|
153
|
+
const res = await fetch(endpoint, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: { 'content-type': 'application/json' },
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
q: text,
|
|
158
|
+
source: 'auto',
|
|
159
|
+
target: targetLang,
|
|
160
|
+
format: 'text'
|
|
161
|
+
}),
|
|
162
|
+
signal: AbortSignal.timeout(15000)
|
|
163
|
+
})
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
const errText = await res.text().catch(() => '')
|
|
166
|
+
throw new Error(`LibreTranslate HTTP ${res.status}: ${errText}`)
|
|
167
|
+
}
|
|
168
|
+
const data = await res.json()
|
|
169
|
+
return data.translatedText || text
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function registerTranslator(wctx, settingsCtx) {
|
|
173
|
+
let ws = null
|
|
174
|
+
try { ws = wctx?.webServer } catch (_) { ws = null }
|
|
175
|
+
if (!ws || typeof ws.register !== 'function') return () => {}
|
|
176
|
+
|
|
177
|
+
const unregs = []
|
|
178
|
+
|
|
179
|
+
// 1. Status endpoint: GET /api/dsh-russian-lang/translator/status
|
|
180
|
+
unregs.push(
|
|
181
|
+
ws.register({
|
|
182
|
+
kind: 'exact',
|
|
183
|
+
path: '/api/dsh-russian-lang/translator/status',
|
|
184
|
+
handler: async (request, response) => {
|
|
185
|
+
try {
|
|
186
|
+
if (request.method !== 'GET') {
|
|
187
|
+
response.writeHead(405, { 'content-type': 'application/json' })
|
|
188
|
+
response.end(JSON.stringify({ error: 'Method not allowed' }))
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
const status = await getTranslatorStatus(settingsCtx)
|
|
192
|
+
response.writeHead(200, {
|
|
193
|
+
'content-type': 'application/json; charset=utf-8',
|
|
194
|
+
'cache-control': 'no-store'
|
|
195
|
+
})
|
|
196
|
+
response.end(JSON.stringify(status))
|
|
197
|
+
} catch (err) {
|
|
198
|
+
response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
|
|
199
|
+
response.end(JSON.stringify({ error: String(err) }))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
// 2. Setup endpoint: POST /api/dsh-russian-lang/translator/setup
|
|
206
|
+
unregs.push(
|
|
207
|
+
ws.register({
|
|
208
|
+
kind: 'exact',
|
|
209
|
+
path: '/api/dsh-russian-lang/translator/setup',
|
|
210
|
+
handler: async (request, response) => {
|
|
211
|
+
try {
|
|
212
|
+
if (request.method !== 'POST') {
|
|
213
|
+
response.writeHead(405, { 'content-type': 'application/json' })
|
|
214
|
+
response.end(JSON.stringify({ error: 'Method not allowed' }))
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
const result = await setupLibreTranslateContainer()
|
|
218
|
+
response.writeHead(200, {
|
|
219
|
+
'content-type': 'application/json; charset=utf-8',
|
|
220
|
+
'cache-control': 'no-store'
|
|
221
|
+
})
|
|
222
|
+
response.end(JSON.stringify(result))
|
|
223
|
+
} catch (err) {
|
|
224
|
+
response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
|
|
225
|
+
response.end(JSON.stringify({ error: String(err) }))
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
// 3. Translation gateway: POST /api/dsh-russian-lang/translate
|
|
232
|
+
unregs.push(
|
|
233
|
+
ws.register({
|
|
234
|
+
kind: 'exact',
|
|
235
|
+
path: '/api/dsh-russian-lang/translate',
|
|
236
|
+
handler: async (request, response) => {
|
|
237
|
+
try {
|
|
238
|
+
if (request.method !== 'POST') {
|
|
239
|
+
response.writeHead(405, { 'content-type': 'application/json' })
|
|
240
|
+
response.end(JSON.stringify({ error: 'Method not allowed' }))
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const body = await readJsonBody(request)
|
|
245
|
+
const text = body.text
|
|
246
|
+
const target = body.target || 'ru'
|
|
247
|
+
|
|
248
|
+
if (!text || typeof text !== 'string') {
|
|
249
|
+
response.writeHead(400, { 'content-type': 'application/json; charset=utf-8' })
|
|
250
|
+
response.end(JSON.stringify({ error: 'Параметр "text" обязателен для перевода.' }))
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const settings = settingsCtx?.settings?.get('russian-lang') || {}
|
|
255
|
+
const engine = settings.translateEngine || 'off'
|
|
256
|
+
const localUrl = settings.localApiUrl || 'http://127.0.0.1:5000'
|
|
257
|
+
|
|
258
|
+
if (engine === 'off') {
|
|
259
|
+
response.writeHead(403, { 'content-type': 'application/json; charset=utf-8' })
|
|
260
|
+
response.end(JSON.stringify({
|
|
261
|
+
error: 'Машинный перевод отключен в настройках плагина. Включите локальный сервер (LibreTranslate) или Google Translate в настройках dsh-russian-lang.'
|
|
262
|
+
}))
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let translatedText = ''
|
|
267
|
+
if (engine === 'local') {
|
|
268
|
+
try {
|
|
269
|
+
translatedText = await translateLibre(text, target, localUrl)
|
|
270
|
+
} catch (err) {
|
|
271
|
+
response.writeHead(503, { 'content-type': 'application/json; charset=utf-8' })
|
|
272
|
+
response.end(JSON.stringify({
|
|
273
|
+
error: 'Локальный переводчик LibreTranslate недоступен (' + err.message + '). Проверьте состояние контейнера в настройках плагина.'
|
|
274
|
+
}))
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
} else if (engine === 'google') {
|
|
278
|
+
translatedText = await translateGoogle(text, target)
|
|
279
|
+
} else {
|
|
280
|
+
response.writeHead(400, { 'content-type': 'application/json; charset=utf-8' })
|
|
281
|
+
response.end(JSON.stringify({ error: 'Неизвестный движок перевода: ' + engine }))
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
response.writeHead(200, {
|
|
286
|
+
'content-type': 'application/json; charset=utf-8',
|
|
287
|
+
'cache-control': 'no-store'
|
|
288
|
+
})
|
|
289
|
+
response.end(JSON.stringify({ translatedText, engine }))
|
|
290
|
+
} catch (err) {
|
|
291
|
+
response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
|
|
292
|
+
response.end(JSON.stringify({ error: String(err) }))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return () => {
|
|
299
|
+
for (const unreg of unregs) {
|
|
300
|
+
try { unreg() } catch (_) {}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-russian-lang",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "Russian localization for DeepSeek Harness (DSH v0.1.6-alpha.1+) web UI: adds Русский as native language option. 100.0% coverage across 11,295 keys (127 namespaces). Features full DSH v0.1.6-alpha.1 core support (terminal, archive, command palette, Auto review, trajectory, PTC/Creator presets) and complete GoodAndReady plugin catalog (subscriptions, tts, image-gen, voice, key-limits, notify, commandcode, plugin-console).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"cordis.patch.yml",
|
|
16
16
|
"README.md",
|
|
17
17
|
"README.ru.md",
|
|
18
|
-
"README.zh.md",
|
|
19
18
|
"LICENSE"
|
|
20
19
|
],
|
|
21
20
|
"dsh": {
|
package/README.zh.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# 📦 @goodandready/dsh-russian-lang
|
|
2
|
-
|
|
3
|
-
<div align="center">
|
|
4
|
-
|
|
5
|
-
<h3>DeepSeek Harness 完整俄语本地化、智能排版与键盘布局自动修复插件</h3>
|
|
6
|
-
|
|
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.20-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="npm version"></a>
|
|
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
|
-
<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
|
-
<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>
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
<p align="center">
|
|
15
|
-
<img src="https://img.shields.io/badge/DSH-v0.1.5--rc.2%2B-blue.svg?style=for-the-badge&labelColor=1e1b4b" alt="DSH v0.1.6-alpha.1+">
|
|
16
|
-
<img src="https://img.shields.io/badge/覆盖率-100%25-10b981.svg?style=for-the-badge&labelColor=064e3b" alt="覆盖率 100%">
|
|
17
|
-
<img src="https://img.shields.io/badge/UI词条-11295-6366f1.svg?style=for-the-badge&labelColor=1e1b4b" alt="11295 词条">
|
|
18
|
-
<img src="https://img.shields.io/badge/人工校验-100%25-0ea5e9.svg?style=for-the-badge&labelColor=082f49" alt="100% 人工校验">
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
<p align="center">
|
|
22
|
-
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者所有项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="作者所有项目"></a>
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
|
-
<table align="center">
|
|
26
|
-
<tr>
|
|
27
|
-
<td align="center">
|
|
28
|
-
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上点个 Star</strong> —— 这能让我知道该插件对您有所帮助,并激励我持续维护和演进。
|
|
29
|
-
<br><br>
|
|
30
|
-
🐛 <strong>如果您发现了 Bug 或有任何建议</strong>,欢迎随时在 GitHub 提交 Issue(支持中文、俄文或英文)。
|
|
31
|
-
</td>
|
|
32
|
-
</tr>
|
|
33
|
-
</table>
|
|
34
|
-
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## ⚡ 概述
|
|
40
|
-
|
|
41
|
-
**`dsh-russian-lang`** 是面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) Web 界面的全功能俄语本地化增强插件。
|
|
42
|
-
|
|
43
|
-
本插件不仅覆盖 DSH 核心及社区 70+ 个主流扩展插件的俄语翻译,更针对俄语用户深度定制了智能化人机交互特性:**100% 专家人工审校的技术术语**、**俄语复数语法形式(Plural)**、**屏幕排版自动规范化**(书名号《》、长破折号、不换行空格),以及通过快捷键 <kbd>Alt+L</kbd> 实现的**错误键盘布局文本一键纠正**。
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## ✨ 核心特性
|
|
48
|
-
|
|
49
|
-
### 1. 🌐 100% 覆盖 DSH 核心 (45+ 命名空间,1780+ 词条)
|
|
50
|
-
完全覆盖终端、会话归档、命令面板、工作区、右侧边栏、模型设置等所有 DSH 原生功能模块。
|
|
51
|
-
|
|
52
|
-
### 2. 🧩 内置 70+ 社区插件模块化翻译 (9500+ 词条)
|
|
53
|
-
采用按需模块化分发架构(单文件 < 50 KiB),包含市场、看板、定时任务、语音 TTS、模型限额、会话控制等全部热门扩展插件。
|
|
54
|
-
|
|
55
|
-
### 3. 🔢 俄语复数语法精确匹配 (Plural)
|
|
56
|
-
自动计算俄语特有的数词变格规则(`one`、`few`、`many`),彻底避免机械英汉翻译中的语法违和感。
|
|
57
|
-
|
|
58
|
-
### 4. ✍️ 智能输入排版增强 (`russian-lang.typography`)
|
|
59
|
-
在输入和渲染时规范排版格式,智能识别单字母连词并在其后插入不换行空格,且绝对保留 Markdown 代码块和行内代码语法不变。
|
|
60
|
-
|
|
61
|
-
### 5. ⌨️ 误打布局实时纠正 (<kbd>Alt+L</kbd>)
|
|
62
|
-
实时检测英俄误打输入(例如错输入 `ghbdtn` 自动识别为 `привет`),按快捷键即可就地无缝纠正。
|
|
63
|
-
|
|
64
|
-
### 6. ⚙️ 自定义键值覆写 (`russian-lang.overrides`)
|
|
65
|
-
支持在 **设置 → 插件 → 俄语本地化** 中自定义特定术语翻译,无需修改任何源代码。
|
|
66
|
-
|
|
67
|
-
### 7. 🔤 DSH 原生架构集成
|
|
68
|
-
原生注册至 **设置 → 通用 → 语言** 菜单,无缝切换 `<html lang="ru-RU">` 并自动激活浏览器拼写检查。
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## 📦 安装方法
|
|
73
|
-
|
|
74
|
-
在终端运行以下命令:
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
dsh plugin --profile web add @goodandready/dsh-russian-lang
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
安装完成后,打开 DSH 网页端,前往 **设置 (Settings) → 通用 (General) → 语言 (Language)**,选择 **«Русский»** 即可。
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## ⚙️ 配置说明 (`settings.yaml`)
|
|
85
|
-
|
|
86
|
-
配置命名空间为 `russian-lang`,所有选项均为可选字段:
|
|
87
|
-
|
|
88
|
-
```yaml
|
|
89
|
-
russian-lang:
|
|
90
|
-
enabled: true # 启用俄语界面
|
|
91
|
-
typography:
|
|
92
|
-
enabled: true # 启用智能排版(引号、破折号、不换行空格)
|
|
93
|
-
yo: false # 自动还原字母 «ё»(默认关闭)
|
|
94
|
-
agentPrompt: false # 提示智能体优先使用俄语回复
|
|
95
|
-
overrides: {} # 针对任意词条的自定义翻译映射
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## 📄 开源许可
|
|
101
|
-
|
|
102
|
-
MIT License © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|