[FIX] web: translate html fields
This commit fixes: - body of a email.template in a small field - translation of website_description of a product does not show the source of the translation - translation of website_description of a product shows the full content for the current language HTML fields are typically fields with longer of content. If the field is an HTML field, it should be displayed in a textarea, like the text fields. Most of HTML fields are split in meaningful chunks of content (using xml_translate or html_translate method). For these fields, the source must be displayed. Also sort the entries on the source to have a consistent order closes odoo/odoo#37215 Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
This commit is contained in:
@@ -833,7 +833,8 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
userLanguageValue: ev.target.value || '',
|
||||
dataPointID: record.id,
|
||||
isComingFromTranslationAlert: ev.data.isComingFromTranslationAlert,
|
||||
isText: record.fields[ev.data.fieldName].type === 'text',
|
||||
isText: result.context.translation_type === 'text',
|
||||
showSrc: result.context.translation_show_src,
|
||||
});
|
||||
return this.translationDialog.open();
|
||||
},
|
||||
|
||||
@@ -23,6 +23,7 @@ odoo.define('web.TranslationDialog', function (require) {
|
||||
* @param {string} [options.dataPointID] the data point id of the record for which we do the translations
|
||||
* @param {boolean} [options.isComingFromTranslationAlert] the initiator of the dialog, might be a link on a field or the translation alert on top of the form
|
||||
* @param {boolean} [options.isText] is the field a text field (multiline) or char (single line)
|
||||
* @param {boolean} [options.showSrc] is the source of the translation should be rendered (for partial translations, i.e. XML content)
|
||||
*
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
@@ -37,6 +38,7 @@ odoo.define('web.TranslationDialog', function (require) {
|
||||
this.isComingFromTranslationAlert = options.isComingFromTranslationAlert;
|
||||
this.currentInterfaceLanguage = session.user_context.lang;
|
||||
this.isText = options.isText;
|
||||
this.showSrc = options.showSrc;
|
||||
|
||||
this._super(parent, _.extend({
|
||||
size: 'large',
|
||||
@@ -66,15 +68,18 @@ odoo.define('web.TranslationDialog', function (require) {
|
||||
id: term.id,
|
||||
lang: term.lang,
|
||||
langName: relatedLanguage[1],
|
||||
source: term.src,
|
||||
// we set the translation value coming from the database, except for the language
|
||||
// the user is currently utilizing. Then we set the translation value coming
|
||||
// from the value of the field in the form
|
||||
value: (term.lang === this.currentInterfaceLanguage &&
|
||||
!this.showSrc &&
|
||||
!this.isComingFromTranslationAlert) ?
|
||||
this.userLanguageValue : term.value || ''
|
||||
};
|
||||
});
|
||||
this.data.sort((left, right) => left.langName < right.langName ? -1 : 1);
|
||||
this.data.sort((left, right) =>
|
||||
(left.langName < right.langName || (left.langName === right.langName && left.source < right.source)) ? -1 : 1);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -89,7 +94,7 @@ odoo.define('web.TranslationDialog', function (require) {
|
||||
return this._rpc({
|
||||
model: 'ir.translation',
|
||||
method: 'search_read',
|
||||
fields: ['lang', 'value'],
|
||||
fields: ['lang', 'src', 'value'],
|
||||
domain: this.domain,
|
||||
});
|
||||
},
|
||||
@@ -132,10 +137,12 @@ odoo.define('web.TranslationDialog', function (require) {
|
||||
if (initialValue.value !== t.value) {
|
||||
updatedTerm[t.dataset.id] = t.value;
|
||||
|
||||
if (initialValue.lang === this.currentInterfaceLanguage) {
|
||||
if (initialValue.lang === this.currentInterfaceLanguage && !this.showSrc) {
|
||||
// when the user has changed the term for the language he is
|
||||
// using in the interface, this change should be reflected
|
||||
// in the form view
|
||||
// partial translations being handled server side are
|
||||
// also ignored
|
||||
var changes = {};
|
||||
changes[this.fieldName] = updatedTerm[initialValue.id];
|
||||
updateFormViewField = {
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
<div t-name="TranslationDialog" class="o_translation_dialog">
|
||||
<div class="row" t-foreach="widget.data" t-as="term">
|
||||
<div t-att-class="widget.currentInterfaceLanguage == term.lang ? 'col-12 col-lg-3 o_language_current' : 'col-12 col-lg-3'">
|
||||
<div t-attf-class="col-12 #{widget.showSrc ? 'col-lg-2' : 'col-lg-3'} #{widget.currentInterfaceLanguage == term.lang ? 'o_language_current' : ''}">
|
||||
<t t-esc="term.langName"></t>
|
||||
</div>
|
||||
<div class="col-12 col-lg-9 translation">
|
||||
<div t-if="widget.showSrc" class="col-12 col-lg-3 source">
|
||||
<t t-esc="term.source"></t>
|
||||
</div>
|
||||
<div t-attf-class="col-12 #{widget.showSrc ? 'col-lg-7' : 'col-lg-9'} translation">
|
||||
<input t-if="!widget.isText" type="text"
|
||||
class="o_field_char o_input"
|
||||
t-att-value="term.value"
|
||||
@@ -14,7 +17,7 @@
|
||||
<textarea t-if="widget.isText"
|
||||
t-esc="term.value"
|
||||
t-att-data-id="term.id"
|
||||
rows="5"
|
||||
t-att-rows="widget.showSrc ? 2 : 5"
|
||||
class="o_field_text o_field_translate o_field_widget o_input">
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
@@ -1109,7 +1109,7 @@ QUnit.module('basic_fields', {
|
||||
});
|
||||
|
||||
QUnit.test('char field translatable', async function (assert) {
|
||||
assert.expect(4);
|
||||
assert.expect(12);
|
||||
|
||||
this.data.partner.fields.foo.translate = true;
|
||||
|
||||
@@ -1128,6 +1128,9 @@ QUnit.module('basic_fields', {
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
session: {
|
||||
user_context: {lang: 'en_US'},
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (route === "/web/dataset/call_button" && args.method === 'translate_fields') {
|
||||
assert.deepEqual(args.args, ["partner",1,"foo"], 'should call "call_button" route');
|
||||
@@ -1137,7 +1140,18 @@ QUnit.module('basic_fields', {
|
||||
});
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||||
return Promise.resolve([["en_US"], ["fr_BE"]]);
|
||||
return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
|
||||
}
|
||||
if (args.method === "search_read" && args.model == "ir.translation") {
|
||||
return Promise.resolve([
|
||||
{lang: 'en_US', src: 'yop', value: 'yop', id: 42},
|
||||
{lang: 'fr_BE', src: 'yop', value: 'valeur français', id: 43}
|
||||
]);
|
||||
}
|
||||
if (args.method === "write" && args.model == "ir.translation") {
|
||||
assert.deepEqual(args.args[1], {value: "english value"},
|
||||
"the new translation value should be written");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
@@ -1150,6 +1164,117 @@ QUnit.module('basic_fields', {
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.containsOnce($(document), '.modal', 'a translate modal should be visible');
|
||||
assert.containsN($('.modal .o_translation_dialog'), '.translation', 2,
|
||||
'two rows should be visible');
|
||||
|
||||
var $enField = $('.modal .o_translation_dialog .translation:first() input');
|
||||
assert.strictEqual($enField.val(), 'yop',
|
||||
'English translation should be filled');
|
||||
assert.strictEqual($('.modal .o_translation_dialog .translation:last() input').val(), 'valeur français',
|
||||
'French translation should be filled');
|
||||
|
||||
await testUtils.fields.editInput($enField, "english value");
|
||||
await testUtils.dom.click($('.modal button.btn-primary')); // save
|
||||
await testUtils.nextTick();
|
||||
|
||||
var $foo = form.$('input[type="text"].o_field_char');
|
||||
assert.strictEqual($foo.val(), "english value",
|
||||
"the new translation was not transfered to modified record");
|
||||
|
||||
await testUtils.fields.editInput($foo, "new english value");
|
||||
|
||||
await testUtils.dom.click($button);
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual($('.modal .o_translation_dialog .translation:first() input').val(), 'new english value',
|
||||
'Modified value should be used instead of translation');
|
||||
assert.strictEqual($('.modal .o_translation_dialog .translation:last() input').val(), 'valeur français',
|
||||
'French translation should be filled');
|
||||
|
||||
form.destroy();
|
||||
|
||||
_t.database.multi_lang = multiLang;
|
||||
});
|
||||
|
||||
QUnit.test('html field translatable', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
this.data.partner.fields.foo.translate = true;
|
||||
|
||||
var multiLang = _t.database.multi_lang;
|
||||
_t.database.multi_lang = true;
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<group>' +
|
||||
'<field name="foo"/>' +
|
||||
'</group>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
session: {
|
||||
user_context: {lang: 'en_US'},
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (route === "/web/dataset/call_button" && args.method === 'translate_fields') {
|
||||
assert.deepEqual(args.args, ["partner",1,"foo"], 'should call "call_button" route');
|
||||
return Promise.resolve({
|
||||
domain: [],
|
||||
context: {
|
||||
search_default_name: 'partner,foo',
|
||||
translation_type: 'char',
|
||||
translation_show_src: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (route === "/web/dataset/call_kw/res.lang/get_installed") {
|
||||
return Promise.resolve([["en_US", "English"], ["fr_BE", "French (Belgium)"]]);
|
||||
}
|
||||
if (args.method === "search_read" && args.model == "ir.translation") {
|
||||
return Promise.resolve([
|
||||
{lang: 'en_US', src: 'first paragraph', value: 'first paragraph', id: 42},
|
||||
{lang: 'en_US', src: 'second paragraph', value: 'second paragraph', id: 43},
|
||||
{lang: 'fr_BE', src: 'first paragraph', value: 'premier paragraphe', id: 44},
|
||||
{lang: 'fr_BE', src: 'second paragraph', value: 'deuxième paragraphe', id: 45},
|
||||
]);
|
||||
}
|
||||
if (args.method === "write" && args.model == "ir.translation") {
|
||||
assert.deepEqual(args.args[1], {value: "first paragraph modified"},
|
||||
"Wrong update on translation");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
await testUtils.form.clickEdit(form);
|
||||
var $foo = form.$('input[type="text"].o_field_char');
|
||||
|
||||
// this will not affect the translate_fields effect until the record is
|
||||
// saved but is set for consistency of the test
|
||||
await testUtils.fields.editInput($foo, "<p>first paragraph</p><p>second paragraph</p>");
|
||||
|
||||
var $button = form.$('input[type="text"].o_field_char + .o_field_translate');
|
||||
await testUtils.dom.click($button);
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.containsOnce($(document), '.modal', 'a translate modal should be visible');
|
||||
assert.containsN($('.modal .o_translation_dialog'), '.translation', 4,
|
||||
'four rows should be visible');
|
||||
|
||||
var $enField = $('.modal .o_translation_dialog .translation:first() input');
|
||||
assert.strictEqual($enField.val(), 'first paragraph',
|
||||
'first part of english translation should be filled');
|
||||
|
||||
await testUtils.fields.editInput($enField, "first paragraph modified");
|
||||
await testUtils.dom.click($('.modal button.btn-primary')); // save
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual($foo.val(), "<p>first paragraph</p><p>second paragraph</p>",
|
||||
"the new partial translation should not be transfered");
|
||||
|
||||
form.destroy();
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ QUnit.module('Views', {
|
||||
"ir.translation": {
|
||||
fields: {
|
||||
lang_code: {type: "char"},
|
||||
src: {type: "char"},
|
||||
value: {type: "char"},
|
||||
res_id: {type: "integer"},
|
||||
name: {type: "char"},
|
||||
|
||||
@@ -745,6 +745,7 @@ class IrTranslation(models.Model):
|
||||
'target': 'current',
|
||||
'flags': {'search_view': True, 'action_buttons': True},
|
||||
'domain': domain,
|
||||
'context': {},
|
||||
}
|
||||
if field:
|
||||
fld = record._fields[field]
|
||||
@@ -763,8 +764,11 @@ class IrTranslation(models.Model):
|
||||
pass
|
||||
|
||||
action['target'] = 'new'
|
||||
action['context']['translation_type'] = 'text' if fld.type in ['text', 'html'] else 'char'
|
||||
action['context']['translation_show_src'] = False
|
||||
if callable(fld.translate):
|
||||
action['view_id'] = self.env.ref('base.view_translation_lang_src_value_tree').id,
|
||||
action['context']['translation_show_src'] = True
|
||||
else:
|
||||
action['view_id'] = self.env.ref('base.view_translation_lang_value_tree').id,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user