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 { | ||||
|             $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}", { | ||||
|  | ||||
| @ -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: | ||||
|    * | ||||
|    * ``` | ||||
|    * <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 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<void>, 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<void>, LocalResolver | ||||
| 
 | ||||
|     // Add the attributes
 | ||||
|     const attributes: o.Expression[] = []; | ||||
|     const allOtherInputs: t.BoundAttribute[] = []; | ||||
| 
 | ||||
|     element.inputs.forEach((input: t.BoundAttribute) => { | ||||
|       const stylingInputWasSet = stylingBuilder.registerBoundInput(input); | ||||
| @ -571,9 +566,10 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver | ||||
|           // TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
 | ||||
|           // arguments
 | ||||
|           i18nAttrs.push(input); | ||||
|         } | ||||
|         } else { | ||||
|           allOtherInputs.push(input); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     outputAttrs.forEach(attr => { | ||||
| @ -585,7 +581,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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.: <div #foo #bar="baz">)
 | ||||
| @ -1147,16 +1144,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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<string>(); | ||||
|     const attrExprs: o.Expression[] = []; | ||||
| 
 | ||||
| @ -1183,7 +1181,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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<void>, 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; | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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: | ||||
|    * | ||||
|    * ``` | ||||
|    * <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`. | ||||
|  * 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]; | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user