Repository: odoo/owl (TypeScript)

Issue:
body of t-set is not translated
[Reproduction on playground](https://odoo.github.io/owl/playground/#eyJqcyI6ImltcG9ydCB7IENvbXBvbmVudCwgbW91bnQgfSBmcm9tIFwiQG9kb28vb3dsXCI7XG5jbGFzcyBSb290IGV4dGVuZHMgQ29tcG9uZW50IHtcbiAgICBzdGF0aWMgdGVtcGxhdGUgPSBcIlJvb3RcIlxufVxubW91bnQoUm9vdCwgZG9jdW1lbnQuYm9keSwgeyB0ZW1wbGF0ZXM6IFRFTVBMQVRFUywgZGV2OiB0cnVlLCB0cmFuc2xhdGVGbjogKCkgPT4gXCJ0cmFuc2xhdGVkXCIgfSk7XG4iLCJjc3MiOiIuZ3JlZXRlciB7XG4gICAgZm9udC1zaXplOiAyMHB4O1xuICAgIHdpZHRoOiAzMDBweDtcbiAgICBoZWlnaHQ6IDEwMHB4O1xuICAgIG1hcmdpbjogNXB4O1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICBsaW5lLWhlaWdodDogMTAwcHg7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2VlZWVlZTtcbiAgICB1c2VyLXNlbGVjdDogbm9uZTtcbn1cbiIsInhtbCI6Ijx0ZW1wbGF0ZXM+XG4gIDx0IHQtbmFtZT1cIlJvb3RcIj5cbiAgICA8dCB0LXNldD1cImxhYmVsXCI+dW50cmFuc2xhdGVkPC90PlxuICAgIDx0IHQtZXNjPVwibGFiZWxcIi8+XG4gIDwvdD5cbjwvdGVtcGxhdGVzPlxuIn0=)


Discussion:

Patch:
diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts
index d56bbb2b2..3c0d6498c 100644
--- a/src/compiler/code_generator.ts
+++ b/src/compiler/code_generator.ts
@@ -447,6 +447,11 @@ export class CodeGenerator {
       .join("");
   }
 
+  translate(str: string): string {
+    const match = translationRE.exec(str) as any;
+    return match[1] + this.translateFn(match[2]) + match[3];
+  }
+
   /**
    * @returns the newly created block name, if any
    */
@@ -527,8 +532,7 @@ export class CodeGenerator {
 
     let value = ast.value;
     if (value && ctx.translate !== false) {
-      const match = translationRE.exec(value) as any;
-      value = match[1] + this.translateFn(match[2]) + match[3];
+      value = this.translate(value);
     }
     if (!ctx.inPreTag) {
       value = value.replace(whitespaceRE, " ");
@@ -1095,10 +1099,11 @@ export class CodeGenerator {
     } else {
       let value: string;
       if (ast.defaultValue) {
+        const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue;
         if (ast.value) {
-          value = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
+          value = `withDefault(${expr}, \`${defaultValue}\`)`;
         } else {
-          value = `\`${ast.defaultValue}\``;
+          value = `\`${defaultValue}\``;
         }
       } else {
         value = expr;


Test patch:
diff --git a/tests/compiler/__snapshots__/translation.test.ts.snap b/tests/compiler/__snapshots__/translation.test.ts.snap
index f40bedda3..6e493cb33 100644
--- a/tests/compiler/__snapshots__/translation.test.ts.snap
+++ b/tests/compiler/__snapshots__/translation.test.ts.snap
@@ -1,5 +1,79 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`translation support body of t-sets are translated 1`] = `
+"function anonymous(app, bdom, helpers
+) {
+  let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+  let { isBoundary, withDefault, setContextValue } = helpers;
+  
+  return function template(ctx, node, key = \\"\\") {
+    ctx = Object.create(ctx);
+    ctx[isBoundary] = 1
+    setContextValue(ctx, \\"label\\", \`translated\`);
+    return text(ctx['label']);
+  }
+}"
+`;
+
+exports[`translation support body of t-sets inside translation=off are not translated 1`] = `
+"function anonymous(app, bdom, helpers
+) {
+  let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+  let { isBoundary, withDefault, setContextValue } = helpers;
+  
+  return function template(ctx, node, key = \\"\\") {
+    ctx = Object.create(ctx);
+    ctx[isBoundary] = 1
+    setContextValue(ctx, \\"label\\", \`untranslated\`);
+    return text(ctx['label']);
+  }
+}"
+`;
+
+exports[`translation support body of t-sets with html content are translated 1`] = `
+"function anonymous(app, bdom, helpers
+) {
+  let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+  let { isBoundary, withDefault, LazyValue, safeOutput } = helpers;
+  
+  let block1 = createBlock(\`<div>translated</div>\`);
+  
+  function value1(ctx, node, key = \\"\\") {
+    return block1();
+  }
+  
+  return function template(ctx, node, key = \\"\\") {
+    ctx = Object.create(ctx);
+    ctx[isBoundary] = 1
+    ctx[\`label\`] = new LazyValue(value1, ctx, this, node, key);
+    return safeOutput(ctx['label']);
+  }
+}"
+`;
+
+exports[`translation support body of t-sets with text and html content are translated 1`] = `
+"function anonymous(app, bdom, helpers
+) {
+  let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+  let { isBoundary, withDefault, LazyValue, safeOutput } = helpers;
+  
+  let block3 = createBlock(\`<div>translated</div>\`);
+  
+  function value1(ctx, node, key = \\"\\") {
+    const b2 = text(\` translated \`);
+    const b3 = block3();
+    return multi([b2, b3]);
+  }
+  
+  return function template(ctx, node, key = \\"\\") {
+    ctx = Object.create(ctx);
+    ctx[isBoundary] = 1
+    ctx[\`label\`] = new LazyValue(value1, ctx, this, node, key);
+    return safeOutput(ctx['label']);
+  }
+}"
+`;
+
 exports[`translation support can set and remove translatable attributes 1`] = `
 "function anonymous(app, bdom, helpers
 ) {
@@ -52,6 +126,21 @@ exports[`translation support some attributes are translated 1`] = `
 }"
 `;
 
+exports[`translation support t-set and falsy t-value: t-body are translated 1`] = `
+"function anonymous(app, bdom, helpers
+) {
+  let { text, createBlock, list, multi, html, toggler, comment } = bdom;
+  let { isBoundary, withDefault, setContextValue } = helpers;
+  
+  return function template(ctx, node, key = \\"\\") {
+    ctx = Object.create(ctx);
+    ctx[isBoundary] = 1
+    setContextValue(ctx, \\"label\\", withDefault(false, \`translated\`));
+    return text(ctx['label']);
+  }
+}"
+`;
+
 exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
 "function anonymous(app, bdom, helpers
 ) {
diff --git a/tests/compiler/translation.test.ts b/tests/compiler/translation.test.ts
index 71dc6ba61..852f8d210 100644
--- a/tests/compiler/translation.test.ts
+++ b/tests/compiler/translation.test.ts
@@ -100,4 +100,74 @@ describe("translation support", () => {
     expect(translateFn).toHaveBeenCalledWith("some  word");
     expect(fixture.innerHTML).toBe("<div>un mot</div>");
   });
+
+  test("body of t-sets are translated", async () => {
+    class SomeComponent extends Component {
+      static template = xml`
+        <t t-set="label">untranslated</t>
+        <t t-esc="label"/>`;
+    }
+
+    const translateFn = () => "translated";
+
+    await mount(SomeComponent, fixture, { translateFn });
+    expect(fixture.innerHTML).toBe("translated");
+  });
+
+  test("body of t-sets inside translation=off are not translated", async () => {
+    class SomeComponent extends Component {
+      static template = xml`
+        <t t-translation="off">
+          <t t-set="label">untranslated</t>
+          <t t-esc="label"/>
+        </t>`;
+    }
+
+    const translateFn = () => "translated";
+
+    await mount(SomeComponent, fixture, { translateFn });
+    expect(fixture.innerHTML).toBe("untranslated");
+  });
+
+  test("body of t-sets with html content are translated", async () => {
+    class SomeComponent extends Component {
+      static template = xml`
+        <t t-set="label"><div>untranslated</div></t>
+        <t t-out="label"/>`;
+    }
+
+    const translateFn = () => "translated";
+
+    await mount(SomeComponent, fixture, { translateFn });
+    expect(fixture.innerHTML).toBe("<div>translated</div>");
+  });
+
+  test("body of t-sets with text and html content are translated", async () => {
+    class SomeComponent extends Component {
+      static template = xml`
+        <t t-set="label">
+          some text
+          <div>untranslated</div>
+        </t>
+        <t t-out="label"/>`;
+    }
+
+    const translateFn = () => "translated";
+
+    await mount(SomeComponent, fixture, { translateFn });
+    expect(fixture.innerHTML).toBe(" translated <div>translated</div>");
+  });
+
+  test("t-set and falsy t-value: t-body are translated", async () => {
+    class SomeComponent extends Component {
+      static template = xml`
+          <t t-set="label" t-value="false">untranslated</t>
+          <t t-esc="label"/>`;
+    }
+
+    const translateFn = () => "translated";
+
+    await mount(SomeComponent, fixture, { translateFn });
+    expect(fixture.innerHTML).toBe("translated");
+  });
 });