fix(core): handle pluralize functions that expect a number (#36901)

Previously we were passing a string form of the value to pluralize
to the `getLocalePluralCase()` function that is extracted from the
locale data. But some locales have functions that rely upon this
value being a number not a string.

Now we convert the value to a number before passing it to the
locale data function.

Fixes #36888

PR Close #36901
This commit is contained in:
Pete Bacon Darwin 2020-05-03 06:58:51 +01:00 committed by Alex Rickabaugh
parent 1c26f40cd4
commit 2ff4b357d7
2 changed files with 72 additions and 18 deletions

View File

@ -8,26 +8,15 @@
import {getLocalePluralCase} from './locale_data_api'; import {getLocalePluralCase} from './locale_data_api';
const pluralMapping = ['zero', 'one', 'two', 'few', 'many'];
/** /**
* Returns the plural case based on the locale * Returns the plural case based on the locale
*/ */
export function getPluralCase(value: any, locale: string): string { export function getPluralCase(value: string, locale: string): string {
const plural = getLocalePluralCase(locale)(value); const plural = getLocalePluralCase(locale)(parseInt(value, 10));
const result = pluralMapping[plural];
switch (plural) { return (result !== undefined) ? result : 'other';
case 0:
return 'zero';
case 1:
return 'one';
case 2:
return 'two';
case 3:
return 'few';
case 4:
return 'many';
default:
return 'other';
}
} }
/** /**

View File

@ -10,6 +10,7 @@
import '@angular/localize/init'; import '@angular/localize/init';
import {CommonModule, registerLocaleData} from '@angular/common'; import {CommonModule, registerLocaleData} from '@angular/common';
import localeEs from '@angular/common/locales/es';
import localeRo from '@angular/common/locales/ro'; import localeRo from '@angular/common/locales/ro';
import {computeMsgId} from '@angular/compiler'; import {computeMsgId} from '@angular/compiler';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {Component, ContentChild, ContentChildren, Directive, ElementRef, HostBinding, Input, LOCALE_ID, NO_ERRORS_SCHEMA, Pipe, PipeTransform, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
@ -770,7 +771,20 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(fixture.nativeElement.innerHTML).toEqual(`<div>autre - 4<!--ICU 5--></div>`); expect(fixture.nativeElement.innerHTML).toEqual(`<div>autre - 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 "ro" locale', () => {
// The "ro" locale has a complex plural function that can handle muliple options
// (and string inputs)
//
// function plural(n: number): number {
// let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length;
// if (i === 1 && v === 0) return 1;
// if (!(v === 0) || n === 0 ||
// !(n === 1) && n % 100 === Math.floor(n % 100) && n % 100 >= 1 && n % 100 <= 19)
// return 3;
// return 5;
// }
//
// Compare this to the "es" locale in the next test
loadTranslations({ loadTranslations({
[computeMsgId( [computeMsgId(
'{VAR_PLURAL, plural, =0 {no email} =one {one email} =few {a few emails} =other {lots of emails}}')]: '{VAR_PLURAL, plural, =0 {no email} =one {one email} =few {a few emails} =other {lots of emails}}')]:
@ -814,6 +828,57 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->'); expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
}); });
it(`should return the correct plural form for ICU expressions when using "es" locale`, () => {
// The "es" locale has a simple plural function that can only handle a few options
// (and not string inputs)
//
// function plural(n: number): number {
// if (n === 1) return 1;
// return 5;
// }
//
// Compare this to the "ro" locale in the previous test
const icuMessage = '{VAR_PLURAL, plural, =0 {no email} =one ' +
'{one email} =few {a few emails} =other {lots of emails}}';
loadTranslations({[computeMsgId(icuMessage)]: icuMessage});
registerLocaleData(localeEs);
TestBed.configureTestingModule({providers: [{provide: LOCALE_ID, useValue: 'es'}]});
// We could also use `TestBed.overrideProvider(LOCALE_ID, {useValue: 'es'});`
const fixture = initWithTemplate(AppComp, `
{count, plural,
=0 {no email}
=one {one email}
=few {a few emails}
=other {lots of emails}
}`);
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
// Change detection cycle, no model changes
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
fixture.componentInstance.count = 3;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 2-->');
fixture.componentInstance.count = 1;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('one email<!--ICU 2-->');
fixture.componentInstance.count = 10;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 2-->');
fixture.componentInstance.count = 20;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 2-->');
fixture.componentInstance.count = 0;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
});
it('projection', () => { it('projection', () => {
loadTranslations({ loadTranslations({
[computeMsgId('{VAR_PLURAL, plural, =1 {one} other {at least {INTERPOLATION} .}}')]: [computeMsgId('{VAR_PLURAL, plural, =1 {one} other {at least {INTERPOLATION} .}}')]: