Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/compiler/code_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
interpolate,
INTERP_REGEXP,
replaceDynamicParts,
translateStringFormat,
} from "./inline_expressions";
import {
AST,
Expand Down Expand Up @@ -615,10 +616,15 @@ export class CodeGenerator {
for (let key in ast.attrs) {
let expr, attrName;
if (key.startsWith("t-attf")) {
expr = interpolate(ast.attrs[key]);
const idx = block!.insertData(expr, "attr");

attrName = key.slice(7);
let toInterpolate = ast.attrs[key];
if (this.translatableAttributes.includes(attrName)) {
const attrTranslationCtx = ast.attrsTranslationCtx?.[key] || ctx.translationCtx;
const translateFn = (s: string) => this.translateFn(s, attrTranslationCtx);
toInterpolate = translateStringFormat(toInterpolate, translateFn);
}
expr = interpolate(toInterpolate);
const idx = block!.insertData(expr, "attr");
attrs["block-attribute-" + idx] = attrName;
} else if (key.startsWith("t-att")) {
attrName = key === "t-att" ? null : key.slice(6);
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/inline_expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,21 @@ export function replaceDynamicParts(s: string, replacer: (s: string) => string)
export function interpolate(s: string): string {
return replaceDynamicParts(s, compileExpr);
}

const INTERP_TRANSLATE_REGEXP = /\{\{(\d+)\}\}/g;
export function translateStringFormat(s: string, translateFn: Function): string {
const dynamic: Array<string> = [];
const repl = (substr: string) => {
const i = dynamic.length;
dynamic.push(substr);
return `{{${i}}}`; // coupled to INTERP_TRANSLATE_REGEXP
};
const rawString = s.replace(INTERP_REGEXP, (s) => repl(s.slice(2, s[0] === "{" ? -2 : -1)));
const translated: string = translateFn(rawString.trim());
if (translated !== undefined && translated !== rawString) {
return translated.replace(INTERP_TRANSLATE_REGEXP, (m, index) => {
return `{{${dynamic[index]}}}`;
});
}
return s;
}
24 changes: 24 additions & 0 deletions tests/compiler/__snapshots__/translation.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,30 @@ exports[`translation support t-translation with several children 1`] = `
}"
`;

exports[`translation support translate t-attf- 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;

let block2 = createBlock(\`<div block-attribute-0=\\"title\\"/>\`);
let block3 = createBlock(\`<div block-attribute-0=\\"data-nope\\"/>\`);
let block4 = createBlock(\`<div block-attribute-0=\\"title\\"/>\`);
let block5 = createBlock(\`<div block-attribute-0=\\"title\\"/>\`);

return function template(ctx, node, key = \\"\\") {
let attr1 = \`trois \${ctx['this'].terms[2]} un \${ctx['this'].terms[0]} deux \${ctx['this'].terms[1]}\`;
const b2 = block2([attr1]);
let attr2 = \`not \${ctx['this'].terms[0]} translated \${ctx['this'].terms[1]} at all \${ctx['this'].terms[2]}\`;
const b3 = block3([attr2]);
let attr3 = \`trop d'arguments \${ctx['this'].terms[0]} et \${undefined}\`;
const b4 = block4([attr3]);
let attr4 = \`pas assez consommé\`;
const b5 = block5([attr4]);
return multi([b2, b3, b4, b5]);
}
}"
`;

exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
"function anonymous(app, bdom, helpers
) {
Expand Down
32 changes: 32 additions & 0 deletions tests/compiler/translation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,38 @@ describe("translation support", () => {
await mount(SomeComponent, fixture);
expect(fixture.outerHTML).toBe("<div><div><div></div><div></div></div></div>");
});

test("translate t-attf-", async () => {
class SomeComponent extends Component {
static template = xml`
<div t-attf-title=" one {{ this.terms[0] }} two {{ this.terms[1] }} three {{ this.terms[2] }} " />
<div t-attf-data-nope="not {{ this.terms[0] }} translated {{ this.terms[1] }} at all {{ this.terms[2] }}" />
<div t-attf-title="too much arguments {{ this.terms[0] }}" />
<div t-attf-title="not enough arguments {{ this.terms[0] }}" />
`;
terms: Array<string> = ["term1", "term2", "term3"];
}

const terms: { [term: string]: string } = {
"one {{0}} two {{1}} three {{2}}": "trois {{2}} un {{0}} deux {{1}}", // full feature
"too much arguments {{0}}": "trop d'arguments {{0}} et {{1}}", // translated has too much placeholders
"not enough arguments {{0}}": "pas assez consommé", // translated has not enough placeholders
};
const translateFn = (term: string) => {
return terms[term] || "should not be here";
};
await mount(SomeComponent, fixture, { translateFn });
expect(fixture.children[0].outerHTML).toBe(
`<div title="trois term3 un term1 deux term2"></div>`
);
expect(fixture.children[1].outerHTML).toBe(
`<div data-nope="not term1 translated term2 at all term3"></div>`
);
expect(fixture.children[2].outerHTML).toBe(
`<div title="trop d'arguments term1 et undefined"></div>`
);
expect(fixture.children[3].outerHTML).toBe(`<div title="pas assez consommé"></div>`);
});
});

describe("translation context", () => {
Expand Down
Loading