[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#120363

X-original-commit: 5ff53d7f289ec531f8369a47d02bd58252bb98a5
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
xO-Tx
2023-05-03 13:57:08 +02:00
parent b521624e66
commit c99cd92c14
6 changed files with 104 additions and 11 deletions
+7
View File
@@ -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
@@ -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 `<select/>` 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`
<WebsiteDialog close="props.close"
title="title"
showSecondaryButton="false">
<input
t-ref="input"
type="text" class="form-control my-3"
t-att-value="optionEl.textContent or ''"
t-on-keyup="onInputKeyup"/>
</WebsiteDialog>
`;
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 <select/>. 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) {
+15 -1
View File
@@ -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();
}
});
},
@@ -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 `<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
@@ -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;
}
}
+1 -1
View File
@@ -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