Public release from ruodoo-project: 19.0 - 2026-05-31 21:19:12 UTC

This commit is contained in:
CI Publish Bot
2026-05-31 21:19:21 +00:00
commit aa4214c195
1213 changed files with 183945 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,13 @@
/** @odoo-module **/
import * as envModule from "@web/env";
import {patch} from "@web/core/utils/patch";
const originalMakeEnv = envModule.makeEnv;
patch(envModule, {
async makeEnv() {
const env = await originalMakeEnv(...arguments);
env.translate = odoo.translate;
return env;
},
});

View File

@ -0,0 +1,54 @@
/** @odoo-module */
import {Dropdown} from "@web/core/dropdown/dropdown";
import {DropdownItem} from "@web/core/dropdown/dropdown_item";
import {FormLabel} from "@web/views/form/form_label";
import {TranslationDialog} from "@web/views/fields/translation_dialog";
import {patch} from "@web/core/utils/patch";
import {browser} from "@web/core/browser/browser";
patch(FormLabel, {
components: {
...FormLabel.components,
Dropdown,
DropdownItem,
},
});
patch(FormLabel.prototype, {
setup() {
super.setup(...arguments);
this.hasTranslation = this.getHasTranslation();
},
getHasTranslation() {
return Boolean(odoo.translate);
},
async onClickTranslate(attributeName) {
const modelId = await this.env.services.orm.searchRead("ir.model", [["model", "=", this.props.record.resModel]], ["id"]);
const fieldRecordId = await this.env.services.orm.searchRead(
"ir.model.fields",
[
["model_id", "=", modelId[0].id],
["name", "=", this.props.fieldName],
],
["id"]
);
this.env.services.dialog.add(TranslationDialog, {
fieldName: attributeName,
resId: fieldRecordId[0].id,
resModel: "ir.model.fields",
userLanguageValue: "",
userUserCurrentLang: true,
isComingFromTranslationAlert: false,
onSave: async () => {
this.env.bus.trigger("CLEAR-CACHES");
// Full page reload — preserves current URL including ?translate=1
browser.location.reload();
},
});
},
});
FormLabel.template = "translation_link_generation.FormLabel";

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="translation_link_generation.FormLabel" t-inherit="web.FormLabel">
<xpath expr="//sup" position="after">
<Dropdown t-if="hasTranslation">
<button type="button" class="btn btn-link btn-primary py-0 px-1">
<i class="fa fa-language" />
</button>
<t t-set-slot="content">
<t t-if="tooltipHelp">
<DropdownItem onSelected="() => this.onClickTranslate('help')">Help</DropdownItem>
</t>
<DropdownItem onSelected="() => this.onClickTranslate('field_description')">String</DropdownItem>
</t>
</Dropdown>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,96 @@
/** @odoo-module */
import {_t, loadLanguages} from "@web/core/l10n/translation";
import {jsToPyLocale} from "@web/core/l10n/utils";
import {TranslationDialog} from "@web/views/fields/translation_dialog";
import {user} from "@web/core/user";
import {onWillStart} from "@odoo/owl";
import {patch} from "@web/core/utils/patch";
import {useService} from "@web/core/utils/hooks";
// Add userUserCurrentLang prop that doesn't exist in Odoo 19's TranslationDialog
TranslationDialog.props = {
...TranslationDialog.props,
userUserCurrentLang: {type: Boolean, optional: true},
};
patch(TranslationDialog.prototype, {
setup() {
this.title = _t("Translate: %s", this.props.fieldName);
this.user = user;
this.orm = useService("orm");
this.terms = [];
this.updatedTerms = {};
this.translateIsCallable = false;
onWillStart(async () => {
const allLanguages = await loadLanguages(this.orm);
const languages = this.props.userUserCurrentLang
? allLanguages.filter((l) => l[0] === jsToPyLocale(user.lang))
: allLanguages;
const [translations, context] = await this.loadTranslations(languages);
this.translateIsCallable = Boolean(context.translation_show_source);
let id = 1;
translations.forEach((t) => (t.id = id++));
this.props.isText = context.translation_type === "text";
this.props.showSource = context.translation_show_source;
this.terms = translations.map((term) => {
const relatedLanguage = languages.find((l) => l[0] === term.lang);
const termInfo = {
...term,
langName: relatedLanguage ? relatedLanguage[1] : term.lang,
value: term.value || "",
};
if (
term.lang === jsToPyLocale(user.lang) &&
!this.props.showSource &&
!this.props.isComingFromTranslationAlert &&
this.props.userLanguageValue
) {
this.updatedTerms[term.id] = this.props.userLanguageValue;
termInfo.value = this.props.userLanguageValue;
}
return termInfo;
});
this.terms.sort((a, b) => a.langName.localeCompare(b.langName));
});
},
async loadTranslations(languages) {
const langs = languages.map((l) => l[0]);
return this.orm.call(
this.props.resModel,
"get_field_translations",
[[this.props.resId], this.props.fieldName, langs]
);
},
async onSave() {
const translations = {};
this.terms.forEach((term) => {
const updatedTermValue = this.updatedTerms[term.id];
if (term.id in this.updatedTerms && term.value !== updatedTermValue) {
if (this.translateIsCallable) {
if (!translations[term.lang]) {
translations[term.lang] = {};
}
const oldTermValue = term.value || term.source;
translations[term.lang][oldTermValue] = updatedTermValue || term.source;
translations[term.lang].source = term.source;
} else {
translations[term.lang] = updatedTermValue || false;
}
}
});
await this.orm.call(
this.props.resModel,
"update_field_translations",
[[this.props.resId], this.props.fieldName, translations]
);
await this.props.onSave();
this.props.close();
},
});

View File

@ -0,0 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 5h12m-6-2v2m1.0482 9.5c-1.52737-1.5822-2.76747-3.4435-3.63633-5.5m6.08813 9h7m-8.5 3 5-10 5 10m-8.2489-16c-.968 5.7702-4.68141 10.6095-9.7511 13.129" stroke="#4a5568" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 345 B

View File

@ -0,0 +1,53 @@
/** @odoo-module **/
// In Odoo 19, Dropdown uses an inline xml`` template (no QWeb template named web.Dropdown).
// MenuTranslationButton is injected into DropdownItem via JS patch + XML template inheritance
// of web.DropdownItem (NOT web.NavBar.DropdownItem which is unused as a component template).
// NavBar is also patched to support translate buttons on section buttons with children.
import {DropdownItem} from "@web/core/dropdown/dropdown_item";
import {NavBar} from "@web/webclient/navbar/navbar";
import {MenuTranslationButton} from "./menu_translation_button";
import {patch} from "@web/core/utils/patch";
// Patch DropdownItem — for leaf menu items (no children)
patch(DropdownItem, {
components: {
...DropdownItem.components,
MenuTranslationButton,
},
});
patch(DropdownItem.prototype, {
setup() {
super.setup(...arguments);
this.hasTranslation = this.getHasTranslation();
},
onClick(ev) {
if (ev.target.classList.contains("translate-middle-y")) {
return;
}
super.onClick(ev);
},
getHasTranslation() {
return Boolean(odoo.translate);
},
});
// Patch NavBar — for section buttons with children (Dropdown-based)
patch(NavBar, {
components: {
...NavBar.components,
MenuTranslationButton,
},
});
patch(NavBar.prototype, {
setup() {
super.setup(...arguments);
// expose to template via __translate__ variable
this.__translate__ = Boolean(odoo.translate);
},
});

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<!-- Patch SectionsMenu: add translate button inside Dropdown-based section buttons (items with children) -->
<t t-name="translation_helper.NavBar.SectionsMenu" t-inherit="web.NavBar.SectionsMenu" t-inherit-mode="extension">
<xpath expr="//button[@t-att-data-menu-xmlid]/span" position="after">
<MenuTranslationButton t-if="__translate__" menuXmlId="section.xmlid" />
</xpath>
</t>
<!-- Patch MenuSlot: add translate button after group headers (items with children inside dropdown) -->
<t t-name="translation_helper.NavBar.SectionsMenu.Dropdown.MenuSlot" t-inherit="web.NavBar.SectionsMenu.Dropdown.MenuSlot" t-inherit-mode="extension">
<xpath expr="//div[hasclass('dropdown-menu_group')]" position="after">
<MenuTranslationButton t-if="__translate__ and item.xmlid" menuXmlId="item.xmlid" />
</xpath>
</t>
</templates>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="translation_helper.DropdownItem" t-inherit="web.DropdownItem" t-inherit-mode="extension">
<xpath expr="//t[@t-slot='default']" position="after">
<MenuTranslationButton t-if="hasTranslation and props.attrs and props.attrs['data-menu-xmlid']" menuXmlId="props.attrs['data-menu-xmlid']" />
</xpath>
</t>
</templates>

View File

@ -0,0 +1,92 @@
/** @odoo-module **/
import {Component, useState} from "@odoo/owl";
import {useOwnedDialogs, useService} from "@web/core/utils/hooks";
import {TranslationDialog} from "@web/views/fields/translation_dialog";
/**
* @param {Object} env - OWL environment from the component (this.env)
* @returns {Function} openTranslationDialog
*/
export function useTranslationDialog(env) {
const addDialog = useOwnedDialogs();
async function openTranslationDialog({name, id}) {
addDialog(TranslationDialog, {
fieldName: "name",
resId: id,
resModel: "ir.ui.menu",
userLanguageValue: name || "",
isComingFromTranslationAlert: false,
userUserCurrentLang: true,
onSave: async () => {
env.bus.trigger("CLEAR-CACHES");
await env.services.action.doAction({
type: "ir.actions.client",
tag: "reload",
});
},
});
}
return openTranslationDialog;
}
export class MenuTranslationButton extends Component {
setup() {
this.orm = useService("orm");
this.state = useState({record: {}});
this.translationDialog = useTranslationDialog(this.env);
}
async onClick() {
const xmlId = this.props.menuXmlId;
const [module, name] = xmlId.split(".");
if (!module || !name) {
console.error("Неверный формат menuXmlId");
return;
}
try {
const [menuIdRecord] = await this.orm.searchRead(
"ir.model.data",
[
["model", "=", "ir.ui.menu"],
["name", "=", name],
["module", "=", module],
],
["res_id"]
);
if (!menuIdRecord) {
console.error("Меню не найдено по XML ID");
return;
}
const menuId = menuIdRecord.res_id;
const [menuRecord] = await this.orm.searchRead("ir.ui.menu", [["id", "=", menuId]], ["id", "name", "action"]);
if (!menuRecord) {
console.error("Запись меню не найдена");
return;
}
this.state.record = menuRecord;
this.translationDialog({
name: menuRecord.name,
id: menuRecord.id,
});
} catch (error) {
console.error("Ошибка при получении меню:", error);
}
}
}
MenuTranslationButton.template = "translation_helper.MenuTranslationButton";
MenuTranslationButton.props = {
menuXmlId: {
type: String,
optional: false,
},
};

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="translation_helper.MenuTranslationButton">
<i class="fa fa-language translate-middle-y" style="margin-left: 5px;" aria-hidden="true" t-on-click.prevent.stop="onClick" />
</t>
</templates>

View File

@ -0,0 +1,14 @@
/** @odoo-module **/
import * as viewHookModule from "@web/views/view_hook";
import { patch } from "@web/core/utils/patch";
import { useComponent } from "@odoo/owl";
import { useTranslateCategory } from "@translation_helper/translate/translate_context";
const originalUseSetupView = viewHookModule.useSetupView;
patch(viewHookModule, {
useSetupView(params) {
useTranslateCategory("view", { component: useComponent() });
return originalUseSetupView(params);
},
});

View File

@ -0,0 +1,12 @@
/** @odoo-module **/
import {registry} from "@web/core/registry";
async function handleResponseFromWizard(env) {
const actionService = env.services.action;
env.bus.trigger("CLEAR-CACHES");
await actionService.doAction({
type: "ir.actions.client",
tag: "soft_reload",
});
}
registry.category("actions").add("translation_helper.response_from_wizard", handleResponseFromWizard);

View File

@ -0,0 +1,16 @@
/** @odoo-module */
import {ResConfigDevTool} from "@web/webclient/settings_form_view/widgets/res_config_dev_tool";
import {patch} from "@web/core/utils/patch";
import {router} from "@web/core/browser/router";
patch(ResConfigDevTool.prototype, {
setup() {
super.setup(...arguments);
this.isTranslate = Boolean(odoo.translate);
},
activateTranslate(value) {
router.pushState({translate: value}, {reload: true});
},
});

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="res_config_dev_tool" t-inherit-mode="extension">
<xpath expr="//div[@id='developer_tool']" position="after">
<div id="translator_tool">
<SettingsBlock title.translate="Translator Tools">
<Setting addLabel="false">
<a t-if="!isTranslate" class="d-block" t-on-click.prevent="() => this.activateTranslate(1)" href="#">Activate the translate mode</a>
<a t-if="isTranslate" class="d-block" t-on-click.prevent="() => this.activateTranslate(0)" href="#">Deactivate the translate mode</a>
</Setting>
</SettingsBlock>
</div>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,4 @@
/** @odoo-module **/
export * from "./translate_context";
export { TranslateMenu } from "./translate_menu";
export { TranslateMenuItems } from "./translate_menu_items";

View File

@ -0,0 +1,6 @@
.o_dialog {
.o_translate_manager .dropdown-toggle {
padding: 0 4px;
margin: 2px 10px 2px 0;
}
}

View File

@ -0,0 +1,57 @@
/** @odoo-module **/
import { useEffect, useEnv, useSubEnv } from "@odoo/owl";
import { registry } from "@web/core/registry";
const translateRegistry = registry.category("translate");
const translateContextSymbol = Symbol("translateContext");
class TranslateContext {
constructor(env, defaultCategories = []) {
this.env = env;
this.categories = new Map(defaultCategories.map(cat => [cat, [{}]]));
}
activateCategory(category, context) {
const contexts = this.categories.get(category) || new Set();
contexts.add(context);
this.categories.set(category, contexts);
return () => {
contexts.delete(context);
if (!contexts.size) this.categories.delete(category);
};
}
async getItems() {
return [...this.categories.entries()]
.flatMap(([category, contexts]) =>
translateRegistry
.category(category)
.getAll()
.map(factory => factory({ env: this.env, ...Object.assign({}, ...contexts) }))
)
.filter(Boolean)
.sort((a, b) => (a.sequence || 1000) - (b.sequence || 1000));
}
}
export function createTranslateContext(env, { categories = [] } = {}) {
return { [translateContextSymbol]: new TranslateContext(env, categories) };
}
export function useOwnTranslateContext({ categories = [] } = {}) {
useSubEnv(createTranslateContext(useEnv(), { categories }));
}
export function useEnvTranslateContext() {
const translateContext = useEnv()[translateContextSymbol];
if (!translateContext) throw new Error("No translate context in environment");
return translateContext;
}
export function useTranslateCategory(category, context = {}) {
const env = useEnv();
if (env.translate) {
const translateContext = useEnvTranslateContext();
useEffect(() => translateContext.activateCategory(category, context), () => []);
}
}

View File

@ -0,0 +1,89 @@
/** @odoo-module **/
import {useEffect, useEnv, useSubEnv} from "@odoo/owl";
import {memoize} from "@web/core/utils/functions";
import {registry} from "@web/core/registry";
const translateRegistry = registry.category("translate");
const getAccessRights = memoize(async function getAccessRights(orm) {
const rightsToCheck = {
"ir.ui.view": "write",
"ir.rule": "read",
"ir.model.access": "read",
};
const proms = Object.entries(rightsToCheck).map(([model, operation]) => {
return orm.call(model, "check_access_rights", [], {
operation,
raise_exception: false,
});
});
const [canEditView, canSeeRecordRules, canSeeModelAccess] = await Promise.all(proms);
const accessRights = {canEditView, canSeeRecordRules, canSeeModelAccess};
return accessRights;
});
class TranslateContext {
constructor(env, defaultCategories) {
this.orm = env.services.orm;
this.categories = new Map(defaultCategories.map((cat) => [cat, new Set()]));
}
activateCategory(category, context) {
const contexts = this.categories.get(category) || new Set();
contexts.add(context);
this.categories.set(category, contexts);
return () => {
contexts.delete(context);
if (contexts.size === 0) {
this.categories.delete(category);
}
};
}
async getItems(env) {
const accessRights = await getAccessRights(this.orm);
return [...this.categories.entries()]
.flatMap(([category, contexts]) => {
return translateRegistry
.category(category)
.getAll()
.map((factory) => factory(Object.assign({env, accessRights}, ...contexts)));
})
.filter(Boolean)
.sort((x, y) => {
const xSeq = x.sequence || 1000;
const ySeq = y.sequence || 1000;
return xSeq - ySeq;
});
}
}
const translateContextSymbol = Symbol("translateContext");
export function createTranslateContext(env, {categories = []} = {}) {
return {[translateContextSymbol]: new TranslateContext(env, categories)};
}
export function useOwnTranslateContext({categories = []} = {}) {
useSubEnv(createTranslateContext(useEnv(), {categories}));
}
export function useEnvTranslateContext() {
const translateContext = useEnv()[translateContextSymbol];
if (!translateContext) {
throw new Error("There is no translate context available in the current environment.");
}
return translateContext;
}
export function useTranslateCategory(category, context = {}) {
const env = useEnv();
if (env.translate) {
const translateContext = useEnvTranslateContext();
useEffect(
() => translateContext.activateCategory(category, context),
() => []
);
}
}

View File

@ -0,0 +1,82 @@
/** @odoo-module */
import {Component} from "@odoo/owl";
import {Dialog} from "@web/core/dialog/dialog";
import {_t} from "@web/core/l10n/translation";
import {editModelTranslate} from "@translation_helper/translate/translate_utils";
import {registry} from "@web/core/registry";
const translateRegistry = registry.category("translate");
function viewSeparator() {
return {type: "separator", sequence: 300};
}
// ------------------------------------------------------------------------------
// Get view
// ------------------------------------------------------------------------------
class GetViewDialog extends Component {
setup() {
this.title = _t("Get View");
}
}
GetViewDialog.template = "web.TranslateMenu.GetViewDialog";
GetViewDialog.components = {Dialog};
GetViewDialog.props = {
arch: {type: Element},
close: {type: Function},
};
// // ------------------------------------------------------------------------------
// // Edit View
// // ------------------------------------------------------------------------------
export function editView({accessRights, component, env}) {
if (!accessRights.canEditView) {
return null;
}
const {viewId, viewType: type} = component.env.config || {};
if (!type) {
return;
}
const displayName = type[0].toUpperCase() + type.slice(1);
const description = _t("Translate View: ") + displayName;
return {
type: "item",
description,
callback: () => {
editModelTranslate(env, description, "ir.ui.view", viewId, "soft_reload");
},
sequence: 350,
};
}
translateRegistry.category("view").add("editView", editView);
translateRegistry.category("view").add("viewSeparator", viewSeparator);
// ------------------------------------------------------------------------------
// Edit SearchView
// ------------------------------------------------------------------------------
export function editSearchView({accessRights, component, env}) {
if (!accessRights.canEditView) {
return null;
}
const {searchViewId} = component.props.info || {};
if (searchViewId === undefined) {
return null;
}
const description = _t("Translate SearchView");
return {
type: "item",
description,
callback: () => {
editModelTranslate(env, description, "ir.ui.view", searchViewId, "reload");
},
sequence: 350,
};
}
translateRegistry.category("view").add("editSearchView", editSearchView);

View File

@ -0,0 +1,61 @@
/** @odoo-module **/
import {Dropdown} from "@web/core/dropdown/dropdown";
import {DropdownItem} from "@web/core/dropdown/dropdown_item";
import {TranslateMenuBasic} from "@translation_helper/translate/translate_menu_basic";
import {_t} from "@web/core/l10n/translation";
import {useCommand} from "@web/core/commands/command_hook";
import {useEnvTranslateContext} from "./translate_context";
import {useService} from "@web/core/utils/hooks";
export class TranslateMenu extends TranslateMenuBasic {
setup() {
super.setup();
const translateContext = useEnvTranslateContext();
this.command = useService("command");
useCommand(
_t("Translate tools..."),
async () => {
const items = await translateContext.getItems(this.env);
let index = 0;
const defaultCategories = items.filter((item) => item.type === "separator").map(() => (index += 1));
const provider = {
async provide() {
const categories = [...defaultCategories];
let category = categories.shift();
const result = [];
items.forEach((item) => {
if (item.type === "item") {
result.push({
name: item.description.toString(),
action: item.callback,
category,
});
} else if (item.type === "separator") {
category = categories.shift();
}
});
return result;
},
};
const configByNamespace = {
default: {
categories: defaultCategories,
emptyMessage: _t("No translate command found"),
placeholder: _t("Choose a translate command..."),
},
};
const commandPaletteConfig = {
configByNamespace,
providers: [provider],
};
return commandPaletteConfig;
},
{
category: "translate",
}
);
}
}
TranslateMenu.components = {Dropdown, DropdownItem};
TranslateMenu.props = {};

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="web.TranslateMenu">
<Dropdown
class="'o_translate_manager'"
beforeOpen="getElements"
position="'bottom-end'"
menuClass="env.inDialog ? 'btn btn-link' : ''"
>
<button type="button" class="o-dropdown--narrow btn btn-link">
<i class="fa fa-language" role="img" aria-label="Open translate tools" />
</button>
<t t-set-slot="content">
<t t-foreach="elements" t-as="element" t-key="element_index">
<DropdownItem t-if="element.type == 'item'" onSelected="element.callback" t-att-attrs="element.href ? {href: element.href} : undefined">
<t t-esc="element.description" />
</DropdownItem>
<div t-if="element.type == 'separator'" role="separator" class="dropdown-divider" />
<t t-if="element.type == 'component'" t-component="element.Component" t-props="element.props" />
</t>
</t>
</Dropdown>
</t>
</templates>

View File

@ -0,0 +1,21 @@
/** @odoo-module **/
import {Component} from "@odoo/owl";
import {Dropdown} from "@web/core/dropdown/dropdown";
import {DropdownItem} from "@web/core/dropdown/dropdown_item";
import {useEnvTranslateContext} from "./translate_context";
export class TranslateMenuBasic extends Component {
setup() {
const translateContext = useEnvTranslateContext();
// Needs to be bound to this for use in template
this.getElements = async () => {
this.elements = await translateContext.getItems(this.env);
};
}
}
TranslateMenuBasic.components = {
Dropdown,
DropdownItem,
};
TranslateMenuBasic.template = "web.TranslateMenu";

View File

@ -0,0 +1,21 @@
/** @odoo-module **/
import {_t} from "@web/core/l10n/translation";
import {browser} from "@web/core/browser/browser";
import {registry} from "@web/core/registry";
import {routeToUrl} from "@web/core/browser/router";
function leaveTranslateMode({env}) {
return {
type: "item",
description: _t("Leave the Translate Tools"),
callback: () => {
const route = env.services.router.current;
route.search.translate = "";
browser.location.href = browser.location.origin + routeToUrl(route);
},
sequence: 450,
};
}
registry.category("translate").category("default").add("leaveTranslateMode", leaveTranslateMode);

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="web.TranslateMenu.SetDefaultDialog">
<Dialog title="title">
<table style="width: 100%">
<tr>
<td>
<label for="formview_default_fields" class="oe_label oe_align_right">
Default:
</label>
</td>
<td class="oe_form_required">
<select id="formview_default_fields" class="o_input" t-model="state.fieldToSet">
<option value="" />
<option t-foreach="defaultFields" t-as="field" t-att-value="field.name" t-key="field.name">
<t t-esc="field.string" /> = <t t-esc="field.displayed" />
</option>
</select>
</td>
</tr>
<tr t-if="conditions.length">
<td>
<label for="formview_default_conditions" class="oe_label oe_align_right">
Condition:
</label>
</td>
<td>
<select id="formview_default_conditions" class="o_input" t-model="state.condition">
<option value="" />
<option t-foreach="conditions" t-as="cond" t-att-value="cond.name + '=' + cond.value" t-key="cond.name">
<t t-esc="cond.string" />=<t t-esc="cond.displayed" />
</option>
</select>
</td>
</tr>
<tr>
<td colspan="2">
<input type="radio" id="formview_default_self" value="self" name="scope" t-model="state.scope" />
<label for="formview_default_self" class="oe_label" style="display: inline;">
Only you
</label>
<br />
<input type="radio" id="formview_default_all" value="all" name="scope" t-model="state.scope" />
<label for="formview_default_all" class="oe_label" style="display: inline;">
All users
</label>
</td>
</tr>
</table>
<t t-set-slot="footer">
<button class="btn btn-secondary" t-on-click="props.close">Close</button>
<button class="btn btn-secondary" t-on-click="saveDefault">Save default</button>
</t>
</Dialog>
</t>
<t t-name="web.TranslateMenu.GetMetadataDialog">
<Dialog title="title">
<table class="table table-sm table-striped">
<tr>
<th>ID:</th>
<td><t t-esc="state.id" /></td>
</tr>
<tr>
<th>XML ID:</th>
<td>
<t t-if='state.xmlids.length > 1'>
<t t-foreach="state.xmlids" t-as="imd" t-key="imd['xmlid']">
<div
t-att-class='"p-0 " + (imd["xmlid"] === state.xmlid ? "fw-bolder " : "") + (imd["noupdate"] === true ? "fst-italic " : "")'
t-esc="imd['xmlid']"
/>
</t>
</t>
<t t-elif="state.xmlid" t-esc="state.xmlid" />
<t t-else="">
/ <a t-on-click="onClickCreateXmlid"> (create)</a>
</t>
</td>
</tr>
<tr>
<th>No Update:</th>
<td>
<t t-esc="state.noupdate" />
<t t-if="state.xmlid">
<a t-on-click="toggleNoupdate"> (change)</a>
</t>
</td>
</tr>
<tr>
<th>Creation User:</th>
<td><t t-esc="state.creator" /></td>
</tr>
<tr>
<th>Creation Date:</th>
<td><t t-esc="state.createDate" /></td>
</tr>
<tr>
<th>Latest Modification by:</th>
<td><t t-esc="state.lastModifiedBy" /></td>
</tr>
<tr>
<th>Latest Modification Date:</th>
<td><t t-esc="state.writeDate" /></td>
</tr>
</table>
</Dialog>
</t>
<t t-name="web.TranslateMenu.GetViewDialog">
<Dialog title="title">
<pre t-esc="props.arch.outerHTML" />
<t t-set-slot="footer">
<button class="btn btn-primary o-default-button" t-on-click="() => props.close()">Close</button>
</t>
</Dialog>
</t>
</templates>

View File

@ -0,0 +1,21 @@
/** @odoo-module **/
import {TranslationDialog} from "@web/views/fields/translation_dialog";
export function editModelTranslate(env, title, model, id, reloadType) {
env.services.dialog.add(TranslationDialog, {
fieldName: "arch_db",
resId: id,
resModel: model,
userLanguageValue: env.services.user.lang,
userUserCurrentLang: true,
isComingFromTranslationAlert: false,
onSave: async () => {
env.bus.trigger("CLEAR-CACHES");
await env.services.action.doAction({
type: "ir.actions.client",
tag: reloadType,
});
},
});
}

View File

@ -0,0 +1,23 @@
/** @odoo-module **/
import {TranslateMenu} from "@translation_helper/translate/translate_menu";
import {WebClient} from "@web/webclient/webclient";
import {patch} from "@web/core/utils/patch";
import {registry} from "@web/core/registry";
import {useOwnTranslateContext} from "@translation_helper/translate/translate_context";
patch(WebClient.prototype, {
setup() {
super.setup();
useOwnTranslateContext({categories: ["default"]});
if (this.env.translate) {
registry.category("systray").add(
"web.translate_mode_menu",
{
Component: TranslateMenu,
},
{sequence: 200}
);
}
},
});