diff --git a/addons/website/i18n/website.pot b/addons/website/i18n/website.pot
index 36425e25623..ca7fa4c4a86 100644
--- a/addons/website/i18n/website.pot
+++ b/addons/website/i18n/website.pot
@@ -9377,6 +9377,13 @@ msgstr ""
msgid "Translate Attribute"
msgstr ""
+#. module: website
+#. odoo-javascript
+#: code:addons/website/static/src/components/translator/translator.js:0
+#, python-format
+msgid "Translate Selection Option"
+msgstr ""
+
#. module: website
#. odoo-javascript
#: code:addons/website/static/src/components/translator/translator.js:0
diff --git a/addons/website/static/src/components/translator/translator.js b/addons/website/static/src/components/translator/translator.js
index 873319afe59..20b6144d45e 100644
--- a/addons/website/static/src/components/translator/translator.js
+++ b/addons/website/static/src/components/translator/translator.js
@@ -4,7 +4,7 @@ import { WebsiteEditorComponent } from '../editor/editor';
import { WebsiteDialog } from '../dialog/dialog';
import localStorage from 'web.local_storage';
-const { useEffect, useRef, Component } = owl;
+const { useEffect, useRef, Component, xml } = owl;
const localStorageNoDialogKey = 'website_translator_nodialog';
@@ -45,6 +45,37 @@ export class AttributeTranslateDialog extends Component {
AttributeTranslateDialog.components = { WebsiteDialog };
AttributeTranslateDialog.template = 'website.AttributeTranslateDialog';
+// Used to translate the text of `` options since it should not be
+// possible to interact with the content of `.o_translation_select` elements.
+export class SelectTranslateDialog extends Component {
+ setup() {
+ this.title = this.env._t("Translate Selection Option");
+ this.inputEl = useRef('input');
+ this.optionEl = this.props.node;
+ }
+
+ onInputKeyup() {
+ const value = this.inputEl.el.value;
+ this.optionEl.textContent = value;
+ this.optionEl.classList.toggle(
+ 'o_option_translated',
+ value !== this.optionEl.dataset.initialTranslationValue
+ );
+ }
+}
+SelectTranslateDialog.components = {WebsiteDialog};
+SelectTranslateDialog.template = xml`
+
+
+
+`;
+
export class TranslatorInfoDialog extends Component {
setup() {
this.strongOkButton = this.env._t("Ok, never show me this again");
@@ -187,6 +218,23 @@ export class WebsiteTranslator extends WebsiteEditorComponent {
}
});
+ // Hack: we add a temporary element to handle option's text
+ // translations from the linked . The final values are
+ // copied to the original element right before save.
+ $editable.filter('[data-oe-translation-initial-sha] > 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);
+ });
+
this.translations = [];
this.$translations = this.getEditableArea().filter('.o_translatable_attribute, .o_translatable_text');
this.$editables = $(this.websiteService.pageDocument).find('.o_editable_translatable_attribute, .o_editable_translatable_text');
@@ -276,9 +324,14 @@ export class WebsiteTranslator extends WebsiteEditorComponent {
});
});
- this.$translations.prependEvent('click.translator', (ev) => {
- this.dialogService.add(AttributeTranslateDialog, { node: ev.target });
- });
+ this.$translations
+ .add(this.getEditableArea().filter('.o_translation_select_option'))
+ .prependEvent('click.translator', (ev) => {
+ const node = ev.target;
+ const isSelectTranslation = !!node.closest('.o_translation_select');
+ this.dialogService.add(isSelectTranslation ?
+ SelectTranslateDialog : AttributeTranslateDialog, {node});
+ });
}
_onSave(ev) {
diff --git a/addons/website/static/src/js/editor/wysiwyg.js b/addons/website/static/src/js/editor/wysiwyg.js
index d85eb04993e..f69e09b1b17 100644
--- a/addons/website/static/src/js/editor/wysiwyg.js
+++ b/addons/website/static/src/js/editor/wysiwyg.js
@@ -308,15 +308,29 @@ snippetsEditor.SnippetsMenu.include({
* @override
*/
async cleanForSave() {
+ const getFromEditable = selector => this.options.editable[0].querySelectorAll(selector);
// Clean unstyled translations
return this._super(...arguments).then(() => {
- for (const el of this.options.editable[0].querySelectorAll('.o_translation_without_style')) {
+ for (const el of getFromEditable('.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;
}
}
+ // Adapt translation values for `select` > `options`s and remove all
+ // temporary `.o_translation_select` elements.
+ for (const optionsEl of getFromEditable('.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 b6b4af7152e..9f93f507934 100644
--- a/addons/website/static/src/scss/website.scss
+++ b/addons/website/static/src/scss/website.scss
@@ -1589,6 +1589,21 @@ ul.o_checklist > li.o_checked::after {
input[value*="data-oe-translation-initial-sha"] {
@extend .o_text_content_invisible;
}
+[data-oe-translation-initial-sha] {
+ > .o_translation_select {
+ border: $input-border-width solid $input-border-color;
+ @include border-radius($input-border-radius, 0);
+
+ // Hide translatable ``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 d64292ea0a8..1c7fa607db1 100644
--- a/addons/website/static/src/scss/website.wysiwyg.scss
+++ b/addons/website/static/src/scss/website.wysiwyg.scss
@@ -22,13 +22,17 @@
}
html[lang] > body.editor_enable [data-oe-translation-state] {
- 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-content-to-translate-color, 0.5) !important;
}
- &.o_dirty {
+ &[data-oe-translation-state="translated"] {
+ &, .o_translation_select_option {
+ background: rgba($o-we-translated-content-color, 0.5) !important;
+ }
+ }
+
+ &.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 dbc1f9b0208..b88854c9ac5 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -147,7 +147,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