From 57d7e75b030ed34992865170ce349442fb3c1d03 Mon Sep 17 00:00:00 2001 From: xO-Tx <mou@odoo.com> Date: Mon, 17 Apr 2023 16:29:06 +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#117519 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com> --- addons/website/i18n/website.pot | 7 ++ .../js/editor/wysiwyg_multizone_translate.js | 99 ++++++++++++++++--- addons/website/static/src/scss/website.scss | 15 +++ .../static/src/scss/website.wysiwyg.scss | 10 +- odoo/tools/translate.py | 2 +- 5 files changed, 117 insertions(+), 16 deletions(-) diff --git a/addons/website/i18n/website.pot b/addons/website/i18n/website.pot index bb3e37c42d02..891c277be713 100644 --- a/addons/website/i18n/website.pot +++ b/addons/website/i18n/website.pot @@ -7453,6 +7453,13 @@ msgstr "" msgid "Translate Attribute" msgstr "" +#. module: website +#. openerp-web +#: code:addons/website/static/src/js/editor/wysiwyg_multizone_translate.js:0 +#, python-format +msgid "Translate Selection Option" +msgstr "" + #. module: website #. openerp-web #: code:addons/website/static/src/xml/translator.xml:0 diff --git a/addons/website/static/src/js/editor/wysiwyg_multizone_translate.js b/addons/website/static/src/js/editor/wysiwyg_multizone_translate.js index 978ac4d98a6d..02e506d4a476 100644 --- a/addons/website/static/src/js/editor/wysiwyg_multizone_translate.js +++ b/addons/website/static/src/js/editor/wysiwyg_multizone_translate.js @@ -69,6 +69,41 @@ var AttributeTranslateDialog = Dialog.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 = Dialog.extend({ + /** + * @constructor + */ + init: function (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: function () { + 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); + }, +}); + var WysiwygTranslate = WysiwygMultizone.extend({ custom_events: _.extend({}, WysiwygMultizone.prototype.custom_events || {}, { ready_to_save: '_onSave', @@ -101,8 +136,9 @@ var WysiwygTranslate = WysiwygMultizone.extend({ return promise.then(function () { self._relocateEditorBar(); var attrs = ['placeholder', 'title', 'alt']; + const $editable = self._getEditableArea(); _.each(attrs, function (attr) { - self._getEditableArea().filter('[' + attr + '*="data-oe-translation-id="]').filter(':empty, input, select, textarea, img').each(function () { + $editable.filter('[' + attr + '*="data-oe-translation-id="]').filter(':empty, input, select, textarea, img').each(function () { var $node = $(this); var translation = $node.data('translation') || {}; var trans = $node.attr(attr); @@ -125,6 +161,25 @@ var WysiwygTranslate = WysiwygMultizone.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. + $editable.filter('[data-oe-translation-id] > select').each((index, select) => { + // Keep the default width of the translation `<span/>`. + select.parentElement.classList.remove('o_is_inline_editable'); + 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.translations = []; self.$editables_attr = self._getEditableArea().filter('.o_translatable_attribute'); self.$editables_attribute = $('.o_editable_translatable_attribute'); @@ -274,18 +329,25 @@ var WysiwygTranslate = WysiwygMultizone.extend({ }); }); - this.$editables_attr.prependEvent('mousedown.translator click.translator mouseup.translator', function (ev) { - if (ev.ctrlKey) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - if (ev.type !== 'mousedown') { - return; - } + this.$editables_attr + .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, {}, targetEl).open(); + } + }); }, //-------------------------------------------------------------------------- @@ -294,6 +356,19 @@ var WysiwygTranslate = WysiwygMultizone.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.el.querySelectorAll('.o_translation_select')) { + 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 7ef3d5562af2..c75b07c6ef6d 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -1551,3 +1551,18 @@ $ribbon-padding: 100px; opacity: 0; } } +[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; + } + } +} diff --git a/addons/website/static/src/scss/website.wysiwyg.scss b/addons/website/static/src/scss/website.wysiwyg.scss index 8bb4f1dcfcb7..e6edd90358b3 100644 --- a/addons/website/static/src/scss/website.wysiwyg.scss +++ b/addons/website/static/src/scss/website.wysiwyg.scss @@ -81,13 +81,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 a6735cbaf3dc..6ba57274d9f6 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -140,7 +140,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 -- GitLab