[ADD] Translation feature

This commit brings back the possibility to translate text nodes and
the attributes "label", "title", "placeholder", and "alt" in an app
configured with a suitable translation function.

It is also possible to deactivate translations under a node via
the directive t-translation="off".

For flexibility it is possible to define the list of translatable
attributes in the app.
This commit is contained in:
Mathieu Duckerts-Antoine
2021-10-13 16:49:34 +02:00
committed by Géry Debongnie
parent e2d98e4c26
commit b8649f1add
8 changed files with 363 additions and 64 deletions
+22 -14
View File
@@ -1,23 +1,22 @@
import {
App,
Component,
onDestroyed,
onWillPatch,
onWillUnmount,
onMounted,
onPatched,
onWillStart,
onWillUpdateProps,
useComponent,
status,
onRender,
Component,
onWillPatch,
onWillStart,
onWillUnmount,
onWillUpdateProps,
status,
useComponent,
} from "../src";
import { TemplateSet, globalTemplates, UTILS } from "../src/qweb/template_helpers";
import { blockDom } from "../src";
import { BDom } from "../src/blockdom";
// import { mountBlock } from "../src/blockdom/block";
import { compileTemplate, Template } from "../src/qweb/compiler";
import { blockDom } from "../src";
import { CompileOptions, compileTemplate, Template } from "../src/qweb/compiler";
import { globalTemplates, TemplateSet, UTILS } from "../src/qweb/template_helpers";
import { xml } from "../src/tags";
// import { UTILS } from "../src/template_utils";
const mount = blockDom.mount;
@@ -37,8 +36,17 @@ export function makeTestFixture() {
return fixture;
}
export function snapshotTemplateCode(template: string) {
expect(compileTemplate(template).toString()).toMatchSnapshot();
export function snapshotTemplateCode(template: string, options?: CompileOptions) {
expect(compileTemplate(template, options).toString()).toMatchSnapshot();
}
export function snapshotApp(app: App) {
const Root = app.Root;
const template = app.rawTemplates[Root.template];
snapshotTemplateCode(template, {
translateFn: app.translateFn,
translatableAttributes: app.translatableAttributes,
});
}
export async function nextTick(): Promise<void> {
@@ -0,0 +1,71 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`translation support can translate node content 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
let block1 = createBlock(\`<div>mot</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`translation support does not translate node content if disabled 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
let block1 = createBlock(\`<div><span>mot</span><span>word</span></div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`translation support some attributes are translated 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
let block1 = createBlock(\`<div><p label=\\"mot\\">mot</p><p title=\\"mot\\">mot</p><p placeholder=\\"mot\\">mot</p><p alt=\\"mot\\">mot</p><p something=\\"word\\">mot</p></div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
let block1 = createBlock(\`<div> mot </div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`translation support can set translatable attributes 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
let block1 = createBlock(\`<div tomato=\\"word\\" potato=\\"mot\\" title=\\"word\\">text</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
+43
View File
@@ -1126,4 +1126,47 @@ describe("qweb parser", () => {
name: "myBlock",
});
});
// ---------------------------------------------------------------------------
// t-translation
// ---------------------------------------------------------------------------
test('t-translation="off"', async () => {
expect(parse(`<t t-translation="off">word</t>`)).toEqual({
type: ASTType.TTranslation,
content: {
type: ASTType.Text,
value: "word",
},
});
expect(parse(`<div t-foreach="list" t-translation="off" t-as="item">word</div>`)).toEqual({
body: {
content: {
attrs: {},
content: [
{
type: 0,
value: "word",
},
],
on: {},
ref: null,
tag: "div",
type: 2,
},
type: 16,
},
collection: "list",
elem: "item",
hasNoComponent: true,
hasNoFirst: true,
hasNoIndex: true,
hasNoLast: true,
hasNoValue: true,
isOnlyChild: false,
key: null,
memo: "",
type: 9,
});
});
});
+116
View File
@@ -0,0 +1,116 @@
import { App, Component } from "../../src";
import { makeTestFixture, snapshotApp } from "../helpers";
import { xml } from "../../src/tags";
let fixture: HTMLElement;
beforeEach(() => {
fixture = makeTestFixture();
});
describe("translation support", () => {
test("can translate node content", async () => {
class SomeComponent extends Component {
static template = xml`<div>word</div>`;
}
const app = new App(SomeComponent);
app.configure({
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
});
const comp = await app.mount(fixture);
const el = comp.el as HTMLElement;
expect(el.outerHTML).toBe("<div>mot</div>");
snapshotApp(app);
});
test("does not translate node content if disabled", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<span>word</span>
<span t-translation="off">word</span>
</div>
`;
}
const app = new App(SomeComponent);
app.configure({
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
});
const comp = await app.mount(fixture);
const el = comp.el as HTMLElement;
expect(el.outerHTML).toBe("<div><span>mot</span><span>word</span></div>");
snapshotApp(app);
});
test("some attributes are translated", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
<p label="word">word</p>
<p title="word">word</p>
<p placeholder="word">word</p>
<p alt="word">word</p>
<p something="word">word</p>
</div>
`;
}
const app = new App(SomeComponent);
app.configure({
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
});
const comp = await app.mount(fixture);
const el = comp.el as HTMLElement;
expect(el.outerHTML).toBe(
'<div><p label="mot">mot</p><p title="mot">mot</p><p placeholder="mot">mot</p><p alt="mot">mot</p><p something="word">mot</p></div>'
);
snapshotApp(app);
});
test("can set translatable attributes", async () => {
class SomeComponent extends Component {
static template = xml`
<div tomato="word" potato="word" title="word">text</div>
`;
}
const app = new App(SomeComponent);
app.configure({
translateFn: (expr: string) => (expr === "word" ? "mot" : expr),
translatableAttributes: ["potato"],
});
const comp = await app.mount(fixture);
const el = comp.el as HTMLElement;
expect(el.outerHTML).toBe('<div tomato="word" potato="mot" title="word">text</div>');
snapshotApp(app);
});
test("translation is done on the trimmed text, with extra spaces readded after", async () => {
class SomeComponent extends Component {
static template = xml`
<div> word </div>
`;
}
const translateFn = jest.fn((expr: string) => (expr === "word" ? "mot" : expr));
const app = new App(SomeComponent);
app.configure({ translateFn });
const comp = await app.mount(fixture);
const el = comp.el as HTMLElement;
expect(el.outerHTML).toBe("<div> mot </div>");
expect(translateFn).toHaveBeenCalledWith("word");
snapshotApp(app);
});
});