diff --git a/addons/web/static/src/legacy/legacy_fields.js b/addons/web/static/src/legacy/legacy_fields.js index e67dfb2ff64..20744407f97 100644 --- a/addons/web/static/src/legacy/legacy_fields.js +++ b/addons/web/static/src/legacy/legacy_fields.js @@ -8,6 +8,7 @@ import { ComponentAdapter } from "web.OwlCompatibility"; import viewUtils from "web.viewUtils"; import { useWowlService } from "@web/legacy/utils"; import { RPCError } from "@web/core/network/rpc_service"; +import { useTranslationDialog } from "@web/views/fields/translation_button"; const { Component, useEffect, xml } = owl; const fieldRegistry = registry.category("fields"); @@ -22,6 +23,7 @@ const legacyFieldTemplate = xml` class FieldAdapter extends ComponentAdapter { setup() { super.setup(); + this.translationDialog = useTranslationDialog(); this.wowlEnv = this.env; this.env = Component.env; this.orm = useWowlService("orm"); diff --git a/addons/web/static/src/views/fields/char/char_field.xml b/addons/web/static/src/views/fields/char/char_field.xml index 00132c8a29d..8496c353ff6 100644 --- a/addons/web/static/src/views/fields/char/char_field.xml +++ b/addons/web/static/src/views/fields/char/char_field.xml @@ -19,11 +19,8 @@ diff --git a/addons/web/static/src/views/fields/text/text_field.xml b/addons/web/static/src/views/fields/text/text_field.xml index 3d9d0a517ec..fbdf81d7166 100644 --- a/addons/web/static/src/views/fields/text/text_field.xml +++ b/addons/web/static/src/views/fields/text/text_field.xml @@ -18,11 +18,8 @@ diff --git a/addons/web/static/src/views/fields/translation_button.js b/addons/web/static/src/views/fields/translation_button.js index a9faf663eaa..769ecffaea0 100644 --- a/addons/web/static/src/views/fields/translation_button.js +++ b/addons/web/static/src/views/fields/translation_button.js @@ -3,14 +3,75 @@ import { localization } from "@web/core/l10n/localization"; import { useService } from "@web/core/utils/hooks"; import { TranslationDialog } from "./translation_dialog"; +import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; -const { Component } = owl; +const { Component, useEnv } = owl; + +/** + * Prepares a function that will open the dialog that allows to edit translation + * values for a given field. + * + * It is mainly a factorization of the feature that is also used + * in legacy_fields. We expect it to be fully implemented in TranslationButton + * when legacy code is removed. + */ +export function useTranslationDialog() { + const dialog = useService("dialog"); + const rpc = useService("rpc"); + const env = useEnv(); + + async function openTranslationDialog({ record, fieldName, updateField }) { + if (!record.resId) { + let _continue = true; + await new Promise((resolve) => { + dialog.add(ConfirmationDialog, { + async confirm() { + _continue = await record.save({ stayInEdition: true }); + resolve(); + }, + cancel() { + _continue = false; + resolve(); + }, + body: env._t( + "You need to save this new record before editing the translation. Do you want to proceed?" + ), + title: env._t("Warning"), + }); + }); + if (!_continue) { + return; + } + } + const { resModel, resId, context } = record; + const result = await rpc("/web/dataset/call_button", { + model: "ir.translation", + method: "translate_fields", + args: [resModel, resId, fieldName], + kwargs: { context }, + }); + + dialog.add(TranslationDialog, { + domain: result.domain, + searchName: result.context.search_default_name, + fieldName: fieldName, + userLanguageValue: record.data[fieldName] || "", + isComingFromTranslationAlert: false, + isText: result.context.translation_type === "text", + showSource: result.context.translation_show_src, + updateField, + }); + } + + return openTranslationDialog; +} export class TranslationButton extends Component { setup() { this.user = useService("user"); this.rpc = useService("rpc"); this.dialog = useService("dialog"); + this.translationDialog = useTranslationDialog(); } get isMultiLang() { @@ -20,24 +81,9 @@ export class TranslationButton extends Component { return this.user.lang.split("_")[0].toUpperCase(); } - async onClick() { - const result = await this.rpc("/web/dataset/call_button", { - model: "ir.translation", - method: "translate_fields", - args: [this.props.resModel, this.props.resId, this.props.fieldName], - kwargs: { context: this.props.context }, - }); - - this.dialog.add(TranslationDialog, { - domain: result.domain, - searchName: result.context.search_default_name, - fieldName: this.props.fieldName, - userLanguageValue: this.props.value || "", - isComingFromTranslationAlert: false, - isText: result.context.translation_type === "text", - showSource: result.context.translation_show_src, - updateField: this.props.updateField, - }); + onClick() { + const { fieldName, record, updateField } = this.props; + this.translationDialog({ fieldName, record, updateField }); } } TranslationButton.template = "web.TranslationButton"; diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index 9fefb804c36..d463e92bb97 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -20,6 +20,7 @@ import { triggerEvent, } from "@web/../tests/helpers/utils"; import { FieldOne2Many } from "web.relational_fields"; +import { FieldChar as LegacyFieldChar } from "web.basic_fields"; import * as legacyFieldRegistry from "web.field_registry"; import { toggleActionMenu, toggleGroupByMenu, toggleMenuItem } from "@web/../tests/search/helpers"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; @@ -36,6 +37,8 @@ import BasicModel from "web.BasicModel"; import { localization } from "@web/core/l10n/localization"; import { SIZES } from "@web/core/ui/ui_service"; +import * as legacyCore from "web.core"; + const fieldRegistry = registry.category("fields"); const serviceRegistry = registry.category("services"); const widgetRegistry = registry.category("view_widgets"); @@ -8030,6 +8033,103 @@ QUnit.module("Views", (hooks) => { assert.strictEqual(target.querySelector(".modal-title").textContent, "Translate: foo"); }); + QUnit.test("ask to save new record before opening translate dialog", async function (assert) { + serverData.models.partner.fields.foo.translate = true; + + patchWithCleanup(localization, { + multiLang: true, + }); + + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + `, + mockRPC(route, args) { + assert.step(args.method); + if (route === "/web/dataset/call_button") { + return { context: {}, domain: [] }; + } + if (args.method === "get_installed") { + return [ + ["CUST", "custom lang"], + ["CUST2", "second custom"], + ]; + } + }, + }); + + assert.verifySteps(["get_views", "onchange"]); + assert.containsOnce(target, ".o_form_editable"); + await click(target, ".o_field_translate.btn-link"); + assert.containsOnce(target, ".modal"); + assert.strictEqual(target.querySelector(".modal-title").textContent, "Warning"); + + await click(target.querySelectorAll(".modal-footer button")[1]); // cancel + assert.verifySteps([]); + + await click(target, ".o_field_translate.btn-link"); + await click(target.querySelectorAll(".modal-footer button")[0]); // save + assert.verifySteps(["create", "read", "translate_fields", "get_installed", "search_read"]); + assert.containsOnce(target, ".modal"); + assert.strictEqual(target.querySelector(".modal-title").textContent, "Translate: foo"); + }); + + QUnit.test("legacy field support translation dialog", async (assert) => { + serverData.models.partner.fields.foo.translate = true; + + patchWithCleanup(legacyCore._t.database, { + multi_lang: true, + parameters: { + code: "CUST", + }, + }); + + const myChar = LegacyFieldChar.extend({ + async _render() { + // DIY because the compatibility layer doesn't support + // legacy widget having multiple nodes in their this.$el. + await this._super(...arguments); + const div = document.createElement("div"); + const $div = $(div).append(this.$el); + this.setElement($div); + }, + }); + + legacyFieldRegistry.add("legacy_char", myChar); + await makeView({ + type: "form", + resModel: "partner", + serverData, + resId: 1, + arch: ` +
+ + `, + mockRPC(route, args) { + assert.step(args.method); + if (route === "/web/dataset/call_button") { + return { context: {}, domain: [] }; + } + if (args.method === "get_installed") { + return [ + ["CUST", "custom lang"], + ["CUST2", "second custom"], + ]; + } + }, + }); + assert.verifySteps(["get_views", "read"]); + await clickEdit(target); + await click(target, ".o_field_legacy_char .o_field_translate.btn-link"); + assert.verifySteps(["translate_fields", "get_installed", "search_read"]); + assert.containsOnce(target, ".modal"); + assert.strictEqual(target.querySelector(".modal-title").textContent, "Translate: foo"); + }); + QUnit.test("translation alerts are preserved on pager change", async function (assert) { serverData.models.partner.fields.foo.translate = true;