@angular/compiler 17.1.0-next.2 → 17.1.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/esm2022/src/render3/partial/class_metadata.mjs +1 -1
  2. package/esm2022/src/render3/partial/directive.mjs +1 -1
  3. package/esm2022/src/render3/partial/factory.mjs +1 -1
  4. package/esm2022/src/render3/partial/injectable.mjs +1 -1
  5. package/esm2022/src/render3/partial/injector.mjs +1 -1
  6. package/esm2022/src/render3/partial/ng_module.mjs +1 -1
  7. package/esm2022/src/render3/partial/pipe.mjs +1 -1
  8. package/esm2022/src/render3/view/compiler.mjs +20 -8
  9. package/esm2022/src/template/pipeline/ir/src/enums.mjs +26 -1
  10. package/esm2022/src/template/pipeline/ir/src/expression.mjs +8 -1
  11. package/esm2022/src/template/pipeline/ir/src/ops/create.mjs +25 -7
  12. package/esm2022/src/template/pipeline/ir/src/ops/host.mjs +3 -2
  13. package/esm2022/src/template/pipeline/ir/src/ops/update.mjs +23 -14
  14. package/esm2022/src/template/pipeline/src/emit.mjs +10 -6
  15. package/esm2022/src/template/pipeline/src/ingest.mjs +86 -36
  16. package/esm2022/src/template/pipeline/src/instruction.mjs +5 -1
  17. package/esm2022/src/template/pipeline/src/phases/apply_i18n_expressions.mjs +20 -4
  18. package/esm2022/src/template/pipeline/src/phases/assign_i18n_slot_dependencies.mjs +43 -5
  19. package/esm2022/src/template/pipeline/src/phases/attribute_extraction.mjs +19 -5
  20. package/esm2022/src/template/pipeline/src/phases/binding_specialization.mjs +4 -4
  21. package/esm2022/src/template/pipeline/src/phases/const_collection.mjs +3 -3
  22. package/esm2022/src/template/pipeline/src/phases/convert_i18n_bindings.mjs +52 -0
  23. package/esm2022/src/template/pipeline/src/phases/extract_i18n_messages.mjs +65 -88
  24. package/esm2022/src/template/pipeline/src/phases/generate_advance.mjs +2 -2
  25. package/esm2022/src/template/pipeline/src/phases/i18n_const_collection.mjs +143 -35
  26. package/esm2022/src/template/pipeline/src/phases/i18n_text_extraction.mjs +5 -3
  27. package/esm2022/src/template/pipeline/src/phases/parse_extracted_styles.mjs +3 -3
  28. package/esm2022/src/template/pipeline/src/phases/phase_remove_content_selectors.mjs +15 -3
  29. package/esm2022/src/template/pipeline/src/phases/propagate_i18n_blocks.mjs +5 -1
  30. package/esm2022/src/template/pipeline/src/phases/reify.mjs +7 -1
  31. package/esm2022/src/template/pipeline/src/phases/remove_unused_i18n_attrs.mjs +33 -0
  32. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.mjs +129 -31
  33. package/esm2022/src/template/pipeline/src/phases/resolve_i18n_expression_placeholders.mjs +10 -5
  34. package/esm2022/src/version.mjs +1 -1
  35. package/fesm2022/compiler.mjs +733 -257
  36. package/fesm2022/compiler.mjs.map +1 -1
  37. package/index.d.ts +1 -1
  38. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Angular v17.1.0-next.2
2
+ * @license Angular v17.1.0-next.3
3
3
  * (c) 2010-2022 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
@@ -4712,7 +4712,7 @@ const I18N_ATTR_PREFIX = 'i18n-';
4712
4712
  /** Prefix of var expressions used in ICUs */
4713
4713
  const I18N_ICU_VAR_PREFIX = 'VAR_';
4714
4714
  /** Prefix of ICU expressions for post processing */
4715
- const I18N_ICU_MAPPING_PREFIX = 'I18N_EXP_';
4715
+ const I18N_ICU_MAPPING_PREFIX$1 = 'I18N_EXP_';
4716
4716
  /** Placeholder wrapper for i18n expressions **/
4717
4717
  const I18N_PLACEHOLDER_SYMBOL = '�';
4718
4718
  function isI18nAttribute(name) {
@@ -8970,6 +8970,10 @@ var OpKind;
8970
8970
  * An i18n context containing information needed to generate an i18n message.
8971
8971
  */
8972
8972
  OpKind[OpKind["I18nContext"] = 43] = "I18nContext";
8973
+ /**
8974
+ * A creation op that corresponds to i18n attributes on an element.
8975
+ */
8976
+ OpKind[OpKind["I18nAttributes"] = 44] = "I18nAttributes";
8973
8977
  })(OpKind || (OpKind = {}));
8974
8978
  /**
8975
8979
  * Distinguishes different kinds of IR expressions.
@@ -9191,6 +9195,20 @@ var I18nParamResolutionTime;
9191
9195
  */
9192
9196
  I18nParamResolutionTime[I18nParamResolutionTime["Postproccessing"] = 1] = "Postproccessing";
9193
9197
  })(I18nParamResolutionTime || (I18nParamResolutionTime = {}));
9198
+ /**
9199
+ * The contexts in which an i18n expression can be used.
9200
+ */
9201
+ var I18nExpressionFor;
9202
+ (function (I18nExpressionFor) {
9203
+ /**
9204
+ * This expression is used as a value (i.e. inside an i18n block).
9205
+ */
9206
+ I18nExpressionFor[I18nExpressionFor["I18nText"] = 0] = "I18nText";
9207
+ /**
9208
+ * This expression is used in a binding.
9209
+ */
9210
+ I18nExpressionFor[I18nExpressionFor["I18nAttribute"] = 1] = "I18nAttribute";
9211
+ })(I18nExpressionFor || (I18nExpressionFor = {}));
9194
9212
  /**
9195
9213
  * Flags that describe what an i18n param value. These determine how the value is serialized into
9196
9214
  * the final map.
@@ -9257,7 +9275,14 @@ var I18nContextKind;
9257
9275
  (function (I18nContextKind) {
9258
9276
  I18nContextKind[I18nContextKind["RootI18n"] = 0] = "RootI18n";
9259
9277
  I18nContextKind[I18nContextKind["Icu"] = 1] = "Icu";
9278
+ I18nContextKind[I18nContextKind["Attr"] = 2] = "Attr";
9260
9279
  })(I18nContextKind || (I18nContextKind = {}));
9280
+ var TemplateKind;
9281
+ (function (TemplateKind) {
9282
+ TemplateKind[TemplateKind["NgTemplate"] = 0] = "NgTemplate";
9283
+ TemplateKind[TemplateKind["Structural"] = 1] = "Structural";
9284
+ TemplateKind[TemplateKind["Block"] = 2] = "Block";
9285
+ })(TemplateKind || (TemplateKind = {}));
9261
9286
 
9262
9287
  /**
9263
9288
  * Marker symbol for `ConsumesSlotOpTrait`.
@@ -9365,12 +9390,11 @@ const NEW_OP = {
9365
9390
  /**
9366
9391
  * Create an `InterpolationTextOp`.
9367
9392
  */
9368
- function createInterpolateTextOp(xref, interpolation, i18nPlaceholders, sourceSpan) {
9393
+ function createInterpolateTextOp(xref, interpolation, sourceSpan) {
9369
9394
  return {
9370
9395
  kind: OpKind.InterpolateText,
9371
9396
  target: xref,
9372
9397
  interpolation,
9373
- i18nPlaceholders,
9374
9398
  sourceSpan,
9375
9399
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9376
9400
  ...TRAIT_CONSUMES_VARS,
@@ -9378,15 +9402,19 @@ function createInterpolateTextOp(xref, interpolation, i18nPlaceholders, sourceSp
9378
9402
  };
9379
9403
  }
9380
9404
  class Interpolation {
9381
- constructor(strings, expressions) {
9405
+ constructor(strings, expressions, i18nPlaceholders) {
9382
9406
  this.strings = strings;
9383
9407
  this.expressions = expressions;
9408
+ this.i18nPlaceholders = i18nPlaceholders;
9409
+ if (i18nPlaceholders.length !== 0 && i18nPlaceholders.length !== expressions.length) {
9410
+ throw new Error(`Expected ${expressions.length} placeholders to match interpolation expression count, but got ${i18nPlaceholders.length}`);
9411
+ }
9384
9412
  }
9385
9413
  }
9386
9414
  /**
9387
9415
  * Create a `BindingOp`, not yet transformed into a particular type of binding.
9388
9416
  */
9389
- function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isTemplate, sourceSpan) {
9417
+ function createBindingOp(target, kind, name, expression, unit, securityContext, isTextAttribute, isStructuralTemplate, i18nContext, sourceSpan) {
9390
9418
  return {
9391
9419
  kind: OpKind.Binding,
9392
9420
  bindingKind: kind,
@@ -9396,7 +9424,8 @@ function createBindingOp(target, kind, name, expression, unit, securityContext,
9396
9424
  unit,
9397
9425
  securityContext,
9398
9426
  isTextAttribute,
9399
- isTemplate,
9427
+ isStructuralTemplate: isStructuralTemplate,
9428
+ i18nContext,
9400
9429
  sourceSpan,
9401
9430
  ...NEW_OP,
9402
9431
  };
@@ -9404,7 +9433,7 @@ function createBindingOp(target, kind, name, expression, unit, securityContext,
9404
9433
  /**
9405
9434
  * Create a `PropertyOp`.
9406
9435
  */
9407
- function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isTemplate, sourceSpan) {
9436
+ function createPropertyOp(target, name, expression, isAnimationTrigger, securityContext, isStructuralTemplate, i18nContext, sourceSpan) {
9408
9437
  return {
9409
9438
  kind: OpKind.Property,
9410
9439
  target,
@@ -9413,7 +9442,8 @@ function createPropertyOp(target, name, expression, isAnimationTrigger, security
9413
9442
  isAnimationTrigger,
9414
9443
  securityContext,
9415
9444
  sanitizer: null,
9416
- isTemplate,
9445
+ isStructuralTemplate,
9446
+ i18nContext,
9417
9447
  sourceSpan,
9418
9448
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9419
9449
  ...TRAIT_CONSUMES_VARS,
@@ -9478,7 +9508,7 @@ function createClassMapOp(xref, expression, sourceSpan) {
9478
9508
  /**
9479
9509
  * Create an `AttributeOp`.
9480
9510
  */
9481
- function createAttributeOp(target, name, expression, securityContext, isTextAttribute, isTemplate, sourceSpan) {
9511
+ function createAttributeOp(target, name, expression, securityContext, isTextAttribute, isStructuralTemplate, i18nContext, sourceSpan) {
9482
9512
  return {
9483
9513
  kind: OpKind.Attribute,
9484
9514
  target,
@@ -9487,7 +9517,8 @@ function createAttributeOp(target, name, expression, securityContext, isTextAttr
9487
9517
  securityContext,
9488
9518
  sanitizer: null,
9489
9519
  isTextAttribute,
9490
- isTemplate,
9520
+ isStructuralTemplate,
9521
+ i18nContext,
9491
9522
  sourceSpan,
9492
9523
  ...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
9493
9524
  ...TRAIT_CONSUMES_VARS,
@@ -9548,15 +9579,18 @@ function createDeferWhenOp(target, expr, prefetch, sourceSpan) {
9548
9579
  /**
9549
9580
  * Create an i18n expression op.
9550
9581
  */
9551
- function createI18nExpressionOp(context, target, handle, expression, i18nPlaceholder, resolutionTime, sourceSpan) {
9582
+ function createI18nExpressionOp(context, target, i18nOwner, handle, expression, i18nPlaceholder, resolutionTime, usage, name, sourceSpan) {
9552
9583
  return {
9553
9584
  kind: OpKind.I18nExpression,
9554
9585
  context,
9555
9586
  target,
9587
+ i18nOwner,
9556
9588
  handle,
9557
9589
  expression,
9558
9590
  i18nPlaceholder,
9559
9591
  resolutionTime,
9592
+ usage,
9593
+ name,
9560
9594
  sourceSpan,
9561
9595
  ...NEW_OP,
9562
9596
  ...TRAIT_CONSUMES_VARS,
@@ -9564,12 +9598,12 @@ function createI18nExpressionOp(context, target, handle, expression, i18nPlaceho
9564
9598
  };
9565
9599
  }
9566
9600
  /**
9567
- *Creates an op to apply i18n expression ops
9601
+ * Creates an op to apply i18n expression ops.
9568
9602
  */
9569
- function createI18nApplyOp(target, handle, sourceSpan) {
9603
+ function createI18nApplyOp(owner, handle, sourceSpan) {
9570
9604
  return {
9571
9605
  kind: OpKind.I18nApply,
9572
- target,
9606
+ owner,
9573
9607
  handle,
9574
9608
  sourceSpan,
9575
9609
  ...NEW_OP,
@@ -10373,6 +10407,7 @@ function transformExpressionsInOp(op, transform, flags) {
10373
10407
  case OpKind.ProjectionDef:
10374
10408
  case OpKind.Template:
10375
10409
  case OpKind.Text:
10410
+ case OpKind.I18nAttributes:
10376
10411
  // These operations contain no expressions.
10377
10412
  break;
10378
10413
  default:
@@ -10393,6 +10428,9 @@ function transformExpressionsInExpression(expr, transform, flags) {
10393
10428
  expr.lhs = transformExpressionsInExpression(expr.lhs, transform, flags);
10394
10429
  expr.rhs = transformExpressionsInExpression(expr.rhs, transform, flags);
10395
10430
  }
10431
+ else if (expr instanceof UnaryOperatorExpr) {
10432
+ expr.expr = transformExpressionsInExpression(expr.expr, transform, flags);
10433
+ }
10396
10434
  else if (expr instanceof ReadPropExpr) {
10397
10435
  expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
10398
10436
  }
@@ -10444,6 +10482,9 @@ function transformExpressionsInExpression(expr, transform, flags) {
10444
10482
  expr.expressions[i] = transformExpressionsInExpression(expr.expressions[i], transform, flags);
10445
10483
  }
10446
10484
  }
10485
+ else if (expr instanceof NotExpr) {
10486
+ expr.condition = transformExpressionsInExpression(expr.condition, transform, flags);
10487
+ }
10447
10488
  else if (expr instanceof ReadVarExpr || expr instanceof ExternalExpr ||
10448
10489
  expr instanceof LiteralExpr) {
10449
10490
  // No action for these types.
@@ -10785,10 +10826,11 @@ function createElementStartOp(tag, xref, namespace, i18nPlaceholder, sourceSpan)
10785
10826
  /**
10786
10827
  * Create a `TemplateOp`.
10787
10828
  */
10788
- function createTemplateOp(xref, tag, functionNameSuffix, namespace, i18nPlaceholder, sourceSpan) {
10829
+ function createTemplateOp(xref, templateKind, tag, functionNameSuffix, namespace, i18nPlaceholder, sourceSpan) {
10789
10830
  return {
10790
10831
  kind: OpKind.Template,
10791
10832
  xref,
10833
+ templateKind,
10792
10834
  attributes: null,
10793
10835
  tag,
10794
10836
  handle: new SlotHandle(),
@@ -10911,14 +10953,15 @@ function createProjectionDefOp(def) {
10911
10953
  ...NEW_OP,
10912
10954
  };
10913
10955
  }
10914
- function createProjectionOp(xref, selector, sourceSpan) {
10956
+ function createProjectionOp(xref, selector, i18nPlaceholder, attributes, sourceSpan) {
10915
10957
  return {
10916
10958
  kind: OpKind.Projection,
10917
10959
  xref,
10918
10960
  handle: new SlotHandle(),
10919
10961
  selector,
10962
+ i18nPlaceholder,
10920
10963
  projectionSlotIndex: 0,
10921
- attributes: [],
10964
+ attributes,
10922
10965
  localRefs: [],
10923
10966
  sourceSpan,
10924
10967
  ...NEW_OP,
@@ -10928,13 +10971,14 @@ function createProjectionOp(xref, selector, sourceSpan) {
10928
10971
  /**
10929
10972
  * Create an `ExtractedAttributeOp`.
10930
10973
  */
10931
- function createExtractedAttributeOp(target, bindingKind, name, expression) {
10974
+ function createExtractedAttributeOp(target, bindingKind, name, expression, i18nContext) {
10932
10975
  return {
10933
10976
  kind: OpKind.ExtractedAttribute,
10934
10977
  target,
10935
10978
  bindingKind,
10936
10979
  name,
10937
10980
  expression,
10981
+ i18nContext,
10938
10982
  ...NEW_OP,
10939
10983
  };
10940
10984
  }
@@ -10977,10 +11021,11 @@ function createDeferOnOp(defer, trigger, prefetch, sourceSpan) {
10977
11021
  /**
10978
11022
  * Create an `ExtractedMessageOp`.
10979
11023
  */
10980
- function createI18nMessageOp(xref, i18nBlock, message, messagePlaceholder, params, postprocessingParams, needsPostprocessing) {
11024
+ function createI18nMessageOp(xref, i18nContext, i18nBlock, message, messagePlaceholder, params, postprocessingParams, needsPostprocessing) {
10981
11025
  return {
10982
11026
  kind: OpKind.I18nMessage,
10983
11027
  xref,
11028
+ i18nContext,
10984
11029
  i18nBlock,
10985
11030
  message,
10986
11031
  messagePlaceholder,
@@ -11043,6 +11088,9 @@ function createIcuEndOp(xref) {
11043
11088
  };
11044
11089
  }
11045
11090
  function createI18nContextOp(contextKind, xref, i18nBlock, message, sourceSpan) {
11091
+ if (i18nBlock === null && contextKind !== I18nContextKind.Attr) {
11092
+ throw new Error('AssertionError: i18nBlock must be provided for non-attribute contexts.');
11093
+ }
11046
11094
  return {
11047
11095
  kind: OpKind.I18nContext,
11048
11096
  contextKind,
@@ -11055,6 +11103,17 @@ function createI18nContextOp(contextKind, xref, i18nBlock, message, sourceSpan)
11055
11103
  ...NEW_OP,
11056
11104
  };
11057
11105
  }
11106
+ function createI18nAttributesOp(xref, handle, target) {
11107
+ return {
11108
+ kind: OpKind.I18nAttributes,
11109
+ xref,
11110
+ handle,
11111
+ target,
11112
+ i18nAttributesConfig: null,
11113
+ ...NEW_OP,
11114
+ ...TRAIT_CONSUMES_SLOT,
11115
+ };
11116
+ }
11058
11117
  function literalOrArrayLiteral$1(value) {
11059
11118
  if (Array.isArray(value)) {
11060
11119
  return literalArr(value.map(literalOrArrayLiteral$1));
@@ -11062,12 +11121,13 @@ function literalOrArrayLiteral$1(value) {
11062
11121
  return literal(value, INFERRED_TYPE);
11063
11122
  }
11064
11123
 
11065
- function createHostPropertyOp(name, expression, isAnimationTrigger, sourceSpan) {
11124
+ function createHostPropertyOp(name, expression, isAnimationTrigger, i18nContext, sourceSpan) {
11066
11125
  return {
11067
11126
  kind: OpKind.HostProperty,
11068
11127
  name,
11069
11128
  expression,
11070
11129
  isAnimationTrigger,
11130
+ i18nContext,
11071
11131
  sourceSpan,
11072
11132
  ...TRAIT_CONSUMES_VARS,
11073
11133
  ...NEW_OP,
@@ -11304,7 +11364,7 @@ function applyI18nExpressions(job) {
11304
11364
  // Only add apply after expressions that are not followed by more expressions.
11305
11365
  if (op.kind === OpKind.I18nExpression && needsApplication(i18nContexts, op)) {
11306
11366
  // TODO: what should be the source span for the apply op?
11307
- OpList.insertAfter(createI18nApplyOp(op.target, op.handle, null), op);
11367
+ OpList.insertAfter(createI18nApplyOp(op.i18nOwner, op.handle, null), op);
11308
11368
  }
11309
11369
  }
11310
11370
  }
@@ -11317,17 +11377,34 @@ function needsApplication(i18nContexts, op) {
11317
11377
  if (op.next?.kind !== OpKind.I18nExpression) {
11318
11378
  return true;
11319
11379
  }
11320
- // If the next op is an expression targeting a different i18n block, we need to apply.
11321
11380
  const context = i18nContexts.get(op.context);
11322
11381
  const nextContext = i18nContexts.get(op.next.context);
11323
- if (context.i18nBlock !== nextContext.i18nBlock) {
11382
+ if (context === undefined) {
11383
+ throw new Error('AssertionError: expected an I18nContextOp to exist for the I18nExpressionOp\'s context');
11384
+ }
11385
+ if (nextContext === undefined) {
11386
+ throw new Error('AssertionError: expected an I18nContextOp to exist for the next I18nExpressionOp\'s context');
11387
+ }
11388
+ // If the next op is an expression targeting a different i18n block (or different element, in the
11389
+ // case of i18n attributes), we need to apply.
11390
+ // First, handle the case of i18n blocks.
11391
+ if (context.i18nBlock !== null) {
11392
+ // This is a block context. Compare the blocks.
11393
+ if (context.i18nBlock !== nextContext.i18nBlock) {
11394
+ return true;
11395
+ }
11396
+ return false;
11397
+ }
11398
+ // Second, handle the case of i18n attributes.
11399
+ if (op.i18nOwner !== op.next.i18nOwner) {
11324
11400
  return true;
11325
11401
  }
11326
11402
  return false;
11327
11403
  }
11328
11404
 
11329
11405
  /**
11330
- * Updates i18n expression ops to depend on the last slot in their owning i18n block.
11406
+ * Updates i18n expression ops to target the last slot in their owning i18n block, and moves them
11407
+ * after the last update instruction that depends on that slot.
11331
11408
  */
11332
11409
  function assignI18nSlotDependencies(job) {
11333
11410
  const i18nLastSlotConsumers = new Map();
@@ -11344,17 +11421,54 @@ function assignI18nSlotDependencies(job) {
11344
11421
  currentI18nOp = op;
11345
11422
  break;
11346
11423
  case OpKind.I18nEnd:
11424
+ if (currentI18nOp === null) {
11425
+ throw new Error('AssertionError: Expected an active I18n block while calculating last slot consumers');
11426
+ }
11427
+ if (lastSlotConsumer === null) {
11428
+ throw new Error('AssertionError: Expected a last slot consumer while calculating last slot consumers');
11429
+ }
11347
11430
  i18nLastSlotConsumers.set(currentI18nOp.xref, lastSlotConsumer);
11348
11431
  currentI18nOp = null;
11349
11432
  break;
11350
11433
  }
11351
11434
  }
11352
- // Assign i18n expressions to target the last slot in its owning block.
11435
+ // Expresions that are currently being moved.
11436
+ let opsToMove = [];
11437
+ // Previously we found the last slot-consuming create mode op in the i18n block. That op will be
11438
+ // the new target for any moved i18n expresion inside the i18n block, and that op's slot is
11439
+ // stored here.
11440
+ let moveAfterTarget = null;
11441
+ // This is the last target in the create IR that we saw during iteration. Eventally, it will be
11442
+ // equal to moveAfterTarget. But wait! We need to find the *last* such op whose target is equal
11443
+ // to `moveAfterTarget`.
11444
+ let previousTarget = null;
11353
11445
  for (const op of unit.update) {
11354
- if (op.kind === OpKind.I18nExpression) {
11355
- op.target = i18nLastSlotConsumers.get(op.target);
11446
+ if (hasDependsOnSlotContextTrait(op)) {
11447
+ // We've found an op that depends on another slot other than the one that we want to move
11448
+ // the expressions to, after previously having seen the one we want to move to.
11449
+ if (moveAfterTarget !== null && previousTarget === moveAfterTarget &&
11450
+ op.target !== previousTarget) {
11451
+ OpList.insertBefore(opsToMove, op);
11452
+ moveAfterTarget = null;
11453
+ opsToMove = [];
11454
+ }
11455
+ previousTarget = op.target;
11456
+ }
11457
+ if (op.kind === OpKind.I18nExpression && op.usage === I18nExpressionFor.I18nText) {
11458
+ // This is an I18nExpressionOps that is used for text (not attributes).
11459
+ OpList.remove(op);
11460
+ opsToMove.push(op);
11461
+ const target = i18nLastSlotConsumers.get(op.i18nOwner);
11462
+ if (target === undefined) {
11463
+ throw new Error('AssertionError: Expected to find a last slot consumer for an I18nExpressionOp');
11464
+ }
11465
+ op.target = target;
11466
+ moveAfterTarget = op.target;
11356
11467
  }
11357
11468
  }
11469
+ if (moveAfterTarget !== null) {
11470
+ unit.update.push(opsToMove);
11471
+ }
11358
11472
  }
11359
11473
  }
11360
11474
 
@@ -11386,22 +11500,36 @@ function extractAttributes(job) {
11386
11500
  break;
11387
11501
  case OpKind.Property:
11388
11502
  if (!op.isAnimationTrigger) {
11389
- OpList.insertBefore(createExtractedAttributeOp(op.target, op.isTemplate ? BindingKind.Template : BindingKind.Property, op.name, null), lookupElement$2(elements, op.target));
11503
+ let bindingKind;
11504
+ if (op.i18nContext !== null) {
11505
+ // If the binding has an i18n context, it is an i18n attribute, and should have that
11506
+ // kind in the consts array.
11507
+ bindingKind = BindingKind.I18n;
11508
+ }
11509
+ else if (op.isStructuralTemplate) {
11510
+ // TODO: How do i18n attributes on templates work?!
11511
+ bindingKind = BindingKind.Template;
11512
+ }
11513
+ else {
11514
+ bindingKind = BindingKind.Property;
11515
+ }
11516
+ OpList.insertBefore(createExtractedAttributeOp(op.target, bindingKind, op.name, null, null), lookupElement$2(elements, op.target));
11390
11517
  }
11391
11518
  break;
11392
11519
  case OpKind.StyleProp:
11393
11520
  case OpKind.ClassProp:
11521
+ // TODO: Can style or class bindings be i18n attributes?
11394
11522
  // The old compiler treated empty style bindings as regular bindings for the purpose of
11395
11523
  // directive matching. That behavior is incorrect, but we emulate it in compatibility
11396
11524
  // mode.
11397
11525
  if (unit.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder &&
11398
11526
  op.expression instanceof EmptyExpr) {
11399
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null), lookupElement$2(elements, op.target));
11527
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null, null), lookupElement$2(elements, op.target));
11400
11528
  }
11401
11529
  break;
11402
11530
  case OpKind.Listener:
11403
11531
  if (!op.isAnimationListener) {
11404
- const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null);
11532
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, BindingKind.Property, op.name, null, null);
11405
11533
  if (job.kind === CompilationJobKind.Host) {
11406
11534
  // This attribute will apply to the enclosing host binding compilation unit, so order
11407
11535
  // doesn't matter.
@@ -11450,7 +11578,7 @@ function extractAttributeOp(unit, op, elements) {
11450
11578
  }
11451
11579
  }
11452
11580
  if (extractable) {
11453
- const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isTemplate ? BindingKind.Template : BindingKind.Attribute, op.name, op.expression);
11581
+ const extractedAttributeOp = createExtractedAttributeOp(op.target, op.isStructuralTemplate ? BindingKind.Template : BindingKind.Attribute, op.name, op.expression, op.i18nContext);
11454
11582
  if (unit.job.kind === CompilationJobKind.Host) {
11455
11583
  // This attribute will apply to the enclosing host binding compilation unit, so order doesn't
11456
11584
  // matter.
@@ -11497,16 +11625,16 @@ function specializeBindings(job) {
11497
11625
  target.nonBindable = true;
11498
11626
  }
11499
11627
  else {
11500
- OpList.replace(op, createAttributeOp(op.target, op.name, op.expression, op.securityContext, op.isTextAttribute, op.isTemplate, op.sourceSpan));
11628
+ OpList.replace(op, createAttributeOp(op.target, op.name, op.expression, op.securityContext, op.isTextAttribute, op.isStructuralTemplate, op.i18nContext, op.sourceSpan));
11501
11629
  }
11502
11630
  break;
11503
11631
  case BindingKind.Property:
11504
11632
  case BindingKind.Animation:
11505
11633
  if (job.kind === CompilationJobKind.Host) {
11506
- OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.sourceSpan));
11634
+ OpList.replace(op, createHostPropertyOp(op.name, op.expression, op.bindingKind === BindingKind.Animation, op.i18nContext, op.sourceSpan));
11507
11635
  }
11508
11636
  else {
11509
- OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isTemplate, op.sourceSpan));
11637
+ OpList.replace(op, createPropertyOp(op.target, op.name, op.expression, op.bindingKind === BindingKind.Animation, op.securityContext, op.isStructuralTemplate, op.i18nContext, op.sourceSpan));
11510
11638
  }
11511
11639
  break;
11512
11640
  case BindingKind.I18n:
@@ -11827,7 +11955,7 @@ class ElementAttributes {
11827
11955
  array.push(...getAttributeNameLiterals$1(name));
11828
11956
  if (kind === BindingKind.Attribute || kind === BindingKind.StyleProperty) {
11829
11957
  if (value === null) {
11830
- throw Error('Attribute & style element attributes must have a value');
11958
+ throw Error('Attribute, i18n attribute, & style element attributes must have a value');
11831
11959
  }
11832
11960
  array.push(value);
11833
11961
  }
@@ -11881,6 +12009,50 @@ function serializeAttributes({ attributes, bindings, classes, i18n, projectAs, s
11881
12009
  return literalArr(attrArray);
11882
12010
  }
11883
12011
 
12012
+ /**
12013
+ * Some binding instructions in the update block may actually correspond to i18n bindings. In that
12014
+ * case, they should be replaced with i18nExp instructions for the dynamic portions.
12015
+ */
12016
+ function convertI18nBindings(job) {
12017
+ const i18nAttributesByElem = new Map();
12018
+ for (const unit of job.units) {
12019
+ for (const op of unit.create) {
12020
+ if (op.kind === OpKind.I18nAttributes) {
12021
+ i18nAttributesByElem.set(op.target, op);
12022
+ }
12023
+ }
12024
+ for (const op of unit.update) {
12025
+ switch (op.kind) {
12026
+ case OpKind.Property:
12027
+ case OpKind.Attribute:
12028
+ if (op.i18nContext === null) {
12029
+ continue;
12030
+ }
12031
+ if (!(op.expression instanceof Interpolation)) {
12032
+ continue;
12033
+ }
12034
+ const i18nAttributesForElem = i18nAttributesByElem.get(op.target);
12035
+ if (i18nAttributesForElem === undefined) {
12036
+ throw new Error('AssertionError: An i18n attribute binding instruction requires the owning element to have an I18nAttributes create instruction');
12037
+ }
12038
+ if (i18nAttributesForElem.target !== op.target) {
12039
+ throw new Error('AssertionError: Expected i18nAttributes target element to match binding target element');
12040
+ }
12041
+ const ops = [];
12042
+ for (let i = 0; i < op.expression.expressions.length; i++) {
12043
+ const expr = op.expression.expressions[i];
12044
+ if (op.expression.i18nPlaceholders.length !== op.expression.expressions.length) {
12045
+ throw new Error(`AssertionError: An i18n attribute binding instruction requires the same number of expressions and placeholders, but found ${op.expression.i18nPlaceholders.length} placeholders and ${op.expression.expressions.length} expressions`);
12046
+ }
12047
+ ops.push(createI18nExpressionOp(op.i18nContext, i18nAttributesForElem.target, i18nAttributesForElem.xref, i18nAttributesForElem.handle, expr, op.expression.i18nPlaceholders[i], I18nParamResolutionTime.Creation, I18nExpressionFor.I18nAttribute, op.name, op.sourceSpan));
12048
+ }
12049
+ OpList.replaceWithMany(op, ops);
12050
+ break;
12051
+ }
12052
+ }
12053
+ }
12054
+ }
12055
+
11884
12056
  /**
11885
12057
  * Create extracted deps functions for defer ops.
11886
12058
  */
@@ -12324,7 +12496,7 @@ function ternaryTransform(e) {
12324
12496
  /**
12325
12497
  * The escape sequence used indicate message param values.
12326
12498
  */
12327
- const ESCAPE = '\uFFFD';
12499
+ const ESCAPE$1 = '\uFFFD';
12328
12500
  /**
12329
12501
  * Marker used to indicate an element tag.
12330
12502
  */
@@ -12373,6 +12545,18 @@ function extractI18nMessages(job) {
12373
12545
  }
12374
12546
  }
12375
12547
  }
12548
+ // TODO: Miles and I think that contexts have a 1-to-1 correspondence with extracted messages, so
12549
+ // this phase can probably be simplified.
12550
+ // Extract messages from contexts of i18n attributes.
12551
+ for (const unit of job.units) {
12552
+ for (const op of unit.create) {
12553
+ if (op.kind !== OpKind.I18nContext || op.contextKind !== I18nContextKind.Attr) {
12554
+ continue;
12555
+ }
12556
+ const i18nMessageOp = createI18nMessage(job, op);
12557
+ unit.create.push(i18nMessageOp);
12558
+ }
12559
+ }
12376
12560
  // Extract messages from root i18n blocks.
12377
12561
  const i18nBlockMessages = new Map();
12378
12562
  for (const unit of job.units) {
@@ -12397,6 +12581,9 @@ function extractI18nMessages(job) {
12397
12581
  }
12398
12582
  const i18nContext = i18nContexts.get(op.context);
12399
12583
  if (i18nContext.contextKind === I18nContextKind.Icu) {
12584
+ if (i18nContext.i18nBlock === null) {
12585
+ throw Error('ICU context should have its i18n block set.');
12586
+ }
12400
12587
  const subMessage = createI18nMessage(job, i18nContext, op.messagePlaceholder);
12401
12588
  unit.create.push(subMessage);
12402
12589
  const rootI18nId = i18nBlocks.get(i18nContext.i18nBlock).root;
@@ -12416,119 +12603,86 @@ function extractI18nMessages(job) {
12416
12603
  * Create an i18n message op from an i18n context op.
12417
12604
  */
12418
12605
  function createI18nMessage(job, context, messagePlaceholder) {
12419
- let [formattedParams, needsPostprocessing] = formatParams(context.params);
12420
- const [formattedPostprocessingParams] = formatParams(context.postprocessingParams);
12421
- needsPostprocessing ||= formattedPostprocessingParams.size > 0;
12422
- return createI18nMessageOp(job.allocateXrefId(), context.i18nBlock, context.message, messagePlaceholder ?? null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
12606
+ let formattedParams = formatParams(context.params);
12607
+ const formattedPostprocessingParams = formatParams(context.postprocessingParams);
12608
+ let needsPostprocessing = formattedPostprocessingParams.size > 0;
12609
+ for (const values of context.params.values()) {
12610
+ if (values.length > 1) {
12611
+ needsPostprocessing = true;
12612
+ }
12613
+ }
12614
+ return createI18nMessageOp(job.allocateXrefId(), context.xref, context.i18nBlock, context.message, messagePlaceholder ?? null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
12423
12615
  }
12424
12616
  /**
12425
12617
  * Formats a map of `I18nParamValue[]` values into a map of `Expression` values.
12426
- * @return A tuple of the formatted params and a boolean indicating whether postprocessing is needed
12427
- * for any of the params
12428
12618
  */
12429
12619
  function formatParams(params) {
12430
12620
  const formattedParams = new Map();
12431
- let needsPostprocessing = false;
12432
12621
  for (const [placeholder, placeholderValues] of params) {
12433
- const [serializedValues, paramNeedsPostprocessing] = formatParamValues(placeholderValues);
12434
- needsPostprocessing ||= paramNeedsPostprocessing;
12622
+ const serializedValues = formatParamValues(placeholderValues);
12435
12623
  if (serializedValues !== null) {
12436
12624
  formattedParams.set(placeholder, literal(serializedValues));
12437
12625
  }
12438
12626
  }
12439
- return [formattedParams, needsPostprocessing];
12627
+ return formattedParams;
12440
12628
  }
12441
12629
  /**
12442
12630
  * Formats an `I18nParamValue[]` into a string (or null for empty array).
12443
- * @return A tuple of the formatted value and a boolean indicating whether postprocessing is needed
12444
- * for the value
12445
12631
  */
12446
12632
  function formatParamValues(values) {
12447
12633
  if (values.length === 0) {
12448
- return [null, false];
12634
+ return null;
12449
12635
  }
12450
- collapseElementTemplatePairs(values);
12451
12636
  const serializedValues = values.map(value => formatValue(value));
12452
12637
  return serializedValues.length === 1 ?
12453
- [serializedValues[0], false] :
12454
- [`${LIST_START_MARKER}${serializedValues.join(LIST_DELIMITER)}${LIST_END_MARKER}`, true];
12455
- }
12456
- /**
12457
- * Collapses element/template pairs that refer to the same subTemplateIndex, i.e. elements and
12458
- * templates that refer to the same element instance.
12459
- *
12460
- * This accounts for the case of a structural directive inside an i18n block, e.g.:
12461
- * ```
12462
- * <div i18n>
12463
- * <div *ngIf="condition">
12464
- * </div>
12465
- * ```
12466
- *
12467
- * In this case, both the element start and template start placeholders are the same,
12468
- * and we collapse them down into a single compound placeholder value. Rather than produce
12469
- * `[\uFFFD#1:1\uFFFD|\uFFFD*2:1\uFFFD]`, we want to produce `\uFFFD#1:1\uFFFD\uFFFD*2:1\uFFFD`,
12470
- * likewise for the closing of the element/template.
12471
- */
12472
- function collapseElementTemplatePairs(values) {
12473
- // Record the indicies of element and template values in the values array by subTemplateIndex.
12474
- const valueIndiciesBySubTemplateIndex = new Map();
12475
- for (let i = 0; i < values.length; i++) {
12476
- const value = values[i];
12477
- if (value.subTemplateIndex !== null &&
12478
- (value.flags & (I18nParamValueFlags.ElementTag | I18nParamValueFlags.TemplateTag))) {
12479
- const valueIndicies = valueIndiciesBySubTemplateIndex.get(value.subTemplateIndex) ?? [];
12480
- valueIndicies.push(i);
12481
- valueIndiciesBySubTemplateIndex.set(value.subTemplateIndex, valueIndicies);
12482
- }
12483
- }
12484
- // For each subTemplateIndex, check if any values can be collapsed.
12485
- for (const [subTemplateIndex, valueIndicies] of valueIndiciesBySubTemplateIndex) {
12486
- if (valueIndicies.length > 1) {
12487
- const elementIndex = valueIndicies.find(index => values[index].flags & I18nParamValueFlags.ElementTag);
12488
- const templateIndex = valueIndicies.find(index => values[index].flags & I18nParamValueFlags.TemplateTag);
12489
- // If the values list contains both an element and template value, we can collapse.
12490
- if (elementIndex !== undefined && templateIndex !== undefined) {
12491
- const elementValue = values[elementIndex];
12492
- const templateValue = values[templateIndex];
12493
- // To match the TemplateDefinitionBuilder output, flip the order depending on whether the
12494
- // values represent a closing or opening tag (or both).
12495
- // TODO(mmalerba): Figure out if this makes a difference in terms of either functionality,
12496
- // or the resulting message ID. If not, we can remove the special-casing in the future.
12497
- let compundValue;
12498
- if ((elementValue.flags & I18nParamValueFlags.OpenTag) &&
12499
- (elementValue.flags & I18nParamValueFlags.CloseTag)) {
12500
- // TODO(mmalerba): Is this a TDB bug? I don't understand why it would put the template
12501
- // value twice.
12502
- compundValue = `${formatValue(templateValue)}${formatValue(elementValue)}${formatValue(templateValue)}`;
12503
- }
12504
- else if (elementValue.flags & I18nParamValueFlags.OpenTag) {
12505
- compundValue = `${formatValue(templateValue)}${formatValue(elementValue)}`;
12506
- }
12507
- else {
12508
- compundValue = `${formatValue(elementValue)}${formatValue(templateValue)}`;
12509
- }
12510
- // Replace the element value with the combined value.
12511
- values.splice(elementIndex, 1, { value: compundValue, subTemplateIndex, flags: I18nParamValueFlags.None });
12512
- // Replace the template value with null to preserve the indicies we calculated earlier.
12513
- values.splice(templateIndex, 1, null);
12514
- }
12515
- }
12516
- }
12517
- // Strip out any nulled out values we introduced above.
12518
- for (let i = values.length - 1; i >= 0; i--) {
12519
- if (values[i] === null) {
12520
- values.splice(i, 1);
12521
- }
12522
- }
12638
+ serializedValues[0] :
12639
+ `${LIST_START_MARKER}${serializedValues.join(LIST_DELIMITER)}${LIST_END_MARKER}`;
12523
12640
  }
12524
12641
  /**
12525
12642
  * Formats a single `I18nParamValue` into a string
12526
12643
  */
12527
12644
  function formatValue(value) {
12645
+ // Element tags with a structural directive use a special form that concatenates the element and
12646
+ // template values.
12647
+ if ((value.flags & I18nParamValueFlags.ElementTag) &&
12648
+ (value.flags & I18nParamValueFlags.TemplateTag)) {
12649
+ if (typeof value.value !== 'object') {
12650
+ throw Error('AssertionError: Expected i18n param value to have an element and template slot');
12651
+ }
12652
+ const elementValue = formatValue({
12653
+ ...value,
12654
+ value: value.value.element,
12655
+ flags: value.flags & ~I18nParamValueFlags.TemplateTag
12656
+ });
12657
+ const templateValue = formatValue({
12658
+ ...value,
12659
+ value: value.value.template,
12660
+ flags: value.flags & ~I18nParamValueFlags.ElementTag
12661
+ });
12662
+ // TODO(mmalerba): This is likely a bug in TemplateDefinitionBuilder, we should not need to
12663
+ // record the template value twice. For now I'm re-implementing the behavior here to keep the
12664
+ // output consistent with TemplateDefinitionBuilder.
12665
+ if ((value.flags & I18nParamValueFlags.OpenTag) &&
12666
+ (value.flags & I18nParamValueFlags.CloseTag)) {
12667
+ return `${templateValue}${elementValue}${templateValue}`;
12668
+ }
12669
+ // To match the TemplateDefinitionBuilder output, flip the order depending on whether the
12670
+ // values represent a closing or opening tag (or both).
12671
+ // TODO(mmalerba): Figure out if this makes a difference in terms of either functionality,
12672
+ // or the resulting message ID. If not, we can remove the special-casing in the future.
12673
+ return value.flags & I18nParamValueFlags.CloseTag ? `${elementValue}${templateValue}` :
12674
+ `${templateValue}${elementValue}`;
12675
+ }
12676
+ // Self-closing tags use a special form that concatenates the start and close tag values.
12677
+ if ((value.flags & I18nParamValueFlags.OpenTag) &&
12678
+ (value.flags & I18nParamValueFlags.CloseTag)) {
12679
+ return `${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.CloseTag })}${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.OpenTag })}`;
12680
+ }
12528
12681
  // If there are no special flags, just return the raw value.
12529
12682
  if (value.flags === I18nParamValueFlags.None) {
12530
12683
  return `${value.value}`;
12531
12684
  }
12685
+ // Encode the remaining flags as part of the value.
12532
12686
  let tagMarker = '';
12533
12687
  let closeMarker = '';
12534
12688
  if (value.flags & I18nParamValueFlags.ElementTag) {
@@ -12541,12 +12695,7 @@ function formatValue(value) {
12541
12695
  closeMarker = value.flags & I18nParamValueFlags.CloseTag ? TAG_CLOSE_MARKER : '';
12542
12696
  }
12543
12697
  const context = value.subTemplateIndex === null ? '' : `${CONTEXT_MARKER}${value.subTemplateIndex}`;
12544
- // Self-closing tags use a special form that concatenates the start and close tag values.
12545
- if ((value.flags & I18nParamValueFlags.OpenTag) &&
12546
- (value.flags & I18nParamValueFlags.CloseTag)) {
12547
- return `${ESCAPE}${tagMarker}${value.value}${context}${ESCAPE}${ESCAPE}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE}`;
12548
- }
12549
- return `${ESCAPE}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE}`;
12698
+ return `${ESCAPE$1}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE$1}`;
12550
12699
  }
12551
12700
 
12552
12701
  /**
@@ -12580,7 +12729,7 @@ function generateAdvance(job) {
12580
12729
  else if (!slotMap.has(op.target)) {
12581
12730
  // We expect ops that _do_ depend on the slot counter to point at declarations that exist in
12582
12731
  // the `slotMap`.
12583
- throw new Error(`AssertionError: reference to unknown slot for var ${op.target}`);
12732
+ throw new Error(`AssertionError: reference to unknown slot for target ${op.target}`);
12584
12733
  }
12585
12734
  const slot = slotMap.get(op.target);
12586
12735
  // Does the slot counter need to be adjusted?
@@ -19790,35 +19939,132 @@ const NG_I18N_CLOSURE_MODE$1 = 'ngI18nClosureMode';
19790
19939
  * considers variables like `I18N_0` as constants and throws an error when their value changes.
19791
19940
  */
19792
19941
  const TRANSLATION_VAR_PREFIX = 'i18n_';
19942
+ /** Prefix of ICU expressions for post processing */
19943
+ const I18N_ICU_MAPPING_PREFIX = 'I18N_EXP_';
19944
+ /**
19945
+ * The escape sequence used for message param values.
19946
+ */
19947
+ const ESCAPE = '\uFFFD';
19793
19948
  /**
19794
19949
  * Lifts i18n properties into the consts array.
19795
19950
  * TODO: Can we use `ConstCollectedExpr`?
19951
+ * TODO: The way the various attributes are linked together is very complex. Perhaps we could
19952
+ * simplify the process, maybe by combining the context and message ops?
19796
19953
  */
19797
19954
  function collectI18nConsts(job) {
19798
19955
  const fileBasedI18nSuffix = job.relativeContextFilePath.replace(/[^A-Za-z0-9]/g, '_').toUpperCase() + '_';
19799
- const messageConstIndices = new Map();
19800
- // Remove all of the i18n message ops into a map.
19956
+ // Step One: Build up various lookup maps we need to collect all the consts.
19957
+ // Context Xref -> Extracted Attribute Op
19958
+ const extractedAttributesByI18nContext = new Map();
19959
+ // Element/ElementStart Xref -> I18n Attributes config op
19960
+ const i18nAttributesByElement = new Map();
19961
+ // Element/ElementStart Xref -> All I18n Expression ops for attrs on that target
19962
+ const i18nExpressionsByElement = new Map();
19963
+ // I18n Message Xref -> I18n Message Op (TODO: use a central op map)
19801
19964
  const messages = new Map();
19965
+ for (const unit of job.units) {
19966
+ for (const op of unit.ops()) {
19967
+ if (op.kind === OpKind.ExtractedAttribute && op.i18nContext !== null) {
19968
+ extractedAttributesByI18nContext.set(op.i18nContext, op);
19969
+ }
19970
+ else if (op.kind === OpKind.I18nAttributes) {
19971
+ i18nAttributesByElement.set(op.target, op);
19972
+ }
19973
+ else if (op.kind === OpKind.I18nExpression && op.usage === I18nExpressionFor.I18nAttribute) {
19974
+ const expressions = i18nExpressionsByElement.get(op.target) ?? [];
19975
+ expressions.push(op);
19976
+ i18nExpressionsByElement.set(op.target, expressions);
19977
+ }
19978
+ else if (op.kind === OpKind.I18nMessage) {
19979
+ messages.set(op.xref, op);
19980
+ }
19981
+ }
19982
+ }
19983
+ // Step Two: Serialize the extracted i18n messages for root i18n blocks and i18n attributes into
19984
+ // the const array.
19985
+ //
19986
+ // Also, each i18n message will have a variable expression that can refer to its
19987
+ // value. Store these expressions in the appropriate place:
19988
+ // 1. For normal i18n content, it also goes in the const array. We save the const index to use
19989
+ // later.
19990
+ // 2. For extracted attributes, it becomes the value of the extracted attribute instruction.
19991
+ // 3. For i18n bindings, it will go in a separate const array instruction below; for now, we just
19992
+ // save it.
19993
+ const i18nValuesByContext = new Map();
19994
+ const messageConstIndices = new Map();
19802
19995
  for (const unit of job.units) {
19803
19996
  for (const op of unit.create) {
19804
19997
  if (op.kind === OpKind.I18nMessage) {
19805
- messages.set(op.xref, op);
19998
+ if (op.messagePlaceholder === null) {
19999
+ const { mainVar, statements } = collectMessage(job, fileBasedI18nSuffix, messages, op);
20000
+ if (op.i18nBlock !== null) {
20001
+ // This is a regular i18n message with a corresponding i18n block. Collect it into the
20002
+ // const array.
20003
+ const i18nConst = job.addConst(mainVar, statements);
20004
+ messageConstIndices.set(op.i18nBlock, i18nConst);
20005
+ }
20006
+ else {
20007
+ // This is an i18n attribute. Extract the initializers into the const pool.
20008
+ job.constsInitializers.push(...statements);
20009
+ // Save the i18n variable value for later.
20010
+ i18nValuesByContext.set(op.i18nContext, mainVar);
20011
+ // This i18n message may correspond to an individual extracted attribute. If so, The
20012
+ // value of that attribute is updated to read the extracted i18n variable.
20013
+ const attributeForMessage = extractedAttributesByI18nContext.get(op.i18nContext);
20014
+ if (attributeForMessage !== undefined) {
20015
+ attributeForMessage.expression = mainVar;
20016
+ }
20017
+ }
20018
+ }
19806
20019
  OpList.remove(op);
19807
20020
  }
19808
20021
  }
19809
20022
  }
19810
- // Serialize the extracted messages for root i18n blocks into the const array.
19811
- for (const op of messages.values()) {
19812
- if (op.kind === OpKind.I18nMessage && op.messagePlaceholder === null) {
19813
- const { mainVar, statements } = collectMessage(job, fileBasedI18nSuffix, messages, op);
19814
- messageConstIndices.set(op.i18nBlock, job.addConst(mainVar, statements));
20023
+ // Step Three: Serialize I18nAttributes configurations into the const array. Each I18nAttributes
20024
+ // instruction has a config array, which contains k-v pairs describing each binding name, and the
20025
+ // i18n variable that provides the value.
20026
+ for (const unit of job.units) {
20027
+ for (const elem of unit.create) {
20028
+ if (isElementOrContainerOp(elem)) {
20029
+ const i18nAttributes = i18nAttributesByElement.get(elem.xref);
20030
+ if (i18nAttributes === undefined) {
20031
+ // This element is not associated with an i18n attributes configuration instruction.
20032
+ continue;
20033
+ }
20034
+ let i18nExpressions = i18nExpressionsByElement.get(elem.xref);
20035
+ if (i18nExpressions === undefined) {
20036
+ // Unused i18nAttributes should have already been removed.
20037
+ // TODO: Should the removal of those dead instructions be merged with this phase?
20038
+ throw new Error('AssertionError: Could not find any i18n expressions associated with an I18nAttributes instruction');
20039
+ }
20040
+ // Find expressions for all the unique property names, removing duplicates.
20041
+ const seenPropertyNames = new Set();
20042
+ i18nExpressions = i18nExpressions.filter(i18nExpr => {
20043
+ const seen = (seenPropertyNames.has(i18nExpr.name));
20044
+ seenPropertyNames.add(i18nExpr.name);
20045
+ return !seen;
20046
+ });
20047
+ const i18nAttributeConfig = i18nExpressions.flatMap(i18nExpr => {
20048
+ const i18nExprValue = i18nValuesByContext.get(i18nExpr.context);
20049
+ if (i18nExprValue === undefined) {
20050
+ throw new Error('AssertionError: Could not find i18n expression\'s value');
20051
+ }
20052
+ return [literal(i18nExpr.name), i18nExprValue];
20053
+ });
20054
+ i18nAttributes.i18nAttributesConfig =
20055
+ job.addConst(new LiteralArrayExpr(i18nAttributeConfig));
20056
+ }
19815
20057
  }
19816
20058
  }
19817
- // Assign const index to i18n ops that messages were extracted from.
20059
+ // Step Four: Propagate the extracted const index into i18n ops that messages were extracted from.
19818
20060
  for (const unit of job.units) {
19819
20061
  for (const op of unit.create) {
19820
20062
  if (op.kind === OpKind.I18nStart) {
19821
- op.messageIndex = messageConstIndices.get(op.root);
20063
+ const msgIndex = messageConstIndices.get(op.root);
20064
+ if (msgIndex === undefined) {
20065
+ throw new Error('AssertionError: Could not find corresponding i18n block index for an i18n message op; was an i18n message incorrectly assumed to correspond to an attribute?');
20066
+ }
20067
+ op.messageIndex = msgIndex;
19822
20068
  }
19823
20069
  }
19824
20070
  }
@@ -19828,18 +20074,23 @@ function collectI18nConsts(job) {
19828
20074
  * This will recursively collect any sub-messages referenced from the parent message as well.
19829
20075
  */
19830
20076
  function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19831
- // Recursively collect any sub-messages, and fill in their placeholders in this message.
20077
+ // Recursively collect any sub-messages, record each sub-message's main variable under its
20078
+ // placeholder so that we can add them to the params for the parent message. It is possible
20079
+ // that multiple sub-messages will share the same placeholder, so we need to track an array of
20080
+ // variables for each placeholder.
19832
20081
  const statements = [];
20082
+ const subMessagePlaceholders = new Map();
19833
20083
  for (const subMessageId of messageOp.subMessages) {
19834
20084
  const subMessage = messages.get(subMessageId);
19835
20085
  const { mainVar: subMessageVar, statements: subMessageStatements } = collectMessage(job, fileBasedI18nSuffix, messages, subMessage);
19836
20086
  statements.push(...subMessageStatements);
19837
- messageOp.params.set(subMessage.messagePlaceholder, subMessageVar);
20087
+ const subMessages = subMessagePlaceholders.get(subMessage.messagePlaceholder) ?? [];
20088
+ subMessages.push(subMessageVar);
20089
+ subMessagePlaceholders.set(subMessage.messagePlaceholder, subMessages);
19838
20090
  }
20091
+ addSubMessageParams(messageOp, subMessagePlaceholders);
19839
20092
  // Sort the params for consistency with TemaplateDefinitionBuilder output.
19840
20093
  messageOp.params = new Map([...messageOp.params.entries()].sort());
19841
- // Check that the message has all of its parameters filled out.
19842
- assertAllParamsResolved(messageOp);
19843
20094
  const mainVar = variable(job.pool.uniqueName(TRANSLATION_VAR_PREFIX));
19844
20095
  // Closure Compiler requires const names to start with `MSG_` but disallows any other
19845
20096
  // const to start with `MSG_`. We define a variable starting with `MSG_` just for the
@@ -19850,10 +20101,11 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19850
20101
  // set in post-processing.
19851
20102
  if (messageOp.needsPostprocessing) {
19852
20103
  // Sort the post-processing params for consistency with TemaplateDefinitionBuilder output.
19853
- messageOp.postprocessingParams = new Map([...messageOp.postprocessingParams.entries()].sort());
20104
+ const postprocessingParams = Object.fromEntries([...messageOp.postprocessingParams.entries()].sort());
20105
+ const formattedPostprocessingParams = formatI18nPlaceholderNamesInMap(postprocessingParams, /* useCamelCase */ false);
19854
20106
  const extraTransformFnParams = [];
19855
20107
  if (messageOp.postprocessingParams.size > 0) {
19856
- extraTransformFnParams.push(literalMap([...messageOp.postprocessingParams].map(([key, value]) => ({ key, value, quoted: true }))));
20108
+ extraTransformFnParams.push(mapLiteral(formattedPostprocessingParams, /* quoted */ true));
19857
20109
  }
19858
20110
  transformFn = (expr) => importExpr(Identifiers.i18nPostprocess).callFn([expr, ...extraTransformFnParams]);
19859
20111
  }
@@ -19861,6 +20113,26 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19861
20113
  statements.push(...getTranslationDeclStmts$1(messageOp.message, mainVar, closureVar, messageOp.params, transformFn));
19862
20114
  return { mainVar, statements };
19863
20115
  }
20116
+ /**
20117
+ * Adds the given subMessage placeholders to the given message op.
20118
+ *
20119
+ * If a placeholder only corresponds to a single sub-message variable, we just set that variable
20120
+ * as the param value. However, if the placeholder corresponds to multiple sub-message
20121
+ * variables, we need to add a special placeholder value that is handled by the post-processing
20122
+ * step. We then add the array of variables as a post-processing param.
20123
+ */
20124
+ function addSubMessageParams(messageOp, subMessagePlaceholders) {
20125
+ for (const [placeholder, subMessages] of subMessagePlaceholders) {
20126
+ if (subMessages.length === 1) {
20127
+ messageOp.params.set(placeholder, subMessages[0]);
20128
+ }
20129
+ else {
20130
+ messageOp.params.set(placeholder, literal(`${ESCAPE}${I18N_ICU_MAPPING_PREFIX}${placeholder}${ESCAPE}`));
20131
+ messageOp.postprocessingParams.set(placeholder, literalArr(subMessages));
20132
+ messageOp.needsPostprocessing = true;
20133
+ }
20134
+ }
20135
+ }
19864
20136
  /**
19865
20137
  * Generate statements that define a given translation message.
19866
20138
  *
@@ -19883,7 +20155,8 @@ function collectMessage(job, fileBasedI18nSuffix, messages, messageOp) {
19883
20155
  * @param closureVar The variable for Closure `goog.getMsg` calls, e.g. `MSG_EXTERNAL_XXX`.
19884
20156
  * @param params Object mapping placeholder names to their values (e.g.
19885
20157
  * `{ "interpolation": "\uFFFD0\uFFFD" }`).
19886
- * @param transformFn Optional transformation function that will be applied to the translation (e.g.
20158
+ * @param transformFn Optional transformation function that will be applied to the translation
20159
+ * (e.g.
19887
20160
  * post-processing).
19888
20161
  * @returns An array of statements that defined a given translation.
19889
20162
  */
@@ -19928,28 +20201,13 @@ function i18nGenerateClosureVar(pool, messageId, fileBasedI18nSuffix, useExterna
19928
20201
  }
19929
20202
  return variable(name);
19930
20203
  }
19931
- /**
19932
- * Asserts that all of the message's placeholders have values.
19933
- */
19934
- function assertAllParamsResolved(op) {
19935
- for (let placeholder in op.message.placeholders) {
19936
- placeholder = placeholder.trimEnd();
19937
- if (!op.params.has(placeholder) && !op.postprocessingParams.has(placeholder)) {
19938
- throw Error(`Failed to resolve i18n placeholder: ${placeholder}`);
19939
- }
19940
- }
19941
- for (let placeholder in op.message.placeholderToMessage) {
19942
- placeholder = placeholder.trimEnd();
19943
- if (!op.params.has(placeholder) && !op.postprocessingParams.has(placeholder)) {
19944
- throw Error(`Failed to resolve i18n message placeholder: ${placeholder}`);
19945
- }
19946
- }
19947
- }
19948
20204
 
19949
20205
  /**
19950
20206
  * Removes text nodes within i18n blocks since they are already hardcoded into the i18n message.
20207
+ * Also, replaces interpolations on these text nodes with i18n expressions of the non-text portions,
20208
+ * which will be applied later.
19951
20209
  */
19952
- function extractI18nText(job) {
20210
+ function convertI18nText(job) {
19953
20211
  for (const unit of job.units) {
19954
20212
  // Remove all text nodes within i18n blocks, their content is already captured in the i18n
19955
20213
  // message.
@@ -20004,7 +20262,7 @@ function extractI18nText(job) {
20004
20262
  const expr = op.interpolation.expressions[i];
20005
20263
  // For now, this i18nExpression depends on the slot context of the enclosing i18n block.
20006
20264
  // Later, we will modify this, and advance to a different point.
20007
- ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.handle, expr, op.i18nPlaceholders[i], resolutionTime, expr.sourceSpan ?? op.sourceSpan));
20265
+ ops.push(createI18nExpressionOp(contextId, i18nOp.xref, i18nOp.xref, i18nOp.handle, expr, op.interpolation.i18nPlaceholders[i], resolutionTime, I18nExpressionFor.I18nText, '', expr.sourceSpan ?? op.sourceSpan));
20008
20266
  }
20009
20267
  OpList.replaceWithMany(op, ops);
20010
20268
  break;
@@ -20542,14 +20800,14 @@ function parseExtractedStyles(job) {
20542
20800
  if (op.name === 'style') {
20543
20801
  const parsedStyles = parse(op.expression.value);
20544
20802
  for (let i = 0; i < parsedStyles.length - 1; i += 2) {
20545
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, parsedStyles[i], literal(parsedStyles[i + 1])), op);
20803
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.StyleProperty, parsedStyles[i], literal(parsedStyles[i + 1]), null), op);
20546
20804
  }
20547
20805
  OpList.remove(op);
20548
20806
  }
20549
20807
  else if (op.name === 'class') {
20550
20808
  const parsedClasses = op.expression.value.trim().split(/\s+/g);
20551
20809
  for (const parsedClass of parsedClasses) {
20552
- OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, parsedClass, null), op);
20810
+ OpList.insertBefore(createExtractedAttributeOp(op.target, BindingKind.ClassName, parsedClass, null, null), op);
20553
20811
  }
20554
20812
  OpList.remove(op);
20555
20813
  }
@@ -20565,18 +20823,30 @@ function parseExtractedStyles(job) {
20565
20823
  function removeContentSelectors(job) {
20566
20824
  for (const unit of job.units) {
20567
20825
  const elements = createOpXrefMap(unit);
20568
- for (const op of unit.update) {
20826
+ for (const op of unit.ops()) {
20569
20827
  switch (op.kind) {
20570
20828
  case OpKind.Binding:
20571
20829
  const target = lookupInXrefMap(elements, op.target);
20572
- if (op.name.toLowerCase() === 'select' && target.kind === OpKind.Projection) {
20830
+ if (isSelectAttribute(op.name) && target.kind === OpKind.Projection) {
20573
20831
  OpList.remove(op);
20574
20832
  }
20575
20833
  break;
20834
+ case OpKind.Projection:
20835
+ // op.attributes is an array of [attr1-name, attr1-value, attr2-name, attr2-value, ...],
20836
+ // find the "select" attribute and remove its name and corresponding value.
20837
+ for (let i = op.attributes.length - 2; i >= 0; i -= 2) {
20838
+ if (isSelectAttribute(op.attributes[i])) {
20839
+ op.attributes.splice(i, 2);
20840
+ }
20841
+ }
20842
+ break;
20576
20843
  }
20577
20844
  }
20578
20845
  }
20579
20846
  }
20847
+ function isSelectAttribute(name) {
20848
+ return name.toLowerCase() === 'select';
20849
+ }
20580
20850
  /**
20581
20851
  * Looks up an element in the given map by xref ID.
20582
20852
  */
@@ -20696,6 +20966,10 @@ function propagateI18nBlocksToTemplates(unit, subTemplateIndex) {
20696
20966
  i18nBlock = op;
20697
20967
  break;
20698
20968
  case OpKind.I18nEnd:
20969
+ // When we exit a root-level i18n block, reset the sub-template index counter.
20970
+ if (i18nBlock.subTemplateIndex === null) {
20971
+ subTemplateIndex = 0;
20972
+ }
20699
20973
  i18nBlock = null;
20700
20974
  break;
20701
20975
  case OpKind.Template:
@@ -21037,6 +21311,10 @@ function i18n(slot, constIndex, subTemplateIndex) {
21037
21311
  function i18nEnd() {
21038
21312
  return call(Identifiers.i18nEnd, [], null);
21039
21313
  }
21314
+ function i18nAttributes(slot, i18nAttributesConfig) {
21315
+ const args = [literal(slot), literal(i18nAttributesConfig)];
21316
+ return call(Identifiers.i18nAttributes, args, null);
21317
+ }
21040
21318
  function property(name, expression, sanitizer, sourceSpan) {
21041
21319
  const args = [literal(name), expression];
21042
21320
  if (sanitizer !== null) {
@@ -21423,6 +21701,12 @@ function reifyCreateOperations(unit, ops) {
21423
21701
  case OpKind.I18n:
21424
21702
  OpList.replace(op, i18n(op.handle.slot, op.messageIndex, op.subTemplateIndex));
21425
21703
  break;
21704
+ case OpKind.I18nAttributes:
21705
+ if (op.i18nAttributesConfig === null) {
21706
+ throw new Error(`AssertionError: i18nAttributesConfig was not set`);
21707
+ }
21708
+ OpList.replace(op, i18nAttributes(op.handle.slot, op.i18nAttributesConfig));
21709
+ break;
21426
21710
  case OpKind.Template:
21427
21711
  if (!(unit instanceof ViewCompilationUnit)) {
21428
21712
  throw new Error(`AssertionError: must be compiling a component`);
@@ -21764,6 +22048,31 @@ function removeI18nContexts(job) {
21764
22048
  }
21765
22049
  }
21766
22050
 
22051
+ /**
22052
+ * i18nAttributes ops will be generated for each i18n attribute. However, not all i18n attribues
22053
+ * will contain dynamic content, and so some of these i18nAttributes ops may be unnecessary.
22054
+ */
22055
+ function removeUnusedI18nAttributesOps(job) {
22056
+ for (const unit of job.units) {
22057
+ const ownersWithI18nExpressions = new Set();
22058
+ for (const op of unit.update) {
22059
+ switch (op.kind) {
22060
+ case OpKind.I18nExpression:
22061
+ ownersWithI18nExpressions.add(op.i18nOwner);
22062
+ }
22063
+ }
22064
+ for (const op of unit.create) {
22065
+ switch (op.kind) {
22066
+ case OpKind.I18nAttributes:
22067
+ if (ownersWithI18nExpressions.has(op.xref)) {
22068
+ continue;
22069
+ }
22070
+ OpList.remove(op);
22071
+ }
22072
+ }
22073
+ }
22074
+ }
22075
+
21767
22076
  /**
21768
22077
  * Inside the body of a repeater, certain context variables (such as `$first`) are ambiently
21769
22078
  * available. This phase finds those variable usages, and replaces them with the appropriate
@@ -21895,10 +22204,14 @@ function resolveI18nElementPlaceholders(job) {
21895
22204
  }
21896
22205
  resolvePlaceholdersForView(job, job.root, i18nContexts, elements);
21897
22206
  }
21898
- function resolvePlaceholdersForView(job, unit, i18nContexts, elements) {
22207
+ /**
22208
+ * Recursively resolves element and template tag placeholders in the given view.
22209
+ */
22210
+ function resolvePlaceholdersForView(job, unit, i18nContexts, elements, pendingStructuralDirective) {
21899
22211
  // Track the current i18n op and corresponding i18n context op as we step through the creation
21900
22212
  // IR.
21901
22213
  let currentOps = null;
22214
+ let pendingStructuralDirectiveCloses = new Map();
21902
22215
  for (const op of unit.create) {
21903
22216
  switch (op.kind) {
21904
22217
  case OpKind.I18nStart:
@@ -21917,14 +22230,14 @@ function resolvePlaceholdersForView(job, unit, i18nContexts, elements) {
21917
22230
  if (currentOps === null) {
21918
22231
  throw Error('i18n tag placeholder should only occur inside an i18n block');
21919
22232
  }
21920
- const { startName, closeName } = op.i18nPlaceholder;
21921
- let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.OpenTag;
21922
- // For self-closing tags, there is no close tag placeholder. Instead, the start tag
21923
- // placeholder accounts for the start and close of the element.
21924
- if (closeName === '') {
21925
- flags |= I18nParamValueFlags.CloseTag;
22233
+ recordElementStart(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22234
+ // If there is a separate close tag placeholder for this element, save the pending
22235
+ // structural directive so we can pass it to the closing tag as well.
22236
+ if (pendingStructuralDirective && op.i18nPlaceholder.closeName) {
22237
+ pendingStructuralDirectiveCloses.set(op.xref, pendingStructuralDirective);
21926
22238
  }
21927
- addParam(currentOps.i18nContext.params, startName, op.handle.slot, currentOps.i18nBlock.subTemplateIndex, flags);
22239
+ // Clear out the pending structural directive now that its been accounted for.
22240
+ pendingStructuralDirective = undefined;
21928
22241
  }
21929
22242
  break;
21930
22243
  case OpKind.ElementEnd:
@@ -21933,42 +22246,134 @@ function resolvePlaceholdersForView(job, unit, i18nContexts, elements) {
21933
22246
  const startOp = elements.get(op.xref);
21934
22247
  if (startOp && startOp.i18nPlaceholder !== undefined) {
21935
22248
  if (currentOps === null) {
21936
- throw Error('i18n tag placeholder should only occur inside an i18n block');
21937
- }
21938
- const { closeName } = startOp.i18nPlaceholder;
21939
- // Self-closing tags don't have a closing tag placeholder.
21940
- if (closeName !== '') {
21941
- addParam(currentOps.i18nContext.params, closeName, startOp.handle.slot, currentOps.i18nBlock.subTemplateIndex, I18nParamValueFlags.ElementTag | I18nParamValueFlags.CloseTag);
22249
+ throw Error('AssertionError: i18n tag placeholder should only occur inside an i18n block');
21942
22250
  }
22251
+ recordElementClose(startOp, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirectiveCloses.get(op.xref));
22252
+ // Clear out the pending structural directive close that was accounted for.
22253
+ pendingStructuralDirectiveCloses.delete(op.xref);
21943
22254
  }
21944
22255
  break;
21945
- case OpKind.Template:
21946
- // For templates with i18n placeholders, record its slot value in the params map under the
21947
- // corresponding template start and close placeholders.
22256
+ case OpKind.Projection:
22257
+ // For content projections with i18n placeholders, record its slot value in the params map
22258
+ // under the corresponding tag start and close placeholders.
21948
22259
  if (op.i18nPlaceholder !== undefined) {
21949
22260
  if (currentOps === null) {
21950
22261
  throw Error('i18n tag placeholder should only occur inside an i18n block');
21951
22262
  }
21952
- let startFlags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.OpenTag;
21953
- const subTemplateIndex = getSubTemplateIndexForTemplateTag(job, currentOps.i18nBlock, op);
21954
- const { startName, closeName } = op.i18nPlaceholder;
21955
- const isSelfClosing = closeName === '';
21956
- if (isSelfClosing) {
21957
- startFlags |= I18nParamValueFlags.CloseTag;
21958
- }
21959
- addParam(currentOps.i18nContext.params, startName, op.handle.slot, subTemplateIndex, startFlags);
22263
+ recordElementStart(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22264
+ recordElementClose(op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22265
+ // Clear out the pending structural directive now that its been accounted for.
22266
+ pendingStructuralDirective = undefined;
22267
+ }
22268
+ break;
22269
+ case OpKind.Template:
22270
+ if (op.i18nPlaceholder === undefined) {
22271
+ // If there is no i18n placeholder, just recurse into the view in case it contains i18n
22272
+ // blocks.
21960
22273
  resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
21961
- if (!isSelfClosing) {
21962
- addParam(currentOps.i18nContext.params, closeName, op.handle.slot, subTemplateIndex, I18nParamValueFlags.TemplateTag | I18nParamValueFlags.CloseTag);
21963
- }
21964
22274
  }
21965
22275
  else {
21966
- resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
22276
+ if (currentOps === null) {
22277
+ throw Error('i18n tag placeholder should only occur inside an i18n block');
22278
+ }
22279
+ if (op.templateKind === TemplateKind.Structural) {
22280
+ // If this is a structural directive template, don't record anything yet. Instead pass
22281
+ // the current template as a pending structural directive to be recorded when we find
22282
+ // the element, content, or template it belongs to. This allows us to create combined
22283
+ // values that represent, e.g. the start of a template and element at the same time.
22284
+ resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements, op);
22285
+ }
22286
+ else {
22287
+ // If this is some other kind of template, we can record its start, recurse into its
22288
+ // view, and then record its end.
22289
+ recordTemplateStart(job, op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22290
+ resolvePlaceholdersForView(job, job.views.get(op.xref), i18nContexts, elements);
22291
+ recordTemplateClose(job, op, currentOps.i18nContext, currentOps.i18nBlock, pendingStructuralDirective);
22292
+ pendingStructuralDirective = undefined;
22293
+ }
21967
22294
  }
21968
22295
  break;
21969
22296
  }
21970
22297
  }
21971
22298
  }
22299
+ /**
22300
+ * Records an i18n param value for the start of an element.
22301
+ */
22302
+ function recordElementStart(op, i18nContext, i18nBlock, structuralDirective) {
22303
+ const { startName, closeName } = op.i18nPlaceholder;
22304
+ let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.OpenTag;
22305
+ let value = op.handle.slot;
22306
+ // If the element is associated with a structural directive, start it as well.
22307
+ if (structuralDirective !== undefined) {
22308
+ flags |= I18nParamValueFlags.TemplateTag;
22309
+ value = { element: value, template: structuralDirective.handle.slot };
22310
+ }
22311
+ // For self-closing tags, there is no close tag placeholder. Instead, the start tag
22312
+ // placeholder accounts for the start and close of the element.
22313
+ if (!closeName) {
22314
+ flags |= I18nParamValueFlags.CloseTag;
22315
+ }
22316
+ addParam(i18nContext.params, startName, value, i18nBlock.subTemplateIndex, flags);
22317
+ }
22318
+ /**
22319
+ * Records an i18n param value for the closing of an element.
22320
+ */
22321
+ function recordElementClose(op, i18nContext, i18nBlock, structuralDirective) {
22322
+ const { closeName } = op.i18nPlaceholder;
22323
+ // Self-closing tags don't have a closing tag placeholder, instead the element closing is
22324
+ // recorded via an additional flag on the element start value.
22325
+ if (closeName) {
22326
+ let flags = I18nParamValueFlags.ElementTag | I18nParamValueFlags.CloseTag;
22327
+ let value = op.handle.slot;
22328
+ // If the element is associated with a structural directive, close it as well.
22329
+ if (structuralDirective !== undefined) {
22330
+ flags |= I18nParamValueFlags.TemplateTag;
22331
+ value = { element: value, template: structuralDirective.handle.slot };
22332
+ }
22333
+ addParam(i18nContext.params, closeName, value, i18nBlock.subTemplateIndex, flags);
22334
+ }
22335
+ }
22336
+ /**
22337
+ * Records an i18n param value for the start of a template.
22338
+ */
22339
+ function recordTemplateStart(job, op, i18nContext, i18nBlock, structuralDirective) {
22340
+ let { startName, closeName } = op.i18nPlaceholder;
22341
+ let flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.OpenTag;
22342
+ // For self-closing tags, there is no close tag placeholder. Instead, the start tag
22343
+ // placeholder accounts for the start and close of the element.
22344
+ if (!closeName) {
22345
+ flags |= I18nParamValueFlags.CloseTag;
22346
+ }
22347
+ // If the template is associated with a structural directive, record the structural directive's
22348
+ // start first. Since this template must be in the structural directive's view, we can just
22349
+ // directly use the current i18n block's sub-template index.
22350
+ if (structuralDirective !== undefined) {
22351
+ addParam(i18nContext.params, startName, structuralDirective.handle.slot, i18nBlock.subTemplateIndex, flags);
22352
+ }
22353
+ // Record the start of the template. For the sub-template index, pass the index for the template's
22354
+ // view, rather than the current i18n block's index.
22355
+ addParam(i18nContext.params, startName, op.handle.slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, op), flags);
22356
+ }
22357
+ /**
22358
+ * Records an i18n param value for the closing of a template.
22359
+ */
22360
+ function recordTemplateClose(job, op, i18nContext, i18nBlock, structuralDirective) {
22361
+ const { startName, closeName } = op.i18nPlaceholder;
22362
+ const flags = I18nParamValueFlags.TemplateTag | I18nParamValueFlags.CloseTag;
22363
+ // Self-closing tags don't have a closing tag placeholder, instead the template's closing is
22364
+ // recorded via an additional flag on the template start value.
22365
+ if (closeName) {
22366
+ // Record the closing of the template. For the sub-template index, pass the index for the
22367
+ // template's view, rather than the current i18n block's index.
22368
+ addParam(i18nContext.params, closeName, op.handle.slot, getSubTemplateIndexForTemplateTag(job, i18nBlock, op), flags);
22369
+ // If the template is associated with a structural directive, record the structural directive's
22370
+ // closing after. Since this template must be in the structural directive's view, we can just
22371
+ // directly use the current i18n block's sub-template index.
22372
+ if (structuralDirective !== undefined) {
22373
+ addParam(i18nContext.params, closeName, structuralDirective.handle.slot, i18nBlock.subTemplateIndex, flags);
22374
+ }
22375
+ }
22376
+ }
21972
22377
  /**
21973
22378
  * Get the subTemplateIndex for the given template op. For template ops, use the subTemplateIndex of
21974
22379
  * the child i18n block inside the template.
@@ -21981,7 +22386,9 @@ function getSubTemplateIndexForTemplateTag(job, i18nOp, op) {
21981
22386
  }
21982
22387
  return i18nOp.subTemplateIndex;
21983
22388
  }
21984
- /** Add a param value to the given params map. */
22389
+ /**
22390
+ * Add a param value to the given params map.
22391
+ */
21985
22392
  function addParam(params, placeholder, value, subTemplateIndex, flags) {
21986
22393
  const values = params.get(placeholder) ?? [];
21987
22394
  values.push({ value, subTemplateIndex, flags });
@@ -22007,14 +22414,19 @@ function resolveI18nExpressionPlaceholders(job) {
22007
22414
  }
22008
22415
  }
22009
22416
  }
22010
- // Keep track of the next available expression index per i18n block.
22417
+ // Keep track of the next available expression index for each i18n message.
22011
22418
  const expressionIndices = new Map();
22419
+ // Keep track of a reference index for each expression.
22420
+ // We use different references for normal i18n expressio and attribute i18n expressions. This is
22421
+ // because child i18n blocks in templates don't get their own context, since they're rolled into
22422
+ // the translated message of the parent, but they may target a different slot.
22423
+ const referenceIndex = (op) => op.usage === I18nExpressionFor.I18nText ? op.i18nOwner : op.context;
22012
22424
  for (const unit of job.units) {
22013
22425
  for (const op of unit.update) {
22014
22426
  if (op.kind === OpKind.I18nExpression) {
22015
22427
  const i18nContext = i18nContexts.get(op.context);
22016
- const index = expressionIndices.get(op.target) || 0;
22017
- const subTemplateIndex = subTemplateIndicies.get(op.target);
22428
+ const index = expressionIndices.get(referenceIndex(op)) || 0;
22429
+ const subTemplateIndex = subTemplateIndicies.get(op.i18nOwner) ?? null;
22018
22430
  // Add the expression index in the appropriate params map.
22019
22431
  const params = op.resolutionTime === I18nParamResolutionTime.Creation ?
22020
22432
  i18nContext.params :
@@ -22026,7 +22438,7 @@ function resolveI18nExpressionPlaceholders(job) {
22026
22438
  flags: I18nParamValueFlags.ExpressionIndex
22027
22439
  });
22028
22440
  params.set(op.i18nPlaceholder, values);
22029
- expressionIndices.set(op.target, index + 1);
22441
+ expressionIndices.set(referenceIndex(op), index + 1);
22030
22442
  }
22031
22443
  }
22032
22444
  }
@@ -23173,11 +23585,11 @@ const phases = [
23173
23585
  { kind: CompilationJobKind.Tmpl, fn: removeContentSelectors },
23174
23586
  { kind: CompilationJobKind.Host, fn: parseHostStyleProperties },
23175
23587
  { kind: CompilationJobKind.Tmpl, fn: emitNamespaceChanges },
23176
- { kind: CompilationJobKind.Both, fn: specializeStyleBindings },
23177
- { kind: CompilationJobKind.Both, fn: specializeBindings },
23178
23588
  { kind: CompilationJobKind.Tmpl, fn: propagateI18nBlocks },
23179
23589
  { kind: CompilationJobKind.Tmpl, fn: wrapI18nIcus },
23180
23590
  { kind: CompilationJobKind.Tmpl, fn: createI18nContexts },
23591
+ { kind: CompilationJobKind.Both, fn: specializeStyleBindings },
23592
+ { kind: CompilationJobKind.Both, fn: specializeBindings },
23181
23593
  { kind: CompilationJobKind.Both, fn: extractAttributes },
23182
23594
  { kind: CompilationJobKind.Both, fn: parseExtractedStyles },
23183
23595
  { kind: CompilationJobKind.Tmpl, fn: removeEmptyBindings },
@@ -23186,7 +23598,10 @@ const phases = [
23186
23598
  { kind: CompilationJobKind.Tmpl, fn: generateConditionalExpressions },
23187
23599
  { kind: CompilationJobKind.Tmpl, fn: createPipes },
23188
23600
  { kind: CompilationJobKind.Tmpl, fn: configureDeferInstructions },
23189
- { kind: CompilationJobKind.Tmpl, fn: extractI18nText },
23601
+ { kind: CompilationJobKind.Tmpl, fn: convertI18nText },
23602
+ { kind: CompilationJobKind.Tmpl, fn: convertI18nBindings },
23603
+ { kind: CompilationJobKind.Tmpl, fn: removeUnusedI18nAttributesOps },
23604
+ { kind: CompilationJobKind.Tmpl, fn: assignI18nSlotDependencies },
23190
23605
  { kind: CompilationJobKind.Tmpl, fn: applyI18nExpressions },
23191
23606
  { kind: CompilationJobKind.Tmpl, fn: createVariadicPipes },
23192
23607
  { kind: CompilationJobKind.Both, fn: generatePureLiteralStructures },
@@ -23216,7 +23631,6 @@ const phases = [
23216
23631
  { kind: CompilationJobKind.Tmpl, fn: collectI18nConsts },
23217
23632
  { kind: CompilationJobKind.Tmpl, fn: collectConstExpressions },
23218
23633
  { kind: CompilationJobKind.Both, fn: collectElementConsts },
23219
- { kind: CompilationJobKind.Tmpl, fn: assignI18nSlotDependencies },
23220
23634
  { kind: CompilationJobKind.Tmpl, fn: removeI18nContexts },
23221
23635
  { kind: CompilationJobKind.Both, fn: countVariables },
23222
23636
  { kind: CompilationJobKind.Tmpl, fn: generateAdvance },
@@ -23371,7 +23785,7 @@ function ingestHostProperty(job, property, isTextAttribute) {
23371
23785
  let expression;
23372
23786
  const ast = property.expression.ast;
23373
23787
  if (ast instanceof Interpolation$1) {
23374
- expression = new Interpolation(ast.strings, ast.expressions.map(expr => convertAst(expr, job, property.sourceSpan)));
23788
+ expression = new Interpolation(ast.strings, ast.expressions.map(expr => convertAst(expr, job, property.sourceSpan)), []);
23375
23789
  }
23376
23790
  else {
23377
23791
  expression = convertAst(ast, job, property.sourceSpan);
@@ -23386,10 +23800,11 @@ function ingestHostProperty(job, property, isTextAttribute) {
23386
23800
  bindingKind = BindingKind.Animation;
23387
23801
  }
23388
23802
  job.root.update.push(createBindingOp(job.root.xref, bindingKind, property.name, expression, null, SecurityContext
23389
- .NONE /* TODO: what should we pass as security context? Passing NONE for now. */, isTextAttribute, false, property.sourceSpan));
23803
+ .NONE /* TODO: what should we pass as security context? Passing NONE for now. */, isTextAttribute, false, /* TODO: How do Host bindings handle i18n attrs? */ null, property.sourceSpan));
23390
23804
  }
23391
23805
  function ingestHostAttribute(job, name, value) {
23392
23806
  const attrBinding = createBindingOp(job.root.xref, BindingKind.Attribute, name, value, null, SecurityContext.NONE, true, false,
23807
+ /* TODO */ null,
23393
23808
  /* TODO: host attribute source spans */ null);
23394
23809
  job.root.update.push(attrBinding);
23395
23810
  }
@@ -23487,10 +23902,11 @@ function ingestTemplate(unit, tmpl) {
23487
23902
  const functionNameSuffix = tagNameWithoutNamespace === null ?
23488
23903
  '' :
23489
23904
  prefixWithNamespace(tagNameWithoutNamespace, namespace);
23490
- const tplOp = createTemplateOp(childView.xref, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan);
23491
- unit.create.push(tplOp);
23492
- ingestBindings(unit, tplOp, tmpl);
23493
- ingestReferences(tplOp, tmpl);
23905
+ const templateKind = isPlainTemplate(tmpl) ? TemplateKind.NgTemplate : TemplateKind.Structural;
23906
+ const templateOp = createTemplateOp(childView.xref, templateKind, tagNameWithoutNamespace, functionNameSuffix, namespace, i18nPlaceholder, tmpl.startSourceSpan);
23907
+ unit.create.push(templateOp);
23908
+ ingestBindings(unit, templateOp, tmpl);
23909
+ ingestReferences(templateOp, tmpl);
23494
23910
  ingestNodes(childView, tmpl.children);
23495
23911
  for (const { name, value } of tmpl.variables) {
23496
23912
  childView.contextVariables.set(name, value !== '' ? value : '$implicit');
@@ -23498,7 +23914,7 @@ function ingestTemplate(unit, tmpl) {
23498
23914
  // If this is a plain template and there is an i18n message associated with it, insert i18n start
23499
23915
  // and end ops. For structural directive templates, the i18n ops will be added when ingesting the
23500
23916
  // element/template the directive is placed on.
23501
- if (isPlainTemplate(tmpl) && tmpl.i18n instanceof Message) {
23917
+ if (templateKind === TemplateKind.NgTemplate && tmpl.i18n instanceof Message) {
23502
23918
  const id = unit.job.allocateXrefId();
23503
23919
  OpList.insertAfter(createI18nStartOp(id, tmpl.i18n), childView.create.head);
23504
23920
  OpList.insertBefore(createI18nEndOp(id), childView.create.tail);
@@ -23508,9 +23924,13 @@ function ingestTemplate(unit, tmpl) {
23508
23924
  * Ingest a literal text node from the AST into the given `ViewCompilation`.
23509
23925
  */
23510
23926
  function ingestContent(unit, content) {
23511
- const op = createProjectionOp(unit.job.allocateXrefId(), content.selector, content.sourceSpan);
23927
+ if (content.i18n !== undefined && !(content.i18n instanceof TagPlaceholder)) {
23928
+ throw Error(`Unhandled i18n metadata type for element: ${content.i18n.constructor.name}`);
23929
+ }
23930
+ const attrs = content.attributes.flatMap(a => [a.name, a.value]);
23931
+ const op = createProjectionOp(unit.job.allocateXrefId(), content.selector, content.i18n, attrs, content.sourceSpan);
23512
23932
  for (const attr of content.attributes) {
23513
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue);
23933
+ ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue, attr.i18n);
23514
23934
  }
23515
23935
  unit.create.push(op);
23516
23936
  }
@@ -23535,6 +23955,7 @@ function ingestBoundText(unit, text, i18nPlaceholders) {
23535
23955
  throw Error(`Unhandled i18n metadata type for text interpolation: ${text.i18n?.constructor.name}`);
23536
23956
  }
23537
23957
  if (i18nPlaceholders === undefined) {
23958
+ // TODO: We probably can just use the placeholders field, instead of walking the AST.
23538
23959
  i18nPlaceholders = text.i18n instanceof Container ?
23539
23960
  text.i18n.children
23540
23961
  .filter((node) => node instanceof Placeholder)
@@ -23550,7 +23971,7 @@ function ingestBoundText(unit, text, i18nPlaceholders) {
23550
23971
  // interpolation. We copy that behavior in compatibility mode.
23551
23972
  // TODO: is it actually correct to generate these extra maps in modern mode?
23552
23973
  const baseSourceSpan = unit.job.compatibility ? null : text.sourceSpan;
23553
- unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, unit.job, baseSourceSpan))), i18nPlaceholders, text.sourceSpan));
23974
+ unit.update.push(createInterpolateTextOp(textXref, new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, unit.job, baseSourceSpan)), i18nPlaceholders), text.sourceSpan));
23554
23975
  }
23555
23976
  /**
23556
23977
  * Ingest an `@if` block into the given `ViewCompilation`.
@@ -23571,14 +23992,21 @@ function ingestIfBlock(unit, ifBlock) {
23571
23992
  if (ifCase.expressionAlias !== null) {
23572
23993
  cView.contextVariables.set(ifCase.expressionAlias.name, CTX_REF);
23573
23994
  }
23574
- const tmplOp = createTemplateOp(cView.xref, tagName, 'Conditional', Namespace.HTML, undefined /* TODO: figure out how i18n works with new control flow */, ifCase.sourceSpan);
23575
- unit.create.push(tmplOp);
23995
+ let ifCaseI18nMeta = undefined;
23996
+ if (ifCase.i18n !== undefined) {
23997
+ if (!(ifCase.i18n instanceof BlockPlaceholder)) {
23998
+ throw Error(`Unhandled i18n metadata type for if block: ${ifCase.i18n?.constructor.name}`);
23999
+ }
24000
+ ifCaseI18nMeta = ifCase.i18n;
24001
+ }
24002
+ const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, tagName, 'Conditional', Namespace.HTML, ifCaseI18nMeta, ifCase.sourceSpan);
24003
+ unit.create.push(templateOp);
23576
24004
  if (firstXref === null) {
23577
24005
  firstXref = cView.xref;
23578
- firstSlotHandle = tmplOp.handle;
24006
+ firstSlotHandle = templateOp.handle;
23579
24007
  }
23580
24008
  const caseExpr = ifCase.expression ? convertAst(ifCase.expression, unit.job, null) : null;
23581
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, tmplOp.xref, tmplOp.handle, ifCase.expressionAlias);
24009
+ const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, templateOp.xref, templateOp.handle, ifCase.expressionAlias);
23582
24010
  conditions.push(conditionalCaseExpr);
23583
24011
  ingestNodes(cView, ifCase.children);
23584
24012
  }
@@ -23594,16 +24022,23 @@ function ingestSwitchBlock(unit, switchBlock) {
23594
24022
  let conditions = [];
23595
24023
  for (const switchCase of switchBlock.cases) {
23596
24024
  const cView = unit.job.allocateView(unit.xref);
23597
- const tmplOp = createTemplateOp(cView.xref, null, 'Case', Namespace.HTML, undefined /* TODO: figure out how i18n works with new control flow */, switchCase.sourceSpan);
23598
- unit.create.push(tmplOp);
24025
+ let switchCaseI18nMeta = undefined;
24026
+ if (switchCase.i18n !== undefined) {
24027
+ if (!(switchCase.i18n instanceof BlockPlaceholder)) {
24028
+ throw Error(`Unhandled i18n metadata type for switch block: ${switchCase.i18n?.constructor.name}`);
24029
+ }
24030
+ switchCaseI18nMeta = switchCase.i18n;
24031
+ }
24032
+ const templateOp = createTemplateOp(cView.xref, TemplateKind.Block, null, 'Case', Namespace.HTML, switchCaseI18nMeta, switchCase.sourceSpan);
24033
+ unit.create.push(templateOp);
23599
24034
  if (firstXref === null) {
23600
24035
  firstXref = cView.xref;
23601
- firstSlotHandle = tmplOp.handle;
24036
+ firstSlotHandle = templateOp.handle;
23602
24037
  }
23603
24038
  const caseExpr = switchCase.expression ?
23604
24039
  convertAst(switchCase.expression, unit.job, switchBlock.startSourceSpan) :
23605
24040
  null;
23606
- const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, tmplOp.xref, tmplOp.handle);
24041
+ const conditionalCaseExpr = new ConditionalCaseExpr(caseExpr, templateOp.xref, templateOp.handle);
23607
24042
  conditions.push(conditionalCaseExpr);
23608
24043
  ingestNodes(cView, switchCase.children);
23609
24044
  }
@@ -23616,7 +24051,7 @@ function ingestDeferView(unit, suffix, children, sourceSpan) {
23616
24051
  }
23617
24052
  const secondaryView = unit.job.allocateView(unit.xref);
23618
24053
  ingestNodes(secondaryView, children);
23619
- const templateOp = createTemplateOp(secondaryView.xref, null, `Defer${suffix}`, Namespace.HTML, undefined, sourceSpan);
24054
+ const templateOp = createTemplateOp(secondaryView.xref, TemplateKind.Block, null, `Defer${suffix}`, Namespace.HTML, undefined, sourceSpan);
23620
24055
  unit.create.push(templateOp);
23621
24056
  return templateOp;
23622
24057
  }
@@ -23711,12 +24146,7 @@ function ingestIcu(unit, icu) {
23711
24146
  const xref = unit.job.allocateXrefId();
23712
24147
  const icuNode = icu.i18n.nodes[0];
23713
24148
  unit.create.push(createIcuStartOp(xref, icu.i18n, icuFromI18nMessage(icu.i18n).name, null));
23714
- const expressionPlaceholder = icuNode.expressionPlaceholder?.trimEnd();
23715
- if (expressionPlaceholder === undefined || icu.vars[expressionPlaceholder] === undefined) {
23716
- throw Error('ICU should have a text binding');
23717
- }
23718
- ingestBoundText(unit, icu.vars[expressionPlaceholder], [expressionPlaceholder]);
23719
- for (const [placeholder, text] of Object.entries(icu.placeholders)) {
24149
+ for (const [placeholder, text] of Object.entries({ ...icu.vars, ...icu.placeholders })) {
23720
24150
  if (text instanceof BoundText) {
23721
24151
  ingestBoundText(unit, text, [placeholder]);
23722
24152
  }
@@ -23812,6 +24242,16 @@ function convertAst(ast, job, baseSourceSpan) {
23812
24242
  else if (ast instanceof LiteralPrimitive) {
23813
24243
  return literal(ast.value, undefined, convertSourceSpan(ast.span, baseSourceSpan));
23814
24244
  }
24245
+ else if (ast instanceof Unary) {
24246
+ switch (ast.operator) {
24247
+ case '+':
24248
+ return new UnaryOperatorExpr(UnaryOperator.Plus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
24249
+ case '-':
24250
+ return new UnaryOperatorExpr(UnaryOperator.Minus, convertAst(ast.expr, job, baseSourceSpan), undefined, convertSourceSpan(ast.span, baseSourceSpan));
24251
+ default:
24252
+ throw new Error(`AssertionError: unknown unary operator ${ast.operator}`);
24253
+ }
24254
+ }
23815
24255
  else if (ast instanceof Binary) {
23816
24256
  const operator = BINARY_OPERATORS.get(ast.operation);
23817
24257
  if (operator === undefined) {
@@ -23870,6 +24310,9 @@ function convertAst(ast, job, baseSourceSpan) {
23870
24310
  else if (ast instanceof EmptyExpr$1) {
23871
24311
  return new EmptyExpr(convertSourceSpan(ast.span, baseSourceSpan));
23872
24312
  }
24313
+ else if (ast instanceof PrefixNot) {
24314
+ return not(convertAst(ast.expression, job, baseSourceSpan), convertSourceSpan(ast.span, baseSourceSpan));
24315
+ }
23873
24316
  else {
23874
24317
  throw new Error(`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`);
23875
24318
  }
@@ -23900,6 +24343,7 @@ function isPlainTemplate(tmpl) {
23900
24343
  */
23901
24344
  function ingestBindings(unit, op, element) {
23902
24345
  let flags = BindingFlags.None;
24346
+ let hasI18nAttributes = false;
23903
24347
  if (element instanceof Template) {
23904
24348
  flags |= BindingFlags.OnNgTemplateElement;
23905
24349
  if (element instanceof Template && isPlainTemplate(element)) {
@@ -23908,10 +24352,12 @@ function ingestBindings(unit, op, element) {
23908
24352
  const templateAttrFlags = flags | BindingFlags.BindingTargetsTemplate | BindingFlags.IsStructuralTemplateAttribute;
23909
24353
  for (const attr of element.templateAttrs) {
23910
24354
  if (attr instanceof TextAttribute) {
23911
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, templateAttrFlags | BindingFlags.TextValue);
24355
+ ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, templateAttrFlags | BindingFlags.TextValue, attr.i18n);
24356
+ hasI18nAttributes ||= attr.i18n !== undefined;
23912
24357
  }
23913
24358
  else {
23914
- ingestBinding(unit, op.xref, attr.name, attr.value, attr.type, attr.unit, attr.securityContext, attr.sourceSpan, templateAttrFlags);
24359
+ ingestBinding(unit, op.xref, attr.name, attr.value, attr.type, attr.unit, attr.securityContext, attr.sourceSpan, templateAttrFlags, attr.i18n);
24360
+ hasI18nAttributes ||= attr.i18n !== undefined;
23915
24361
  }
23916
24362
  }
23917
24363
  }
@@ -23919,10 +24365,12 @@ function ingestBindings(unit, op, element) {
23919
24365
  // This is only attribute TextLiteral bindings, such as `attr.foo="bar"`. This can never be
23920
24366
  // `[attr.foo]="bar"` or `attr.foo="{{bar}}"`, both of which will be handled as inputs with
23921
24367
  // `BindingType.Attribute`.
23922
- ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, flags | BindingFlags.TextValue);
24368
+ ingestBinding(unit, op.xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, flags | BindingFlags.TextValue, attr.i18n);
24369
+ hasI18nAttributes ||= attr.i18n !== undefined;
23923
24370
  }
23924
24371
  for (const input of element.inputs) {
23925
- ingestBinding(unit, op.xref, input.name, input.value, input.type, input.unit, input.securityContext, input.sourceSpan, flags);
24372
+ ingestBinding(unit, op.xref, input.name, input.value, input.type, input.unit, input.securityContext, input.sourceSpan, flags, input.i18n);
24373
+ hasI18nAttributes ||= input.i18n !== undefined;
23926
24374
  }
23927
24375
  for (const output of element.outputs) {
23928
24376
  let listenerOp;
@@ -23932,7 +24380,7 @@ function ingestBindings(unit, op, element) {
23932
24380
  }
23933
24381
  }
23934
24382
  if (element instanceof Template && !isPlainTemplate(element)) {
23935
- unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, output.name, null));
24383
+ unit.create.push(createExtractedAttributeOp(op.xref, BindingKind.Property, output.name, null, null));
23936
24384
  continue;
23937
24385
  }
23938
24386
  listenerOp = createListenerOp(op.xref, op.handle, output.name, op.tag, output.phase, false, output.sourceSpan);
@@ -23961,6 +24409,10 @@ function ingestBindings(unit, op, element) {
23961
24409
  listenerOp.handlerOps.push(createStatementOp(new ReturnStatement(returnExpr, returnExpr.sourceSpan)));
23962
24410
  unit.create.push(listenerOp);
23963
24411
  }
24412
+ // TODO: Perhaps we could do this in a phase? (It likely wouldn't change the slot indices.)
24413
+ if (hasI18nAttributes) {
24414
+ unit.create.push(createI18nAttributesOp(unit.job.allocateXrefId(), new SlotHandle(), op.xref));
24415
+ }
23964
24416
  }
23965
24417
  const BINDING_KINDS = new Map([
23966
24418
  [0 /* e.BindingType.Property */, BindingKind.Property],
@@ -23989,22 +24441,34 @@ var BindingFlags;
23989
24441
  */
23990
24442
  BindingFlags[BindingFlags["OnNgTemplateElement"] = 8] = "OnNgTemplateElement";
23991
24443
  })(BindingFlags || (BindingFlags = {}));
23992
- function ingestBinding(view, xref, name, value, type, unit, securityContext, sourceSpan, flags) {
24444
+ function ingestBinding(view, xref, name, value, type, unit, securityContext, sourceSpan, flags, i18nMeta) {
23993
24445
  if (value instanceof ASTWithSource) {
23994
24446
  value = value.ast;
23995
24447
  }
24448
+ let i18nContext = null;
24449
+ if (i18nMeta !== undefined) {
24450
+ if (!(i18nMeta instanceof Message)) {
24451
+ throw Error(`Unhandled i18n metadata type for binding: ${i18nMeta.constructor.name}`);
24452
+ }
24453
+ i18nContext = view.job.allocateXrefId();
24454
+ view.create.push(createI18nContextOp(I18nContextKind.Attr, i18nContext, null, i18nMeta, null));
24455
+ }
23996
24456
  if (flags & BindingFlags.OnNgTemplateElement && !(flags & BindingFlags.BindingTargetsTemplate) &&
23997
24457
  type === 0 /* e.BindingType.Property */) {
23998
24458
  // This binding only exists for later const extraction, and is not an actual binding to be
23999
24459
  // created.
24000
- view.create.push(createExtractedAttributeOp(xref, BindingKind.Property, name, null));
24460
+ view.create.push(createExtractedAttributeOp(xref, BindingKind.Property, name, null, i18nContext));
24001
24461
  return;
24002
24462
  }
24003
24463
  let expression;
24004
24464
  // TODO: We could easily generate source maps for subexpressions in these cases, but
24005
24465
  // TemplateDefinitionBuilder does not. Should we do so?
24006
24466
  if (value instanceof Interpolation$1) {
24007
- expression = new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, view.job, null)));
24467
+ let i18nPlaceholders = [];
24468
+ if (i18nMeta !== undefined) {
24469
+ i18nPlaceholders = Object.keys(i18nMeta.placeholders);
24470
+ }
24471
+ expression = new Interpolation(value.strings, value.expressions.map(expr => convertAst(expr, view.job, null)), i18nPlaceholders);
24008
24472
  }
24009
24473
  else if (value instanceof AST) {
24010
24474
  expression = convertAst(value, view.job, null);
@@ -24013,7 +24477,7 @@ function ingestBinding(view, xref, name, value, type, unit, securityContext, sou
24013
24477
  expression = value;
24014
24478
  }
24015
24479
  const kind = BINDING_KINDS.get(type);
24016
- view.update.push(createBindingOp(xref, kind, name, expression, unit, securityContext, !!(flags & BindingFlags.TextValue), !!(flags & BindingFlags.IsStructuralTemplateAttribute), sourceSpan));
24480
+ view.update.push(createBindingOp(xref, kind, name, expression, unit, securityContext, !!(flags & BindingFlags.TextValue), !!(flags & BindingFlags.IsStructuralTemplateAttribute), i18nContext, sourceSpan));
24017
24481
  }
24018
24482
  /**
24019
24483
  * Process all of the local references on an element-like structure in the template AST and
@@ -24101,7 +24565,7 @@ function ingestControlFlowInsertionPoint(unit, xref, node) {
24101
24565
  // and they can be used in directive matching (in the case of `Template.templateAttrs`).
24102
24566
  if (root !== null) {
24103
24567
  for (const attr of root.attributes) {
24104
- ingestBinding(unit, xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue);
24568
+ ingestBinding(unit, xref, attr.name, literal(attr.value), 1 /* e.BindingType.Attribute */, null, SecurityContext.NONE, attr.sourceSpan, BindingFlags.TextValue, attr.i18n);
24105
24569
  }
24106
24570
  const tagName = root instanceof Element$1 ? root.name : root.tagName;
24107
24571
  // Don't pass along `ng-template` tag name since it enables directive matching.
@@ -27184,7 +27648,7 @@ class TemplateDefinitionBuilder {
27184
27648
  else {
27185
27649
  // ... otherwise we need to activate post-processing
27186
27650
  // to replace ICU placeholders with proper values
27187
- const placeholder = wrapI18nPlaceholder(`${I18N_ICU_MAPPING_PREFIX}${key}`);
27651
+ const placeholder = wrapI18nPlaceholder(`${I18N_ICU_MAPPING_PREFIX$1}${key}`);
27188
27652
  params[key] = literal(placeholder);
27189
27653
  icuMapping[key] = literalArr(refs);
27190
27654
  }
@@ -29544,6 +30008,8 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29544
30008
  }
29545
30009
  return emitHostBindingFunction(hostJob);
29546
30010
  }
30011
+ let bindingId = 0;
30012
+ const getNextBindingId = () => `${bindingId++}`;
29547
30013
  const bindingContext = variable(CONTEXT_NAME);
29548
30014
  const styleBuilder = new StylingBuilder(bindingContext);
29549
30015
  const { styleAttr, classAttr } = hostBindingsMetadata.specialAttributes;
@@ -29596,7 +30062,7 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29596
30062
  for (const binding of allOtherBindings) {
29597
30063
  // resolve literal arrays and literal objects
29598
30064
  const value = binding.expression.visit(getValueConverter());
29599
- const bindingExpr = bindingFn(bindingContext, value);
30065
+ const bindingExpr = bindingFn(bindingContext, value, getNextBindingId);
29600
30066
  const { bindingName, instruction, isAttribute } = getBindingNameAndInstruction(binding);
29601
30067
  const securityContexts = bindingParser.calcPossibleSecurityContexts(selector, bindingName, isAttribute)
29602
30068
  .filter(context => context !== SecurityContext.NONE);
@@ -29675,10 +30141,12 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29675
30141
  // at the top of this method when all the input bindings were counted.
29676
30142
  totalHostVarsCount +=
29677
30143
  Math.max(call.allocateBindingSlots - MIN_STYLING_BINDING_SLOTS_REQUIRED, 0);
30144
+ const { params, stmts } = convertStylingCall(call, bindingContext, bindingFn, getNextBindingId);
30145
+ updateVariables.push(...stmts);
29678
30146
  updateInstructions.push({
29679
30147
  reference: instruction.reference,
29680
- paramsOrFn: convertStylingCall(call, bindingContext, bindingFn),
29681
- span: null
30148
+ paramsOrFn: params,
30149
+ span: null,
29682
30150
  });
29683
30151
  }
29684
30152
  });
@@ -29699,11 +30167,19 @@ function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindin
29699
30167
  }
29700
30168
  return null;
29701
30169
  }
29702
- function bindingFn(implicit, value) {
29703
- return convertPropertyBinding(null, implicit, value, 'b');
30170
+ function bindingFn(implicit, value, getNextBindingIdFn) {
30171
+ return convertPropertyBinding(null, implicit, value, getNextBindingIdFn());
29704
30172
  }
29705
- function convertStylingCall(call, bindingContext, bindingFn) {
29706
- return call.params(value => bindingFn(bindingContext, value).currValExpr);
30173
+ function convertStylingCall(call, bindingContext, bindingFn, getNextBindingIdFn) {
30174
+ const stmts = [];
30175
+ const params = call.params(value => {
30176
+ const result = bindingFn(bindingContext, value, getNextBindingIdFn);
30177
+ if (Array.isArray(result.stmts) && result.stmts.length > 0) {
30178
+ stmts.push(...result.stmts);
30179
+ }
30180
+ return result.currValExpr;
30181
+ });
30182
+ return { params, stmts };
29707
30183
  }
29708
30184
  function getBindingNameAndInstruction(binding) {
29709
30185
  let bindingName = binding.name;
@@ -31279,7 +31755,7 @@ function publishFacade(global) {
31279
31755
  * @description
31280
31756
  * Entry point for all public APIs of the compiler package.
31281
31757
  */
31282
- const VERSION = new Version('17.1.0-next.2');
31758
+ const VERSION = new Version('17.1.0-next.3');
31283
31759
 
31284
31760
  class CompilerConfig {
31285
31761
  constructor({ defaultEncapsulation = ViewEncapsulation.Emulated, preserveWhitespaces, strictInjectionParameters } = {}) {
@@ -32845,7 +33321,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$6 = '12.0.0';
32845
33321
  function compileDeclareClassMetadata(metadata) {
32846
33322
  const definitionMap = new DefinitionMap();
32847
33323
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$6));
32848
- definitionMap.set('version', literal('17.1.0-next.2'));
33324
+ definitionMap.set('version', literal('17.1.0-next.3'));
32849
33325
  definitionMap.set('ngImport', importExpr(Identifiers.core));
32850
33326
  definitionMap.set('type', metadata.type);
32851
33327
  definitionMap.set('decorators', metadata.decorators);
@@ -32953,7 +33429,7 @@ function createDirectiveDefinitionMap(meta) {
32953
33429
  // in 16.1 is actually used.
32954
33430
  const minVersion = hasTransformFunctions ? MINIMUM_PARTIAL_LINKER_VERSION$5 : '14.0.0';
32955
33431
  definitionMap.set('minVersion', literal(minVersion));
32956
- definitionMap.set('version', literal('17.1.0-next.2'));
33432
+ definitionMap.set('version', literal('17.1.0-next.3'));
32957
33433
  // e.g. `type: MyDirective`
32958
33434
  definitionMap.set('type', meta.type.value);
32959
33435
  if (meta.isStandalone) {
@@ -33230,7 +33706,7 @@ const MINIMUM_PARTIAL_LINKER_VERSION$4 = '12.0.0';
33230
33706
  function compileDeclareFactoryFunction(meta) {
33231
33707
  const definitionMap = new DefinitionMap();
33232
33708
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$4));
33233
- definitionMap.set('version', literal('17.1.0-next.2'));
33709
+ definitionMap.set('version', literal('17.1.0-next.3'));
33234
33710
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33235
33711
  definitionMap.set('type', meta.type.value);
33236
33712
  definitionMap.set('deps', compileDependencies(meta.deps));
@@ -33265,7 +33741,7 @@ function compileDeclareInjectableFromMetadata(meta) {
33265
33741
  function createInjectableDefinitionMap(meta) {
33266
33742
  const definitionMap = new DefinitionMap();
33267
33743
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$3));
33268
- definitionMap.set('version', literal('17.1.0-next.2'));
33744
+ definitionMap.set('version', literal('17.1.0-next.3'));
33269
33745
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33270
33746
  definitionMap.set('type', meta.type.value);
33271
33747
  // Only generate providedIn property if it has a non-null value
@@ -33316,7 +33792,7 @@ function compileDeclareInjectorFromMetadata(meta) {
33316
33792
  function createInjectorDefinitionMap(meta) {
33317
33793
  const definitionMap = new DefinitionMap();
33318
33794
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$2));
33319
- definitionMap.set('version', literal('17.1.0-next.2'));
33795
+ definitionMap.set('version', literal('17.1.0-next.3'));
33320
33796
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33321
33797
  definitionMap.set('type', meta.type.value);
33322
33798
  definitionMap.set('providers', meta.providers);
@@ -33349,7 +33825,7 @@ function createNgModuleDefinitionMap(meta) {
33349
33825
  throw new Error('Invalid path! Local compilation mode should not get into the partial compilation path');
33350
33826
  }
33351
33827
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION$1));
33352
- definitionMap.set('version', literal('17.1.0-next.2'));
33828
+ definitionMap.set('version', literal('17.1.0-next.3'));
33353
33829
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33354
33830
  definitionMap.set('type', meta.type.value);
33355
33831
  // We only generate the keys in the metadata if the arrays contain values.
@@ -33400,7 +33876,7 @@ function compileDeclarePipeFromMetadata(meta) {
33400
33876
  function createPipeDefinitionMap(meta) {
33401
33877
  const definitionMap = new DefinitionMap();
33402
33878
  definitionMap.set('minVersion', literal(MINIMUM_PARTIAL_LINKER_VERSION));
33403
- definitionMap.set('version', literal('17.1.0-next.2'));
33879
+ definitionMap.set('version', literal('17.1.0-next.3'));
33404
33880
  definitionMap.set('ngImport', importExpr(Identifiers.core));
33405
33881
  // e.g. `type: MyPipe`
33406
33882
  definitionMap.set('type', meta.type.value);