From c0ebc646b862829294439b66bd0769a9727fce43 Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 1 Apr 2026 00:27:47 +0200 Subject: [PATCH] some fixes on JSON translation. see #247 --- js/mzta-compose-script.js | 114 +++++++++++++++++++++++++++----------- js/mzta-storage.js | 9 ++- mzta-background.js | 21 +++++-- 3 files changed, 106 insertions(+), 38 deletions(-) diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index a3183507..b60d006b 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -177,6 +177,21 @@ function _updatePanelMargins() { if (lastPanel) lastPanel.style.marginBottom = '1rem'; } +function _isHtml(text) { + return /<[a-z][^>]*>/i.test(text); +} + +function _renderSafeHtml(container, html) { + container.textContent = ''; + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + doc.querySelectorAll('script, img').forEach(el => el.remove()); + while (doc.body.firstChild) { + container.appendChild(doc.body.firstChild); + } + container.querySelectorAll('p').forEach(p => { p.style.marginBlockStart = '0'; }); +} + function createThreeDotsMenu(isDark, menuItems, panelColors) { const wrapper = document.createElement('div'); wrapper.style.cssText = 'position: relative; display: inline-flex; align-items: center;'; @@ -1264,54 +1279,89 @@ switch (message.command) { subjectEl.textContent = translationData.translated_subject; translationTextWrapper.appendChild(subjectEl); } - translationText.textContent = translationData.translated_text || ''; + const bodyText = translationData.translated_text || ''; + const bodyIsHtml = _isHtml(bodyText); + if (bodyIsHtml) { + translationText.style.whiteSpace = ''; + _renderSafeHtml(translationText, bodyText); + } else { + translationText.textContent = bodyText; + } } translationTextWrapper.appendChild(translationText); const maxLenTranslation = translationData.maxDisplayLength || 0; const fullTranslationText = translationData.translated_text || ''; + const fullTranslationIsHtml = _isHtml(fullTranslationText); if (!translationData.error && translationData.translation_status !== '-1' && maxLenTranslation > 0 && fullTranslationText.length > maxLenTranslation) { translationText.style.overflow = 'hidden'; translationText.style.transition = 'max-height 0.2s ease'; - let cutPos = fullTranslationText.lastIndexOf(' ', maxLenTranslation); - if (cutPos <= 0) cutPos = maxLenTranslation; - const truncatedTranslation = fullTranslationText.substring(0, cutPos) + '\u2026'; - translationText.textContent = truncatedTranslation; - - requestAnimationFrame(() => { - translationText.style.maxHeight = translationText.scrollHeight + 'px'; - }); - const toggleLink = document.createElement('a'); toggleLink.textContent = browser.i18n.getMessage("translate_see_more") || "See more"; toggleLink.href = '#'; toggleLink.style.cssText = `display: inline-block; margin-top: 4px; font-size: 13px; color: ${colors.linkColor}; cursor: pointer; text-decoration: underline;`; - let expanded = false; - toggleLink.addEventListener('click', (e) => { - e.preventDefault(); - if (!expanded) { - translationText.textContent = fullTranslationText; + if (fullTranslationIsHtml) { + const collapsedMaxHeight = '4.2em'; + translationText.style.maxHeight = collapsedMaxHeight; + + let expanded = false; + toggleLink.addEventListener('click', (e) => { + e.preventDefault(); + if (!expanded) { + translationText.style.maxHeight = translationText.scrollHeight + 'px'; + toggleLink.textContent = browser.i18n.getMessage("translate_see_less") || "See less"; + } else { + translationText.style.maxHeight = collapsedMaxHeight; + toggleLink.textContent = browser.i18n.getMessage("translate_see_more") || "See more"; + } + expanded = !expanded; + }); + + requestAnimationFrame(() => { + if (translationText.scrollHeight > translationText.clientHeight) { + translationTextWrapper.appendChild(toggleLink); + } else { + translationText.style.maxHeight = ''; + translationText.style.overflow = ''; + } + }); + } else { + let cutPos = fullTranslationText.lastIndexOf(' ', maxLenTranslation); + if (cutPos <= 0) cutPos = maxLenTranslation; + const truncatedTranslation = fullTranslationText.substring(0, cutPos) + '\u2026'; + translationText.textContent = truncatedTranslation; + + requestAnimationFrame(() => { translationText.style.maxHeight = translationText.scrollHeight + 'px'; - toggleLink.textContent = browser.i18n.getMessage("translate_see_less") || "See less"; - } else { - translationText.textContent = truncatedTranslation; - const collapsedHeight = translationText.scrollHeight; - translationText.textContent = fullTranslationText; - translationText.style.maxHeight = translationText.scrollHeight + 'px'; - requestAnimationFrame(() => { - translationText.style.maxHeight = collapsedHeight + 'px'; - }); - translationText.addEventListener('transitionend', function handler() { - translationText.removeEventListener('transitionend', handler); + }); + + let expanded = false; + toggleLink.addEventListener('click', (e) => { + e.preventDefault(); + if (!expanded) { + translationText.textContent = fullTranslationText; + translationText.style.maxHeight = translationText.scrollHeight + 'px'; + toggleLink.textContent = browser.i18n.getMessage("translate_see_less") || "See less"; + } else { translationText.textContent = truncatedTranslation; - }); - toggleLink.textContent = browser.i18n.getMessage("translate_see_more") || "See more"; - } - expanded = !expanded; - }); - translationTextWrapper.appendChild(toggleLink); + const collapsedHeight = translationText.scrollHeight; + translationText.textContent = fullTranslationText; + translationText.style.maxHeight = translationText.scrollHeight + 'px'; + requestAnimationFrame(() => { + translationText.style.maxHeight = collapsedHeight + 'px'; + }); + translationText.addEventListener('transitionend', function handler() { + translationText.removeEventListener('transitionend', handler); + translationText.textContent = truncatedTranslation; + }); + toggleLink.textContent = browser.i18n.getMessage("translate_see_more") || "See more"; + } + expanded = !expanded; + }); + translationTextWrapper.appendChild(toggleLink); + } } translationContainer.appendChild(translationTextWrapper); diff --git a/js/mzta-storage.js b/js/mzta-storage.js index ba332f35..ce3bfa08 100644 --- a/js/mzta-storage.js +++ b/js/mzta-storage.js @@ -274,8 +274,13 @@ export class taStorage { /** * Write the translation field for a given Message-ID. * @param {string} messageId - The Message-ID header string. - * @param {string} translated_text - The translated text. - * @param {string} lang - Target language code. + * @param {Object} data - Translation data object. + * @param {string} [data.translated_text=''] - The translated body text. + * @param {string} [data.translated_subject=''] - The translated subject. + * @param {string} [data.translation_status=''] - Status: "1" = ok, "-1" = skipped. + * @param {string} [data.lang=''] - Target language code. + * @param {boolean} [data.error=false] - Whether an error occurred. + * @param {string} [data.message=''] - Error message. * @param {boolean} [force=true] - If true, overwrite existing translation data. */ async writeTranslation(messageId, data, force = true) { diff --git a/mzta-background.js b/mzta-background.js index a78699f7..5e9344b1 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -430,7 +430,18 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { case 'chatgpt_saveTranslation': async function _saveTranslationFromWebchat(msg) { try { - let translatedText = msg.text.trim(); + let rawText = msg.text.trim(); + let translatedBody = ''; + let translatedSubject = ''; + let translationStatus = ''; + try { + const parsed = JSON.parse(rawText); + translatedBody = parsed.body || ''; + translatedSubject = parsed.subject || ''; + translationStatus = String(parsed.status || ''); + } catch (e) { + translatedBody = rawText; + } let prefs_tr = await browser.storage.sync.get({ translate_lang: prefs_default.translate_lang, default_chatgpt_lang: prefs_default.default_chatgpt_lang, @@ -438,7 +449,9 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { }); let lang = prefs_tr.translate_lang || prefs_tr.default_chatgpt_lang || ''; const translationData = { - translated_text: translatedText, + translated_text: translatedBody, + translated_subject: translatedSubject, + translation_status: translationStatus, lang: lang, headerMessageId: msg.headerMessageId }; @@ -777,7 +790,7 @@ async function _generateTranslationForMessage(headerMessageId, tabId = null, opt taWorkingStatus.stopWorking(); return; } - const { promptText } = await taPromptUtils.buildTranslationPrompt(fullMessage, lang); + const { promptText } = await taPromptUtils.buildTranslationPrompt(fullMessage); const cmd = new mzta_specialCommand({ prompt: promptText, @@ -996,7 +1009,7 @@ async function _openTranslationWebchat(headerMessageId, tabId) { taLog.warn("Translation skipped: no language configured (translate_lang and default_chatgpt_lang are both empty)."); return; } - const { promptText, promptInfo } = await taPromptUtils.buildTranslationPrompt(curr_message_full, lang); + const { promptText, promptInfo } = await taPromptUtils.buildTranslationPrompt(curr_message_full); promptInfo.headerMessageId = headerMessageId; promptInfo.translationTabId = tabId;