feat(ivy): i18n compiler support for element attributes (#26280)
PR Close #26280
This commit is contained in:
parent
3f8ac238f1
commit
39f42bad1c
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
|
import {setup} from '@angular/compiler/test/aot/test_util';
|
||||||
import {compile, expectEmit} from './mock_compile';
|
import {compile, expectEmit} from './mock_compile';
|
||||||
|
|
||||||
const TRANSLATION_NAME_REGEXP = /^MSG_[A-Z0-9]+/;
|
const TRANSLATION_NAME_REGEXP = /^MSG_[A-Z0-9]+/;
|
||||||
|
@ -38,28 +38,28 @@ describe('i18n support in the view compiler', () => {
|
||||||
|
|
||||||
@NgModule({declarations: [MyComponent]})
|
@NgModule({declarations: [MyComponent]})
|
||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
const $msg_1$ = goog.getMsg("Hello world");
|
const $msg_1$ = goog.getMsg("Hello world");
|
||||||
const $msg_2$ = goog.getMsg("farewell");
|
const $msg_2$ = goog.getMsg("farewell");
|
||||||
…
|
…
|
||||||
template: function MyComponent_Template(rf, ctx) {
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
…
|
…
|
||||||
$r3$.ɵtext(1, $msg_1$);
|
$r3$.ɵtext(1, $msg_1$);
|
||||||
…
|
…
|
||||||
$r3$.ɵtext(3,"&");
|
$r3$.ɵtext(3,"&");
|
||||||
…
|
…
|
||||||
$r3$.ɵtext(5, $msg_2$);
|
$r3$.ɵtext(5, $msg_2$);
|
||||||
…
|
…
|
||||||
$r3$.ɵtext(7, $msg_2$);
|
$r3$.ɵtext(7, $msg_2$);
|
||||||
…
|
…
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`;
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
expectEmit(result.source, template, 'Incorrect template', {
|
expectEmit(result.source, template, 'Incorrect template', {
|
||||||
|
@ -84,40 +84,40 @@ describe('i18n support in the view compiler', () => {
|
||||||
|
|
||||||
@NgModule({declarations: [MyComponent]})
|
@NgModule({declarations: [MyComponent]})
|
||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
/**
|
/**
|
||||||
* @desc desc
|
* @desc desc
|
||||||
*/
|
*/
|
||||||
const $msg_1$ = goog.getMsg("introduction");
|
const $MSG_APP_SPEC_TS_0$ = goog.getMsg("introduction");
|
||||||
const $c1$ = ["title", $msg_1$];
|
const $_c1$ = ["title", $MSG_APP_SPEC_TS_0$, 0];
|
||||||
…
|
…
|
||||||
/**
|
/**
|
||||||
* @desc desc
|
* @desc desc
|
||||||
* @meaning meaning
|
* @meaning meaning
|
||||||
*/
|
*/
|
||||||
const $msg_2$ = goog.getMsg("Hello world");
|
const $MSG_APP_SPEC_TS_2$ = goog.getMsg("Hello world");
|
||||||
…
|
…
|
||||||
template: function MyComponent_Template(rf, ctx) {
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
if (rf & 1) {
|
if (rf & 1) {
|
||||||
$r3$.ɵelementStart(0, "div", $c1$);
|
$r3$.ɵelementStart(0, "div");
|
||||||
$r3$.ɵtext(1, $msg_2$);
|
$r3$.ɵi18nAttribute(1, $_c1$);
|
||||||
$r3$.ɵelementEnd();
|
$r3$.ɵtext(2, $MSG_APP_SPEC_TS_2$);
|
||||||
|
$r3$.ɵelementEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
expectEmit(result.source, template, 'Incorrect template', {
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
'$msg_1$': TRANSLATION_NAME_REGEXP,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('static attributes', () => {
|
describe('element attributes', () => {
|
||||||
|
|
||||||
it('should translate static attributes', () => {
|
it('should translate static attributes', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
|
@ -134,29 +134,168 @@ describe('i18n support in the view compiler', () => {
|
||||||
|
|
||||||
@NgModule({declarations: [MyComponent]})
|
@NgModule({declarations: [MyComponent]})
|
||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const template = `
|
const template = `
|
||||||
/**
|
const $_c0$ = ["id", "static"];
|
||||||
* @desc d
|
/**
|
||||||
* @meaning m
|
* @desc d
|
||||||
*/
|
* @meaning m
|
||||||
const $msg_1$ = goog.getMsg("introduction");
|
*/
|
||||||
const $c1$ = ["id", "static", "title", $msg_1$];
|
const $MSG_APP_SPEC_TS_1$ = goog.getMsg("introduction");
|
||||||
…
|
const $_c2$ = ["title", MSG_APP_SPEC_TS_1, 0];
|
||||||
template: function MyComponent_Template(rf, ctx) {
|
…
|
||||||
if (rf & 1) {
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
$r3$.ɵelement(0, "div", $c1$);
|
if (rf & 1) {
|
||||||
|
$r3$.ɵelementStart(0, "div", $_c0$);
|
||||||
|
$r3$.ɵi18nAttribute(1, $_c2$);
|
||||||
|
$r3$.ɵelementEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
`;
|
||||||
`;
|
|
||||||
|
|
||||||
const result = compile(files, angularFiles);
|
const result = compile(files, angularFiles);
|
||||||
expectEmit(result.source, template, 'Incorrect template', {
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
'$msg_1$': TRANSLATION_NAME_REGEXP,
|
});
|
||||||
});
|
|
||||||
|
it('should support interpolation', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`
|
||||||
|
<div i18n id="dynamic-1"
|
||||||
|
i18n-title="m|d" title="intro {{ valueA | uppercase }}"
|
||||||
|
i18n-aria-label="m1|d1" aria-label="{{ valueB }}"
|
||||||
|
i18n-aria-roledescription aria-roledescription="static text"
|
||||||
|
></div>
|
||||||
|
<div i18n id="dynamic-2"
|
||||||
|
i18n-title="m2|d2" title="{{ valueA }} and {{ valueB }} and again {{ valueA + valueB }}"
|
||||||
|
i18n-aria-roledescription aria-roledescription="{{ valueC }}"
|
||||||
|
></div>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class MyComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const template = String.raw `
|
||||||
|
const $_c0$ = ["id", "dynamic-1"];
|
||||||
|
/**
|
||||||
|
* @desc d
|
||||||
|
* @meaning m
|
||||||
|
*/
|
||||||
|
const $MSG_APP_SPEC_TS_1$ = goog.getMsg("intro \uFFFD0\uFFFD");
|
||||||
|
/**
|
||||||
|
* @desc d1
|
||||||
|
* @meaning m1
|
||||||
|
*/
|
||||||
|
const $MSG_APP_SPEC_TS_2$ = goog.getMsg("\uFFFD0\uFFFD");
|
||||||
|
const $MSG_APP_SPEC_TS_3$ = goog.getMsg("static text");
|
||||||
|
const $_c4$ = ["title", $MSG_APP_SPEC_TS_1$, 1, "aria-label", $MSG_APP_SPEC_TS_2$, 1, "aria-roledescription", $MSG_APP_SPEC_TS_3$, 0];
|
||||||
|
const $_c5$ = ["id", "dynamic-2"];
|
||||||
|
/**
|
||||||
|
* @desc d2
|
||||||
|
* @meaning m2
|
||||||
|
*/
|
||||||
|
const $MSG_APP_SPEC_TS_6$ = goog.getMsg("\uFFFD0\uFFFD and \uFFFD1\uFFFD and again \uFFFD2\uFFFD");
|
||||||
|
const $MSG_APP_SPEC_TS_7$ = goog.getMsg("\uFFFD0\uFFFD");
|
||||||
|
const $_c8$ = ["title", $MSG_APP_SPEC_TS_6$, 3, "aria-roledescription", $MSG_APP_SPEC_TS_7$, 1];
|
||||||
|
…
|
||||||
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵelementStart(0, "div", $_c0$);
|
||||||
|
$r3$.ɵpipe(1, "uppercase");
|
||||||
|
$r3$.ɵi18nAttribute(2, $_c4$);
|
||||||
|
$r3$.ɵelementEnd();
|
||||||
|
$r3$.ɵelementStart(3, "div", $_c5$);
|
||||||
|
$r3$.ɵi18nAttribute(4, $_c8$);
|
||||||
|
$r3$.ɵelementEnd();
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(1, 0, ctx.valueA)));
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
|
||||||
|
$r3$.ɵi18nApply(2);
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueA));
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueB));
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind((ctx.valueA + ctx.valueB)));
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind(ctx.valueC));
|
||||||
|
$r3$.ɵi18nApply(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly bind to context in nested template', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-component',
|
||||||
|
template: \`
|
||||||
|
<div *ngFor="let outer of items">
|
||||||
|
<div i18n-title="m|d" title="different scope {{ outer | uppercase }}">
|
||||||
|
</div>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class MyComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComponent]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const template = String.raw `
|
||||||
|
const $_c0$ = ["ngFor", "", 1, "ngForOf"];
|
||||||
|
/**
|
||||||
|
* @desc d
|
||||||
|
* @meaning m
|
||||||
|
*/
|
||||||
|
const $MSG_APP_SPEC_TS__1$ = goog.getMsg("different scope \uFFFD0\uFFFD");
|
||||||
|
const $_c2$ = ["title", $MSG_APP_SPEC_TS__1$, 1];
|
||||||
|
function MyComponent_div_Template_0(rf, ctx) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵelementStart(0, "div");
|
||||||
|
$r3$.ɵelementStart(1, "div");
|
||||||
|
$r3$.ɵpipe(2, "uppercase");
|
||||||
|
$r3$.ɵi18nAttribute(3, $_c2$);
|
||||||
|
$r3$.ɵelementEnd();
|
||||||
|
$r3$.ɵelementEnd();
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
const $outer_r1$ = ctx.$implicit;
|
||||||
|
$r3$.ɵi18nExp($r3$.ɵbind($r3$.ɵpipeBind1(2, 0, $outer_r1$)));
|
||||||
|
$r3$.ɵi18nApply(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
…
|
||||||
|
template: function MyComponent_Template(rf, ctx) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵtemplate(0, MyComponent_div_Template_0, 4, 2, null, $_c0$);
|
||||||
|
}
|
||||||
|
if (rf & 2) {
|
||||||
|
$r3$.ɵelementProperty(0, "ngForOf", $r3$.ɵbind(ctx.items));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, template, 'Incorrect template');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,12 @@ export class Identifiers {
|
||||||
static pipeBind4: o.ExternalReference = {name: 'ɵpipeBind4', moduleName: CORE};
|
static pipeBind4: o.ExternalReference = {name: 'ɵpipeBind4', moduleName: CORE};
|
||||||
static pipeBindV: o.ExternalReference = {name: 'ɵpipeBindV', moduleName: CORE};
|
static pipeBindV: o.ExternalReference = {name: 'ɵpipeBindV', moduleName: CORE};
|
||||||
|
|
||||||
|
static i18nAttribute: o.ExternalReference = {name: 'ɵi18nAttribute', moduleName: CORE};
|
||||||
|
static i18nExp: o.ExternalReference = {name: 'ɵi18nExp', moduleName: CORE};
|
||||||
|
static i18nStart: o.ExternalReference = {name: 'ɵi18nStart', moduleName: CORE};
|
||||||
|
static i18nEnd: o.ExternalReference = {name: 'ɵi18nEnd', moduleName: CORE};
|
||||||
|
static i18nApply: o.ExternalReference = {name: 'ɵi18nApply', moduleName: CORE};
|
||||||
|
|
||||||
static load: o.ExternalReference = {name: 'ɵload', moduleName: CORE};
|
static load: o.ExternalReference = {name: 'ɵload', moduleName: CORE};
|
||||||
static loadQueryList: o.ExternalReference = {name: 'ɵloadQueryList', moduleName: CORE};
|
static loadQueryList: o.ExternalReference = {name: 'ɵloadQueryList', moduleName: CORE};
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {htmlAstToRender3Ast} from '../r3_template_transform';
|
||||||
|
|
||||||
import {R3QueryMetadata} from './api';
|
import {R3QueryMetadata} from './api';
|
||||||
import {parseStyle} from './styling';
|
import {parseStyle} from './styling';
|
||||||
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, isI18NAttribute, mapToExpression, trimTrailingNulls, unsupported} from './util';
|
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, assembleI18nTemplate, getAttrsForDirectiveMatching, invalid, isI18NAttribute, mapToExpression, trimTrailingNulls, unsupported} from './util';
|
||||||
|
|
||||||
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -243,6 +243,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
// LocalResolver
|
// LocalResolver
|
||||||
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
|
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
|
||||||
|
|
||||||
|
i18nTranslate(label: string, meta?: string): o.Expression {
|
||||||
|
return this.constantPool.getTranslation(label, parseI18nMeta(meta), this.fileBasedI18nSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
visitContent(ngContent: t.Content) {
|
visitContent(ngContent: t.Content) {
|
||||||
const slot = this.allocateDataSlot();
|
const slot = this.allocateDataSlot();
|
||||||
const selectorIndex = ngContent.selectorIndex;
|
const selectorIndex = ngContent.selectorIndex;
|
||||||
|
@ -306,7 +310,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
|
|
||||||
let isNonBindableMode: boolean = false;
|
let isNonBindableMode: boolean = false;
|
||||||
|
|
||||||
// Handle i18n attributes
|
// Handle i18n and ngNonBindable attributes
|
||||||
for (const attr of element.attributes) {
|
for (const attr of element.attributes) {
|
||||||
const name = attr.name;
|
const name = attr.name;
|
||||||
const value = attr.value;
|
const value = attr.value;
|
||||||
|
@ -346,6 +350,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
const classInputs: t.BoundAttribute[] = [];
|
const classInputs: t.BoundAttribute[] = [];
|
||||||
const allOtherInputs: t.BoundAttribute[] = [];
|
const allOtherInputs: t.BoundAttribute[] = [];
|
||||||
|
|
||||||
|
const i18nAttrs: Array<{name: string, value: string | AST}> = [];
|
||||||
|
|
||||||
element.inputs.forEach((input: t.BoundAttribute) => {
|
element.inputs.forEach((input: t.BoundAttribute) => {
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
// [attr.style] or [attr.class] should not be treated as styling-based
|
// [attr.style] or [attr.class] should not be treated as styling-based
|
||||||
|
@ -360,6 +366,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
} else if (isClassBinding(input)) {
|
} else if (isClassBinding(input)) {
|
||||||
// this should always go first in the compilation (for [class])
|
// this should always go first in the compilation (for [class])
|
||||||
classInputs.splice(0, 0, input);
|
classInputs.splice(0, 0, input);
|
||||||
|
} else if (attrI18nMetas.hasOwnProperty(input.name)) {
|
||||||
|
i18nAttrs.push({name: input.name, value: input.value});
|
||||||
} else {
|
} else {
|
||||||
allOtherInputs.push(input);
|
allOtherInputs.push(input);
|
||||||
}
|
}
|
||||||
|
@ -394,13 +402,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
staticClassesMap ![className] = true;
|
staticClassesMap ![className] = true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
attributes.push(o.literal(name));
|
|
||||||
if (attrI18nMetas.hasOwnProperty(name)) {
|
if (attrI18nMetas.hasOwnProperty(name)) {
|
||||||
const meta = parseI18nMeta(attrI18nMetas[name]);
|
i18nAttrs.push({name, value});
|
||||||
const variable = this.constantPool.getTranslation(value, meta, this.fileBasedI18nSuffix);
|
|
||||||
attributes.push(variable);
|
|
||||||
} else {
|
} else {
|
||||||
attributes.push(o.literal(value));
|
attributes.push(o.literal(name), o.literal(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -482,7 +487,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
const implicit = o.variable(CONTEXT_NAME);
|
const implicit = o.variable(CONTEXT_NAME);
|
||||||
|
|
||||||
const createSelfClosingInstruction = !hasStylingInstructions && !isNgContainer &&
|
const createSelfClosingInstruction = !hasStylingInstructions && !isNgContainer &&
|
||||||
element.children.length === 0 && element.outputs.length === 0;
|
element.children.length === 0 && element.outputs.length === 0 && i18nAttrs.length === 0;
|
||||||
|
|
||||||
if (createSelfClosingInstruction) {
|
if (createSelfClosingInstruction) {
|
||||||
this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters));
|
this.creationInstruction(element.sourceSpan, R3.element, trimTrailingNulls(parameters));
|
||||||
|
@ -495,6 +500,41 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
this.creationInstruction(element.sourceSpan, R3.disableBindings);
|
this.creationInstruction(element.sourceSpan, R3.disableBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process i18n element attributes
|
||||||
|
if (i18nAttrs.length) {
|
||||||
|
let hasBindings: boolean = false;
|
||||||
|
const i18nAttrArgs: o.Expression[] = [];
|
||||||
|
i18nAttrs.forEach(({name, value}) => {
|
||||||
|
const meta = attrI18nMetas[name];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// in case of static string value, 3rd argument is 0 declares
|
||||||
|
// that there are no expressions defined in this translation
|
||||||
|
i18nAttrArgs.push(o.literal(name), this.i18nTranslate(value, meta), o.literal(0));
|
||||||
|
} else {
|
||||||
|
const converted = value.visit(this._valueConverter);
|
||||||
|
if (converted instanceof Interpolation) {
|
||||||
|
const {strings, expressions} = converted;
|
||||||
|
const label = assembleI18nTemplate(strings);
|
||||||
|
i18nAttrArgs.push(
|
||||||
|
o.literal(name), this.i18nTranslate(label, meta), o.literal(expressions.length));
|
||||||
|
expressions.forEach(expression => {
|
||||||
|
hasBindings = true;
|
||||||
|
const binding = this.convertExpressionBinding(implicit, expression);
|
||||||
|
this.updateInstruction(element.sourceSpan, R3.i18nExp, [binding]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (i18nAttrArgs.length) {
|
||||||
|
const index: o.Expression = o.literal(this.allocateDataSlot());
|
||||||
|
const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true);
|
||||||
|
this.creationInstruction(element.sourceSpan, R3.i18nAttribute, [index, args]);
|
||||||
|
if (hasBindings) {
|
||||||
|
this.updateInstruction(element.sourceSpan, R3.i18nApply, [index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// initial styling for static style="..." attributes
|
// initial styling for static style="..." attributes
|
||||||
if (hasStylingInstructions) {
|
if (hasStylingInstructions) {
|
||||||
const paramsList: (o.Expression)[] = [];
|
const paramsList: (o.Expression)[] = [];
|
||||||
|
@ -791,8 +831,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
// i0.ɵtext(1, MSG_XYZ);
|
// i0.ɵtext(1, MSG_XYZ);
|
||||||
// ```
|
// ```
|
||||||
visitSingleI18nTextChild(text: t.Text, i18nMeta: string) {
|
visitSingleI18nTextChild(text: t.Text, i18nMeta: string) {
|
||||||
const meta = parseI18nMeta(i18nMeta);
|
const variable = this.i18nTranslate(text.value, i18nMeta);
|
||||||
const variable = this.constantPool.getTranslation(text.value, meta, this.fileBasedI18nSuffix);
|
|
||||||
this.creationInstruction(
|
this.creationInstruction(
|
||||||
text.sourceSpan, R3.text, [o.literal(this.allocateDataSlot()), variable]);
|
text.sourceSpan, R3.text, [o.literal(this.allocateDataSlot()), variable]);
|
||||||
}
|
}
|
||||||
|
@ -840,6 +879,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
this._bindingSlots += value instanceof Interpolation ? value.expressions.length : 1;
|
this._bindingSlots += value instanceof Interpolation ? value.expressions.length : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private convertExpressionBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||||
|
const convertedPropertyBinding =
|
||||||
|
convertPropertyBinding(this, implicit, value, this.bindingContext(), BindingForm.TrySimple);
|
||||||
|
const valExpr = convertedPropertyBinding.currValExpr;
|
||||||
|
return o.importExpr(R3.bind).callFn([valExpr]);
|
||||||
|
}
|
||||||
|
|
||||||
private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
|
private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
|
||||||
o.Expression {
|
o.Expression {
|
||||||
const interpolationFn =
|
const interpolationFn =
|
||||||
|
|
|
@ -35,6 +35,9 @@ export const I18N_ATTR_PREFIX = 'i18n-';
|
||||||
export const MEANING_SEPARATOR = '|';
|
export const MEANING_SEPARATOR = '|';
|
||||||
export const ID_SEPARATOR = '@@';
|
export const ID_SEPARATOR = '@@';
|
||||||
|
|
||||||
|
/** Placeholder wrapper for i18n expressions **/
|
||||||
|
export const I18N_PLACEHOLDER_SYMBOL = '<27>';
|
||||||
|
|
||||||
/** Non bindable attribute name **/
|
/** Non bindable attribute name **/
|
||||||
export const NON_BINDABLE_ATTR = 'ngNonBindable';
|
export const NON_BINDABLE_ATTR = 'ngNonBindable';
|
||||||
|
|
||||||
|
@ -71,6 +74,21 @@ export function isI18NAttribute(name: string): boolean {
|
||||||
return name === I18N_ATTR || name.startsWith(I18N_ATTR_PREFIX);
|
return name === I18N_ATTR || name.startsWith(I18N_ATTR_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function wrapI18nPlaceholder(content: string | number): string {
|
||||||
|
return `${I18N_PLACEHOLDER_SYMBOL}${content}${I18N_PLACEHOLDER_SYMBOL}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assembleI18nTemplate(strings: Array<string>): string {
|
||||||
|
if (!strings.length) return '';
|
||||||
|
let acc = '';
|
||||||
|
const lastIdx = strings.length - 1;
|
||||||
|
for (let i = 0; i < lastIdx; i++) {
|
||||||
|
acc += `${strings[i]}${wrapI18nPlaceholder(i)}`;
|
||||||
|
}
|
||||||
|
acc += strings[lastIdx];
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
export function asLiteral(value: any): o.Expression {
|
export function asLiteral(value: any): o.Expression {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return o.literalArr(value.map(asLiteral));
|
return o.literalArr(value.map(asLiteral));
|
||||||
|
|
|
@ -105,6 +105,10 @@ export {
|
||||||
PipeDef as ɵPipeDef,
|
PipeDef as ɵPipeDef,
|
||||||
PipeDefWithMeta as ɵPipeDefWithMeta,
|
PipeDefWithMeta as ɵPipeDefWithMeta,
|
||||||
whenRendered as ɵwhenRendered,
|
whenRendered as ɵwhenRendered,
|
||||||
|
i18nAttribute as ɵi18nAttribute,
|
||||||
|
i18nExp as ɵi18nExp,
|
||||||
|
i18nStart as ɵi18nStart,
|
||||||
|
i18nEnd as ɵi18nEnd,
|
||||||
i18nApply as ɵi18nApply,
|
i18nApply as ɵi18nApply,
|
||||||
i18nExpMapping as ɵi18nExpMapping,
|
i18nExpMapping as ɵi18nExpMapping,
|
||||||
i18nInterpolation1 as ɵi18nInterpolation1,
|
i18nInterpolation1 as ɵi18nInterpolation1,
|
||||||
|
|
|
@ -283,6 +283,22 @@ function appendI18nNode(
|
||||||
return tNode;
|
return tNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function i18nAttribute(index: number, attrs: any[]): void {
|
||||||
|
// placeholder for i18nAttribute function
|
||||||
|
}
|
||||||
|
|
||||||
|
export function i18nExp(expression: any): void {
|
||||||
|
// placeholder for i18nExp function
|
||||||
|
}
|
||||||
|
|
||||||
|
export function i18nStart(index: number, message: string, subTemplateIndex: number = 0): void {
|
||||||
|
// placeholder for i18nExp function
|
||||||
|
}
|
||||||
|
|
||||||
|
export function i18nEnd(): void {
|
||||||
|
// placeholder for i18nEnd function
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a list of instructions generated by `i18nMapping()` to transform the template accordingly.
|
* Takes a list of instructions generated by `i18nMapping()` to transform the template accordingly.
|
||||||
*
|
*
|
||||||
|
|
|
@ -86,6 +86,10 @@ export {
|
||||||
} from './instructions';
|
} from './instructions';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
i18nAttribute,
|
||||||
|
i18nExp,
|
||||||
|
i18nStart,
|
||||||
|
i18nEnd,
|
||||||
i18nApply,
|
i18nApply,
|
||||||
i18nMapping,
|
i18nMapping,
|
||||||
i18nInterpolation1,
|
i18nInterpolation1,
|
||||||
|
|
|
@ -98,6 +98,11 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||||
'ɵtextBinding': r3.textBinding,
|
'ɵtextBinding': r3.textBinding,
|
||||||
'ɵembeddedViewStart': r3.embeddedViewStart,
|
'ɵembeddedViewStart': r3.embeddedViewStart,
|
||||||
'ɵembeddedViewEnd': r3.embeddedViewEnd,
|
'ɵembeddedViewEnd': r3.embeddedViewEnd,
|
||||||
|
'ɵi18nAttribute': r3.i18nAttribute,
|
||||||
|
'ɵi18nExp': r3.i18nExp,
|
||||||
|
'ɵi18nStart': r3.i18nStart,
|
||||||
|
'ɵi18nEnd': r3.i18nEnd,
|
||||||
|
'ɵi18nApply': r3.i18nApply,
|
||||||
|
|
||||||
'ɵsanitizeHtml': sanitization.sanitizeHtml,
|
'ɵsanitizeHtml': sanitization.sanitizeHtml,
|
||||||
'ɵsanitizeStyle': sanitization.sanitizeStyle,
|
'ɵsanitizeStyle': sanitization.sanitizeStyle,
|
||||||
|
|
Loading…
Reference in New Issue