diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 12bdb601..2e9377f2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -821,7 +821,7 @@ "description": "" }, "prompt_translate_this_full_text": { - "message": "Translate the following email in", + "message": "Translate the email below into {%thunderai_translate_lang%}.\n\nRules:\n- Translate both the subject and the body.\n- Return the result as a JSON object with three fields: \"subject\", \"body\" and \"status\".\n- If the translation has been done the status is equal to 1.\n- If the email is written in one of these languages \"{%thunderai_translate_exclude_lang%}\" or in the {%thunderai_translate_lang%} language, return an empty string for the body and the subject and set the status to -1.\n- Do not add explanations, notes, or any text outside the JSON.\n\nMail subject: {%mail_subject%}\n\nMail body: {%mail_html_body%}\n\nGenerate a response in JSON format only. The output should be only a JSON object. Here is an example of the JSON format to be used:\n{\n\"subject\": \"subject translation\",\n\"body\": \"body translation\",\n\"status\": \"status result\"\n}", "description": "" }, "prompt_this_full_text": { @@ -2189,6 +2189,10 @@ "message": "Translation language is not configured. Please set a language in the Translation settings or set a default language in the General settings.", "description": "" }, + "translate_skipped": { + "message": "Translation skipped: Language excluded or identical to target.", + "description": "Shown in the translation banner when the email language matches the excluded or target language" + }, "antispam_by": { "message": "Antispam by", "description": "" diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index 831e7654..a3183507 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -1255,14 +1255,22 @@ switch (message.command) { translationText.style.cssText = 'white-space: pre-wrap; line-height: 1.5;'; if (translationData.error) { translationText.textContent = translationData.message || browser.i18n.getMessage("translate_error"); + } else if (translationData.translation_status === '-1') { + translationText.textContent = browser.i18n.getMessage("translate_skipped"); } else { + if (translationData.translated_subject) { + const subjectEl = document.createElement('div'); + subjectEl.style.cssText = 'font-weight: bold; margin-bottom: 4px;'; + subjectEl.textContent = translationData.translated_subject; + translationTextWrapper.appendChild(subjectEl); + } translationText.textContent = translationData.translated_text || ''; } translationTextWrapper.appendChild(translationText); const maxLenTranslation = translationData.maxDisplayLength || 0; const fullTranslationText = translationData.translated_text || ''; - if (!translationData.error && maxLenTranslation > 0 && fullTranslationText.length > maxLenTranslation) { + if (!translationData.error && translationData.translation_status !== '-1' && maxLenTranslation > 0 && fullTranslationText.length > maxLenTranslation) { translationText.style.overflow = 'hidden'; translationText.style.transition = 'max-height 0.2s ease'; diff --git a/js/mzta-storage.js b/js/mzta-storage.js index 9b29c6b5..ba332f35 100644 --- a/js/mzta-storage.js +++ b/js/mzta-storage.js @@ -278,7 +278,15 @@ export class taStorage { * @param {string} lang - Target language code. * @param {boolean} [force=true] - If true, overwrite existing translation data. */ - async writeTranslation(messageId, translated_text, lang, force = true, error = false, error_message = '') { + async writeTranslation(messageId, data, force = true) { + const { + translated_text = '', + translated_subject = '', + translation_status = '', + lang = '', + error = false, + message = '', + } = data || {}; this.taLog.log('[writeTranslation] messageId: ' + messageId + ', lang: ' + lang + ', force: ' + force); try { let key = this._buildKey(messageId); @@ -288,7 +296,15 @@ export class taStorage { return; } let now = Date.now(); - record[taStorage.FIELD_TRANSLATION] = { translated_text: translated_text, lang: lang, error: error, message: error_message, ts: now }; + record[taStorage.FIELD_TRANSLATION] = { + translated_text, + translated_subject, + translation_status, + lang, + error, + message, + ts: now + }; record.ts = now; await messenger.storage.local.set({ [key]: record }); } catch (e) { diff --git a/js/mzta-translationstore.js b/js/mzta-translationstore.js index 4f3dbe48..1307596a 100644 --- a/js/mzta-translationstore.js +++ b/js/mzta-translationstore.js @@ -49,7 +49,7 @@ export class taTranslationStore { async saveTranslation(data, data_id) { this.taLog.log("[saveTranslation] data_id: " + data_id); try { - await this._storage.writeTranslation(data_id, data.translated_text || '', data.lang || '', true, data.error || false, data.message || ''); + await this._storage.writeTranslation(data_id, data, true); await browser.storage.session.remove(this._processing_prefix + data_id); } catch (e) { this.taLog.error("[saveTranslation] error: " + e); @@ -81,6 +81,8 @@ export class taTranslationStore { return { headerMessageId: data_id, translated_text: translation.translated_text || '', + translated_subject: translation.translated_subject || '', + translation_status: translation.translation_status || '', lang: translation.lang || '', error: translation.error || false, message: translation.message || '', diff --git a/js/mzta-utils-prompt.js b/js/mzta-utils-prompt.js index b123ff29..a100e265 100644 --- a/js/mzta-utils-prompt.js +++ b/js/mzta-utils-prompt.js @@ -173,7 +173,7 @@ export const taPromptUtils = { return { promptText, promptInfo: prompt }; }, - async buildTranslationPrompt(fullMessage, lang) { + async buildTranslationPrompt(fullMessage) { const specialPrompts = await getSpecialPrompts(); const prompt = specialPrompts.find(p => p.id === 'prompt_translate_this'); @@ -183,12 +183,19 @@ export const taPromptUtils = { } const bodyHtml = getMailBody(fullMessage); - let bodyText = htmlBodyToPlainText(bodyHtml.html); - if (bodyText.length === 0) { - bodyText = bodyHtml.text || ''; - } + const mailSubject = fullMessage.headers?.subject?.[0] || ''; - const fullPrompt = promptText + " " + lang + ". \"" + bodyText + "\""; + const finalSubs = await placeholdersUtils.getPlaceholdersValues({ + prompt_text: promptText, + msg_text: { html: bodyHtml.html, text: bodyHtml.text }, + mail_subject: mailSubject, + }); + + const fullPrompt = placeholdersUtils.replacePlaceholders({ + text: promptText, + replacements: finalSubs, + use_default_value: false, + }); return { promptText: fullPrompt, promptInfo: prompt }; }, diff --git a/mzta-background.js b/mzta-background.js index ffb87c08..a78699f7 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -789,8 +789,22 @@ async function _generateTranslationForMessage(headerMessageId, tabId = null, opt await cmd.initWorker(); const aiResponse = await cmd.sendPrompt(); + let translatedBody = ''; + let translatedSubject = ''; + let translationStatus = ''; + try { + const parsed = JSON.parse(aiResponse); + translatedBody = parsed.body || ''; + translatedSubject = parsed.subject || ''; + translationStatus = String(parsed.status || ''); + } catch (e) { + translatedBody = aiResponse; + } + const translationData = { - translated_text: aiResponse, + translated_text: translatedBody, + translated_subject: translatedSubject, + translation_status: translationStatus, lang: lang, headerMessageId: headerMessageId };