refactor(i18n): I18nPipe uses NgLocalization (#9313)
and some refactoring
This commit is contained in:
parent
54dbed4f48
commit
fed1672a43
|
@ -11,3 +11,4 @@ export * from './src/directives';
|
|||
export * from './src/forms-deprecated';
|
||||
export * from './src/common_directives';
|
||||
export * from './src/location';
|
||||
export {NgLocalization} from './src/localization';
|
|
@ -15,7 +15,7 @@ export {CORE_DIRECTIVES} from './directives/core_directives';
|
|||
export {NgClass} from './directives/ng_class';
|
||||
export {NgFor} from './directives/ng_for';
|
||||
export {NgIf} from './directives/ng_if';
|
||||
export {NgLocalization, NgPlural, NgPluralCase} from './directives/ng_plural';
|
||||
export {NgPlural, NgPluralCase} from './directives/ng_plural';
|
||||
export {NgStyle} from './directives/ng_style';
|
||||
export {NgSwitch, NgSwitchCase, NgSwitchDefault} from './directives/ng_switch';
|
||||
export {NgTemplateOutlet} from './directives/ng_template_outlet';
|
||||
|
|
|
@ -7,19 +7,10 @@
|
|||
*/
|
||||
|
||||
import {AfterContentInit, Attribute, ContentChildren, Directive, Input, QueryList, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {Map} from '../facade/collection';
|
||||
import {NumberWrapper, isPresent} from '../facade/lang';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
import {SwitchView} from './ng_switch';
|
||||
|
||||
const _CATEGORY_DEFAULT = 'other';
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
|
||||
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category.
|
||||
|
@ -35,9 +26,6 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
|
|||
* value matches aren't found and the value maps to its category using the `getPluralCategory`
|
||||
* function provided.
|
||||
*
|
||||
* If no matching views are found for a switch expression, inner elements marked
|
||||
* `[ngPluralCase]="other"` will be displayed.
|
||||
*
|
||||
* ```typescript
|
||||
* class MyLocalization extends NgLocalization {
|
||||
* getPluralCategory(value: any) {
|
||||
|
@ -88,7 +76,6 @@ export class NgPluralCase {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
|
@ -96,7 +83,7 @@ export class NgPluralCase {
|
|||
export class NgPlural implements AfterContentInit {
|
||||
private _switchValue: number;
|
||||
private _activeView: SwitchView;
|
||||
private _caseViews = new Map<any, SwitchView>();
|
||||
private _caseViews: {[k: string]: SwitchView} = {};
|
||||
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
|
||||
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
@ -109,7 +96,7 @@ export class NgPlural implements AfterContentInit {
|
|||
|
||||
ngAfterContentInit() {
|
||||
this.cases.forEach((pluralCase: NgPluralCase): void => {
|
||||
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
|
||||
this._caseViews[pluralCase.value] = pluralCase._view;
|
||||
});
|
||||
this._updateView();
|
||||
}
|
||||
|
@ -118,10 +105,9 @@ export class NgPlural implements AfterContentInit {
|
|||
_updateView(): void {
|
||||
this._clearViews();
|
||||
|
||||
var view: SwitchView = this._caseViews.get(this._switchValue);
|
||||
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
|
||||
|
||||
this._activateView(view);
|
||||
var key = getPluralCategory(
|
||||
this._switchValue, Object.getOwnPropertyNames(this._caseViews), this._localization);
|
||||
this._activateView(this._caseViews[key]);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -135,22 +121,4 @@ export class NgPlural implements AfterContentInit {
|
|||
this._activeView = view;
|
||||
this._activeView.create();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getCategoryView(value: number): SwitchView {
|
||||
var category: string = this._localization.getPluralCategory(value);
|
||||
var categoryView: SwitchView = this._caseViews.get(category);
|
||||
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === '='; }
|
||||
|
||||
/** @internal */
|
||||
_formatValue(pluralCase: NgPluralCase): any {
|
||||
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* Returns the plural category for a given value.
|
||||
* - "=value" when the case exists,
|
||||
* - the plural category otherwise
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function getPluralCategory(
|
||||
value: number, cases: string[], ngLocalization: NgLocalization): string {
|
||||
const nbCase = `=${value}`;
|
||||
|
||||
return cases.indexOf(nbCase) > -1 ? nbCase : ngLocalization.getPluralCategory(value);
|
||||
}
|
|
@ -7,22 +7,21 @@
|
|||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {StringWrapper, isPresent, isStringMap} from '../facade/lang';
|
||||
import {StringWrapper, isBlank, isStringMap} from '../facade/lang';
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
const _INTERPOLATION_REGEXP: RegExp = /#/g;
|
||||
|
||||
/**
|
||||
*
|
||||
* Maps a value to a string that pluralizes the value properly.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* expression | i18nPlural:mapping
|
||||
*
|
||||
* where `expression` is a number and `mapping` is an object that indicates the proper text for
|
||||
* when the `expression` evaluates to 0, 1, or some other number. You can interpolate the actual
|
||||
* value into the text using the `#` sign.
|
||||
* where `expression` is a number and `mapping` is an object that mimics the ICU format,
|
||||
* see http://userguide.icu-project.org/formatparse/messages
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
|
@ -33,7 +32,7 @@ const _INTERPOLATION_REGEXP: RegExp = /#/g;
|
|||
*
|
||||
* class MyApp {
|
||||
* messages: any[];
|
||||
* messageMapping: any = {
|
||||
* messageMapping: {[k:string]: string} = {
|
||||
* '=0': 'No messages.',
|
||||
* '=1': 'One message.',
|
||||
* 'other': '# messages.'
|
||||
|
@ -46,17 +45,17 @@ const _INTERPOLATION_REGEXP: RegExp = /#/g;
|
|||
*/
|
||||
@Pipe({name: 'i18nPlural', pure: true})
|
||||
export class I18nPluralPipe implements PipeTransform {
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
transform(value: number, pluralMap: {[count: string]: string}): string {
|
||||
var key: string;
|
||||
var valueStr: string;
|
||||
if (isBlank(value)) return '';
|
||||
|
||||
if (!isStringMap(pluralMap)) {
|
||||
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
|
||||
}
|
||||
|
||||
key = value === 0 || value === 1 ? `=${value}` : 'other';
|
||||
valueStr = isPresent(value) ? value.toString() : '';
|
||||
const key = getPluralCategory(value, Object.getOwnPropertyNames(pluralMap), this._localization);
|
||||
|
||||
return StringWrapper.replaceAll(pluralMap[key], _INTERPOLATION_REGEXP, valueStr);
|
||||
return StringWrapper.replaceAll(pluralMap[key], _INTERPOLATION_REGEXP, value.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isStringMap} from '../facade/lang';
|
||||
import {isBlank, isStringMap} from '../facade/lang';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Generic selector that displays the string that matches the current value.
|
||||
|
@ -46,10 +44,12 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
|||
@Pipe({name: 'i18nSelect', pure: true})
|
||||
export class I18nSelectPipe implements PipeTransform {
|
||||
transform(value: string, mapping: {[key: string]: string}): string {
|
||||
if (isBlank(value)) return '';
|
||||
|
||||
if (!isStringMap(mapping)) {
|
||||
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
|
||||
}
|
||||
|
||||
return StringMapWrapper.contains(mapping, value) ? mapping[value] : mapping['other'];
|
||||
return mapping.hasOwnProperty(value) ? mapping[value] : '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {NgPlural, NgPluralCase, NgLocalization} from '@angular/common';
|
|||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
beforeEachProviders(() => [{provide: NgLocalization, useClass: TestLocalizationMap}]);
|
||||
beforeEachProviders(() => [{provide: NgLocalization, useClass: TestLocalization}]);
|
||||
|
||||
it('should display the template according to the exact value',
|
||||
inject(
|
||||
|
@ -142,22 +142,21 @@ export function main() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TestLocalizationMap extends NgLocalization {
|
||||
class TestLocalization extends NgLocalization {
|
||||
getPluralCategory(value: number): string {
|
||||
if (value > 1 && value < 4) {
|
||||
return 'few';
|
||||
} else if (value >= 4 && value < 10) {
|
||||
return 'many';
|
||||
} else {
|
||||
return 'other';
|
||||
}
|
||||
if (value >= 4 && value < 10) {
|
||||
return 'many';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'test-cmp', directives: [NgPluralCase, NgPlural], template: ''})
|
||||
@Component({selector: 'test-cmp', directives: [NgPlural, NgPluralCase], template: ''})
|
||||
class TestComponent {
|
||||
switchValue: number = null;
|
||||
}
|
||||
|
|
|
@ -6,21 +6,26 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {I18nPluralPipe} from '@angular/common';
|
||||
import {I18nPluralPipe, NgLocalization} from '@angular/common';
|
||||
import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('I18nPluralPipe', () => {
|
||||
var localization: NgLocalization;
|
||||
var pipe: I18nPluralPipe;
|
||||
var mapping = {'=0': 'No messages.', '=1': 'One message.', 'other': 'There are some messages.'};
|
||||
var interpolatedMapping = {
|
||||
|
||||
var mapping = {
|
||||
'=0': 'No messages.',
|
||||
'=1': 'One message.',
|
||||
'other': 'There are # messages, that is #.'
|
||||
'many': 'Many messages.',
|
||||
'other': 'There are # messages, that is #.',
|
||||
};
|
||||
|
||||
beforeEach(() => { pipe = new I18nPluralPipe(); });
|
||||
beforeEach(() => {
|
||||
localization = new TestLocalization();
|
||||
pipe = new I18nPluralPipe(localization);
|
||||
});
|
||||
|
||||
it('should be marked as pure',
|
||||
() => { expect(new PipeResolver().resolve(I18nPluralPipe).pure).toEqual(true); });
|
||||
|
@ -36,19 +41,19 @@ export function main() {
|
|||
expect(val).toEqual('One message.');
|
||||
});
|
||||
|
||||
it('should return other text if value is anything other than 0 or 1', () => {
|
||||
var val = pipe.transform(6, mapping);
|
||||
expect(val).toEqual('There are some messages.');
|
||||
it('should return category messages', () => {
|
||||
var val = pipe.transform(4, mapping);
|
||||
expect(val).toEqual('Many messages.');
|
||||
});
|
||||
|
||||
it('should interpolate the value into the text where indicated', () => {
|
||||
var val = pipe.transform(6, interpolatedMapping);
|
||||
var val = pipe.transform(6, mapping);
|
||||
expect(val).toEqual('There are 6 messages, that is 6.');
|
||||
});
|
||||
|
||||
it('should use \'other\' if value is undefined', () => {
|
||||
var val = pipe.transform(void(0), interpolatedMapping);
|
||||
expect(val).toEqual('There are messages, that is .');
|
||||
it('should use "" if value is undefined', () => {
|
||||
var val = pipe.transform(void(0), mapping);
|
||||
expect(val).toEqual('');
|
||||
});
|
||||
|
||||
it('should not support bad arguments',
|
||||
|
@ -57,3 +62,7 @@ export function main() {
|
|||
|
||||
});
|
||||
}
|
||||
|
||||
class TestLocalization extends NgLocalization {
|
||||
getPluralCategory(value: number): string { return value > 1 && value < 6 ? 'many' : 'other'; }
|
||||
}
|
||||
|
|
|
@ -31,14 +31,14 @@ export function main() {
|
|||
expect(val).toEqual('Invite her.');
|
||||
});
|
||||
|
||||
it('should return other text if value is anything other than male or female', () => {
|
||||
it('should return "" if value is anything other than male or female', () => {
|
||||
var val = pipe.transform('Anything else', mapping);
|
||||
expect(val).toEqual('Invite them.');
|
||||
expect(val).toEqual('');
|
||||
});
|
||||
|
||||
it('should use \'other\' if value is undefined', () => {
|
||||
it('should use "" if value is undefined', () => {
|
||||
var val = pipe.transform(void(0), mapping);
|
||||
expect(val).toEqual('Invite them.');
|
||||
expect(val).toEqual('');
|
||||
});
|
||||
|
||||
it('should not support bad arguments',
|
||||
|
|
|
@ -120,7 +120,7 @@ export function isType(obj: any): boolean {
|
|||
return isFunction(obj);
|
||||
}
|
||||
|
||||
export function isStringMap(obj: any): boolean {
|
||||
export function isStringMap(obj: any): obj is Object {
|
||||
return typeof obj === 'object' && obj !== null;
|
||||
}
|
||||
|
||||
|
|
|
@ -820,6 +820,7 @@ const COMMON = [
|
|||
'HashLocationStrategy.pushState(state:any, title:string, path:string, queryParams:string):any',
|
||||
'HashLocationStrategy.replaceState(state:any, title:string, path:string, queryParams:string):any',
|
||||
'I18nPluralPipe',
|
||||
'I18nPluralPipe.constructor(_localization:NgLocalization)',
|
||||
'I18nPluralPipe.transform(value:number, pluralMap:{[count:string]:string}):string',
|
||||
'I18nSelectPipe',
|
||||
'I18nSelectPipe.transform(value:string, mapping:{[key:string]:string}):string',
|
||||
|
|
Loading…
Reference in New Issue