diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts
index 558e00eaf9..6f5a741f96 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts
@@ -197,7 +197,7 @@ describe('i18n support in the view compiler', () => {
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("Content A");
}
- const $_c2$ = [${AttributeMarker.Bindings}, "title"];
+ const $_c2$ = [${AttributeMarker.I18n}, "title"];
var $I18N_3$;
if (ngI18nClosureMode) {
/**
@@ -330,7 +330,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = `
- const $_c0$ = ["id", "static", ${AttributeMarker.Bindings}, "title"];
+ const $_c0$ = ["id", "static", ${AttributeMarker.I18n}, "title"];
var $I18N_1$;
if (ngI18nClosureMode) {
/**
@@ -371,7 +371,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
- const $_c0$ = ["id", "dynamic-1", ${AttributeMarker.Bindings}, "aria-roledescription", "title", "aria-label"];
+ const $_c0$ = ["id", "dynamic-1", ${AttributeMarker.I18n}, "aria-roledescription", "title", "aria-label"];
var $I18N_1$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_5526535577705876535$$APP_SPEC_TS_1$ = goog.getMsg("static text");
@@ -417,7 +417,7 @@ describe('i18n support in the view compiler', () => {
"title", $I18N_2$,
"aria-label", $I18N_3$
];
- const $_c2$ = ["id", "dynamic-2", ${AttributeMarker.Bindings}, "title", "aria-roledescription"];
+ const $_c2$ = ["id", "dynamic-2", ${AttributeMarker.I18n}, "title", "aria-roledescription"];
var $I18N_6$;
if (ngI18nClosureMode) {
/**
@@ -487,7 +487,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
- const $_c0$ = [${AttributeMarker.Bindings}, "title"];
+ const $_c0$ = [${AttributeMarker.I18n}, "title"];
var $I18N_1$;
if (ngI18nClosureMode) {
/**
@@ -532,7 +532,7 @@ describe('i18n support in the view compiler', () => {
const output = String.raw `
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
- const $_c1$ = [${AttributeMarker.Bindings}, "title"];
+ const $_c1$ = [${AttributeMarker.I18n}, "title"];
var $I18N_1$;
if (ngI18nClosureMode) {
/**
@@ -599,7 +599,7 @@ describe('i18n support in the view compiler', () => {
const output = String.raw `
const $_c0$ = [
"id", "dynamic-1",
- ${AttributeMarker.Bindings}, "aria-roledescription", "title", "aria-label"
+ ${AttributeMarker.I18n}, "aria-roledescription", "title", "aria-label"
];
var $I18N_1$;
if (ngI18nClosureMode) {
@@ -646,7 +646,7 @@ describe('i18n support in the view compiler', () => {
"title", $I18N_2$,
"aria-label", $I18N_3$
];
- const $_c2$ = ["id", "dynamic-2", ${AttributeMarker.Bindings}, "title", "aria-roledescription"];
+ const $_c2$ = ["id", "dynamic-2", ${AttributeMarker.I18n}, "title", "aria-roledescription"];
var $I18N_6$;
if (ngI18nClosureMode) {
/**
@@ -719,7 +719,7 @@ describe('i18n support in the view compiler', () => {
const output = String.raw `
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
- const $_c1$ = [${AttributeMarker.Bindings}, "title"];
+ const $_c1$ = [${AttributeMarker.I18n}, "title"];
var $I18N_2$;
if (ngI18nClosureMode) {
/**
@@ -776,7 +776,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
- const $_c0$ = [${AttributeMarker.Bindings}, "title"];
+ const $_c0$ = [${AttributeMarker.I18n}, "title"];
var $I18N_0$;
if (ngI18nClosureMode) {
/**
@@ -1256,7 +1256,7 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
- const $_c1$ = [${AttributeMarker.Bindings}, "title"];
+ const $_c1$ = [${AttributeMarker.I18n}, "title"];
var $I18N_2$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_4782264005467235841$$APP_SPEC_TS_3$ = goog.getMsg("Span title {$interpolation} and {$interpolation_1}", {
@@ -1448,7 +1448,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵɵelement(0, "img", $_c0$);
}
}
- const $_c3$ = ["src", "logo.png", ${AttributeMarker.Bindings}, "title"];
+ const $_c3$ = ["src", "logo.png", ${AttributeMarker.I18n}, "title"];
var $I18N_2$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_2367729185105559721$$APP_SPEC_TS__2$ = goog.getMsg("App logo #{$interpolation}", {
diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts
index 18bc01d523..f228d6ead0 100644
--- a/packages/compiler/src/core.ts
+++ b/packages/compiler/src/core.ts
@@ -491,5 +491,21 @@ export const enum AttributeMarker {
* ['attr', 'value', AttributeMarker.ProjectAs, ['', 'title', '']]
* ```
*/
- ProjectAs = 5
+ ProjectAs = 5,
+
+ /**
+ * Signals that the following attribute will be translated by runtime i18n
+ *
+ * For example, given the following HTML:
+ *
+ * ```
+ *
+ * ```
+ *
+ * the generated code is:
+ *
+ * ```
+ * var _c1 = ['moo', 'car', AttributeMarker.I18n, 'foo', 'bar'];
+ */
+ I18n = 6,
}
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index 49444431ad..6b19902457 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -518,7 +518,6 @@ export class TemplateDefinitionBuilder implements t.Visitor
, LocalResolver
const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = [];
const outputAttrs: t.TextAttribute[] = [];
- const allOtherInputs: (t.TextAttribute | t.BoundAttribute)[] = [];
const [namespaceKey, elementName] = splitNsName(element.name);
const isNgContainer = checkIsNgContainer(element.name);
@@ -539,11 +538,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
// arguments
i18nAttrs.push(attr);
- // We treat this attribute as if it was a binding so that we don't risk calling inputs
- // with the untranslated value.
- // Also this generates smaller templates until FW-1248 is fixed.
- // TODO(FW-1332): Create an AttributeMarker for i18n attributes
- allOtherInputs.push(attr);
} else {
outputAttrs.push(attr);
}
@@ -561,6 +555,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// Add the attributes
const attributes: o.Expression[] = [];
+ const allOtherInputs: t.BoundAttribute[] = [];
element.inputs.forEach((input: t.BoundAttribute) => {
const stylingInputWasSet = stylingBuilder.registerBoundInput(input);
@@ -571,8 +566,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
// arguments
i18nAttrs.push(input);
+ } else {
+ allOtherInputs.push(input);
}
- allOtherInputs.push(input);
}
});
@@ -585,7 +581,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
});
// add attributes for directive and projection matching purposes
- attributes.push(...this.prepareNonRenderAttrs(allOtherInputs, element.outputs, stylingBuilder));
+ attributes.push(...this.prepareNonRenderAttrs(
+ allOtherInputs, element.outputs, stylingBuilder, [], i18nAttrs));
parameters.push(this.toAttrsParam(attributes));
// local refs (ex.: )
@@ -1147,16 +1144,17 @@ export class TemplateDefinitionBuilder implements t.Visitor
, LocalResolver
* CLASSES, class1, class2,
* STYLES, style1, value1, style2, value2,
* BINDINGS, name1, name2, name3,
- * TEMPLATE, name4, name5, ...]
+ * TEMPLATE, name4, name5, name6,
+ * I18N, name7, name8, ...]
* ```
*
* Note that this function will fully ignore all synthetic (@foo) attribute values
* because those values are intended to always be generated as property instructions.
*/
private prepareNonRenderAttrs(
- inputs: (t.TextAttribute|t.BoundAttribute)[], outputs: t.BoundEvent[],
- styles?: StylingBuilder,
- templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] {
+ inputs: t.BoundAttribute[], outputs: t.BoundEvent[], styles?: StylingBuilder,
+ templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [],
+ i18nAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] {
const alreadySeen = new Set();
const attrExprs: o.Expression[] = [];
@@ -1183,7 +1181,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
const attrsStartIndex = attrExprs.length;
for (let i = 0; i < inputs.length; i++) {
- const input = inputs[i] as t.BoundAttribute;
+ const input = inputs[i];
if (input.type !== BindingType.Animation) {
addAttrExpr(input.name);
}
@@ -1210,6 +1208,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
templateAttrs.forEach(attr => addAttrExpr(attr.name));
}
+ if (i18nAttrs.length) {
+ attrExprs.push(o.literal(core.AttributeMarker.I18n));
+ i18nAttrs.forEach(attr => addAttrExpr(attr.name));
+ }
+
return attrExprs;
}
diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts
index cb6f99b466..4e88949712 100644
--- a/packages/core/src/render3/interfaces/node.ts
+++ b/packages/core/src/render3/interfaces/node.ts
@@ -180,7 +180,23 @@ export const enum AttributeMarker {
* ['attr', 'value', AttributeMarker.ProjectAs, ['', 'title', '']]
* ```
*/
- ProjectAs = 5
+ ProjectAs = 5,
+
+ /**
+ * Signals that the following attribute will be translated by runtime i18n
+ *
+ * For example, given the following HTML:
+ *
+ * ```
+ *
+ * ```
+ *
+ * the generated code is:
+ *
+ * ```
+ * var _c1 = ['moo', 'car', AttributeMarker.I18n, 'foo', 'bar'];
+ */
+ I18n,
}
/**
diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts
index b291587bc2..a65facee12 100644
--- a/packages/core/src/render3/node_selector_matcher.ts
+++ b/packages/core/src/render3/node_selector_matcher.ts
@@ -171,17 +171,18 @@ function readClassValueFromTNode(tNode: TNode): string {
* Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
* The following table summarizes which types of attributes we attempt to match:
*
- * =========================================================================================
- * Modes | Normal Attributes | Bindings Attributes | Template Attributes
- * =========================================================================================
- * Inline + Projection | YES | YES | NO
- * -----------------------------------------------------------------------------------------
- * Inline + Directive | NO | NO | YES
- * -----------------------------------------------------------------------------------------
- * Non-inline + Projection | YES | YES | NO
- * -----------------------------------------------------------------------------------------
- * Non-inline + Directive | YES | YES | NO
- * =========================================================================================
+ * ===========================================================================================================
+ * Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n
+ * Attributes
+ * ===========================================================================================================
+ * Inline + Projection | YES | YES | NO | YES
+ * -----------------------------------------------------------------------------------------------------------
+ * Inline + Directive | NO | NO | YES | NO
+ * -----------------------------------------------------------------------------------------------------------
+ * Non-inline + Projection | YES | YES | NO | YES
+ * -----------------------------------------------------------------------------------------------------------
+ * Non-inline + Directive | YES | YES | NO | YES
+ * ===========================================================================================================
*
* @param name the name of the attribute to find
* @param attrs the attribute array to examine
@@ -203,7 +204,8 @@ function findAttrIndexInNode(
const maybeAttrName = attrs[i];
if (maybeAttrName === name) {
return i;
- } else if (maybeAttrName === AttributeMarker.Bindings) {
+ } else if (
+ maybeAttrName === AttributeMarker.Bindings || maybeAttrName === AttributeMarker.I18n) {
bindingsMode = true;
} else if (maybeAttrName === AttributeMarker.Classes) {
let value = attrs[++i];
diff --git a/packages/core/src/render3/util/attrs_utils.ts b/packages/core/src/render3/util/attrs_utils.ts
index 445404948a..c64e2df6ce 100644
--- a/packages/core/src/render3/util/attrs_utils.ts
+++ b/packages/core/src/render3/util/attrs_utils.ts
@@ -109,8 +109,9 @@ export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): num
* attribute values in a `TAttributes` array are only the names of attributes,
* and not name-value pairs.
* @param marker The attribute marker to test.
- * @returns true if the marker is a "name-only" marker (e.g. `Bindings` or `Template`).
+ * @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`).
*/
export function isNameOnlyAttributeMarker(marker: string | AttributeMarker | CssSelector) {
- return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template;
+ return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template ||
+ marker === AttributeMarker.I18n;
}