[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:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user