diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts
index 10d9a22852..5780326502 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts
@@ -3036,6 +3036,52 @@ describe('i18n support in the view compiler', () => {
verify(input, output, {exceptions});
});
+ it('nested with interpolations in "other" blocks', () => {
+ const input = `
+
{count, plural,
+ =0 {zero}
+ =2 {{{count}} {name, select,
+ cat {cats}
+ dog {dogs}
+ other {animals}} !}
+ other {other - {{count}}}
+ }
+ `;
+
+ const output = String.raw `
+ var $I18N_0$;
+ if (ngI18nClosureMode) {
+ const $MSG_EXTERNAL_6870293071705078389$$APP_SPEC_TS_1$ = goog.getMsg("{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}");
+ $I18N_0$ = $MSG_EXTERNAL_6870293071705078389$$APP_SPEC_TS_1$;
+ }
+ else {
+ $I18N_0$ = $r3$.ɵɵi18nLocalize("{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}} !} other {other - {INTERPOLATION}}}");
+ }
+ $I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, {
+ "VAR_SELECT": "\uFFFD0\uFFFD",
+ "VAR_PLURAL": "\uFFFD1\uFFFD",
+ "INTERPOLATION": "\uFFFD2\uFFFD"
+ });
+ …
+ consts: 2,
+ vars: 3,
+ template: function MyComponent_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵelementStart(0, "div");
+ $r3$.ɵɵi18n(1, $I18N_0$);
+ $r3$.ɵɵelementEnd();
+ }
+ if (rf & 2) {
+ $r3$.ɵɵselect(1);
+ $r3$.ɵɵi18nExp(ctx.name)(ctx.count)(ctx.count);
+ $r3$.ɵɵi18nApply(1);
+ }
+ }
+ `;
+
+ verify(input, output);
+ });
+
it('should handle icus in different contexts', () => {
const input = `
diff --git a/packages/compiler/src/render3/view/i18n/serializer.ts b/packages/compiler/src/render3/view/i18n/serializer.ts
index 1ef7d2492b..e56ffef389 100644
--- a/packages/compiler/src/render3/view/i18n/serializer.ts
+++ b/packages/compiler/src/render3/view/i18n/serializer.ts
@@ -16,18 +16,19 @@ import {formatI18nPlaceholderName} from './util';
*/
class SerializerVisitor implements i18n.Visitor {
/**
- * Flag that indicates that we are processing elements of an ICU.
+ * Keeps track of ICU nesting level, allowing to detect that we are processing elements of an ICU.
*
- * This flag is needed due to the fact that placeholders in ICUs and in other messages are
- * represented differently in Closure:
+ * This is needed due to the fact that placeholders in ICUs and in other messages are represented
+ * differently in Closure:
* - {$placeholder} in non-ICU case
* - {PLACEHOLDER} inside ICU
*/
- private insideIcu = false;
+ private icuNestingLevel = 0;
private formatPh(value: string): string {
- const formatted = formatI18nPlaceholderName(value, /* useCamelCase */ !this.insideIcu);
- return this.insideIcu ? `{${formatted}}` : `{$${formatted}}`;
+ const isInsideIcu = this.icuNestingLevel > 0;
+ const formatted = formatI18nPlaceholderName(value, /* useCamelCase */ !isInsideIcu);
+ return isInsideIcu ? `{${formatted}}` : `{$${formatted}}`;
}
visitText(text: i18n.Text, context: any): any { return text.value; }
@@ -37,11 +38,11 @@ class SerializerVisitor implements i18n.Visitor {
}
visitIcu(icu: i18n.Icu, context: any): any {
- this.insideIcu = true;
+ this.icuNestingLevel++;
const strCases =
Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
const result = `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
- this.insideIcu = false;
+ this.icuNestingLevel--;
return result;
}
diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts
index eccc3b883a..d90726b5b9 100644
--- a/packages/core/test/acceptance/i18n_spec.ts
+++ b/packages/core/test/acceptance/i18n_spec.ts
@@ -639,6 +639,36 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
.toEqual(`
4 animaux!
`);
});
+ it('nested with interpolations in "other" blocks', () => {
+ // Note: for some reason long string causing clang to reformat the entire file.
+ const key = '{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, ' +
+ 'cat {cats} dog {dogs} other {animals}}!} other {other - {INTERPOLATION}}}';
+ const translation =
+ '{VAR_PLURAL, plural, =0 {zero} =2 {{INTERPOLATION} {VAR_SELECT, select, ' +
+ 'cat {chats} dog {chients} other {animaux}}!} other {other - {INTERPOLATION}}}';
+ ɵi18nConfigureLocalize({translations: {[key]: translation}});
+
+ const fixture = initWithTemplate(AppComp, `
{count, plural,
+ =0 {zero}
+ =2 {{{count}} {name, select,
+ cat {cats}
+ dog {dogs}
+ other {animals}
+ }!}
+ other {other - {{count}}}
+ }
`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`
zero
`);
+
+ fixture.componentRef.instance.count = 2;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`
2 animaux!
`);
+
+ fixture.componentRef.instance.count = 4;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(`
other - 4
`);
+ });
+
it('should return the correct plural form for ICU expressions when using a specific locale',
() => {
registerLocaleData(localeRo);