From 2ff4b357d7c701c4df4bcdb22f6831c94bc69991 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 3 May 2020 06:58:51 +0100 Subject: [PATCH] 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 --- packages/core/src/i18n/localization.ts | 23 ++------ packages/core/test/acceptance/i18n_spec.ts | 67 +++++++++++++++++++++- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/packages/core/src/i18n/localization.ts b/packages/core/src/i18n/localization.ts index beeea95c51..41c146db8d 100644 --- a/packages/core/src/i18n/localization.ts +++ b/packages/core/src/i18n/localization.ts @@ -8,26 +8,15 @@ import {getLocalePluralCase} from './locale_data_api'; +const pluralMapping = ['zero', 'one', 'two', 'few', 'many']; + /** * Returns the plural case based on the locale */ -export function getPluralCase(value: any, locale: string): string { - const plural = getLocalePluralCase(locale)(value); - - switch (plural) { - case 0: - return 'zero'; - case 1: - return 'one'; - case 2: - return 'two'; - case 3: - return 'few'; - case 4: - return 'many'; - default: - return 'other'; - } +export function getPluralCase(value: string, locale: string): string { + const plural = getLocalePluralCase(locale)(parseInt(value, 10)); + const result = pluralMapping[plural]; + return (result !== undefined) ? result : 'other'; } /** diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 6fd2e8e631..6a272efaab 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -10,6 +10,7 @@ import '@angular/localize/init'; import {CommonModule, registerLocaleData} from '@angular/common'; +import localeEs from '@angular/common/locales/es'; import localeRo from '@angular/common/locales/ro'; 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'; @@ -770,7 +771,20 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML).toEqual(`
autre - 4
`); }); - 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({ [computeMsgId( '{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'); }); + 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'); + + // Change detection cycle, no model changes + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); + + fixture.componentInstance.count = 3; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + + fixture.componentInstance.count = 1; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('one email'); + + fixture.componentInstance.count = 10; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + + fixture.componentInstance.count = 20; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + + fixture.componentInstance.count = 0; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); + }); + it('projection', () => { loadTranslations({ [computeMsgId('{VAR_PLURAL, plural, =1 {one} other {at least {INTERPOLATION} .}}')]: