@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/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
- // 2. Длинное тире (-- -> —)
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
- request.on('data', (chunk) => {
21
- body += chunk
22
- if (body.length > limit) {
23
- reject(new Error('Payload too large'))
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
- request.on('error', reject)
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
- (url.protocol === 'http:' || url.protocol === 'https:') &&
67
- isLoopback(url.hostname) &&
68
- url.host === host
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
- response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
238
- response.end(JSON.stringify({ error: String(err) }))
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
- response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
272
- response.end(JSON.stringify({ error: String(err) }))
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
- response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
347
- response.end(JSON.stringify({ error: String(err) }))
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.8",
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",