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
This commit is contained in:
parent
d545bbeee4
commit
63e458dd3a
|
@ -3036,6 +3036,52 @@ describe('i18n support in the view compiler', () => {
|
||||||
verify(input, output, {exceptions});
|
verify(input, output, {exceptions});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('nested with interpolations in "other" blocks', () => {
|
||||||
|
const input = `
|
||||||
|
<div i18n>{count, plural,
|
||||||
|
=0 {zero}
|
||||||
|
=2 {{{count}} {name, select,
|
||||||
|
cat {cats}
|
||||||
|
dog {dogs}
|
||||||
|
other {animals}} !}
|
||||||
|
other {other - {{count}}}
|
||||||
|
}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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', () => {
|
it('should handle icus in different contexts', () => {
|
||||||
const input = `
|
const input = `
|
||||||
<div i18n>
|
<div i18n>
|
||||||
|
|
|
@ -16,18 +16,19 @@ import {formatI18nPlaceholderName} from './util';
|
||||||
*/
|
*/
|
||||||
class SerializerVisitor implements i18n.Visitor {
|
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
|
* This is needed due to the fact that placeholders in ICUs and in other messages are represented
|
||||||
* represented differently in Closure:
|
* differently in Closure:
|
||||||
* - {$placeholder} in non-ICU case
|
* - {$placeholder} in non-ICU case
|
||||||
* - {PLACEHOLDER} inside ICU
|
* - {PLACEHOLDER} inside ICU
|
||||||
*/
|
*/
|
||||||
private insideIcu = false;
|
private icuNestingLevel = 0;
|
||||||
|
|
||||||
private formatPh(value: string): string {
|
private formatPh(value: string): string {
|
||||||
const formatted = formatI18nPlaceholderName(value, /* useCamelCase */ !this.insideIcu);
|
const isInsideIcu = this.icuNestingLevel > 0;
|
||||||
return this.insideIcu ? `{${formatted}}` : `{$${formatted}}`;
|
const formatted = formatI18nPlaceholderName(value, /* useCamelCase */ !isInsideIcu);
|
||||||
|
return isInsideIcu ? `{${formatted}}` : `{$${formatted}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitText(text: i18n.Text, context: any): any { return text.value; }
|
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 {
|
visitIcu(icu: i18n.Icu, context: any): any {
|
||||||
this.insideIcu = true;
|
this.icuNestingLevel++;
|
||||||
const strCases =
|
const strCases =
|
||||||
Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
|
Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
|
||||||
const result = `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
|
const result = `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
|
||||||
this.insideIcu = false;
|
this.icuNestingLevel--;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -639,6 +639,36 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
|
||||||
.toEqual(`<div>4 animaux<!--nested ICU 0-->!<!--ICU 5--></div>`);
|
.toEqual(`<div>4 animaux<!--nested ICU 0-->!<!--ICU 5--></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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, `<div i18n>{count, plural,
|
||||||
|
=0 {zero}
|
||||||
|
=2 {{{count}} {name, select,
|
||||||
|
cat {cats}
|
||||||
|
dog {dogs}
|
||||||
|
other {animals}
|
||||||
|
}!}
|
||||||
|
other {other - {{count}}}
|
||||||
|
}</div>`);
|
||||||
|
expect(fixture.nativeElement.innerHTML).toEqual(`<div>zero<!--ICU 5--></div>`);
|
||||||
|
|
||||||
|
fixture.componentRef.instance.count = 2;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.innerHTML)
|
||||||
|
.toEqual(`<div>2 animaux<!--nested ICU 0-->!<!--ICU 5--></div>`);
|
||||||
|
|
||||||
|
fixture.componentRef.instance.count = 4;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement.innerHTML).toEqual(`<div>other - 4<!--ICU 5--></div>`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the correct plural form for ICU expressions when using a specific locale',
|
it('should return the correct plural form for ICU expressions when using a specific locale',
|
||||||
() => {
|
() => {
|
||||||
registerLocaleData(localeRo);
|
registerLocaleData(localeRo);
|
||||||
|
|
Loading…
Reference in New Issue