diff --git a/docs/owl.js b/docs/owl.js index 5b691f7b..406a7566 100644 --- a/docs/owl.js +++ b/docs/owl.js @@ -3738,6 +3738,7 @@ function createContext(parentCtx, params) { index: 0, forceNewBlock: true, translate: parentCtx.translate, + translationCtx: parentCtx.translationCtx, tKeyExpr: null, nameSpace: parentCtx.nameSpace, tModelSelectedExpr: parentCtx.tModelSelectedExpr, @@ -3840,6 +3841,7 @@ class CodeGenerator { forceNewBlock: false, isLast: true, translate: true, + translationCtx: "", tKeyExpr: null, }); // define blocks and utility functions @@ -3977,9 +3979,9 @@ class CodeGenerator { }) .join(""); } - translate(str) { + translate(str, translationCtx) { const match = translationRE.exec(str); - return match[1] + this.translateFn(match[2]) + match[3]; + return match[1] + this.translateFn(match[2], translationCtx) + match[3]; } /** * @returns the newly created block name, if any @@ -4020,7 +4022,9 @@ class CodeGenerator { return this.compileTSlot(ast, ctx); case 16 /* TTranslation */: return this.compileTTranslation(ast, ctx); - case 17 /* TPortal */: + case 17 /* TTranslationContext */: + return this.compileTTranslationContext(ast, ctx); + case 18 /* TPortal */: return this.compileTPortal(ast, ctx); } } @@ -4058,7 +4062,7 @@ class CodeGenerator { let { block, forceNewBlock } = ctx; let value = ast.value; if (value && ctx.translate !== false) { - value = this.translate(value); + value = this.translate(value, ctx.translationCtx); } if (!ctx.inPreTag) { value = value.replace(whitespaceRE, " "); @@ -4093,6 +4097,7 @@ class CodeGenerator { return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`; } compileTDomNode(ast, ctx) { + var _a; let { block, forceNewBlock } = ctx; const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns; let codeIdx = this.target.code.length; @@ -4148,7 +4153,8 @@ class CodeGenerator { } } else if (this.translatableAttributes.includes(key)) { - attrs[key] = this.translateFn(ast.attrs[key]); + const attrTranslationCtx = ((_a = ast.attrsTranslationCtx) === null || _a === void 0 ? void 0 : _a[key]) || ctx.translationCtx; + attrs[key] = this.translateFn(ast.attrs[key], attrTranslationCtx); } else { expr = `"${ast.attrs[key]}"`; @@ -4592,7 +4598,7 @@ class CodeGenerator { else { let value; if (ast.defaultValue) { - const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue); + const defaultValue = toStringExpression(ctx.translate ? this.translate(ast.defaultValue, ctx.translationCtx) : ast.defaultValue); if (ast.value) { value = `withDefault(${expr}, ${defaultValue})`; } @@ -4626,9 +4632,10 @@ class CodeGenerator { * "some-prop" "state" "'some-prop': ctx['state']" * "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])" */ - formatProp(name, value) { + formatProp(name, value, attrsTranslationCtx, translationCtx) { if (name.endsWith(".translate")) { - value = toStringExpression(this.translateFn(value)); + const attrTranslationCtx = (attrsTranslationCtx === null || attrsTranslationCtx === void 0 ? void 0 : attrsTranslationCtx[name]) || translationCtx; + value = toStringExpression(this.translateFn(value, attrTranslationCtx)); } else { value = this.captureExpression(value); @@ -4650,8 +4657,8 @@ class CodeGenerator { name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`; return `${name}: ${value || undefined}`; } - formatPropObject(obj) { - return Object.entries(obj).map(([k, v]) => this.formatProp(k, v)); + formatPropObject(obj, attrsTranslationCtx, translationCtx) { + return Object.entries(obj).map(([k, v]) => this.formatProp(k, v, attrsTranslationCtx, translationCtx)); } getPropString(props, dynProps) { let propString = `{${props.join(",")}}`; @@ -4664,7 +4671,9 @@ class CodeGenerator { let { block } = ctx; // props const hasSlotsProp = "slots" in (ast.props || {}); - const props = ast.props ? this.formatPropObject(ast.props) : []; + const props = ast.props + ? this.formatPropObject(ast.props, ast.propsTranslationCtx, ctx.translationCtx) + : []; // slots let slotDef = ""; if (ast.slots) { @@ -4687,7 +4696,7 @@ class CodeGenerator { params.push(`__scope: "${scope}"`); } if (ast.slots[slotName].attrs) { - params.push(...this.formatPropObject(ast.slots[slotName].attrs)); + params.push(...this.formatPropObject(ast.slots[slotName].attrs, ast.slots[slotName].attrsTranslationCtx, ctx.translationCtx)); } const slotInfo = `{${params.join(", ")}}`; slotStr.push(`'${slotName}': ${slotInfo}`); @@ -4800,7 +4809,9 @@ class CodeGenerator { if (isMultiple) { key = this.generateComponentKey(key); } - const props = ast.attrs ? this.formatPropObject(ast.attrs) : []; + const props = ast.attrs + ? this.formatPropObject(ast.attrs, ast.attrsTranslationCtx, ctx.translationCtx) + : []; const scope = this.getPropString(props, dynProps); if (ast.defaultContent) { const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx); @@ -4833,6 +4844,12 @@ class CodeGenerator { } return null; } + compileTTranslationContext(ast, ctx) { + if (ast.content) { + return this.compileAST(ast.content, Object.assign({}, ctx, { translationCtx: ast.translationCtx })); + } + return null; + } compileTPortal(ast, ctx) { if (!this.staticDefs.find((d) => d.id === "Portal")) { this.staticDefs.push({ id: "Portal", expr: `app.Portal` }); @@ -4902,6 +4919,7 @@ function parseNode(node, ctx) { parseTOutNode(node, ctx) || parseTKey(node, ctx) || parseTTranslation(node, ctx) || + parseTTranslationContext(node, ctx) || parseTSlot(node, ctx) || parseComponent(node, ctx) || parseDOMNode(node, ctx) || @@ -5010,6 +5028,7 @@ function parseDOMNode(node, ctx) { node.removeAttribute("t-ref"); const nodeAttrsNames = node.getAttributeNames(); let attrs = null; + let attrsTranslationCtx = null; let on = null; let model = null; for (let attr of nodeAttrsNames) { @@ -5070,6 +5089,11 @@ function parseDOMNode(node, ctx) { else if (attr === "xmlns") { ns = value; } + else if (attr.startsWith("t-translation-context-")) { + const attrName = attr.slice(22); + attrsTranslationCtx = attrsTranslationCtx || {}; + attrsTranslationCtx[attrName] = value; + } else if (attr !== "t-name") { if (attr.startsWith("t-") && !attr.startsWith("t-att")) { throw new OwlError(`Unknown QWeb directive: '${attr}'`); @@ -5091,6 +5115,7 @@ function parseDOMNode(node, ctx) { tag: tagName, dynamicTag, attrs, + attrsTranslationCtx, on, ref, content: children, @@ -5231,7 +5256,15 @@ function parseTCall(node, ctx) { if (ast && ast.type === 11 /* TComponent */) { return { ...ast, - slots: { default: { content: tcall, scope: null, on: null, attrs: null } }, + slots: { + default: { + content: tcall, + scope: null, + on: null, + attrs: null, + attrsTranslationCtx: null, + }, + }, }; } } @@ -5346,9 +5379,15 @@ function parseComponent(node, ctx) { node.removeAttribute("t-slot-scope"); let on = null; let props = null; + let propsTranslationCtx = null; for (let name of node.getAttributeNames()) { const value = node.getAttribute(name); - if (name.startsWith("t-")) { + if (name.startsWith("t-translation-context-")) { + const attrName = name.slice(22); + propsTranslationCtx = propsTranslationCtx || {}; + propsTranslationCtx[attrName] = value; + } + else if (name.startsWith("t-")) { if (name.startsWith("t-on-")) { on = on || {}; on[name.slice(5)] = value; @@ -5392,6 +5431,7 @@ function parseComponent(node, ctx) { const slotAst = parseNode(slotNode, ctx); let on = null; let attrs = null; + let attrsTranslationCtx = null; let scope = null; for (let attributeName of slotNode.getAttributeNames()) { const value = slotNode.getAttribute(attributeName); @@ -5399,6 +5439,11 @@ function parseComponent(node, ctx) { scope = value; continue; } + else if (attributeName.startsWith("t-translation-context-")) { + const attrName = attributeName.slice(22); + attrsTranslationCtx = attrsTranslationCtx || {}; + attrsTranslationCtx[attrName] = value; + } else if (attributeName.startsWith("t-on-")) { on = on || {}; on[attributeName.slice(5)] = value; @@ -5409,17 +5454,32 @@ function parseComponent(node, ctx) { } } slots = slots || {}; - slots[name] = { content: slotAst, on, attrs, scope }; + slots[name] = { content: slotAst, on, attrs, attrsTranslationCtx, scope }; } // default slot const defaultContent = parseChildNodes(clone, ctx); slots = slots || {}; // t-set-slot="default" has priority over content if (defaultContent && !slots.default) { - slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope }; + slots.default = { + content: defaultContent, + on, + attrs: null, + attrsTranslationCtx: null, + scope: defaultSlotScope, + }; } } - return { type: 11 /* TComponent */, name, isDynamic, dynamicProps, props, slots, on }; + return { + type: 11 /* TComponent */, + name, + isDynamic, + dynamicProps, + props, + propsTranslationCtx, + slots, + on, + }; } // ----------------------------------------------------------------------------- // Slots @@ -5431,6 +5491,7 @@ function parseTSlot(node, ctx) { const name = node.getAttribute("t-slot"); node.removeAttribute("t-slot"); let attrs = null; + let attrsTranslationCtx = null; let on = null; for (let attributeName of node.getAttributeNames()) { const value = node.getAttribute(attributeName); @@ -5438,6 +5499,11 @@ function parseTSlot(node, ctx) { on = on || {}; on[attributeName.slice(5)] = value; } + else if (attributeName.startsWith("t-translation-context-")) { + const attrName = attributeName.slice(22); + attrsTranslationCtx = attrsTranslationCtx || {}; + attrsTranslationCtx[attrName] = value; + } else { attrs = attrs || {}; attrs[attributeName] = value; @@ -5447,10 +5513,14 @@ function parseTSlot(node, ctx) { type: 14 /* TSlot */, name, attrs, + attrsTranslationCtx, on, defaultContent: parseChildNodes(node, ctx), }; } +// ----------------------------------------------------------------------------- +// Translation +// ----------------------------------------------------------------------------- function parseTTranslation(node, ctx) { if (node.getAttribute("t-translation") !== "off") { return null; @@ -5462,6 +5532,21 @@ function parseTTranslation(node, ctx) { }; } // ----------------------------------------------------------------------------- +// Translation Context +// ----------------------------------------------------------------------------- +function parseTTranslationContext(node, ctx) { + const translationCtx = node.getAttribute("t-translation-context"); + if (!translationCtx) { + return null; + } + node.removeAttribute("t-translation-context"); + return { + type: 17 /* TTranslationContext */, + content: parseNode(node, ctx), + translationCtx, + }; +} +// ----------------------------------------------------------------------------- // Portal // ----------------------------------------------------------------------------- function parseTPortal(node, ctx) { @@ -5478,7 +5563,7 @@ function parseTPortal(node, ctx) { }; } return { - type: 17 /* TPortal */, + type: 18 /* TPortal */, target, content, }; @@ -5620,7 +5705,7 @@ function compile(template, options = { } // do not modify manually. This file is generated by the release script. -const version = "2.5.3"; +const version = "2.6.0"; // ----------------------------------------------------------------------------- // Scheduler @@ -6098,6 +6183,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat export { App, Component, EventBus, OwlError, __info__, batched, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml }; -__info__.date = '2025-01-10T10:10:53.709Z'; -__info__.hash = 'b31fa81'; +__info__.date = '2025-01-15T10:40:24.184Z'; +__info__.hash = 'a9be149'; __info__.url = 'https://github.com/odoo/owl'; diff --git a/package-lock.json b/package-lock.json index 07cb05c7..18310104 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@odoo/owl", - "version": "2.5.3", + "version": "2.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 65b3c4d0..d5bad0ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@odoo/owl", - "version": "2.5.3", + "version": "2.6.0", "description": "Odoo Web Library (OWL)", "main": "dist/owl.cjs.js", "module": "dist/owl.es.js", diff --git a/src/version.ts b/src/version.ts index 80c8c9a9..3acc922c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ // do not modify manually. This file is generated by the release script. -export const version = "2.5.3"; +export const version = "2.6.0";