feat(ivy): add `AttributeMarker.I18n` for i18n attributes (#30402)
`i18nAttributes` instructions always occur after the element instruction. This means that we need to treat `i18n-` attributes differently. By defining a specific `AttributeMarker` we can ensure that we won't trigger directive inputs with untranslated attribute values. FW-1332 #resolve PR Close #30402
This commit is contained in:
parent
91699259b2
commit
53c6b78c51
|
@ -197,7 +197,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
else {
|
else {
|
||||||
$I18N_0$ = $r3$.ɵɵi18nLocalize("Content A");
|
$I18N_0$ = $r3$.ɵɵi18nLocalize("Content A");
|
||||||
}
|
}
|
||||||
const $_c2$ = [${AttributeMarker.Bindings}, "title"];
|
const $_c2$ = [${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_3$;
|
var $I18N_3$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -330,7 +330,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const output = `
|
const output = `
|
||||||
const $_c0$ = ["id", "static", ${AttributeMarker.Bindings}, "title"];
|
const $_c0$ = ["id", "static", ${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_1$;
|
var $I18N_1$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -371,7 +371,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const output = String.raw `
|
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$;
|
var $I18N_1$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
const $MSG_EXTERNAL_5526535577705876535$$APP_SPEC_TS_1$ = goog.getMsg("static text");
|
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$,
|
"title", $I18N_2$,
|
||||||
"aria-label", $I18N_3$
|
"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$;
|
var $I18N_6$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -487,7 +487,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const output = String.raw `
|
const output = String.raw `
|
||||||
const $_c0$ = [${AttributeMarker.Bindings}, "title"];
|
const $_c0$ = [${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_1$;
|
var $I18N_1$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -532,7 +532,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
|
|
||||||
const output = String.raw `
|
const output = String.raw `
|
||||||
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
|
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
|
||||||
const $_c1$ = [${AttributeMarker.Bindings}, "title"];
|
const $_c1$ = [${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_1$;
|
var $I18N_1$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -599,7 +599,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
const output = String.raw `
|
const output = String.raw `
|
||||||
const $_c0$ = [
|
const $_c0$ = [
|
||||||
"id", "dynamic-1",
|
"id", "dynamic-1",
|
||||||
${AttributeMarker.Bindings}, "aria-roledescription", "title", "aria-label"
|
${AttributeMarker.I18n}, "aria-roledescription", "title", "aria-label"
|
||||||
];
|
];
|
||||||
var $I18N_1$;
|
var $I18N_1$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
|
@ -646,7 +646,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
"title", $I18N_2$,
|
"title", $I18N_2$,
|
||||||
"aria-label", $I18N_3$
|
"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$;
|
var $I18N_6$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -719,7 +719,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
|
|
||||||
const output = String.raw `
|
const output = String.raw `
|
||||||
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
|
const $_c0$ = [${AttributeMarker.Template}, "ngFor", "ngForOf"];
|
||||||
const $_c1$ = [${AttributeMarker.Bindings}, "title"];
|
const $_c1$ = [${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_2$;
|
var $I18N_2$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -776,7 +776,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const output = String.raw `
|
const output = String.raw `
|
||||||
const $_c0$ = [${AttributeMarker.Bindings}, "title"];
|
const $_c0$ = [${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_0$;
|
var $I18N_0$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
/**
|
/**
|
||||||
|
@ -1256,7 +1256,7 @@ describe('i18n support in the view compiler', () => {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const output = String.raw `
|
const output = String.raw `
|
||||||
const $_c1$ = [${AttributeMarker.Bindings}, "title"];
|
const $_c1$ = [${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_2$;
|
var $I18N_2$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
const $MSG_EXTERNAL_4782264005467235841$$APP_SPEC_TS_3$ = goog.getMsg("Span title {$interpolation} and {$interpolation_1}", {
|
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$);
|
$r3$.ɵɵelement(0, "img", $_c0$);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const $_c3$ = ["src", "logo.png", ${AttributeMarker.Bindings}, "title"];
|
const $_c3$ = ["src", "logo.png", ${AttributeMarker.I18n}, "title"];
|
||||||
var $I18N_2$;
|
var $I18N_2$;
|
||||||
if (ngI18nClosureMode) {
|
if (ngI18nClosureMode) {
|
||||||
const $MSG_EXTERNAL_2367729185105559721$$APP_SPEC_TS__2$ = goog.getMsg("App logo #{$interpolation}", {
|
const $MSG_EXTERNAL_2367729185105559721$$APP_SPEC_TS__2$ = goog.getMsg("App logo #{$interpolation}", {
|
||||||
|
|
|
@ -491,5 +491,21 @@ export const enum AttributeMarker {
|
||||||
* ['attr', 'value', AttributeMarker.ProjectAs, ['', 'title', '']]
|
* ['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:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <div moo="car" foo="value" i18n-foo [bar]="binding" i18n-bar>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* the generated code is:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* var _c1 = ['moo', 'car', AttributeMarker.I18n, 'foo', 'bar'];
|
||||||
|
*/
|
||||||
|
I18n = 6,
|
||||||
}
|
}
|
||||||
|
|
|
@ -518,7 +518,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
|
|
||||||
const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = [];
|
const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = [];
|
||||||
const outputAttrs: t.TextAttribute[] = [];
|
const outputAttrs: t.TextAttribute[] = [];
|
||||||
const allOtherInputs: (t.TextAttribute | t.BoundAttribute)[] = [];
|
|
||||||
|
|
||||||
const [namespaceKey, elementName] = splitNsName(element.name);
|
const [namespaceKey, elementName] = splitNsName(element.name);
|
||||||
const isNgContainer = checkIsNgContainer(element.name);
|
const isNgContainer = checkIsNgContainer(element.name);
|
||||||
|
@ -539,11 +538,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
// TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
|
// TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
|
||||||
// arguments
|
// arguments
|
||||||
i18nAttrs.push(attr);
|
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 {
|
} else {
|
||||||
outputAttrs.push(attr);
|
outputAttrs.push(attr);
|
||||||
}
|
}
|
||||||
|
@ -561,6 +555,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
|
|
||||||
// Add the attributes
|
// Add the attributes
|
||||||
const attributes: o.Expression[] = [];
|
const attributes: o.Expression[] = [];
|
||||||
|
const allOtherInputs: t.BoundAttribute[] = [];
|
||||||
|
|
||||||
element.inputs.forEach((input: t.BoundAttribute) => {
|
element.inputs.forEach((input: t.BoundAttribute) => {
|
||||||
const stylingInputWasSet = stylingBuilder.registerBoundInput(input);
|
const stylingInputWasSet = stylingBuilder.registerBoundInput(input);
|
||||||
|
@ -571,8 +566,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
// TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
|
// TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
|
||||||
// arguments
|
// arguments
|
||||||
i18nAttrs.push(input);
|
i18nAttrs.push(input);
|
||||||
|
} else {
|
||||||
|
allOtherInputs.push(input);
|
||||||
}
|
}
|
||||||
allOtherInputs.push(input);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -585,7 +581,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
});
|
});
|
||||||
|
|
||||||
// add attributes for directive and projection matching purposes
|
// 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));
|
parameters.push(this.toAttrsParam(attributes));
|
||||||
|
|
||||||
// local refs (ex.: <div #foo #bar="baz">)
|
// local refs (ex.: <div #foo #bar="baz">)
|
||||||
|
@ -1147,16 +1144,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
* CLASSES, class1, class2,
|
* CLASSES, class1, class2,
|
||||||
* STYLES, style1, value1, style2, value2,
|
* STYLES, style1, value1, style2, value2,
|
||||||
* BINDINGS, name1, name2, name3,
|
* 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
|
* Note that this function will fully ignore all synthetic (@foo) attribute values
|
||||||
* because those values are intended to always be generated as property instructions.
|
* because those values are intended to always be generated as property instructions.
|
||||||
*/
|
*/
|
||||||
private prepareNonRenderAttrs(
|
private prepareNonRenderAttrs(
|
||||||
inputs: (t.TextAttribute|t.BoundAttribute)[], outputs: t.BoundEvent[],
|
inputs: t.BoundAttribute[], outputs: t.BoundEvent[], styles?: StylingBuilder,
|
||||||
styles?: StylingBuilder,
|
templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = [],
|
||||||
templateAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] {
|
i18nAttrs: (t.BoundAttribute|t.TextAttribute)[] = []): o.Expression[] {
|
||||||
const alreadySeen = new Set<string>();
|
const alreadySeen = new Set<string>();
|
||||||
const attrExprs: o.Expression[] = [];
|
const attrExprs: o.Expression[] = [];
|
||||||
|
|
||||||
|
@ -1183,7 +1181,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
const attrsStartIndex = attrExprs.length;
|
const attrsStartIndex = attrExprs.length;
|
||||||
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
const input = inputs[i] as t.BoundAttribute;
|
const input = inputs[i];
|
||||||
if (input.type !== BindingType.Animation) {
|
if (input.type !== BindingType.Animation) {
|
||||||
addAttrExpr(input.name);
|
addAttrExpr(input.name);
|
||||||
}
|
}
|
||||||
|
@ -1210,6 +1208,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
templateAttrs.forEach(attr => addAttrExpr(attr.name));
|
templateAttrs.forEach(attr => addAttrExpr(attr.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i18nAttrs.length) {
|
||||||
|
attrExprs.push(o.literal(core.AttributeMarker.I18n));
|
||||||
|
i18nAttrs.forEach(attr => addAttrExpr(attr.name));
|
||||||
|
}
|
||||||
|
|
||||||
return attrExprs;
|
return attrExprs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -180,7 +180,23 @@ export const enum AttributeMarker {
|
||||||
* ['attr', 'value', AttributeMarker.ProjectAs, ['', 'title', '']]
|
* ['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:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <div moo="car" foo="value" i18n-foo [bar]="binding" i18n-bar>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* the generated code is:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* var _c1 = ['moo', 'car', AttributeMarker.I18n, 'foo', 'bar'];
|
||||||
|
*/
|
||||||
|
I18n,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -171,17 +171,18 @@ function readClassValueFromTNode(tNode: TNode): string {
|
||||||
* Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
|
* Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
|
||||||
* The following table summarizes which types of attributes we attempt to match:
|
* The following table summarizes which types of attributes we attempt to match:
|
||||||
*
|
*
|
||||||
* =========================================================================================
|
* ===========================================================================================================
|
||||||
* Modes | Normal Attributes | Bindings Attributes | Template Attributes
|
* Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n
|
||||||
* =========================================================================================
|
* Attributes
|
||||||
* Inline + Projection | YES | YES | NO
|
* ===========================================================================================================
|
||||||
* -----------------------------------------------------------------------------------------
|
* Inline + Projection | YES | YES | NO | YES
|
||||||
* Inline + Directive | NO | NO | YES
|
* -----------------------------------------------------------------------------------------------------------
|
||||||
* -----------------------------------------------------------------------------------------
|
* Inline + Directive | NO | NO | YES | NO
|
||||||
* Non-inline + Projection | YES | YES | NO
|
* -----------------------------------------------------------------------------------------------------------
|
||||||
* -----------------------------------------------------------------------------------------
|
* Non-inline + Projection | YES | YES | NO | YES
|
||||||
* Non-inline + Directive | YES | YES | NO
|
* -----------------------------------------------------------------------------------------------------------
|
||||||
* =========================================================================================
|
* Non-inline + Directive | YES | YES | NO | YES
|
||||||
|
* ===========================================================================================================
|
||||||
*
|
*
|
||||||
* @param name the name of the attribute to find
|
* @param name the name of the attribute to find
|
||||||
* @param attrs the attribute array to examine
|
* @param attrs the attribute array to examine
|
||||||
|
@ -203,7 +204,8 @@ function findAttrIndexInNode(
|
||||||
const maybeAttrName = attrs[i];
|
const maybeAttrName = attrs[i];
|
||||||
if (maybeAttrName === name) {
|
if (maybeAttrName === name) {
|
||||||
return i;
|
return i;
|
||||||
} else if (maybeAttrName === AttributeMarker.Bindings) {
|
} else if (
|
||||||
|
maybeAttrName === AttributeMarker.Bindings || maybeAttrName === AttributeMarker.I18n) {
|
||||||
bindingsMode = true;
|
bindingsMode = true;
|
||||||
} else if (maybeAttrName === AttributeMarker.Classes) {
|
} else if (maybeAttrName === AttributeMarker.Classes) {
|
||||||
let value = attrs[++i];
|
let value = attrs[++i];
|
||||||
|
|
|
@ -109,8 +109,9 @@ export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): num
|
||||||
* attribute values in a `TAttributes` array are only the names of attributes,
|
* attribute values in a `TAttributes` array are only the names of attributes,
|
||||||
* and not name-value pairs.
|
* and not name-value pairs.
|
||||||
* @param marker The attribute marker to test.
|
* @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) {
|
export function isNameOnlyAttributeMarker(marker: string | AttributeMarker | CssSelector) {
|
||||||
return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template;
|
return marker === AttributeMarker.Bindings || marker === AttributeMarker.Template ||
|
||||||
|
marker === AttributeMarker.I18n;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue