2017-03-13 06:40:22 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2017-03-13 06:40:22 -04:00
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {NgLocalization} from '@angular/common';
|
2020-04-27 07:20:17 -04:00
|
|
|
import {Serializer} from '@angular/compiler/src/i18n';
|
|
|
|
import {MessageBundle} from '@angular/compiler/src/i18n/message_bundle';
|
|
|
|
import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser';
|
|
|
|
import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config';
|
|
|
|
import {Component, DebugElement, TRANSLATIONS, TRANSLATIONS_FORMAT} from '@angular/core';
|
|
|
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
2017-03-13 06:40:22 -04:00
|
|
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
|
|
|
import {stringifyElement} from '@angular/platform-browser/testing/src/browser_util';
|
|
|
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
|
|
|
|
2020-04-27 07:20:17 -04:00
|
|
|
import {SpyResourceLoader} from '../spies';
|
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'i18n-cmp',
|
|
|
|
template: '',
|
|
|
|
})
|
|
|
|
export class I18nComponent {
|
2020-04-27 07:18:53 -04:00
|
|
|
count?: number;
|
|
|
|
sex?: string;
|
|
|
|
sexB?: string;
|
2017-03-13 06:40:22 -04:00
|
|
|
response: any = {getItemsList: (): any[] => []};
|
|
|
|
}
|
|
|
|
|
|
|
|
export class FrLocalization extends NgLocalization {
|
perf: switch angular to use StaticInjector instead of ReflectiveInjector
This change allows ReflectiveInjector to be tree shaken resulting
in not needed Reflect polyfil and smaller bundles.
Code savings for HelloWorld using Closure:
Reflective: bundle.js: 105,864(34,190 gzip)
Static: bundle.js: 154,889(33,555 gzip)
645( 2%)
BREAKING CHANGE:
`platformXXXX()` no longer accepts providers which depend on reflection.
Specifically the method signature when from `Provider[]` to
`StaticProvider[]`.
Example:
Before:
```
[
MyClass,
{provide: ClassA, useClass: SubClassA}
]
```
After:
```
[
{provide: MyClass, deps: [Dep1,...]},
{provide: ClassA, useClass: SubClassA, deps: [Dep1,...]}
]
```
NOTE: This only applies to platform creation and providers for the JIT
compiler. It does not apply to `@Compotent` or `@NgModule` provides
declarations.
Benchpress note: Previously Benchpress also supported reflective
provides, which now require static providers.
DEPRECATION:
- `ReflectiveInjector` is now deprecated as it will be remove. Use
`Injector.create` as a replacement.
closes #18496
2017-08-03 15:33:29 -04:00
|
|
|
public static PROVIDE = {provide: NgLocalization, useClass: FrLocalization, deps: []};
|
2017-03-13 06:40:22 -04:00
|
|
|
getPluralCategory(value: number): string {
|
|
|
|
switch (value) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
|
|
|
return 'one';
|
|
|
|
default:
|
|
|
|
return 'other';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function validateHtml(
|
|
|
|
tb: ComponentFixture<I18nComponent>, cmp: I18nComponent, el: DebugElement) {
|
|
|
|
expectHtml(el, 'h1').toBe('<h1>attributs i18n sur les balises</h1>');
|
|
|
|
expectHtml(el, '#i18n-1').toBe('<div id="i18n-1"><p>imbriqué</p></div>');
|
|
|
|
expectHtml(el, '#i18n-2').toBe('<div id="i18n-2"><p>imbriqué</p></div>');
|
|
|
|
expectHtml(el, '#i18n-3').toBe('<div id="i18n-3"><p><i>avec des espaces réservés</i></p></div>');
|
|
|
|
expectHtml(el, '#i18n-3b')
|
|
|
|
.toBe(
|
|
|
|
'<div id="i18n-3b"><p><i class="preserved-on-placeholders">avec des espaces réservés</i></p></div>');
|
2018-03-29 01:10:08 -04:00
|
|
|
expectHtml(el, '#i18n-4')
|
|
|
|
.toBe('<p data-html="<b>gras</b>" id="i18n-4" title="sur des balises non traductibles"></p>');
|
2017-03-13 06:40:22 -04:00
|
|
|
expectHtml(el, '#i18n-5').toBe('<p id="i18n-5" title="sur des balises traductibles"></p>');
|
|
|
|
expectHtml(el, '#i18n-6').toBe('<p id="i18n-6" title=""></p>');
|
|
|
|
|
|
|
|
cmp.count = 0;
|
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('zero');
|
|
|
|
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('zero');
|
|
|
|
cmp.count = 1;
|
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('un');
|
|
|
|
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('un');
|
|
|
|
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('un');
|
|
|
|
cmp.count = 2;
|
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('deux');
|
|
|
|
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('deux');
|
|
|
|
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('deux');
|
|
|
|
cmp.count = 3;
|
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('beaucoup');
|
|
|
|
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
|
|
|
|
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('beaucoup');
|
|
|
|
|
2018-01-22 15:23:10 -05:00
|
|
|
cmp.sex = 'male';
|
|
|
|
cmp.sexB = 'female';
|
2017-03-13 06:40:22 -04:00
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('homme');
|
|
|
|
expect(el.query(By.css('#i18n-8b')).nativeElement).toHaveText('femme');
|
2018-01-22 15:23:10 -05:00
|
|
|
cmp.sex = 'female';
|
2017-03-13 06:40:22 -04:00
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('femme');
|
2017-07-13 07:55:15 -04:00
|
|
|
cmp.sex = '0';
|
|
|
|
tb.detectChanges();
|
|
|
|
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('autre');
|
2017-03-13 06:40:22 -04:00
|
|
|
|
|
|
|
cmp.count = 123;
|
|
|
|
tb.detectChanges();
|
|
|
|
expectHtml(el, '#i18n-9').toEqual('<div id="i18n-9">count = 123</div>');
|
|
|
|
|
|
|
|
cmp.sex = 'f';
|
|
|
|
tb.detectChanges();
|
|
|
|
expectHtml(el, '#i18n-10').toEqual('<div id="i18n-10">sexe = f</div>');
|
|
|
|
|
|
|
|
expectHtml(el, '#i18n-11').toEqual('<div id="i18n-11">custom name</div>');
|
|
|
|
expectHtml(el, '#i18n-12').toEqual('<h1 id="i18n-12">Balises dans les commentaires html</h1>');
|
|
|
|
expectHtml(el, '#i18n-13').toBe('<div id="i18n-13" title="dans une section traductible"></div>');
|
|
|
|
expectHtml(el, '#i18n-15').toMatch(/ca <b>devrait<\/b> marcher/);
|
|
|
|
expectHtml(el, '#i18n-16').toMatch(/avec un ID explicite/);
|
2020-04-27 07:20:17 -04:00
|
|
|
|
|
|
|
expectHtml(el, '#i18n-17-5').toContain('Pas de réponse');
|
|
|
|
cmp.response.getItemsList = () => ['a'];
|
|
|
|
tb.detectChanges();
|
|
|
|
expectHtml(el, '#i18n-17-5').toContain('Une réponse');
|
|
|
|
cmp.response.getItemsList = () => ['a', 'b'];
|
|
|
|
tb.detectChanges();
|
|
|
|
expectHtml(el, '#i18n-17-5').toContain('2 réponses');
|
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
expectHtml(el, '#i18n-18')
|
|
|
|
.toEqual('<div id="i18n-18">FOO<a title="dans une section traductible">BAR</a></div>');
|
|
|
|
}
|
|
|
|
|
|
|
|
function expectHtml(el: DebugElement, cssSelector: string): any {
|
|
|
|
return expect(stringifyElement(el.query(By.css(cssSelector)).nativeElement));
|
|
|
|
}
|
|
|
|
|
|
|
|
export const HTML = `
|
|
|
|
<div>
|
|
|
|
<h1 i18n>i18n attribute on tags</h1>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
<div id="i18n-1"><p i18n>nested</p></div>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
|
|
|
|
<div id="i18n-3b"><p i18n><i class="preserved-on-placeholders">with placeholders</i></p></div>
|
2017-07-12 10:27:53 -04:00
|
|
|
<div id="i18n-3c"><div i18n><div>with <div>nested</div> placeholders</div></div></div>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
<div>
|
2018-03-29 01:10:08 -04:00
|
|
|
<p id="i18n-4" i18n-title title="on not translatable node" i18n-data-html data-html="<b>bold</b>"></p>
|
2017-03-13 06:40:22 -04:00
|
|
|
<p id="i18n-5" i18n i18n-title title="on translatable node"></p>
|
|
|
|
<p id="i18n-6" i18n-title title></p>
|
|
|
|
</div>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
|
|
|
<!-- no ph below because the ICU node is the only child of the div, i.e. no text nodes -->
|
2017-03-13 06:40:22 -04:00
|
|
|
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
<div i18n id="i18n-8">
|
2018-01-22 15:23:10 -05:00
|
|
|
{sex, select, male {m} female {f} other {other}}
|
2017-03-13 06:40:22 -04:00
|
|
|
</div>
|
|
|
|
<div i18n id="i18n-8b">
|
2018-01-22 15:23:10 -05:00
|
|
|
{sexB, select, male {m} female {f}}
|
2017-03-13 06:40:22 -04:00
|
|
|
</div>
|
2018-01-22 15:23:10 -05:00
|
|
|
|
2017-03-13 06:40:22 -04:00
|
|
|
<div i18n id="i18n-9">{{ "count = " + count }}</div>
|
|
|
|
<div i18n id="i18n-10">sex = {{ sex }}</div>
|
2018-01-22 15:23:10 -05:00
|
|
|
<div i18n id="i18n-11">{{ "custom name" //i18n(ph="CUSTOM_NAME") }}</div>
|
2017-03-13 06:40:22 -04:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- i18n -->
|
2018-01-22 15:23:10 -05:00
|
|
|
<h1 id="i18n-12" >Markers in html comments</h1>
|
2017-03-13 06:40:22 -04:00
|
|
|
<div id="i18n-13" i18n-title title="in a translatable section"></div>
|
|
|
|
<div id="i18n-14">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
|
|
|
<!-- /i18n -->
|
|
|
|
|
|
|
|
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
|
|
|
|
|
|
|
<div id="i18n-16" i18n="@@i18n16">with an explicit ID</div>
|
|
|
|
<div id="i18n-17" i18n="@@i18n17">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
|
|
|
|
|
|
|
<!-- make sure that ICU messages are not treated as text nodes -->
|
2020-04-27 07:20:17 -04:00
|
|
|
<div id="i18n-17-5" i18n="desc">{
|
2017-03-13 06:40:22 -04:00
|
|
|
response.getItemsList().length,
|
|
|
|
plural,
|
|
|
|
=0 {Found no results}
|
|
|
|
=1 {Found one result}
|
|
|
|
other {Found {{response.getItemsList().length}} results}
|
|
|
|
}</div>
|
|
|
|
|
|
|
|
<div i18n id="i18n-18">foo<a i18n-title title="in a translatable section">bar</a></div>
|
|
|
|
|
2020-04-27 07:20:17 -04:00
|
|
|
<div id="i18n-19" i18n>{{ 'test' //i18n(ph="map name") }}</div>
|
2017-03-13 06:40:22 -04:00
|
|
|
`;
|
2020-04-27 07:20:17 -04:00
|
|
|
|
|
|
|
export async function configureCompiler(translationsToMerge: string, format: string) {
|
|
|
|
TestBed.configureCompiler({
|
|
|
|
providers: [
|
|
|
|
SpyResourceLoader.PROVIDE,
|
|
|
|
FrLocalization.PROVIDE,
|
|
|
|
{provide: TRANSLATIONS, useValue: translationsToMerge},
|
|
|
|
{provide: TRANSLATIONS_FORMAT, useValue: format},
|
|
|
|
]
|
|
|
|
});
|
|
|
|
TestBed.configureTestingModule({declarations: [I18nComponent]});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createComponent(html: string) {
|
|
|
|
const tb: ComponentFixture<I18nComponent> =
|
|
|
|
TestBed.overrideTemplate(I18nComponent, html).createComponent(I18nComponent);
|
|
|
|
return {tb, cmp: tb.componentInstance, el: tb.debugElement};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function serializeTranslations(html: string, serializer: Serializer) {
|
|
|
|
const catalog = new MessageBundle(new HtmlParser, [], {});
|
|
|
|
catalog.updateFromTemplate(html, 'file.ts', DEFAULT_INTERPOLATION_CONFIG);
|
|
|
|
return catalog.write(serializer);
|
|
|
|
}
|