Skip to content

Commit

Permalink
feat(i18n): prepend, append HTML content support
Browse files Browse the repository at this point in the history
  • Loading branch information
Sayan751 committed Aug 27, 2019
1 parent 2576139 commit b9aeca8
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 15 deletions.
4 changes: 3 additions & 1 deletion docs/user-docs/2. app-basics/20. internationalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ This is the most common use-case, as well as the default behavior.
<span t="key"></span>
```
Given the above translation, and the view, Aurelia replaces the `textContent` of the `span` with "Hello World".
The same result can also be achieved by explicitly using the `[text]` attribute like `<span t="[text]key"></span>`.

Note that the key expression can also be constructed in view-model and be bound to `t` using `t.bind` syntax.

Expand Down Expand Up @@ -398,7 +399,8 @@ This will set the `innerHTML` of the element instead of the `textContent` proper
##### `[append]` or `[prepend]` translations

So far we have seen that contents are replaced.
There are 2 special attributes `[append]`, and `[prepend]` which can be used to append or prepend content to the existing content of the element.
There are two special attributes `[append]`, and `[prepend]` which can be used to append or prepend content to the existing content of the element.
These also support HTML content.

```json translation.json
{
Expand Down
175 changes: 175 additions & 0 deletions packages/__tests__/i18n/t/translation-integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ describe('translation-integration', function () {

html: 'this is a <i>HTML</i> content',
pre: 'tic ',
preHtml: '<b>tic</b><span>foo</span> ',
mid: 'tac',
midHtml: '<i>tac</i>',
post: ' toe',
postHtml: ' <b>toe</b><span>bar</span>',

imgPath: 'foo.jpg'
};
Expand Down Expand Up @@ -327,6 +329,30 @@ describe('translation-integration', function () {

assert.equal((host as Element).querySelector('span').innerHTML, 'tic <i>tac</i>');
});
it('works for html content for [prepend] + textContent', async function () {

@customElement({
name: 'app', template: `<span t='[prepend]preHtml;[html]mid'></span>`
})
class App { }

const host = DOM.createElement('app');
await setup(host, new App());

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac');
});
it('works for html content for [prepend] + innerHtml', async function () {

@customElement({
name: 'app', template: `<span t='[prepend]preHtml;[html]midHtml'></span>`
})
class App { }

const host = DOM.createElement('app');
await setup(host, new App());

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> <i>tac</i>');
});

it('works for [append] only', async function () {

Expand Down Expand Up @@ -364,6 +390,30 @@ describe('translation-integration', function () {

assert.equal((host as Element).querySelector('span').innerHTML, '<i>tac</i> toe');
});
it('works for html content for [append] + textContent', async function () {

@customElement({
name: 'app', template: `<span t='[append]postHtml;[html]mid'></span>`
})
class App { }

const host = DOM.createElement('app');
await setup(host, new App());

assert.equal((host as Element).querySelector('span').innerHTML, 'tac <b>toe</b><span>bar</span>');
});
it('works for html content for [append]', async function () {

@customElement({
name: 'app', template: `<span t='[append]postHtml;[html]midHtml'></span>`
})
class App { }

const host = DOM.createElement('app');
await setup(host, new App());

assert.equal((host as Element).querySelector('span').innerHTML, '<i>tac</i> <b>toe</b><span>bar</span>');
});

it('works for [prepend] and [append]', async function () {

Expand Down Expand Up @@ -401,6 +451,131 @@ describe('translation-integration', function () {

assert.equal((host as Element).querySelector('span').innerHTML, 'tic <i>tac</i> toe');
});
it('works for html resource for [prepend] and [append] + textContent', async function () {

@customElement({
name: 'app', template: `<span t='[prepend]preHtml;[append]postHtml;mid'></span>`
})
class App { }

const host = DOM.createElement('app');
await setup(host, new App());

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac <b>toe</b><span>bar</span>');
});
it('works for html resource for [prepend] and [append] + innerHtml', async function () {

@customElement({
name: 'app', template: `<span t='[prepend]preHtml;[append]postHtml;[html]midHtml'></span>`
})
class App { }

const host = DOM.createElement('app');
await setup(host, new App());

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> <i>tac</i> <b>toe</b><span>bar</span>');
});

it('works correctly with the change of both [prepend], and [append] - textContent', async function () {

@customElement({
name: 'app', template: `<span t.bind='keyExpr'>tac</span>`
})
class App {
public keyExpr: string = '[prepend]preHtml;[append]postHtml';
}

const host = DOM.createElement('app');
const app = new App();
const { ctx } = await setup(host, app);

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac <b>toe</b><span>bar</span>');
app.keyExpr = '[prepend]pre;[append]post';

ctx.lifecycle.processRAFQueue(LifecycleFlags.none);

assert.equal((host as Element).querySelector('span').innerHTML, 'tic tac toe');
});
it('works correctly with the change of both [prepend], and [append] - textContent', async function () {

@customElement({
name: 'app', template: `<span t.bind='keyExpr'>tac</span>`
})
class App {
public keyExpr: string = '[prepend]pre;[append]post';
}

const host = DOM.createElement('app');
const app = new App();
const { ctx } = await setup(host, app);

assert.equal((host as Element).querySelector('span').innerHTML, 'tic tac toe');
app.keyExpr = '[prepend]preHtml;[append]postHtml';

ctx.lifecycle.processRAFQueue(LifecycleFlags.none);

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac <b>toe</b><span>bar</span>');
});
it('works correctly with the removal of [append]', async function () {

@customElement({
name: 'app', template: `<span t.bind='keyExpr'>tac</span>`
})
class App {
public keyExpr: string = '[prepend]preHtml;[append]postHtml';
}

const host = DOM.createElement('app');
const app = new App();
const { ctx } = await setup(host, app);

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac <b>toe</b><span>bar</span>');
app.keyExpr = '[prepend]preHtml';

ctx.lifecycle.processRAFQueue(LifecycleFlags.none);

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac');
});
it('works correctly with the removal of [prepend]', async function () {

@customElement({
name: 'app', template: `<span t.bind='keyExpr'>tac</span>`
})
class App {
public keyExpr: string = '[prepend]preHtml;[append]postHtml';
}

const host = DOM.createElement('app');
const app = new App();
const { ctx } = await setup(host, app);

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac <b>toe</b><span>bar</span>');
app.keyExpr = '[append]postHtml';

ctx.lifecycle.processRAFQueue(LifecycleFlags.none);

assert.equal((host as Element).querySelector('span').innerHTML, 'tac <b>toe</b><span>bar</span>');
});
it('works correctly with the removal of both [prepend] and [append]', async function () {

@customElement({
name: 'app', template: `<span t.bind='keyExpr'>tac</span>`
})
class App {
public keyExpr: string = '[prepend]preHtml;[append]postHtml';
}

const host = DOM.createElement('app');
const app = new App();
const { ctx } = await setup(host, app);

assert.equal((host as Element).querySelector('span').innerHTML, '<b>tic</b><span>foo</span> tac <b>toe</b><span>bar</span>');
app.keyExpr = '[html]midHtml';

ctx.lifecycle.processRAFQueue(LifecycleFlags.none);

assert.equal((host as Element).querySelector('span').innerHTML, '<i>tac</i>');
});
});

describe('updates translation', function () {
Expand Down
24 changes: 10 additions & 14 deletions packages/i18n/src/t/translation-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,30 +221,26 @@ export class TranslationBinding implements IPartialConnectableBinding {
private prepareTemplate(content: ContentValue, marker: string, fallBackContents: ChildNode[]) {
const template = DOM.createTemplate() as HTMLTemplateElement;

this.addTextContentToTemplate(template, content.prepend, marker);
this.addContentToTemplate(template, content.prepend, marker);

// build content: prioritize [html], then textContent, and falls back to original content
if (content.innerHTML) {
const fragment = DOM.createDocumentFragment(content.innerHTML) as DocumentFragment;
for (const child of toArray(fragment.childNodes)) {
Reflect.set(child, marker, true);
template.content.append(child);
}
} else if (!this.addTextContentToTemplate(template, content.textContent, marker)) {
if (!this.addContentToTemplate(template, content.innerHTML || content.textContent, marker)) {
for (const fallbackContent of fallBackContents) {
template.content.append(fallbackContent);
}
}

this.addTextContentToTemplate(template, content.append, marker);
this.addContentToTemplate(template, content.append, marker);
return template;
}

private addTextContentToTemplate(template: HTMLTemplateElement, additionalText: string | undefined, marker: string) {
if (additionalText) {
const addendum = DOM.createTextNode(additionalText) as Node;
Reflect.set(addendum, marker, true);
template.content.append(addendum);
private addContentToTemplate(template: HTMLTemplateElement, content: string | undefined, marker: string) {
if (content) {
const addendum = DOM.createDocumentFragment(content) as Node;
for (const child of toArray(addendum.childNodes)) {
Reflect.set(child, marker, true);
template.content.append(child);
}
return true;
}
return false;
Expand Down

0 comments on commit b9aeca8

Please sign in to comment.