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;