@@ -472,6 +472,46 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
472
472
this . i18n = null ; // reset local i18n context
473
473
}
474
474
475
+ private i18nAttributesInstruction (
476
+ nodeIndex : number , attrs : ( t . TextAttribute | t . BoundAttribute ) [ ] ,
477
+ sourceSpan : ParseSourceSpan ) : void {
478
+ let hasBindings : boolean = false ;
479
+ const i18nAttrArgs : o . Expression [ ] = [ ] ;
480
+ const bindings : ChainableBindingInstruction [ ] = [ ] ;
481
+ attrs . forEach ( attr => {
482
+ const message = attr . i18n ! as i18n . Message ;
483
+ if ( attr instanceof t . TextAttribute ) {
484
+ i18nAttrArgs . push ( o . literal ( attr . name ) , this . i18nTranslate ( message ) ) ;
485
+ } else {
486
+ const converted = attr . value . visit ( this . _valueConverter ) ;
487
+ this . allocateBindingSlots ( converted ) ;
488
+ if ( converted instanceof Interpolation ) {
489
+ const placeholders = assembleBoundTextPlaceholders ( message ) ;
490
+ const params = placeholdersToParams ( placeholders ) ;
491
+ i18nAttrArgs . push ( o . literal ( attr . name ) , this . i18nTranslate ( message , params ) ) ;
492
+ converted . expressions . forEach ( expression => {
493
+ hasBindings = true ;
494
+ bindings . push ( {
495
+ sourceSpan,
496
+ value : ( ) => this . convertPropertyBinding ( expression ) ,
497
+ } ) ;
498
+ } ) ;
499
+ }
500
+ }
501
+ } ) ;
502
+ if ( bindings . length > 0 ) {
503
+ this . updateInstructionChainWithAdvance ( nodeIndex , R3 . i18nExp , bindings ) ;
504
+ }
505
+ if ( i18nAttrArgs . length > 0 ) {
506
+ const index : o . Expression = o . literal ( this . allocateDataSlot ( ) ) ;
507
+ const args = this . constantPool . getConstLiteral ( o . literalArr ( i18nAttrArgs ) , true ) ;
508
+ this . creationInstruction ( sourceSpan , R3 . i18nAttributes , [ index , args ] ) ;
509
+ if ( hasBindings ) {
510
+ this . updateInstruction ( sourceSpan , R3 . i18nApply , [ index ] ) ;
511
+ }
512
+ }
513
+ }
514
+
475
515
private getNamespaceInstruction ( namespaceKey : string | null ) {
476
516
switch ( namespaceKey ) {
477
517
case 'math' :
@@ -548,10 +588,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
548
588
stylingBuilder . registerClassAttr ( value ) ;
549
589
} else {
550
590
if ( attr . i18n ) {
551
- // Place attributes into a separate array for i18n processing, but also keep such
552
- // attributes in the main list to make them available for directive matching at runtime.
553
- // TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
554
- // arguments
555
591
i18nAttrs . push ( attr ) ;
556
592
} else {
557
593
outputAttrs . push ( attr ) ;
@@ -575,10 +611,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
575
611
const stylingInputWasSet = stylingBuilder . registerBoundInput ( input ) ;
576
612
if ( ! stylingInputWasSet ) {
577
613
if ( input . type === BindingType . Property && input . i18n ) {
578
- // Place attributes into a separate array for i18n processing, but also keep such
579
- // attributes in the main list to make them available for directive matching at runtime.
580
- // TODO(FW-1248): prevent attributes duplication in `i18nAttributes` and `elementStart`
581
- // arguments
582
614
i18nAttrs . push ( input ) ;
583
615
} else {
584
616
allOtherInputs . push ( input ) ;
@@ -631,43 +663,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
631
663
this . creationInstruction ( element . sourceSpan , R3 . disableBindings ) ;
632
664
}
633
665
634
- // process i18n element attributes
635
- if ( i18nAttrs . length ) {
636
- let hasBindings : boolean = false ;
637
- const i18nAttrArgs : o . Expression [ ] = [ ] ;
638
- const bindings : ChainableBindingInstruction [ ] = [ ] ;
639
- i18nAttrs . forEach ( attr => {
640
- const message = attr . i18n ! as i18n . Message ;
641
- if ( attr instanceof t . TextAttribute ) {
642
- i18nAttrArgs . push ( o . literal ( attr . name ) , this . i18nTranslate ( message ) ) ;
643
- } else {
644
- const converted = attr . value . visit ( this . _valueConverter ) ;
645
- this . allocateBindingSlots ( converted ) ;
646
- if ( converted instanceof Interpolation ) {
647
- const placeholders = assembleBoundTextPlaceholders ( message ) ;
648
- const params = placeholdersToParams ( placeholders ) ;
649
- i18nAttrArgs . push ( o . literal ( attr . name ) , this . i18nTranslate ( message , params ) ) ;
650
- converted . expressions . forEach ( expression => {
651
- hasBindings = true ;
652
- bindings . push ( {
653
- sourceSpan : element . sourceSpan ,
654
- value : ( ) => this . convertPropertyBinding ( expression )
655
- } ) ;
656
- } ) ;
657
- }
658
- }
659
- } ) ;
660
- if ( bindings . length ) {
661
- this . updateInstructionChainWithAdvance ( elementIndex , R3 . i18nExp , bindings ) ;
662
- }
663
- if ( i18nAttrArgs . length ) {
664
- const index : o . Expression = o . literal ( this . allocateDataSlot ( ) ) ;
665
- const args = this . constantPool . getConstLiteral ( o . literalArr ( i18nAttrArgs ) , true ) ;
666
- this . creationInstruction ( element . sourceSpan , R3 . i18nAttributes , [ index , args ] ) ;
667
- if ( hasBindings ) {
668
- this . updateInstruction ( element . sourceSpan , R3 . i18nApply , [ index ] ) ;
669
- }
670
- }
666
+ if ( i18nAttrs . length > 0 ) {
667
+ this . i18nAttributesInstruction ( elementIndex , i18nAttrs , element . sourceSpan ) ;
671
668
}
672
669
673
670
// Generate Listeners (outputs)
@@ -850,6 +847,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
850
847
this . matchDirectives ( NG_TEMPLATE_TAG_NAME , template ) ;
851
848
852
849
// prepare attributes parameter (including attributes used for directive matching)
850
+ // TODO (FW-1942): exclude i18n attributes from the main attribute list and pass them
851
+ // as an `i18nAttrs` argument of the `getAttributeExpressions` function below.
853
852
const attrsExprs : o . Expression [ ] = this . getAttributeExpressions (
854
853
template . attributes , template . inputs , template . outputs , undefined , template . templateAttrs ,
855
854
undefined ) ;
@@ -894,8 +893,17 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
894
893
// handle property bindings e.g. ɵɵproperty('ngForOf', ctx.items), et al;
895
894
this . templatePropertyBindings ( templateIndex , template . templateAttrs ) ;
896
895
897
- // Only add normal input/output binding instructions on explicit ng-template elements.
896
+ // Only add normal input/output binding instructions on explicit < ng-template> elements.
898
897
if ( template . tagName === NG_TEMPLATE_TAG_NAME ) {
898
+ // Add i18n attributes that may act as inputs to directives. If such attributes are present,
899
+ // generate `i18nAttributes` instruction. Note: we generate it only for explicit <ng-template>
900
+ // elements, in case of inline templates, corresponding instructions will be generated in the
901
+ // nested template function.
902
+ const i18nAttrs : t . TextAttribute [ ] = template . attributes . filter ( attr => ! ! attr . i18n ) ;
903
+ if ( i18nAttrs . length > 0 ) {
904
+ this . i18nAttributesInstruction ( templateIndex , i18nAttrs , template . sourceSpan ) ;
905
+ }
906
+
899
907
// Add the input bindings
900
908
this . templatePropertyBindings ( templateIndex , template . inputs ) ;
901
909
// Generate listeners for directive output
0 commit comments