opencode-translate 0.0.11 → 0.0.12

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/package.json +1 -1
  2. package/src/activation.ts +33 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-translate",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "OpenCode plugin that lets the user chat in a configured source language while the main chat loop only sees English.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/activation.ts CHANGED
@@ -355,26 +355,27 @@ export function createHooks(ctx: PluginInput, rawOptions: PluginOptions = {}, de
355
355
  })
356
356
 
357
357
  const sourceHash = hashText(part.text)
358
+ // Compute metadata against the ORIGINAL `part.text` so the
359
+ // stored `translate_source_hash` keeps a stable round-trip
360
+ // identity even after we splice the preview into the part
361
+ // text below.
358
362
  part.metadata = {
359
363
  ...(part.metadata ?? {}),
360
364
  ...mergeTranslatedMetadata(activeState, part, english),
361
365
  }
362
- // Hide the user's source-language text from the LLM. The TUI
363
- // still renders it because `ignored:true` only affects the
364
- // user-side LLM serializer (`message-v2.ts:773`).
366
+ // Inline the `→ EN: ...` preview into the source-language
367
+ // part. OpenCode's `UserMessageDisplay`
368
+ // (packages/ui/src/components/message-part.tsx) renders only
369
+ // ONE text part per user message — the first non-synthetic —
370
+ // so a sibling `synthetic:false + ignored:true` preview part
371
+ // would never reach the screen. Combining them here keeps
372
+ // the original visible alongside the English twin while
373
+ // `ignored:true` still hides the whole thing from the LLM
374
+ // serializer (`message-v2.ts:773`); the LLM-only synthetic
375
+ // twin below carries the clean English prompt.
376
+ part.text = `${part.text}\n\n_→ EN: ${english}_`
365
377
  part.ignored = true
366
378
 
367
- // UI-only preview so the user can verify the translation that
368
- // was sent to the LLM.
369
- nextParts.push(
370
- createSyntheticTextPart(part.sessionID, part.messageID, `→ EN: ${english}`, {
371
- translate_role: "translation_preview",
372
- translate_nonce: activeState.translate_nonce,
373
- translate_source_hash: sourceHash,
374
- translate_part_index: currentEligibleIndex,
375
- }),
376
- )
377
-
378
379
  // LLM-only English twin. This is the actual prompt the model
379
380
  // sees in place of the now-`ignored` source-language part.
380
381
  nextParts.push(
@@ -386,22 +387,27 @@ export function createHooks(ctx: PluginInput, rawOptions: PluginOptions = {}, de
386
387
  }),
387
388
  )
388
389
  } catch (error) {
389
- // Fall back to sending the original text to the LLM so the user
390
- // still gets a response. Surface the error as a synthetic part.
390
+ // Translation failed. Append a visible warning to the
391
+ // source-language part (same UI constraint as the success
392
+ // path: only one text part per user message renders) and
393
+ // route the original, untranslated text to the LLM via a
394
+ // dedicated fallback twin so the model still gets a clean
395
+ // prompt without the warning text leaking into context.
391
396
  translationErrors.push({ part, error })
392
- const wrapped = buildInboundTranslationError(activeState.translate_source_lang, normalizeReason(error))
397
+ const reason = normalizeReason(error)
398
+ const wrapped = buildInboundTranslationError(activeState.translate_source_lang, reason)
393
399
  await logError(client, wrapped)
400
+
401
+ const originalText = part.text
402
+ part.text = `${originalText}\n\n_⚠️ Translation failed: ${reason}. Original text was sent to the model._`
403
+ part.ignored = true
404
+
394
405
  nextParts.push(
395
- createSyntheticTextPart(
396
- part.sessionID,
397
- part.messageID,
398
- `⚠️ Translation failed: ${normalizeReason(error)}. Original text will be sent to the model.`,
399
- {
400
- translate_role: "translation_failure",
401
- translate_nonce: activeState.translate_nonce,
402
- translate_part_index: currentEligibleIndex,
403
- },
404
- ),
406
+ createLlmOnlyTextPart(part.sessionID, part.messageID, originalText, {
407
+ translate_role: "llm_only_fallback",
408
+ translate_nonce: activeState.translate_nonce,
409
+ translate_part_index: currentEligibleIndex,
410
+ }),
405
411
  )
406
412
  }
407
413
  }