dsh-think-translate 1.0.5 → 1.0.6

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 (2) hide show
  1. package/lib/index.js +22 -12
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -247,7 +247,7 @@ async function viaBing(text, to) {
247
247
  // common Chinese words untranslated (这个/方案/关键/针对...). Giving explicit
248
248
  // mappings (zh -> ja) turns near-miss output into clean translations; the
249
249
  // "never leave source words" rule applies to every target language.
250
- const OPENAI_SYSTEM = 'You are a translation engine. Translate the user text into the requested target language. Output ONLY the translation — no explanations, no quotes, no trailing commentary. Never keep a source-language word untranslated in the output. Common mappings (Chinese -> Japanese): 这个 -> この; 那个 -> その; 方案 -> 案/プラン; 关键 -> 要点/鍵; 针对 -> 対して; 进行 -> 行う; 通过 -> 通じて; 以及 -> および; 对于 -> に対して; 需要 -> 必要がある; 我们 -> 私たち; 可以 -> できる; 问题 -> 問題; 情况 -> 状況; 内容 -> 内容; 相关 -> 関連; 以下 -> 以下; 上述 -> 前述; 当前 -> 現在; 整体 -> 全体; 主要 -> 主に; 具体 -> 具体的に; 方面 -> 面/点; 部分 -> 部分; 实现 -> 実装/実現; 使用 -> 使用する; 提供 -> 提供する; 支持 -> サポートする; 处理 -> 処理する; 解决 -> 解決する; 提高 -> 向上させる; 降低 -> 低下させる; 增加 -> 増加させる; 减少 -> 減らす. Keep code blocks, commands, file paths, URLs, and identifiers verbatim.'
250
+ const OPENAI_SYSTEM = 'You are a translation engine. Translate the user text into the requested target language. Output ONLY the translation — no explanations, no quotes, no trailing commentary. Never keep a source-language word untranslated in the output. Common mappings (Chinese -> Japanese): 然后 -> その後/そして; 所以 -> だから; 因为 -> なぜなら; 因此 -> そのため; 这个 -> この; 那个 -> その; 方案 -> 案/プラン; 关键 -> 要点/鍵; 针对 -> 対して; 进行 -> 行う; 通过 -> 通じて; 以及 -> および; 对于 -> に対して; 需要 -> 必要がある; 我们 -> 私たち; 可以 -> できる; 问题 -> 問題; 情况 -> 状況; 内容 -> 内容; 相关 -> 関連; 以下 -> 以下; 上述 -> 前述; 当前 -> 現在; 整体 -> 全体; 主要 -> 主に; 具体 -> 具体的に; 方面 -> 面/点; 部分 -> 部分; 实现 -> 実装/実現; 使用 -> 使用する; 提供 -> 提供する; 支持 -> サポートする; 处理 -> 処理する; 解决 -> 解決する; 提高 -> 向上させる; 降低 -> 低下させる; 增加 -> 増加させる; 减少 -> 減らす; 性能瓶颈 -> パフォーマンスのボトルネック; 优化方案 -> 最適化案; 优化 -> 最適化; 数据流 -> データフロー; 方向 -> 方向; 架构 -> アーキテクチャ; 模块 -> モジュール; 系统 -> システム; 结果 -> 結果; 评估 -> 評価する; 分析 -> 分析する; 提出 -> 提案する; 实施 -> 実施する; 选择 -> 選択する; 比较 -> 比較する. Keep code blocks, commands, file paths, URLs, and identifiers verbatim.'
251
251
 
252
252
  async function viaOpenAI(text, to, cfg) {
253
253
  if (!cfg.apiKey) throw new Error('openai: missing apiKey')
@@ -420,19 +420,27 @@ function needsTranslate(text, target) {
420
420
  }
421
421
  function chunkText(text) {
422
422
  if (text.length <= 1200) return [text]
423
- const sentences = text.match(/[\s\S]*?[.。!?!?;\n]|[\s\S]+$/g) || [text]
423
+ // Split on blank-line paragraphs first so each translated chunk keeps its
424
+ // own paragraph; join('\n\n') on the caller side restores the paragraph
425
+ // breaks that sentence-level chunking used to flatten (2026-08-26).
426
+ // A single oversized paragraph is sub-split on sentence boundaries.
427
+ const paras = text.split(/\n{2,}/).filter(function (p) { return p.trim() })
424
428
  const chunks = []
425
- let cur = ''
426
- for (const sn of sentences) {
427
- if (cur && cur.length + sn.length > 1200) { chunks.push(cur); cur = '' }
428
- if (sn.length > 1200) {
429
- for (let i = 0; i < sn.length; i += 1100) chunks.push(sn.slice(i, i + 1100))
430
- cur = ''
431
- } else {
432
- cur += sn
429
+ for (const para of paras) {
430
+ if (para.length <= 1200) { chunks.push(para); continue }
431
+ const sentences = para.match(/[\s\S]*?[.。!?!?;\n]|[\s\S]+$/g) || [para]
432
+ let cur = ''
433
+ for (const sn of sentences) {
434
+ if (cur && cur.length + sn.length > 1200) { chunks.push(cur); cur = '' }
435
+ if (sn.length > 1200) {
436
+ for (let i = 0; i < sn.length; i += 1100) chunks.push(sn.slice(i, i + 1100))
437
+ cur = ''
438
+ } else {
439
+ cur += sn
440
+ }
433
441
  }
442
+ if (cur.trim()) chunks.push(cur)
434
443
  }
435
- if (cur.trim()) chunks.push(cur)
436
444
  return chunks.length ? chunks : [text]
437
445
  }
438
446
 
@@ -452,7 +460,9 @@ async function translateViaChain(text, target, cfg) {
452
460
  state.lastError = null
453
461
  // Carry the concrete model name for openai (local Ollama) so the UI can
454
462
  // show e.g. "local model: qwen2.5:14b" instead of the bare provider id.
455
- return { ok: true, text: outs.join(''), provider: id, ...(id === 'openai' ? { model: cfg.providers.openai.model } : {}) }
463
+ // Join chunks with a blank line so paragraph structure survives
464
+ // chunking (sentence-level chunking used to collapse them).
465
+ return { ok: true, text: outs.join('\n\n'), provider: id, ...(id === 'openai' ? { model: cfg.providers.openai.model } : {}) }
456
466
  } catch (e) {
457
467
  lastErr = e
458
468
  if (id === 'bing') bingCache = null
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-think-translate",
3
3
  "description": "Display-layer translation for the DSH web app: thinking chain, task cards, and optional answer text, in 8 target languages (local Ollama / Google / Bing, fail-open)",
4
- "version": "1.0.5",
4
+ "version": "1.0.6",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {