From 8d19f0f1fde6fe271db348cae47128157d079292 Mon Sep 17 00:00:00 2001 From: xO-Tx <mou@odoo.com> Date: Fri, 28 Apr 2023 13:48:36 +0000 Subject: [PATCH] [FIX] website, tools: make select options translatable Steps to reproduce: - Go to a website page > Add a 'Form' block > Add a new 'Selection' field. - Go to the page (in 'edit_translations' mode) > The selection field options are not translatable. The goal of this commit is to make the select options translatable by adding an intermediate `.o_translation_select` element. This element will handle option's text translations from the linked `<select/>`. The final values are copied to the original element right before save. opw-3233360 closes odoo/odoo#120028 X-original-commit: 57d7e75b030ed34992865170ce349442fb3c1d03 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com> --- addons/website/i18n/website.pot | 7 ++ .../website/static/src/js/menu/translate.js | 96 ++++++++++++++++--- addons/website/static/src/scss/website.scss | 15 +++ .../static/src/scss/website.wysiwyg.scss | 10 +- odoo/tools/translate.py | 2 +- 5 files changed, 115 insertions(+), 15 deletions(-) diff --git a/addons/website/i18n/website.pot b/addons/website/i18n/website.pot index b2e36122d34f..6b349790cee8 100644 --- a/addons/website/i18n/website.pot +++ b/addons/website/i18n/website.pot @@ -9726,6 +9726,13 @@ msgstr "" msgid "Translate Attribute" msgstr "" +#. module: website +#. openerp-web +#: code:addons/website/static/src/js/menu/translate.js:0 +#, python-format +msgid "Translate Selection Option" +msgstr "" + #. module: website #. openerp-web #: code:addons/website/static/src/js/menu/translate.js:0 diff --git a/addons/website/static/src/js/menu/translate.js b/addons/website/static/src/js/menu/translate.js index f432f0696878..1be1b30c630c 100644 --- a/addons/website/static/src/js/menu/translate.js +++ b/addons/website/static/src/js/menu/translate.js @@ -96,6 +96,41 @@ var AttributeTranslateDialog = weDialog.extend({ }, }); +// Used to translate the text of `<select/>` options since it should not be +// possible to interact with the content of `.o_translation_select` elements. +const SelectTranslateDialog = weDialog.extend({ + /** + * @constructor + */ + init(parent, options) { + this._super(parent, { + ...options, + title: _t("Translate Selection Option"), + buttons: [ + {text: _t("Close"), click: this.save} + ], + }); + this.optionEl = this.options.targetEl; + this.translationObject = this.optionEl.closest('[data-oe-translation-id]'); + }, + /** + * @override + */ + start() { + const inputEl = document.createElement('input'); + inputEl.className = 'form-control my-3'; + inputEl.value = this.optionEl.textContent; + inputEl.addEventListener('keyup', () => { + this.optionEl.textContent = inputEl.value; + const translationUpdated = inputEl.value !== this.optionEl.dataset.initialTranslationValue; + this.translationObject.classList.toggle('o_dirty', translationUpdated); + this.optionEl.classList.add('o_option_translated'); + }); + this.el.appendChild(inputEl); + return this._super(...arguments); + }, +}); + const savableSelector = '[data-oe-translation-id], ' + '[data-oe-model][data-oe-id][data-oe-field], ' + '[placeholder*="data-oe-translation-id="], ' + @@ -269,6 +304,25 @@ var TranslatePageMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ } }); + // Hack: we add a temporary element to handle option's text + // translations from the linked <select/>. The final values are + // copied to the original element right before save. + self.selectTranslationEls = []; + $editable.filter('[data-oe-translation-id] > select').each((index, select) => { + const selectTranslationEl = document.createElement('div'); + selectTranslationEl.className = 'o_translation_select'; + const optionNames = [...select.options].map(option => option.text); + optionNames.forEach(option => { + const optionEl = document.createElement('div'); + optionEl.textContent = option; + optionEl.dataset.initialTranslationValue = option; + optionEl.className = 'o_translation_select_option'; + selectTranslationEl.appendChild(optionEl); + }); + select.before(selectTranslationEl); + self.selectTranslationEls.push(selectTranslationEl); + }); + self.translations = []; self.$translations = self._getEditableArea().filter('.o_translatable_attribute, .o_translatable_text'); self.$editables = $('.o_editable_translatable_attribute, .o_editable_translatable_text'); @@ -370,18 +424,25 @@ var TranslatePageMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ }); }); - this.$translations.prependEvent('mousedown.translator click.translator mouseup.translator', function (ev) { - if (ev.ctrlKey) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - if (ev.type !== 'mousedown') { - return; - } + this.$translations + .add(this._getEditableArea().filter('.o_translation_select_option')) + .prependEvent('mousedown.translator click.translator mouseup.translator', function (ev) { + if (ev.ctrlKey) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + if (ev.type !== 'mousedown') { + return; + } - new AttributeTranslateDialog(self, {}, ev.target).open(); - }); + const targetEl = ev.target; + if (targetEl.closest('.o_translation_select')) { + new SelectTranslateDialog(self, {size: 'medium', targetEl}).open(); + } else { + new AttributeTranslateDialog(self, {}, ev.target).open(); + } + }); }, //-------------------------------------------------------------------------- @@ -390,6 +451,19 @@ var TranslatePageMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ _onSave: function (ev) { ev.stopPropagation(); + // Adapt translation values for `select` > `options`s and remove all + // temporary `.o_translation_select` elements. + for (const optionsEl of this.selectTranslationEls) { + const selectEl = optionsEl.nextElementSibling; + const translatedOptions = optionsEl.children; + const selectOptions = selectEl.tagName === 'SELECT' ? [...selectEl.options] : []; + if (selectOptions.length === translatedOptions.length) { + selectOptions.map((option, i) => { + option.text = translatedOptions[i].textContent; + }); + } + optionsEl.remove(); + } }, }); diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index 78fda3581fbd..7b77734109cf 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -1639,6 +1639,21 @@ ul.o_checklist > li.o_checked::after { input[value*="data-oe-translation-id"] { @extend .o_text_content_invisible; } +[data-oe-translation-id] { + > .o_translation_select { + border: $input-border-width solid $input-border-color; + @include border-radius($input-border-radius, 0); + + // Hide translatable `<select/>`s since we use `.o_translation_select` + // elements to handle translations. + + select { + display: none !important; + } + > div:not(:last-child) { + border-bottom: inherit; + } + } +} //------------------------------------------------------------------------------ // Website Animate diff --git a/addons/website/static/src/scss/website.wysiwyg.scss b/addons/website/static/src/scss/website.wysiwyg.scss index e45f7447741c..f5460cf19a24 100644 --- a/addons/website/static/src/scss/website.wysiwyg.scss +++ b/addons/website/static/src/scss/website.wysiwyg.scss @@ -85,13 +85,17 @@ } html[lang] > body.editor_enable [data-oe-translation-state] { - background: rgba($o-we-content-to-translate-color, 0.5) !important; + &, .o_translation_select_option { + background: rgba($o-we-content-to-translate-color, 0.5) !important; + } &[data-oe-translation-state="translated"] { - background: rgba($o-we-translated-content-color, 0.5) !important; + &, .o_translation_select_option { + background: rgba($o-we-translated-content-color, 0.5) !important; + } } - &.o_dirty { + &.o_dirty, .o_option_translated { background: rgba($o-we-translated-content-color, 0.25) !important; } } diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 22f5f5e56fad..b5620a6c2d1d 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -141,7 +141,7 @@ TRANSLATED_ELEMENTS = { 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'keygen', 'mark', 'math', 'meter', 'output', 'progress', 'q', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', - 'sup', 'time', 'u', 'var', 'wbr', 'text', + 'sup', 'time', 'u', 'var', 'wbr', 'text', 'select', 'option', } # Which attributes must be translated. This is a dict, where the value indicates -- GitLab