From 63e458dd3a705fdd610a0be516b4da308ba4f730 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 11 Jul 2019 11:59:22 -0700 Subject: [PATCH] fix(ivy): handle ICUs with placeholders in case other nested ICUs are present (#31516) Prior to this fix, the logic to set the right placeholder format for ICUs was a bit incorrect: if there was a nested ICU in one of the root ICU cases, that led to a problem where placeholders in subsequent branches used the wrong ({$placeholder}) format instead of {PLACEHOLDER} one. This commit updates the logic to make sure we properly transform all placeholders even if nested ICUs are present. PR Close #31516 --- .../compliance/r3_view_compiler_i18n_spec.ts | 46 +++++++++++++++++++ .../src/render3/view/i18n/serializer.ts | 17 +++---- packages/core/test/acceptance/i18n_spec.ts | 30 ++++++++++++ 3 files changed, 85 insertions(+), 8 deletions(-) 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);