From 06603e9dc7db71b5a3f6e58f38533c17dc012d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Thu, 2 Nov 2023 21:48:07 +0100 Subject: [PATCH] [FIX] mail: show translations for emails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Due to shadowRoot, emails were not properly translated. closes odoo/odoo#140847 Signed-off-by: Alexandre Kühn (aku) --- addons/mail/static/src/core/common/message.js | 33 ++++++++++++---- .../tests/translation/translation_test.js | 39 +++++++++++++++++++ addons/web/static/tests/utils.js | 5 ++- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/addons/mail/static/src/core/common/message.js b/addons/mail/static/src/core/common/message.js index 0b0d53e06ce..51f8587f34c 100644 --- a/addons/mail/static/src/core/common/message.js +++ b/addons/mail/static/src/core/common/message.js @@ -100,6 +100,8 @@ export class Message extends Component { emailHeaderOpen: false, showTranslation: false, }); + /** @type {ShadowRoot} */ + this.shadowRoot; this.root = useRef("root"); this.hasTouch = hasTouch; this.messageBody = useRef("body"); @@ -136,11 +138,7 @@ export class Message extends Component { this.prepareMessageBody(this.messageBody.el); } if (this.shadowBody.el) { - const shadowRoot = this.shadowBody.el.attachShadow({ mode: "open" }); - const body = document.createElement("span"); - body.innerHTML = - this.props.messageSearch?.highlight(this.message.body) ?? this.message.body; - this.prepareMessageBody(body); + this.shadowRoot = this.shadowBody.el.attachShadow({ mode: "open" }); const color = cookie.get("color_scheme") === "dark" ? "white" : "black"; const shadowStyle = document.createElement("style"); shadowStyle.innerHTML = ` @@ -159,11 +157,32 @@ export class Message extends Component { } `; if (cookie.get("color_scheme") === "dark") { - shadowRoot.appendChild(shadowStyle); + this.shadowRoot.appendChild(shadowStyle); } - shadowRoot.appendChild(body); } }); + useEffect( + () => { + if (this.shadowBody.el) { + const body = document.createElement("span"); + body.innerHTML = this.state.showTranslation + ? this.message.translationValue + : this.props.messageSearch?.highlight(this.message.body) ?? + this.message.body; + this.prepareMessageBody(body); + this.shadowRoot.appendChild(body); + return () => { + this.shadowRoot.removeChild(body); + }; + } + }, + () => [ + this.state.showTranslation, + this.message.translationValue, + this.props.messageSearch?.searchTerm, + this.message.body, + ] + ); } get attClass() { diff --git a/addons/mail/static/tests/translation/translation_test.js b/addons/mail/static/tests/translation/translation_test.js index fa2ed1db1fe..5d0067b0461 100644 --- a/addons/mail/static/tests/translation/translation_test.js +++ b/addons/mail/static/tests/translation/translation_test.js @@ -46,3 +46,42 @@ QUnit.test("Toggle display of original/translated version of chatter message", a // The translation button should not trigger more than one external request for a single message. assert.verifySteps(["Request"]); }); + +QUnit.test("translation of email message", async () => { + const pyEnv = await startServer(); + const partnerId = pyEnv["res.partner"].create({}); + pyEnv["mail.message"].create({ + model: "res.partner", + body: "Al mal tiempo, buena cara.", + message_type: "email", + author_id: partnerId, + res_id: partnerId, + }); + const { openFormView } = await start({ + mockRPC(route) { + if (route === "/mail/message/translate") { + return { body: "To bad weather, good face.", lang_name: "Spanish", error: null }; + } + }, + }); + await openFormView("res.partner", partnerId); + await contains("span", { + text: "Al mal tiempo, buena cara.", + parent: [".o-mail-Message-body > div", { shadowRoot: true }], + }); + await click("button[title='Expand']"); + await click("span[title='Translate']"); + await contains("span", { + text: "To bad weather, good face.", + parent: [".o-mail-Message-body > div", { shadowRoot: true }], + }); + await contains(".o-mail-Message-body", { + text: "(Translated from: Spanish)", + }); + await click("button[title='Expand']"); + await click("span[title='Revert']"); + await contains("span", { + text: "Al mal tiempo, buena cara.", + parent: [".o-mail-Message-body > div", { shadowRoot: true }], + }); +}); diff --git a/addons/web/static/tests/utils.js b/addons/web/static/tests/utils.js index 27b2b57e69c..f6eac54e638 100644 --- a/addons/web/static/tests/utils.js +++ b/addons/web/static/tests/utils.js @@ -226,6 +226,7 @@ if (window.QUnit) { * Note: when using one of the scrollTop options, it is advised to ensure the height is not going * to change soon, by checking with a preceding contains that all the expected elements are in DOM. * @property {boolean} [setFocus] if provided, focuses the first found element. + * @property {boolean} [shadowRoot] if provided, targets the shadowRoot of the found elements. * @property {number|"bottom"} [setScroll] if provided, sets the scrollTop on the first found * element. * @property {HTMLElement} [target=getFixture()] @@ -521,7 +522,9 @@ class Contains { if (!target) { return; } - const baseRes = [...target.querySelectorAll(this.selector)]; + const baseRes = [...target.querySelectorAll(this.selector)] + .map((el) => (this.options.shadowRoot ? el.shadowRoot : el)) + .filter((el) => el); /** @type {Contains[]} */ this.childrenContains = []; const res = baseRes.filter((el, currentIndex) => {