diff options
| author | Vincent Schippefilt <vsc@odoo.com> | 2019-08-20 07:46:52 +0000 |
|---|---|---|
| committer | Vincent Schippefilt <vsc@odoo.com> | 2019-09-09 13:09:53 +0000 |
| commit | 029dd1a214f727e25b479610df96b7ee94dd8127 (patch) | |
| tree | 5b33cb401962523f20c768043def21a7b0349cfc /addons/web/static | |
| parent | 263849c3365af3d62fa5b9ceaff65b2f7b84c092 (diff) | |
[IMP] web: edit translations in new dialog
Before this commit, translations were edited in a list view with a filter
on the field that needed to be translated.
After this commit, the translations for each fields are translated in a
dedicated dialog. The changes done to the current language are taken
into account immediately, both ways, from the dialog to the form and
from the form to the dialog.
Translating terms is also available in create mode, the user will be
prompted to save before editing a translation
Task ID: 2028152
closes odoo/odoo#36185
Signed-off-by: VincentSchippefilt <VincentSchippefilt@users.noreply.github.com>
Diffstat (limited to 'addons/web/static')
| -rw-r--r-- | addons/web/static/src/js/fields/basic_fields.js | 33 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_controller.js | 53 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form/form_renderer.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/translation_dialog.js | 172 | ||||
| -rw-r--r-- | addons/web/static/src/scss/form_view.scss | 19 | ||||
| -rw-r--r-- | addons/web/static/src/scss/form_view_extra.scss | 5 | ||||
| -rw-r--r-- | addons/web/static/src/scss/list_view.scss | 13 | ||||
| -rw-r--r-- | addons/web/static/src/scss/translation_dialog.scss | 10 | ||||
| -rw-r--r-- | addons/web/static/src/xml/translation_dialog.xml | 23 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/basic_fields_tests.js | 70 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/test_utils.js | 3 | ||||
| -rw-r--r-- | addons/web/static/tests/views/form_tests.js | 30 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 66 |
13 files changed, 445 insertions, 58 deletions
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index bc4b4e6b7d3..da46488cffa 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -40,13 +40,9 @@ var TranslatableFieldMixin = { * @returns {jQuery} */ _renderTranslateButton: function () { - if (_t.database.multi_lang && this.field.translate && this.res_id) { + if (_t.database.multi_lang && this.field.translate) { var lang = _t.database.parameters.code.split('_')[0].toUpperCase(); - return $('<button>', { - type: 'button', - 'class': 'o_field_translate fa fa-globe btn btn-link', - 'data-code': lang, - }) + return $(`<span class="o_field_translate btn btn-link">${lang}</span>`) .on('click', this._onTranslate.bind(this)); } return $(); @@ -59,10 +55,16 @@ var TranslatableFieldMixin = { /** * open the translation view for the current field * + * @param {MouseEvent} ev * @private */ - _onTranslate: function () { - this.trigger_up('translate', {fieldName: this.name, id: this.dataPointID}); + _onTranslate: function (ev) { + ev.preventDefault(); + this.trigger_up('translate', { + fieldName: this.name, + id: this.dataPointID, + isComingFromTranslationAlert: false, + }); }, }; @@ -213,7 +215,9 @@ var InputField = DebouncedField.extend({ if (!event || event === this.lastChangeEvent) { this.isDirty = false; } - if (this.isDirty || (event && event.target === this && event.data.changes[this.name] === this.value)) { + if (this.isDirty || (event && event.target === this && + event.data.changes && + event.data.changes[this.name] === this.value)) { if (this.attrs.decorations) { // if a field is modified, then it could have triggered an onchange // which changed some of its decorations. Since we bypass the @@ -498,7 +502,10 @@ var FieldChar = InputField.extend(TranslatableFieldMixin, { if (this.field.size && this.field.size > 0) { this.$el.attr('maxlength', this.field.size); } - this.$el = this.$el.add(this._renderTranslateButton()); + if (this.field.translate) { + this.$el = this.$el.add(this._renderTranslateButton()); + this.$el.addClass('o_field_translate'); + } return def; }, /** @@ -1327,8 +1334,10 @@ var FieldText = InputField.extend(TranslatableFieldMixin, { start: function () { if (this.mode === 'edit') { dom.autoresize(this.$el, this.autoResizeOptions); - - this.$el = this.$el.add(this._renderTranslateButton()); + if (this.field.translate) { + this.$el = this.$el.add(this._renderTranslateButton()); + this.$el.addClass('o_field_translate'); + } } return this._super(); }, diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 2163f074cb0..be0b9a00bf3 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -12,6 +12,7 @@ var core = require('web.core'); var Dialog = require('web.Dialog'); var FieldManagerMixin = require('web.FieldManagerMixin'); var Pager = require('web.Pager'); +var TranslationDialog = require('web.TranslationDialog'); var _t = core._t; @@ -332,6 +333,23 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { return this.renderer.confirmChange(state, id, fields, e); }, /** + * Ask the user to confirm he wants to save the record + * @private + */ + _confirmSaveNewRecord: function () { + var self = this; + var def = new Promise(function (resolve, reject) { + var message = _t("You need to save this new record before editing the translation. Do you want to proceed?"); + var dialog = Dialog.confirm(self, message, { + title: _t("Warning"), + confirm_callback: resolve.bind(self, true), + cancel_callback: reject, + }); + dialog.on('closed', self, reject); + }); + return def; + }, + /** * Delete records (and ask for confirmation if necessary) * * @param {string[]} ids list of local record ids @@ -789,28 +807,35 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @private * @param {OdooEvent} ev */ - _onTranslate: function (ev) { + _onTranslate: async function (ev) { ev.stopPropagation(); - var self = this; - var record = this.model.get(ev.data.id, {raw: true}); - this._rpc({ + + if (this.model.isNew(ev.data.id)) { + await this._confirmSaveNewRecord(); + var updatedFields = await this.saveRecord(ev.data.id, { stayInEdit: true }); + await this._confirmChange(ev.data.id, updatedFields, ev); + } + var record = this.model.get(ev.data.id, { raw: true }); + var result = await this._rpc({ route: '/web/dataset/call_button', params: { model: 'ir.translation', method: 'translate_fields', args: [record.model, record.res_id, ev.data.fieldName], - kwargs: {context: record.getContext()}, + kwargs: { context: record.getContext() }, } - }).then(function (result) { - self.do_action(result, { - on_reverse_breadcrumb: function () { - if (!_.isEmpty(self.renderer.alertFields)) { - self.renderer.displayTranslationAlert(); - } - return false; - }, - }); }); + + this.translationDialog = new TranslationDialog(this, { + domain: result.domain, + searchName: result.context.search_default_name, + fieldName: record.fieldsInfo[record.viewType][ev.data.fieldName].name, + userLanguageValue: ev.target.value || '', + dataPointID: record.id, + isComingFromTranslationAlert: ev.data.isComingFromTranslationAlert, + isText: record.fields[ev.data.fieldName].type === 'text', + }); + return this.translationDialog.open(); }, }); diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index decea8867fc..e1395eebb9a 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -1083,7 +1083,11 @@ var FormRenderer = BasicRenderer.extend({ */ _onTranslate: function (ev) { ev.preventDefault(); - this.trigger_up('translate', {fieldName: ev.target.name, id: this.state.id}); + this.trigger_up('translate', { + fieldName: ev.target.name, + id: this.state.id, + isComingFromTranslationAlert: true, + }); }, /** * remove alert fields of record from alertFields object diff --git a/addons/web/static/src/js/widgets/translation_dialog.js b/addons/web/static/src/js/widgets/translation_dialog.js new file mode 100644 index 00000000000..39d841a48d0 --- /dev/null +++ b/addons/web/static/src/js/widgets/translation_dialog.js @@ -0,0 +1,172 @@ +odoo.define('web.TranslationDialog', function (require) { + 'use strict'; + + var core = require('web.core'); + var Dialog = require('web.Dialog'); + var session = require('web.session'); + + var _t = core._t; + + var TranslationDialog = Dialog.extend({ + xmlDependencies: (Dialog.prototype.xmlDependencies || []) + .concat(['/web/static/src/xml/translation_dialog.xml']), + template: 'TranslationDialog', + + /** + * @constructor + * @param {Widget} parent + * @param {Object} [options] + * @param {string} [options.domain] the domain needed to get the translation terms + * @param {string} [options.fieldName] the name of the field currently translated (from the model of the form view) + * @param {string} [options.searchName] the name of the actual field that is the reference for translation (in the form of model,field) + * @param {string} [options.userLanguageValue] the value of the translation in the language of the user, as seen in the from view (might be empty) + * @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) + * + */ + init: function (parent, options) { + options = options || {}; + + this.fieldName = options.fieldName; + this.domain = options.domain; + this.searchName = options.searchName; + this.userLanguageValue = options.userLanguageValue; + this.domain.push(['name', "=", `${this.searchName}`]); + this.dataPointID = options.dataPointID; + this.isComingFromTranslationAlert = options.isComingFromTranslationAlert; + this.currentInterfaceLanguage = session.user_context.lang; + this.isText = options.isText; + + this._super(parent, _.extend({ + size: 'large', + title: _t('Translate: ') + `${this.fieldName}`, + buttons: [ + { text: _t('Save'), classes: 'btn-primary', close: true, click: this._onSave.bind(this) }, + { text: _t('Discard'), close: true }, + ], + }, options)); + }, + /** + * @override + */ + willStart: function () { + return Promise.all([ + this._super(), + this._loadLanguages().then((l) => { + this.languages = l; + }), + this._loadTranslations().then((t) => { + this.translations = t; + }) + ]).then(() => { + this.data = this.translations.map((term) => { + let relatedLanguage = this.languages.find((language) => language[0] === term.lang); + return { + id: term.id, + lang: term.lang, + langName: relatedLanguage[1], + // 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.isComingFromTranslationAlert) ? + this.userLanguageValue : term.value || '' + }; + }); + this.data.sort((left, right) => left.langName < right.langName ? -1 : 1); + }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * Load the translation terms for the installed language, for the current model and res_id + * @private + */ + _loadTranslations: function () { + return this._rpc({ + model: 'ir.translation', + method: 'search_read', + fields: ['lang', 'value'], + domain: this.domain, + }); + }, + /** + * Load the installed languages long names and code + * + * The result of the call is put in cache on the prototype of this dialog. + * If any new language is installed, a full page refresh will happen, + * so there is no need invalidate it. + * @private + */ + _loadLanguages: function () { + if (TranslationDialog.prototype.installedLanguagesCache) + return Promise.resolve(TranslationDialog.prototype.installedLanguagesCache); + + return this._rpc({ + model: 'res.lang', + method: 'get_installed', + fields: ['code', 'name', 'iso_code'], + }).then((installedLanguages) => { + TranslationDialog.prototype.installedLanguagesCache = installedLanguages; + return TranslationDialog.prototype.installedLanguagesCache + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** + * Save all the terms that have been updated + * @private + * @returns a promise that is resolved when all the save have occured + */ + _onSave: function () { + var updatedTerm = {}; + var updateFormViewField; + + this.el.querySelectorAll('input[type=text],textarea').forEach((t) => { + var initialValue = this.data.find((d) => d.id == t.dataset.id); + if (initialValue.value !== t.value) { + updatedTerm[t.dataset.id] = t.value; + + if (initialValue.lang === this.currentInterfaceLanguage) { + // 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 + var changes = {}; + changes[this.fieldName] = updatedTerm[initialValue.id]; + updateFormViewField = { + dataPointID: this.dataPointID, + changes: changes, + doNotSetDirty: false, + }; + } + } + }); + + // updatedTerm only contains the id and values of the terms that + // have been updated by the user + var saveUpdatedTermsProms = Object.keys(updatedTerm).map((id) => { + var writeTranslation = { + model: 'ir.translation', + method: 'write', + context: this.context, + args: [[parseInt(id, 10)], { value: updatedTerm[id] }] + }; + return this._rpc(writeTranslation); + }); + return Promise.all(saveUpdatedTermsProms).then(() => { + // we might have to update the value of the field on the form + // view that opened the translation dialog + if (updateFormViewField) { + this.trigger_up('field_changed', updateFormViewField); + } + }); + } + }); + + return TranslationDialog; +}); diff --git a/addons/web/static/src/scss/form_view.scss b/addons/web/static/src/scss/form_view.scss index 532d3ab3de4..2744f1b1080 100644 --- a/addons/web/static/src/scss/form_view.scss +++ b/addons/web/static/src/scss/form_view.scss @@ -575,14 +575,19 @@ } // Translate icon - .o_field_translate { - padding: 0; - cursor: pointer; - width: 35px !important; - vertical-align: top; - &::before { // seems to be useful for some old browser + span.o_field_translate { + padding: 0 $o-form-spacing-unit 0 0 !important; + vertical-align: top; position: relative; - content: attr(data-code); + margin-left: -35px; + width: 35px !important; // important is usefull for textarea + display: inline-block; + text-align: right; + border: none; // usefull for textarea + } + input, textarea { + &.o_field_translate { + padding-right: 25px; } } diff --git a/addons/web/static/src/scss/form_view_extra.scss b/addons/web/static/src/scss/form_view_extra.scss index 77b713784fe..b3157b0ace6 100644 --- a/addons/web/static/src/scss/form_view_extra.scss +++ b/addons/web/static/src/scss/form_view_extra.scss @@ -80,11 +80,6 @@ font-weight: bold; } - // Translate icon - .o_field_translate { - margin-left: -35px; - } - // Specific style classes .o_group.o_inner_group.oe_subtotal_footer { .oe_subtotal_footer_separator { diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss index 4b480ce3367..0d29c3cec21 100644 --- a/addons/web/static/src/scss/list_view.scss +++ b/addons/web/static/src/scss/list_view.scss @@ -257,9 +257,16 @@ @include o-position-absolute(0, 0); } } - > .o_field_translate { - @include o-position-absolute(0, 5px); - width: 16px; // to have some space on the right + > input.o_field_translate, textarea.o_field_translate { + padding-right: 25px; + + span.o_field_translate { + margin-left: -35px; + padding: 0px 1px; + text-align: right; + width: 35px; + vertical-align: top; + font-size: 12px; + } } > .o_row_handle { visibility: hidden; // hide sequence when editing diff --git a/addons/web/static/src/scss/translation_dialog.scss b/addons/web/static/src/scss/translation_dialog.scss new file mode 100644 index 00000000000..477f9371454 --- /dev/null +++ b/addons/web/static/src/scss/translation_dialog.scss @@ -0,0 +1,10 @@ +// translation dialog +.o_translation_dialog { + .o_language_current { + font-weight: bold; + } + + .row { + margin-bottom: 9px; + } +} diff --git a/addons/web/static/src/xml/translation_dialog.xml b/addons/web/static/src/xml/translation_dialog.xml new file mode 100644 index 00000000000..32e2a036e45 --- /dev/null +++ b/addons/web/static/src/xml/translation_dialog.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<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'"> + <t t-esc="term.langName"></t> + </div> + <div class="col-12 col-lg-9 translation"> + <input t-if="!widget.isText" type="text" + class="o_field_char o_input" + t-att-value="term.value" + t-att-data-id="term.id"/> + + <textarea t-if="widget.isText" + t-esc="term.value" + t-att-data-id="term.id" + rows="5" + class="o_field_text o_field_translate o_field_widget o_input"> + </textarea> + </div> + </div> + </div> +</templates> diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index a5620212500..e8060e23e58 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -124,6 +124,19 @@ QUnit.module('basic_fields', { position: "after", }] }, + "ir.translation": { + fields: { + lang_code: {type: "char"}, + value: {type: "char"}, + res_id: {type: "integer"} + }, + records: [{ + id: 99, + res_id: 37, + value: '', + lang_code: 'en_US' + }] + }, }; } }, function () { @@ -1059,7 +1072,7 @@ QUnit.module('basic_fields', { }); QUnit.test('char field translatable', async function (assert) { - assert.expect(3); + assert.expect(4); this.data.partner.fields.foo.translate = true; @@ -1081,7 +1094,13 @@ QUnit.module('basic_fields', { 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(); + return Promise.resolve({ + domain: [], + context: {search_default_name: 'partnes,foo'}, + }); + } + if (route === "/web/dataset/call_kw/res.lang/get_installed") { + return Promise.resolve([["en_US"], ["fr_BE"]]); } return this._super.apply(this, arguments); }, @@ -1089,10 +1108,27 @@ QUnit.module('basic_fields', { await testUtils.form.clickEdit(form); var $button = form.$('input[type="text"].o_field_char + .o_field_translate'); assert.strictEqual($button.length, 1, "should have a translate button"); + assert.strictEqual($button.text(), 'EN', 'the button should have as test the current language'); await testUtils.dom.click($button); + await testUtils.nextTick(); + + assert.containsOnce($(document), '.modal', 'a translate modal should be visible'); + form.destroy(); - form = await createView({ + _t.database.multi_lang = multiLang; + }); + + QUnit.test('char field translatable in create mode', async function (assert) { + assert.expect(1); + + 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, @@ -1104,8 +1140,8 @@ QUnit.module('basic_fields', { '</sheet>' + '</form>', }); - $button = form.$('input[type="text"].o_field_char + .o_field_translate'); - assert.strictEqual($button.length, 0, "should not have a translate button in create mode"); + var $button = form.$('input[type="text"].o_field_char + .o_field_translate'); + assert.strictEqual($button.length, 1, "should have a translate button in create mode"); form.destroy(); _t.database.multi_lang = multiLang; @@ -1716,7 +1752,13 @@ QUnit.module('basic_fields', { mockRPC: function (route, args) { if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { assert.deepEqual(args.args, ["partner",1,"txt"], 'should call "call_button" route'); - return Promise.resolve(); + return Promise.resolve({ + domain: [], + context: {search_default_name: 'partnes,foo'}, + }); + } + if (route === "/web/dataset/call_kw/res.lang/get_installed") { + return Promise.resolve([["en_US"], ["fr_BE"]]); } return this._super.apply(this, arguments); }, @@ -1725,9 +1767,19 @@ QUnit.module('basic_fields', { var $button = form.$('textarea + .o_field_translate'); assert.strictEqual($button.length, 1, "should have a translate button"); await testUtils.dom.click($button); + assert.containsOnce($(document), '.modal', 'there should be a translation modal'); form.destroy(); + _t.database.multi_lang = multiLang; + }); + + QUnit.test('text field translatable in create mode', async function (assert) { + assert.expect(1); + + this.data.partner.fields.txt.translate = true; - form = await createView({ + var multiLang = _t.database.multi_lang; + _t.database.multi_lang = true; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1739,8 +1791,8 @@ QUnit.module('basic_fields', { '</sheet>' + '</form>', }); - $button = form.$('textarea + .o_field_translate'); - assert.strictEqual($button.length, 0, "should not have a translate button in create mode"); + var $button = form.$('textarea + .o_field_translate'); + assert.strictEqual($button.length, 1, "should have a translate button in create mode"); form.destroy(); _t.database.multi_lang = multiLang; diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 24ebb0db105..60428ad552d 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -103,7 +103,8 @@ async function nextTick() { return Promise.all([ session.is_bound, ajax.loadXML('/web/static/src/xml/debug.xml', core.qweb), - ajax.loadXML('/web/static/src/xml/dialog.xml', core.qweb) + ajax.loadXML('/web/static/src/xml/dialog.xml', core.qweb), + ajax.loadXML('/web/static/src/xml/translation_dialog.xml', core.qweb), ]).then(function () { setTimeout(function () { // jquery autocomplete refines the search in a setTimeout() parameterized diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 1c0c9ae9485..8e4b2e82d02 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -104,6 +104,19 @@ QUnit.module('Views', { {id: 14, display_name: "silver", color: 5}, ] }, + "ir.translation": { + fields: { + lang_code: {type: "char"}, + value: {type: "char"}, + res_id: {type: "integer"} + }, + records: [{ + id: 99, + res_id: 12, + value: '', + lang_code: 'en_US' + }] + }, }; this.actions = [{ id: 1, @@ -6135,10 +6148,17 @@ QUnit.module('Views', { mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/product/get_formview_id') { return Promise.resolve(false); - } else if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { + } + if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { assert.deepEqual(args.args, ["product",37,"name"], 'should call "call_button" route'); nbTranslateCalls++; - return Promise.resolve(); + return Promise.resolve({ + domain: [], + context: {search_default_name: 'partnes,foo'}, + }); + } + if (route === "/web/dataset/call_kw/res.lang/get_installed") { + return Promise.resolve([["en_US"], ["fr_BE"]]); } return this._super.apply(this, arguments); }, @@ -6146,12 +6166,10 @@ QUnit.module('Views', { await testUtils.form.clickEdit(form); await testUtils.dom.click(form.$('[name="product_id"] .o_external_button')); - - assert.strictEqual($('.modal-body .o_field_translate').length, 1, + assert.containsOnce($('.modal-body'), 'span.o_field_translate', "there should be a translate button in the modal"); - await testUtils.dom.click($('.modal-body .o_field_translate')); - + await testUtils.dom.click($('.modal-body span.o_field_translate')); assert.strictEqual(nbTranslateCalls, 1, "should call_button translate once"); form.destroy(); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 546af1638fa..e49bd499c42 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -94,6 +94,30 @@ QUnit.module('Views', { {id: "2-20170808020000", name: "virtual"}, ] }, + "ir.translation": { + fields: { + lang_code: {type: "char"}, + value: {type: "char"}, + res_id: {type: "integer"}, + name: {type: "char"}, + lang: {type: "char"}, + }, + records: [{ + id: 99, + res_id: 1, + value: '', + lang_code: 'en_US', + lang: 'en_US', + name: 'foo,foo' + },{ + id: 100, + res_id: 1, + value: '', + lang_code: 'fr_BE', + lang: 'fr_BE', + name: 'foo,foo' + }] + }, }; } }, function () { @@ -2007,6 +2031,48 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('fields are translatable in list view', async function (assert) { + assert.expect(3); + + var multiLang = _t.database.multi_lang; + _t.database.multi_lang = true; + this.data.foo.fields.foo.translate = true; + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + mockRPC: function (route, args) { + if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { + return Promise.resolve({ + domain: [], + context: {search_default_name: 'foo,foo'}, + }); + } + if (route === "/web/dataset/call_kw/res.lang/get_installed") { + return Promise.resolve([["en_US","English"], ["fr_BE", "Frenglish"]]); + } + return this._super.apply(this, arguments); + }, + arch: '<tree editable="top">' + + '<field name="foo" required="1"/>' + + '</tree>', + }); + + await testUtils.dom.click(list.$('.o_data_row:first > td:not(.o_list_record_selector)').first()); + assert.hasClass(list.$('.o_data_row:first'), 'o_selected_row'); + + await testUtils.dom.click(list.$('input.o_field_translate+span.o_field_translate')); + await testUtils.nextTick(); + + assert.containsOnce($('body'), '.o_translation_dialog'); + assert.containsN($('.o_translation_dialog'), '.translation>input.o_field_char', 2, + 'modal should have 2 languages to translate'); + + _t.database.multi_lang = multiLang; + list.destroy(); + }); + QUnit.test('long words in text cells should break into smaller lines', async function (assert) { assert.expect(2); |
