@goodandready/dsh-russian-lang 0.3.8 → 0.3.10
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 -1
- package/lib/client.js +190 -464
- package/lib/client.template.js +56 -152
- package/lib/locales/core.json +1 -1
- package/lib/pure.js +6 -11
- package/lib/translator.js +51 -20
- package/package.json +1 -1
package/lib/pure.js
CHANGED
|
@@ -468,23 +468,18 @@ const SHORT_WORDS_RE = /(^|[\s(«])([вВнНсСпПоОкКуУиИаА]|из|
|
|
|
468
468
|
*/
|
|
469
469
|
export const formatInputLive = (text) => {
|
|
470
470
|
if (!text || typeof text !== 'string') return text
|
|
471
|
-
|
|
472
|
-
// Разбиваем по блокам кода (inline `...` или fenced ```...```), чтобы не портить синтаксис
|
|
473
471
|
const parts = text.split(/(```[\s\S]*?```|`[^`\n]*`)/g)
|
|
474
472
|
for (let i = 0; i < parts.length; i += 2) {
|
|
475
473
|
let s = parts[i]
|
|
476
474
|
if (!s) continue
|
|
477
|
-
|
|
478
|
-
|
|
475
|
+
const lq = [], cf = []
|
|
476
|
+
s = s.replace(/"([^"\n]*[a-zA-Z][^"\n]*)"/g, (m) => `\x01L${lq.push(m) - 1}\x01`)
|
|
477
|
+
s = s.replace(/(^|\s)(--[a-zA-Z0-9_\u0400-\u04FF-]+)/g, (m, p1, p2) => `${p1}\x01C${cf.push(p2) - 1}\x01`)
|
|
478
|
+
s = s.replace(/(^|\s)(--)(?=\s|$)/g, (m, p1, p2) => /[a-zA-Z]/.test(text) ? `${p1}\x01C${cf.push(p2) - 1}\x01` : m)
|
|
479
479
|
s = s.replace(/--/g, '—')
|
|
480
|
-
|
|
481
|
-
// 3. Кавычки-ёлочки («...») с учётом открывающейся/закрывающейся
|
|
482
|
-
s = s.replace(/(^|[\s([{-])"/g, '$1«')
|
|
483
|
-
s = s.replace(/"/g, '»')
|
|
484
|
-
|
|
485
|
-
// 4. Неразрывный пробел после коротких слов/предлогов
|
|
480
|
+
s = s.replace(/(^|[\s([{-])"/g, '$1«').replace(/"/g, '»')
|
|
486
481
|
s = s.replace(SHORT_WORDS_RE, '$1$2\u00A0')
|
|
487
|
-
|
|
482
|
+
s = s.replace(/\x01C(\d+)\x01/g, (_, j) => cf[+j]).replace(/\x01L(\d+)\x01/g, (_, j) => lq[+j])
|
|
488
483
|
parts[i] = s
|
|
489
484
|
}
|
|
490
485
|
return parts.join('')
|
package/lib/translator.js
CHANGED
|
@@ -14,23 +14,53 @@ function execPromise(cmd, args, timeoutMs = 15000) {
|
|
|
14
14
|
})
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function readJsonBody(request, limit = 500_000) {
|
|
17
|
+
export function readJsonBody(request, limit = 500_000) {
|
|
18
18
|
return new Promise((resolve, reject) => {
|
|
19
|
+
// 1. Ранняя проверка Content-Length
|
|
20
|
+
const contentLength = request?.headers?.['content-length']
|
|
21
|
+
if (contentLength && parseInt(contentLength, 10) > limit) {
|
|
22
|
+
const err = new Error('Payload too large')
|
|
23
|
+
err.statusCode = 413
|
|
24
|
+
return reject(err)
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
let body = ''
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
let bytesReceived = 0
|
|
29
|
+
let settled = false
|
|
30
|
+
|
|
31
|
+
const onData = (chunk) => {
|
|
32
|
+
if (settled) return
|
|
33
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))
|
|
34
|
+
bytesReceived += buf.length
|
|
35
|
+
if (bytesReceived > limit) {
|
|
36
|
+
settled = true
|
|
37
|
+
if (typeof request.removeListener === 'function') request.removeListener('data', onData)
|
|
38
|
+
if (typeof request.destroy === 'function') request.destroy()
|
|
39
|
+
const err = new Error('Payload too large')
|
|
40
|
+
err.statusCode = 413
|
|
41
|
+
return reject(err)
|
|
24
42
|
}
|
|
25
|
-
|
|
43
|
+
body += buf.toString('utf8')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
request.on('data', onData)
|
|
47
|
+
|
|
26
48
|
request.on('end', () => {
|
|
49
|
+
if (settled) return
|
|
50
|
+
settled = true
|
|
27
51
|
try {
|
|
28
52
|
resolve(body ? JSON.parse(body) : {})
|
|
29
53
|
} catch (err) {
|
|
54
|
+
err.statusCode = 400
|
|
30
55
|
reject(err)
|
|
31
56
|
}
|
|
32
57
|
})
|
|
33
|
-
|
|
58
|
+
|
|
59
|
+
request.on('error', (err) => {
|
|
60
|
+
if (settled) return
|
|
61
|
+
settled = true
|
|
62
|
+
reject(err)
|
|
63
|
+
})
|
|
34
64
|
})
|
|
35
65
|
}
|
|
36
66
|
|
|
@@ -54,7 +84,6 @@ export const TRANSLATOR_HEADER = 'x-dsh-translator'
|
|
|
54
84
|
|
|
55
85
|
export function isTrustedTranslatorRequest(request) {
|
|
56
86
|
if (header(request, TRANSLATOR_HEADER) !== '1') return false
|
|
57
|
-
if (!isLoopback(request?.socket?.remoteAddress)) return false
|
|
58
87
|
const site = header(request, 'sec-fetch-site')
|
|
59
88
|
if (site !== undefined && site !== 'same-origin') return false
|
|
60
89
|
const origin = header(request, 'origin')
|
|
@@ -62,11 +91,10 @@ export function isTrustedTranslatorRequest(request) {
|
|
|
62
91
|
if (origin === undefined || host === undefined) return false
|
|
63
92
|
try {
|
|
64
93
|
const url = new URL(origin)
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
94
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false
|
|
95
|
+
// Same-origin check: Origin host must match Host header.
|
|
96
|
+
// Accepts loopback, LAN, and Tailscale addresses alike.
|
|
97
|
+
return url.host === host
|
|
70
98
|
} catch {
|
|
71
99
|
return false
|
|
72
100
|
}
|
|
@@ -136,7 +164,7 @@ export async function setupLibreTranslateContainer() {
|
|
|
136
164
|
'--name', 'libretranslate',
|
|
137
165
|
'--restart', 'unless-stopped',
|
|
138
166
|
'-p', '127.0.0.1:5000:5000',
|
|
139
|
-
'libretranslate/libretranslate',
|
|
167
|
+
'libretranslate/libretranslate:v1.5.3',
|
|
140
168
|
'--load-only', 'en,ru,zh'
|
|
141
169
|
], 30000)
|
|
142
170
|
return { ok: true, action: 'created', message: 'Контейнер libretranslate создан и запускается (~600 МБ RAM).' }
|
|
@@ -234,8 +262,9 @@ export function registerTranslator(wctx, settingsCtx) {
|
|
|
234
262
|
})
|
|
235
263
|
response.end(JSON.stringify(status))
|
|
236
264
|
} catch (err) {
|
|
237
|
-
|
|
238
|
-
response.
|
|
265
|
+
const status = err && err.statusCode ? err.statusCode : 500
|
|
266
|
+
response.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
|
267
|
+
response.end(JSON.stringify({ error: err && err.message ? err.message : String(err) }))
|
|
239
268
|
}
|
|
240
269
|
}
|
|
241
270
|
})
|
|
@@ -268,8 +297,9 @@ export function registerTranslator(wctx, settingsCtx) {
|
|
|
268
297
|
})
|
|
269
298
|
response.end(JSON.stringify(result))
|
|
270
299
|
} catch (err) {
|
|
271
|
-
|
|
272
|
-
response.
|
|
300
|
+
const status = err && err.statusCode ? err.statusCode : 500
|
|
301
|
+
response.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
|
302
|
+
response.end(JSON.stringify({ error: err && err.message ? err.message : String(err) }))
|
|
273
303
|
}
|
|
274
304
|
}
|
|
275
305
|
})
|
|
@@ -343,8 +373,9 @@ export function registerTranslator(wctx, settingsCtx) {
|
|
|
343
373
|
})
|
|
344
374
|
response.end(JSON.stringify({ translatedText, engine }))
|
|
345
375
|
} catch (err) {
|
|
346
|
-
|
|
347
|
-
response.
|
|
376
|
+
const status = err && err.statusCode ? err.statusCode : 500
|
|
377
|
+
response.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
|
378
|
+
response.end(JSON.stringify({ error: err && err.message ? err.message : String(err) }))
|
|
348
379
|
}
|
|
349
380
|
}
|
|
350
381
|
})
|
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.10",
|
|
4
4
|
"description": "Russian localization for DeepSeek Harness (DSH v0.1.7-rc.1+) web UI: adds Русский as native language option. 100.0% coverage across 11,295 keys (127 namespaces). Features full DSH v0.1.7-rc.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",
|