Skip to content

Commit 57b0271

Browse files
mmalerbadylhunn
authored andcommitted
refactor(compiler): Add support for ng-content in i18n blocks (#53327)
Adds support for handling i18n paceholders on ng-content and adds a new test to verify behavior. PR Close #53327
1 parent a9afc43 commit 57b0271

File tree

6 files changed

+76
-2
lines changed

6 files changed

+76
-2
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/TEST_CASES.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,20 @@
242242
}
243243
],
244244
"skipForTemplatePipeline": true
245+
},
246+
{
247+
"description": "should handle ng-content in i18n block",
248+
"inputFiles": [
249+
"nested_ng-content.ts"
250+
],
251+
"expectations": [
252+
{
253+
"extraChecks": [
254+
"verifyPlaceholdersIntegrity",
255+
"verifyUniqueConsts"
256+
]
257+
}
258+
]
245259
}
246260
]
247261
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const $_c2$ = [[["special"]], "*"];
2+
const $_c3$ = ["special", "*"];
3+
4+
decls: 4,
5+
vars: 0,
6+
consts: () => {
7+
__i18nMsgWithPostprocess__('{$startTagNgContent}{$closeTagNgContent}{$startTagNgContent_1}{$closeTagNgContent}', [['closeTagNgContent', String.raw`[\uFFFD/#2\uFFFD|\uFFFD/#3\uFFFD]`], ['startTagNgContent', String.raw`\uFFFD#2\uFFFD`], ['startTagNgContent_1', String.raw`\uFFFD#3\uFFFD`]], {original_code: { 'closeTagNgContent': '</ng-content>', 'startTagNgContent': '<ng-content select=\"special\">', 'startTagNgContent_1': '<ng-content>' }}, {}, [])
8+
return [$i18n_0$];
9+
},
10+
template: function MyComponent_Template(rf, ctx) {
11+
if (rf & 1) {
12+
i0.ɵɵprojectionDef($_c2$);
13+
i0.ɵɵelementStart(0, "div");
14+
i0.ɵɵi18nStart(1, 0);
15+
i0.ɵɵprojection(2);
16+
i0.ɵɵprojection(3, 1);
17+
i0.ɵɵi18nEnd();
18+
i0.ɵɵelementEnd();
19+
}
20+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Component} from '@angular/core';
2+
3+
@Component({
4+
selector: 'my-component',
5+
standalone: true,
6+
template: `
7+
<div i18n>
8+
<ng-content select="special"></ng-content>
9+
<ng-content></ng-content>
10+
</div>
11+
`,
12+
})
13+
export class MyComponent {
14+
}

packages/compiler/src/template/pipeline/ir/src/ops/create.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,16 +584,20 @@ export interface ProjectionOp extends Op<CreateOp>, ConsumesSlotOpTrait {
584584

585585
selector: string;
586586

587+
i18nPlaceholder?: i18n.TagPlaceholder;
588+
587589
sourceSpan: ParseSourceSpan;
588590
}
589591

590592
export function createProjectionOp(
591-
xref: XrefId, selector: string, sourceSpan: ParseSourceSpan): ProjectionOp {
593+
xref: XrefId, selector: string, i18nPlaceholder: i18n.TagPlaceholder|undefined,
594+
sourceSpan: ParseSourceSpan): ProjectionOp {
592595
return {
593596
kind: OpKind.Projection,
594597
xref,
595598
handle: new SlotHandle(),
596599
selector,
600+
i18nPlaceholder,
597601
projectionSlotIndex: 0,
598602
attributes: [],
599603
localRefs: [],

packages/compiler/src/template/pipeline/src/ingest.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,11 @@ function ingestTemplate(unit: ViewCompilationUnit, tmpl: t.Template): void {
232232
* Ingest a literal text node from the AST into the given `ViewCompilation`.
233233
*/
234234
function ingestContent(unit: ViewCompilationUnit, content: t.Content): void {
235-
const op = ir.createProjectionOp(unit.job.allocateXrefId(), content.selector, content.sourceSpan);
235+
if (content.i18n !== undefined && !(content.i18n instanceof i18n.TagPlaceholder)) {
236+
throw Error(`Unhandled i18n metadata type for element: ${content.i18n.constructor.name}`);
237+
}
238+
const op = ir.createProjectionOp(
239+
unit.job.allocateXrefId(), content.selector, content.i18n, content.sourceSpan);
236240
for (const attr of content.attributes) {
237241
ingestBinding(
238242
unit, op.xref, attr.name, o.literal(attr.value), e.BindingType.Attribute, null,

packages/compiler/src/template/pipeline/src/phases/resolve_i18n_element_placeholders.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ function resolvePlaceholdersForView(
8686
}
8787
}
8888
break;
89+
case ir.OpKind.Projection:
90+
debugger;
91+
// For content projections with i18n placeholders, record its slot value in the params map
92+
// under the corresponding tag start and close placeholders.
93+
if (op.i18nPlaceholder !== undefined) {
94+
if (currentOps === null) {
95+
throw Error('i18n tag placeholder should only occur inside an i18n block');
96+
}
97+
const {startName, closeName} = op.i18nPlaceholder;
98+
let flags = ir.I18nParamValueFlags.ElementTag;
99+
addParam(
100+
currentOps.i18nContext.params, startName, op.handle.slot!,
101+
currentOps.i18nBlock.subTemplateIndex, flags | ir.I18nParamValueFlags.OpenTag);
102+
addParam(
103+
currentOps.i18nContext.params, closeName, op.handle.slot!,
104+
currentOps.i18nBlock.subTemplateIndex, flags | ir.I18nParamValueFlags.CloseTag);
105+
}
106+
break;
89107
case ir.OpKind.Template:
90108
// For templates with i18n placeholders, record its slot value in the params map under the
91109
// corresponding template start and close placeholders.

0 commit comments

Comments
 (0)