diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 50e1532c330..c8e7310e478 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -1075,6 +1075,10 @@ const Wysiwyg = Widget.extend({ return; } let $node = $(field); + // Do not forward "unstyled" copies to other nodes. + if ($node.hasClass('o_translation_without_style')) { + return; + } let $nodes = $odooFields.filter(function () { return this !== field; }); @@ -1127,6 +1131,16 @@ const Wysiwyg = Widget.extend({ } const html = $node.html(); for (const node of $nodes) { + if (node.classList.contains('o_translation_without_style')) { + // For generated elements such as the navigation + // labels of website's table of content, only the + // text of the referenced translation must be used. + const text = $node.text(); + if (node.innerText !== text) { + node.innerText = text; + } + continue; + } if (node.innerHTML !== html) { node.innerHTML = html; } diff --git a/addons/website/i18n/website.pot b/addons/website/i18n/website.pot index bc17580432d..20d7991f896 100644 --- a/addons/website/i18n/website.pot +++ b/addons/website/i18n/website.pot @@ -9009,6 +9009,13 @@ msgid "" "site." msgstr "" +#. module: website +#. odoo-javascript +#: code:addons/website/static/src/components/translator/translator.js:0 +#, python-format +msgid "This translation is not editable." +msgstr "" + #. module: website #. odoo-javascript #: code:addons/website/static/src/components/dialog/seo.js:0 @@ -9205,6 +9212,13 @@ msgstr "" msgid "Translate Attribute" msgstr "" +#. module: website +#. odoo-javascript +#: code:addons/website/static/src/components/translator/translator.js:0 +#, python-format +msgid "Translate header in the text. Menu is generated automatically." +msgstr "" + #. module: website #. odoo-javascript #: code:addons/website/static/src/components/translator/translator.xml:0 diff --git a/addons/website/static/src/components/translator/translator.js b/addons/website/static/src/components/translator/translator.js index 82c857988e5..8d746b0e037 100644 --- a/addons/website/static/src/components/translator/translator.js +++ b/addons/website/static/src/components/translator/translator.js @@ -205,6 +205,40 @@ export class WebsiteTranslator extends WebsiteEditorComponent { styleEl.sheet.insertRule(`[data-oe-translation-state].o_dirty {background: ${translatedColor} !important;}`); styleEl.sheet.insertRule(`[data-oe-translation-state="translated"] {background: ${translatedColor} !important;}`); styleEl.sheet.insertRule(`[data-oe-translation-state] {background: ${toTranslateColor} !important;}`); + + const showNotification = ev => { + let message = this.env._t('This translation is not editable.'); + if (ev.target.closest('.s_table_of_content_navbar_wrap')) { + message = this.env._t('Translate header in the text. Menu is generated automatically.'); + } + this.env.services.notification.add(message, { + type: 'info', + sticky: false, + }); + }; + for (const translationEl of $editable) { + if (translationEl.closest('.o_not_editable')) { + translationEl.addEventListener('click', showNotification); + } + if (translationEl.closest('.s_table_of_content_navbar_wrap')) { + // Make sure the same translation ids are used. + const href = translationEl.closest('a').getAttribute('href'); + const headerEl = translationEl.closest('.s_table_of_content').querySelector(`${href} [data-oe-translation-initial-sha]`); + if (headerEl) { + if (translationEl.dataset.oeTranslationInitialSha !== headerEl.dataset.oeTranslationInitialSha) { + // Use the same identifier for the generated navigation + // label and its associated header so that the general + // synchronization mechanism kicks in. + // The initial value is kept to be restored before save + // in order to keep the translation of the unstyled + // label distinct from the one of the header. + translationEl.dataset.oeTranslationSaveSha = translationEl.dataset.oeTranslationInitialSha; + translationEl.dataset.oeTranslationInitialSha = headerEl.dataset.oeTranslationInitialSha; + } + translationEl.classList.add('o_translation_without_style'); + } + } + } } markTranslatableNodes() { diff --git a/addons/website/static/src/js/editor/wysiwyg.js b/addons/website/static/src/js/editor/wysiwyg.js index 22e936b54eb..fb7e9d6a19a 100644 --- a/addons/website/static/src/js/editor/wysiwyg.js +++ b/addons/website/static/src/js/editor/wysiwyg.js @@ -301,6 +301,26 @@ snippetsEditor.SnippetsMenu.include({ return this._super(...arguments); }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + async cleanForSave() { + // Clean unstyled translations + return this._super(...arguments).then(() => { + for (const el of this.options.editable[0].querySelectorAll('.o_translation_without_style')) { + el.classList.remove('o_translation_without_style'); + if (el.dataset.oeTranslationSaveSha) { + el.dataset.oeTranslationInitialSha = el.dataset.oeTranslationSaveSha; + delete el.dataset.oeTranslationSaveSha; + } + } + }); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- diff --git a/addons/website/static/src/snippets/s_table_of_content/000.js b/addons/website/static/src/snippets/s_table_of_content/000.js index 2b7b0ade23a..712390fd775 100644 --- a/addons/website/static/src/snippets/s_table_of_content/000.js +++ b/addons/website/static/src/snippets/s_table_of_content/000.js @@ -12,6 +12,7 @@ const TableOfContent = publicWidget.Widget.extend({ * @override */ async start() { + this._stripNavbarStyles(); await this._super(...arguments); this.$scrollingElement = $().getScrollingElement(); this.previousPosition = -1; @@ -36,6 +37,21 @@ const TableOfContent = publicWidget.Widget.extend({ // Private //-------------------------------------------------------------------------- + /** + * @private + */ + _stripNavbarStyles() { + // This is needed for styles added on translations when the master text + // has no style. + for (let el of this.el.querySelectorAll('.s_table_of_content_navbar .table_of_content_link')) { + const translationEl = el.querySelector('span[data-oe-translation-state]'); + if (translationEl) { + el = translationEl; + } + const text = el.textContent; // Get text from el. + el.textContent = text; // Replace all of el's content with that text. + } + }, /** * @private */