diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts
new file mode 100644
index 0000000000..f63ed86800
--- /dev/null
+++ b/packages/core/test/acceptance/i18n_spec.ts
@@ -0,0 +1,964 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * 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 {Component, ContentChild, ContentChildren, Directive, HostBinding, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
+import {TestBed} from '@angular/core/testing';
+import {expect} from '@angular/platform-browser/testing/src/matchers';
+import {onlyInIvy} from '@angular/private/testing';
+
+
+onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({declarations: [AppComp, DirectiveWithTplRef]});
+ });
+
+ it('should translate text', () => {
+ ɵi18nConfigureLocalize({translations: {'text': 'texte'}});
+ const fixture = initWithTemplate(AppComp, `
text
`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`texte
`);
+ });
+
+ it('should support interpolations', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'Hello {$interpolation}!': 'Bonjour {$interpolation}!'}});
+ const fixture = initWithTemplate(AppComp, `Hello {{name}}!
`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`Bonjour Angular!
`);
+ fixture.componentRef.instance.name = `John`;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(`Bonjour John!
`);
+ });
+
+ it('should support interpolations with custom interpolation config', () => {
+ ɵi18nConfigureLocalize({translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
+ const interpolation = ['{%', '%}'] as[string, string];
+ TestBed.overrideComponent(AppComp, {set: {interpolation}});
+ const fixture = initWithTemplate(AppComp, `Hello {% name %}
`);
+
+ expect(fixture.nativeElement.innerHTML).toBe('Bonjour Angular
');
+ });
+
+ it('should support interpolations with complex expressions', () => {
+ ɵi18nConfigureLocalize({
+ translations:
+ {'{$interpolation} - {$interpolation_1}': '{$interpolation} - {$interpolation_1} (fr)'}
+ });
+ const fixture =
+ initWithTemplate(AppComp, `{{ name | uppercase }} - {{ obj?.a?.b }}
`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`ANGULAR - (fr)
`);
+ fixture.componentRef.instance.obj = {a: {b: 'value'}};
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(`ANGULAR - value (fr)
`);
+ });
+
+ it('should support elements', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Hello {$startTagSpan}world{$closeTagSpan} and {$startTagDiv}universe{$closeTagDiv}!':
+ 'Bonjour {$startTagSpan}monde{$closeTagSpan} et {$startTagDiv}univers{$closeTagDiv}!'
+ }
+ });
+ const fixture = initWithTemplate(
+ AppComp, `Hello
world and
universe
!
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`Bonjour
monde et
univers
!
`);
+ });
+
+ it('should support removing elements', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Hello {$startBoldText}my{$closeBoldText}{$startTagSpan}world{$closeTagSpan}':
+ 'Bonjour {$startTagSpan}monde{$closeTagSpan}'
+ }
+ });
+ const fixture =
+ initWithTemplate(AppComp, `Hello myworld
!
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`Bonjour monde
!
`);
+ });
+
+ it('should support moving elements', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Hello {$startTagSpan}world{$closeTagSpan} and {$startTagDiv}universe{$closeTagDiv}!':
+ 'Bonjour {$startTagDiv}univers{$closeTagDiv} et {$startTagSpan}monde{$closeTagSpan}!'
+ }
+ });
+ const fixture = initWithTemplate(
+ AppComp, `Hello
world and
universe
!
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`Bonjour
univers
et
monde!
`);
+ });
+
+ it('should support template directives', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Content: {$startTagDiv}before{$startTagSpan}middle{$closeTagSpan}after{$closeTagDiv}!':
+ 'Contenu: {$startTagDiv}avant{$startTagSpan}milieu{$closeTagSpan}après{$closeTagDiv}!'
+ }
+ });
+ const fixture = initWithTemplate(
+ AppComp,
+ `Content:
beforemiddleafter
!
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`Contenu:
avantmilieuaprès
!
`);
+
+ fixture.componentRef.instance.visible = false;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(`Contenu: !
`);
+ });
+
+ it('should support multiple i18n blocks', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'trad {$interpolation}': 'traduction {$interpolation}',
+ 'start {$interpolation} middle {$interpolation_1} end':
+ 'start {$interpolation_1} middle {$interpolation} end',
+ '{$startTagC}trad{$closeTagC}{$startTagD}{$closeTagD}{$startTagE}{$closeTagE}':
+ '{$startTagE}{$closeTagE}{$startTagC}traduction{$closeTagC}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+ `);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ ``);
+ });
+
+ it('should support multiple sibling i18n blocks', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Section 1': 'Section un',
+ 'Section 2': 'Section deux',
+ 'Section 3': 'Section trois',
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+
Section 1
+
Section 2
+
Section 3
+
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`Section un
Section deux
Section trois
`);
+ });
+
+ it('should support multiple sibling i18n blocks inside of a template directive', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Section 1': 'Section un',
+ 'Section 2': 'Section deux',
+ 'Section 3': 'Section trois',
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+ - Section 1
+ - Section 2
+ - Section 3
+
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `- Section un
- Section deux
- Section trois
- Section un
- Section deux
- Section trois
- Section un
- Section deux
- Section trois
`);
+ });
+
+ it('should properly escape quotes in content', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '\'Single quotes\' and "Double quotes"': '\'Guillemets simples\' et "Guillemets doubles"'
+ }
+ });
+ const fixture =
+ initWithTemplate(AppComp, `'Single quotes' and "Double quotes"
`);
+
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual('\'Guillemets simples\' et "Guillemets doubles"
');
+ });
+
+ it('should correctly bind to context in nested template', () => {
+ ɵi18nConfigureLocalize({translations: {'Item {$interpolation}': 'Article {$interpolation}'}});
+ const fixture = initWithTemplate(AppComp, `
+
+ `);
+
+ const element = fixture.nativeElement;
+ for (let i = 0; i < element.children.length; i++) {
+ const child = element.children[i];
+ expect(child).toHaveText(`Article ${i + 1}`);
+ }
+ });
+
+ it('should ignore i18n attributes on self-closing tags', () => {
+ const fixture = initWithTemplate(AppComp, '
');
+ expect(fixture.nativeElement.innerHTML).toBe(`
`);
+ });
+
+ it('should handle i18n attribute with directives', () => {
+ ɵi18nConfigureLocalize({translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
+ const fixture = initWithTemplate(AppComp, `Hello {{ name }}
`);
+ expect(fixture.nativeElement.firstChild).toHaveText('Bonjour Angular');
+ });
+
+ it('should work correctly with event listeners', () => {
+ ɵi18nConfigureLocalize({translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
+
+ @Component(
+ {selector: 'app-comp', template: `Hello {{ name }}
`})
+ class ListenerComp {
+ name = `Angular`;
+ clicks = 0;
+
+ onClick() { this.clicks++; }
+ }
+
+ TestBed.configureTestingModule({declarations: [ListenerComp]});
+ const fixture = TestBed.createComponent(ListenerComp);
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement.firstChild;
+ const instance = fixture.componentInstance;
+
+ expect(element).toHaveText('Bonjour Angular');
+ expect(instance.clicks).toBe(0);
+
+ element.click();
+ expect(instance.clicks).toBe(1);
+ });
+
+ describe('ng-container and ng-template support', () => {
+ it('should support ng-container', () => {
+ ɵi18nConfigureLocalize({translations: {'text': 'texte'}});
+ const fixture = initWithTemplate(AppComp, `text`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`texte`);
+ });
+
+ it('should handle single translation message within ng-template', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
+ const fixture =
+ initWithTemplate(AppComp, `Hello {{ name }}`);
+
+ const element = fixture.nativeElement;
+ expect(element).toHaveText('Bonjour Angular');
+ });
+
+ it('should be able to act as child elements inside i18n block (plain text content)', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagNgTemplate} Hello {$closeTagNgTemplate}{$startTagNgContainer} Bye {$closeTagNgContainer}':
+ '{$startTagNgTemplate} Bonjour {$closeTagNgTemplate}{$startTagNgContainer} Au revoir {$closeTagNgContainer}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+
+ Hello
+
+
+ Bye
+
+
+ `);
+
+ const element = fixture.nativeElement.firstChild;
+ expect(element.textContent.replace(/\s+/g, ' ').trim()).toBe('Bonjour Au revoir');
+ });
+
+ it('should be able to act as child elements inside i18n block (text + tags)', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagNgTemplate}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgContainer}':
+ '{$startTagNgTemplate}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgContainer}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+
+ Hello
+
+
+ Hello
+
+
+ `);
+
+ const element = fixture.nativeElement;
+ const spans = element.getElementsByTagName('span');
+ for (let i = 0; i < spans.length; i++) {
+ expect(spans[i]).toHaveText('Bonjour');
+ }
+ });
+
+ it('should be able to handle deep nested levels with templates', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagSpan} Hello - 1 {$closeTagSpan}{$startTagSpan_1} Hello - 2 {$startTagSpan_1} Hello - 3 {$startTagSpan_1} Hello - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Hello - 5 {$closeTagSpan}':
+ '{$startTagSpan} Bonjour - 1 {$closeTagSpan}{$startTagSpan_1} Bonjour - 2 {$startTagSpan_1} Bonjour - 3 {$startTagSpan_1} Bonjour - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Bonjour - 5 {$closeTagSpan}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+
+ Hello - 1
+
+
+ Hello - 2
+
+ Hello - 3
+
+ Hello - 4
+
+
+
+
+ Hello - 5
+
+
+ `);
+
+ const element = fixture.nativeElement;
+ const spans = element.getElementsByTagName('span');
+ for (let i = 0; i < spans.length; i++) {
+ expect(spans[i].innerHTML).toContain(`Bonjour - ${i + 1}`);
+ }
+ });
+
+ it('should handle self-closing tags as content', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagSpan}My logo{$tagImg}{$closeTagSpan}':
+ '{$startTagSpan}Mon logo{$tagImg}{$closeTagSpan}'
+ }
+ });
+ const content = `My logo
`;
+ const fixture = initWithTemplate(AppComp, `
+
+ ${content}
+
+
+ ${content}
+
+ `);
+
+ const element = fixture.nativeElement;
+ const spans = element.getElementsByTagName('span');
+ for (let i = 0; i < spans.length; i++) {
+ const child = spans[i];
+ expect(child).toHaveText('Mon logo');
+ }
+ });
+ });
+
+ describe('should support ICU expressions', () => {
+ it('with no root node', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
+ '{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
+ }
+ });
+ const fixture =
+ initWithTemplate(AppComp, `{count, select, 10 {ten} 20 {twenty} other {other}}`);
+
+ const element = fixture.nativeElement;
+ expect(element).toHaveText('autre');
+ });
+
+ it('with no i18n tag', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
+ '{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
+ }
+ });
+ const fixture = initWithTemplate(
+ AppComp, `{count, select, 10 {ten} 20 {twenty} other {other}}
`);
+
+ const element = fixture.nativeElement;
+ expect(element).toHaveText('autre');
+ });
+
+ it('multiple', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_PLURAL, plural, =0 {no {$startBoldText}emails{$closeBoldText}!} =1 {one {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}':
+ '{VAR_PLURAL, plural, =0 {aucun {$startBoldText}email{$closeBoldText}!} =1 {un {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}',
+ '{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `{count, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {{{count}} emails}
+ } - {name, select,
+ other {({{name}})}
+ }
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`aucun email! - (Angular)
`);
+
+ fixture.componentRef.instance.count = 4;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `4 emails - (Angular)
`);
+
+ fixture.componentRef.instance.count = 0;
+ fixture.componentRef.instance.name = 'John';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`aucun email! - (John)
`);
+ });
+
+ it('with custom interpolation config', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_SELECT, select, 10 {ten} other {{$interpolation}}}':
+ '{VAR_SELECT, select, 10 {dix} other {{$interpolation}}}'
+ }
+ });
+ const interpolation = ['{%', '%}'] as[string, string];
+ TestBed.overrideComponent(AppComp, {set: {interpolation}});
+ const fixture =
+ initWithTemplate(AppComp, `{count, select, 10 {ten} other {{% name %}}}
`);
+
+ expect(fixture.nativeElement).toHaveText(`Angular`);
+ });
+
+ it('inside HTML elements', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_PLURAL, plural, =0 {no {$startBoldText}emails{$closeBoldText}!} =1 {one {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}':
+ '{VAR_PLURAL, plural, =0 {aucun {$startBoldText}email{$closeBoldText}!} =1 {un {$startItalicText}email{$closeItalicText}} other {{$interpolation} {$startTagSpan}emails{$closeTagSpan}}}',
+ '{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `{count, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {{{count}} emails}
+ } - {name, select,
+ other {({{name}})}
+ }
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `aucun email! - (Angular)
`);
+
+ fixture.componentRef.instance.count = 4;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `4 emails - (Angular)
`);
+
+ fixture.componentRef.instance.count = 0;
+ fixture.componentRef.instance.name = 'John';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `aucun email! - (John)
`);
+ });
+
+ it('inside template directives', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `{name, select,
+ other {({{name}})}
+ }
`);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`(Angular)
`);
+
+ fixture.componentRef.instance.visible = false;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(``);
+ });
+
+ it('inside ng-container', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_SELECT, select, other {(name)}}': '{VAR_SELECT, select, other {({$interpolation})}}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `{name, select,
+ other {({{name}})}
+ }`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`(Angular)`);
+ });
+
+ it('inside ', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
+ '{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autre}}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+ {count, select, 10 {ten} 20 {twenty} other {other}}
+
+ `);
+
+ const element = fixture.nativeElement;
+ expect(element).toHaveText('autre');
+ });
+
+ it('nested', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{VAR_PLURAL, plural, =0 {zero} other {{$interpolation} {VAR_SELECT, select, cat {cats} dog {dogs} other {animals}}!}}':
+ '{VAR_PLURAL, plural, =0 {zero} other {{$interpolation} {VAR_SELECT, select, cat {chats} dog {chients} other {animaux}}!}}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `{count, plural,
+ =0 {zero}
+ other {{{count}} {name, select,
+ cat {cats}
+ dog {dogs}
+ other {animals}
+ }!}
+ }
`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`zero
`);
+
+ fixture.componentRef.instance.count = 4;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(`4 animaux!
`);
+ });
+ });
+
+ describe('should support attributes', () => {
+ it('text', () => {
+ ɵi18nConfigureLocalize({translations: {'text': 'texte'}});
+ const fixture = initWithTemplate(AppComp, ``);
+ expect(fixture.nativeElement.innerHTML).toEqual(``);
+ });
+
+ it('interpolations', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'hello {$interpolation}': 'bonjour {$interpolation}'}});
+ const fixture =
+ initWithTemplate(AppComp, ``);
+ expect(fixture.nativeElement.innerHTML).toEqual(``);
+
+ fixture.componentRef.instance.name = 'John';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(``);
+ });
+
+ it('multiple attributes', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'hello {$interpolation}': 'bonjour {$interpolation}'}});
+ const fixture = initWithTemplate(
+ AppComp,
+ ``);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(``);
+
+ fixture.componentRef.instance.name = 'John';
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(``);
+ });
+
+ it('on removed elements', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'text': 'texte', '{$startTagSpan}content{$closeTagSpan}': 'contenu'}});
+ const fixture =
+ initWithTemplate(AppComp, `content
`);
+ expect(fixture.nativeElement.innerHTML).toEqual(`contenu
`);
+ });
+
+ it('with custom interpolation config', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
+ const interpolation = ['{%', '%}'] as[string, string];
+ TestBed.overrideComponent(AppComp, {set: {interpolation}});
+ const fixture =
+ initWithTemplate(AppComp, ``);
+
+ const element = fixture.nativeElement.firstChild;
+ expect(element.title).toBe('Bonjour Angular');
+ });
+
+ it('in nested template', () => {
+ ɵi18nConfigureLocalize({translations: {'Item {$interpolation}': 'Article {$interpolation}'}});
+ const fixture = initWithTemplate(AppComp, `
+ `);
+
+ const element = fixture.nativeElement;
+ for (let i = 0; i < element.children.length; i++) {
+ const child = element.children[i];
+ expect((child as any).innerHTML).toBe(``);
+ }
+ });
+
+ it('should add i18n attributes on self-closing tags', () => {
+ ɵi18nConfigureLocalize(
+ {translations: {'Hello {$interpolation}': 'Bonjour {$interpolation}'}});
+ const fixture =
+ initWithTemplate(AppComp, `
`);
+
+ const element = fixture.nativeElement.firstChild;
+ expect(element.title).toBe('Bonjour Angular');
+ });
+ });
+
+ it('should work with directives and host bindings', () => {
+ let directiveInstances: ClsDir[] = [];
+
+ @Directive({selector: '[test]'})
+ class ClsDir {
+ @HostBinding('className')
+ klass = 'foo';
+
+ constructor() { directiveInstances.push(this); }
+ }
+
+ @Component({
+ selector: `my-app`,
+ template: `
+
+ trad: {exp1, plural,
+ =0 {no emails!}
+ =1 {one email}
+ other {{{exp1}} emails}
+ }
+
`
+ })
+ class MyApp {
+ exp1 = 1;
+ exp2 = 2;
+ }
+
+ TestBed.configureTestingModule({declarations: [ClsDir, MyApp]});
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'start {$interpolation} middle {$interpolation_1} end':
+ 'début {$interpolation_1} milieu {$interpolation} fin',
+ '{VAR_PLURAL, plural, =0 {no {$startBoldText}emails{$closeBoldText}!} =1 {one {$startItalicText}email{$closeItalicText}} other {{$interpolation} emails}}':
+ '{VAR_PLURAL, plural, =0 {aucun {$startBoldText}email{$closeBoldText}!} =1 {un {$startItalicText}email{$closeItalicText}} other {{$interpolation} emails}}',
+ ' trad: {$icu}': ' traduction: {$icu}'
+ }
+ });
+ const fixture = TestBed.createComponent(MyApp);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ ` traduction: un email
`);
+
+ directiveInstances.forEach(instance => instance.klass = 'bar');
+ fixture.componentRef.instance.exp1 = 2;
+ fixture.componentRef.instance.exp2 = 3;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ ` traduction: 2 emails
`);
+ });
+
+ it('should support adding/moving/removing nodes', () => {
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagDiv2}{$closeTagDiv2}{$startTagDiv3}{$closeTagDiv3}{$startTagDiv4}{$closeTagDiv4}{$startTagDiv5}{$closeTagDiv5}{$startTagDiv6}{$closeTagDiv6}{$startTagDiv7}{$closeTagDiv7}{$startTagDiv8}{$closeTagDiv8}':
+ '{$startTagDiv2}{$closeTagDiv2}{$startTagDiv8}{$closeTagDiv8}{$startTagDiv4}{$closeTagDiv4}{$startTagDiv5}{$closeTagDiv5}Bonjour monde{$startTagDiv3}{$closeTagDiv3}{$startTagDiv7}{$closeTagDiv7}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+ `);
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ ``);
+ });
+
+ describe('projection', () => {
+ it('should project the translations', () => {
+ @Component({selector: 'child', template: '
'})
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+ I am projected from
+ {{name}}
+
+
+
+
`
+ })
+ class Parent {
+ name: string = 'Parent';
+ }
+ TestBed.configureTestingModule({declarations: [Parent, Child]});
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Child of {$interpolation}': 'Enfant de {$interpolation}',
+ '{$startTagChild}I am projected from {$startBoldText}{$interpolation}{$startTagRemoveMe_1}{$closeTagRemoveMe_1}{$closeBoldText}{$startTagRemoveMe_2}{$closeTagRemoveMe_2}{$closeTagChild}{$startTagRemoveMe_3}{$closeTagRemoveMe_3}':
+ '{$startTagChild}Je suis projeté depuis {$startBoldText}{$interpolation}{$closeBoldText}{$closeTagChild}'
+ }
+ });
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `Je suis projeté depuis Parent
`);
+ });
+
+ it('should project a translated i18n block', () => {
+ @Component({selector: 'child', template: '
'})
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+
+
+ I am projected from {{name}}
+
+
+
`
+ })
+ class Parent {
+ name: string = 'Parent';
+ }
+ TestBed.configureTestingModule({declarations: [Parent, Child]});
+ ɵi18nConfigureLocalize({
+ translations: {
+ 'Child of {$interpolation}': 'Enfant de {$interpolation}',
+ 'I am projected from {$interpolation}': 'Je suis projeté depuis {$interpolation}'
+ }
+ });
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual(
+ `Je suis projeté depuis Parent
`);
+
+ // it should be able to render a new component with the same template code
+ const fixture2 = TestBed.createComponent(Parent);
+ fixture2.detectChanges();
+ expect(fixture.nativeElement.innerHTML).toEqual(fixture2.nativeElement.innerHTML);
+
+ fixture2.componentRef.instance.name = 'Parent 2';
+ fixture2.detectChanges();
+ expect(fixture2.nativeElement.innerHTML)
+ .toEqual(
+ `Je suis projeté depuis Parent 2
`);
+
+ // The first fixture should not have changed
+ expect(fixture.nativeElement.innerHTML).not.toEqual(fixture2.nativeElement.innerHTML);
+ });
+
+ it('should re-project translations when multiple projections', () => {
+ @Component({selector: 'grand-child', template: '
'})
+ class GrandChild {
+ }
+
+ @Component(
+ {selector: 'child', template: ''})
+ class Child {
+ }
+
+ @Component({selector: 'parent', template: `Hello World!`})
+ class Parent {
+ name: string = 'Parent';
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startBoldText}Hello{$closeBoldText} World!':
+ '{$startBoldText}Bonjour{$closeBoldText} monde!'
+ }
+ });
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual('Bonjour monde!
');
+ });
+
+ // FW-1319 Runtime i18n should be able to remove projected placeholders
+ xit('should be able to remove projected placeholders', () => {
+ @Component({selector: 'grand-child', template: '
'})
+ class GrandChild {
+ }
+
+ @Component(
+ {selector: 'child', template: ''})
+ class Child {
+ }
+
+ @Component({selector: 'parent', template: `Hello World!`})
+ class Parent {
+ name: string = 'Parent';
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
+ ɵi18nConfigureLocalize(
+ {translations: {'{$startBoldText}Hello{$closeBoldText} World!': 'Bonjour monde!'}});
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual('Bonjour monde!
');
+ });
+
+ // FW-1312: Wrong i18n code generated by the compiler when the template has 2 empty `span`
+ xit('should project translations with selectors', () => {
+ @Component({selector: 'child', template: ``})
+ class Child {
+ }
+
+ @Component({
+ selector: 'parent',
+ template: `
+
+
+
+
+ `
+ })
+ class Parent {
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child]});
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagSpan}{$closeTagSpan}{$startTagSpan_1}{$closeTagSpan}':
+ '{$startTagSpan}Contenu{$closeTagSpan}'
+ }
+ });
+ const fixture = TestBed.createComponent(Parent);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.innerHTML)
+ .toEqual('Contenu');
+ });
+ });
+
+ describe('queries', () => {
+ function toHtml(element: Element): string {
+ return element.innerHTML.replace(/\sng-reflect-\S*="[^"]*"/g, '')
+ .replace(//g, '');
+ }
+
+ it('detached nodes should still be part of query', () => {
+ @Directive({selector: '[text]', inputs: ['text'], exportAs: 'textDir'})
+ class TextDirective {
+ // TODO(issue/24571): remove '!'.
+ text !: string;
+ constructor() {}
+ }
+
+ @Component({selector: 'div-query', template: ''})
+ class DivQuery {
+ // TODO(issue/24571): remove '!'.
+ @ContentChild(TemplateRef) template !: TemplateRef;
+
+ // TODO(issue/24571): remove '!'.
+ @ViewChild('vc', {read: ViewContainerRef})
+ vc !: ViewContainerRef;
+
+ // TODO(issue/24571): remove '!'.
+ @ContentChildren(TextDirective, {descendants: true})
+ query !: QueryList;
+
+ create() { this.vc.createEmbeddedView(this.template); }
+
+ destroy() { this.vc.clear(); }
+ }
+
+ TestBed.configureTestingModule({declarations: [TextDirective, DivQuery]});
+ ɵi18nConfigureLocalize({
+ translations: {
+ '{$startTagNgTemplate}{$startTagDiv_1}{$startTagDiv}{$startTagSpan}Content{$closeTagSpan}{$closeTagDiv}{$closeTagDiv}{$closeTagNgTemplate}':
+ '{$startTagNgTemplate}Contenu{$closeTagNgTemplate}'
+ }
+ });
+ const fixture = initWithTemplate(AppComp, `
+
+
+
+
+
+ `);
+ const q = fixture.debugElement.children[0].references.q;
+ expect(q.query.length).toEqual(0);
+
+ // Create embedded view
+ q.create();
+ fixture.detectChanges();
+ expect(q.query.length).toEqual(1);
+ expect(toHtml(fixture.nativeElement))
+ .toEqual(`Contenu`);
+
+ // Disable ng-if
+ fixture.componentInstance.visible = false;
+ fixture.detectChanges();
+ expect(q.query.length).toEqual(0);
+ expect(toHtml(fixture.nativeElement))
+ .toEqual(`Contenu`);
+ });
+ });
+});
+
+function initWithTemplate(compType: Type, template: string) {
+ TestBed.overrideComponent(compType, {set: {template}});
+ const fixture = TestBed.createComponent(compType);
+ fixture.detectChanges();
+ return fixture;
+}
+
+@Component({selector: 'app-comp', template: ``})
+class AppComp {
+ name = `Angular`;
+ visible = true;
+ count = 0;
+}
+
+@Directive({
+ selector: '[tplRef]',
+})
+class DirectiveWithTplRef {
+ constructor(public vcRef: ViewContainerRef, public tplRef: TemplateRef<{}>) {}
+ ngOnInit() { this.vcRef.createEmbeddedView(this.tplRef, {}); }
+}
diff --git a/packages/core/test/i18n_integration_spec.ts b/packages/core/test/i18n_integration_spec.ts
deleted file mode 100644
index af09a356a7..0000000000
--- a/packages/core/test/i18n_integration_spec.ts
+++ /dev/null
@@ -1,640 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * 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 {Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
-import {TestBed} from '@angular/core/testing';
-import {expect} from '@angular/platform-browser/testing/src/matchers';
-import {onlyInIvy} from '@angular/private/testing';
-
-@Directive({
- selector: '[tplRef]',
-})
-class DirectiveWithTplRef {
- constructor(public vcRef: ViewContainerRef, public tplRef: TemplateRef<{}>) {}
- ngOnInit() { this.vcRef.createEmbeddedView(this.tplRef, {}); }
-}
-
-@Component({selector: 'my-comp', template: ''})
-class MyComp {
- name = 'John';
- items = ['1', '2', '3'];
- obj = {a: {b: 'value'}};
- visible = true;
- age = 20;
- count = 2;
- otherLabel = 'other label';
- clicks = 0;
-
- onClick() { this.clicks++; }
-}
-
-const TRANSLATIONS: any = {
- 'one': 'un',
- 'two': 'deux',
- 'more than two': 'plus que deux',
- 'ten': 'dix',
- 'twenty': 'vingt',
- 'other': 'autres',
- 'Hello': 'Bonjour',
- 'Hello {$interpolation}': 'Bonjour {$interpolation}',
- 'Bye': 'Au revoir',
- 'Item {$interpolation}': 'Article {$interpolation}',
- '\'Single quotes\' and "Double quotes"': '\'Guillemets simples\' et "Guillemets doubles"',
- 'My logo': 'Mon logo',
- '{$interpolation} - {$interpolation_1}': '{$interpolation} - {$interpolation_1} (fr)',
- '{$startTagSpan}My logo{$tagImg}{$closeTagSpan}':
- '{$startTagSpan}Mon logo{$tagImg}{$closeTagSpan}',
- '{$startTagNgTemplate} Hello {$closeTagNgTemplate}{$startTagNgContainer} Bye {$closeTagNgContainer}':
- '{$startTagNgTemplate} Bonjour {$closeTagNgTemplate}{$startTagNgContainer} Au revoir {$closeTagNgContainer}',
- '{$startTagNgTemplate}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgContainer}':
- '{$startTagNgTemplate}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgContainer}',
- '{$startTagNgTemplate}{$startTagSpan}Hello{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan_1}Hello{$closeTagSpan}{$closeTagNgContainer}':
- '{$startTagNgTemplate}{$startTagSpan}Bonjour{$closeTagSpan}{$closeTagNgTemplate}{$startTagNgContainer}{$startTagSpan_1}Bonjour{$closeTagSpan}{$closeTagNgContainer}',
- '{$startTagSpan} Hello - 1 {$closeTagSpan}{$startTagSpan_1} Hello - 2 {$startTagSpan_1} Hello - 3 {$startTagSpan_1} Hello - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Hello - 5 {$closeTagSpan}':
- '{$startTagSpan} Bonjour - 1 {$closeTagSpan}{$startTagSpan_1} Bonjour - 2 {$startTagSpan_1} Bonjour - 3 {$startTagSpan_1} Bonjour - 4 {$closeTagSpan}{$closeTagSpan}{$closeTagSpan}{$startTagSpan} Bonjour - 5 {$closeTagSpan}',
- '{VAR_SELECT, select, 10 {ten} 20 {twenty} other {other}}':
- '{VAR_SELECT, select, 10 {dix} 20 {vingt} other {autres}}',
- '{VAR_SELECT, select, 1 {one} 2 {two} other {more than two}}':
- '{VAR_SELECT, select, 1 {un} 2 {deux} other {plus que deux}}',
- '{VAR_SELECT, select, 10 {10 - {$startBoldText}ten{$closeBoldText}} 20 {20 - {$startItalicText}twenty{$closeItalicText}} other {{$startTagDiv}{$startUnderlinedText}other{$closeUnderlinedText}{$closeTagDiv}}}':
- '{VAR_SELECT, select, 10 {10 - {$startBoldText}dix{$closeBoldText}} 20 {20 - {$startItalicText}vingt{$closeItalicText}} other {{$startTagDiv}{$startUnderlinedText}autres{$closeUnderlinedText}{$closeTagDiv}}}',
- '{VAR_SELECT_2, select, 10 {ten - {VAR_SELECT, select, 1 {one} 2 {two} other {more than two}}} 20 {twenty - {VAR_SELECT_1, select, 1 {one} 2 {two} other {more than two}}} other {other}}':
- '{VAR_SELECT_2, select, 10 {dix - {VAR_SELECT, select, 1 {un} 2 {deux} other {plus que deux}}} 20 {vingt - {VAR_SELECT_1, select, 1 {un} 2 {deux} other {plus que deux}}} other {autres}}',
- '{$startTagNgTemplate}{$startTagDiv_1}{$startTagDiv}{$startTagSpan}Content{$closeTagSpan}{$closeTagDiv}{$closeTagDiv}{$closeTagNgTemplate}':
- '{$startTagNgTemplate}Contenu{$closeTagNgTemplate}'
-};
-
-const getFixtureWithOverrides = (overrides = {}) => {
- TestBed.overrideComponent(MyComp, {set: overrides});
- const fixture = TestBed.createComponent(MyComp);
- fixture.detectChanges();
- return fixture;
-};
-
-onlyInIvy('Ivy i18n logic').describe('i18n', function() {
-
- beforeEach(() => {
- ɵi18nConfigureLocalize({translations: TRANSLATIONS});
- TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTplRef]});
- });
-
- describe('attributes', () => {
- it('should translate static attributes', () => {
- const title = 'Hello';
- const template = ``;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.title).toBe('Bonjour');
- });
-
- it('should support interpolation', () => {
- const title = 'Hello {{ name }}';
- const template = ``;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.title).toBe('Bonjour John');
- });
-
- it('should support interpolation with custom interpolation config', () => {
- const title = 'Hello {% name %}';
- const template = ``;
- const interpolation = ['{%', '%}'] as[string, string];
- const fixture = getFixtureWithOverrides({template, interpolation});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.title).toBe('Bonjour John');
- });
-
- it('should correctly bind to context in nested template', () => {
- const title = 'Item {{ id }}';
- const template = `
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- for (let i = 0; i < element.children.length; i++) {
- const child = element.children[i];
- expect((child as any).innerHTML).toBe(``);
- }
- });
-
- it('should work correctly when placed on i18n root node', () => {
- const title = 'Hello {{ name }}';
- const content = 'Hello';
- const template = `
- ${content}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.title).toBe('Bonjour John');
- expect(element).toHaveText('Bonjour');
- });
-
- it('should add i18n attributes on self-closing tags', () => {
- const title = 'Hello {{ name }}';
- const template = `
`;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.title).toBe('Bonjour John');
- });
- });
-
- describe('nested nodes', () => {
- it('should handle static content', () => {
- const content = 'Hello';
- const template = `${content}
`;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('Bonjour');
- });
-
- it('should support interpolation', () => {
- const content = 'Hello {{ name }}';
- const template = `${content}
`;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('Bonjour John');
- });
-
- it('should support interpolation with custom interpolation config', () => {
- const content = 'Hello {% name %}';
- const template = `${content}
`;
- const interpolation = ['{%', '%}'] as[string, string];
- const fixture = getFixtureWithOverrides({template, interpolation});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('Bonjour John');
- });
-
- it('should support interpolations with complex expressions', () => {
- const template = `{{ name | uppercase }} - {{ obj?.a?.b }}
`;
- const fixture = getFixtureWithOverrides({template});
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('JOHN - value (fr)');
- });
-
- it('should properly escape quotes in content', () => {
- const content = `'Single quotes' and "Double quotes"`;
- const template = `${content}
`;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('\'Guillemets simples\' et "Guillemets doubles"');
- });
-
- it('should correctly bind to context in nested template', () => {
- const content = 'Item {{ id }}';
- const template = `
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- for (let i = 0; i < element.children.length; i++) {
- const child = element.children[i];
- expect(child).toHaveText(`Article ${i + 1}`);
- }
- });
-
- it('should handle i18n attributes inside i18n section', () => {
- const title = 'Hello {{ name }}';
- const template = `
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- const content = ``;
- expect(element.innerHTML).toBe(content);
- });
-
- it('should handle i18n blocks in nested templates', () => {
- const content = 'Hello {{ name }}';
- const template = `
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.children[0]).toHaveText('Bonjour John');
- });
-
- it('should ignore i18n attributes on self-closing tags', () => {
- const template = '
';
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element.innerHTML).toBe(template.replace(' i18n', ''));
- });
-
- it('should handle i18n attribute with directives', () => {
- const content = 'Hello {{ name }}';
- const template = `
- ${content}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('Bonjour John');
- });
-
- it('should work correctly with event listeners', () => {
- const content = 'Hello {{ name }}';
- const template = `
- ${content}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- const instance = fixture.componentInstance;
-
- expect(element).toHaveText('Bonjour John');
- expect(instance.clicks).toBe(0);
-
- element.click();
- expect(instance.clicks).toBe(1);
- });
- });
-
- describe('ng-container and ng-template support', () => {
- it('should handle single translation message within ng-container', () => {
- const content = 'Hello {{ name }}';
- const template = `
- ${content}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('Bonjour John');
- });
-
- it('should handle single translation message within ng-template', () => {
- const content = 'Hello {{ name }}';
- const template = `
- ${content}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText('Bonjour John');
- });
-
- it('should be able to act as child elements inside i18n block (plain text content)', () => {
- const hello = 'Hello';
- const bye = 'Bye';
- const template = `
-
-
- ${hello}
-
-
- ${bye}
-
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element.textContent.replace(/\s+/g, ' ').trim()).toBe('Bonjour Au revoir');
- });
-
- it('should be able to act as child elements inside i18n block (text + tags)', () => {
- const content = 'Hello';
- const template = `
-
-
- ${content}
-
-
- ${content}
-
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- const spans = element.getElementsByTagName('span');
- for (let i = 0; i < spans.length; i++) {
- expect(spans[i]).toHaveText('Bonjour');
- }
- });
-
- it('should be able to handle deep nested levels with templates', () => {
- const content = 'Hello';
- const template = `
-
-
- ${content} - 1
-
-
- ${content} - 2
-
- ${content} - 3
-
- ${content} - 4
-
-
-
-
- ${content} - 5
-
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- const spans = element.getElementsByTagName('span');
- for (let i = 0; i < spans.length; i++) {
- expect(spans[i].innerHTML).toContain(`Bonjour - ${i + 1}`);
- }
- });
-
- it('should handle self-closing tags as content', () => {
- const label = 'My logo';
- const content = `${label}
`;
- const template = `
-
- ${content}
-
-
- ${content}
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- const spans = element.getElementsByTagName('span');
- for (let i = 0; i < spans.length; i++) {
- const child = spans[i];
- expect(child).toHaveText('Mon logo');
- }
- });
- });
-
- describe('ICU logic', () => {
- it('should handle single ICUs', () => {
- const template = `
- {age, select, 10 {ten} 20 {twenty} other {other}}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText('vingt');
- });
-
- it('should support ICU-only templates', () => {
- const template = `
- {age, select, 10 {ten} 20 {twenty} other {other}}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText('vingt');
- });
-
- it('should support ICUs generated outside of i18n blocks', () => {
- const template = `
- {age, select, 10 {ten} 20 {twenty} other {other}}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText('vingt');
- });
-
- it('should support interpolation', () => {
- const template = `
- {age, select, 10 {ten} other {{{ otherLabel }}}}
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText(fixture.componentInstance.otherLabel);
- });
-
- it('should support interpolation with custom interpolation config', () => {
- const template = `
- {age, select, 10 {ten} other {{% otherLabel %}}}
- `;
- const interpolation = ['{%', '%}'] as[string, string];
- const fixture = getFixtureWithOverrides({template, interpolation});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText(fixture.componentInstance.otherLabel);
- });
-
- it('should handle ICUs with HTML tags inside', () => {
- const template = `
-
- {age, select, 10 {10 -
ten} 20 {20 -
twenty} other {
other
}}
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- const italicTags = element.getElementsByTagName('i');
- expect(italicTags.length).toBe(1);
- expect(italicTags[0].innerHTML).toBe('vingt');
- });
-
- it('should handle multiple ICUs in one block', () => {
- const template = `
-
- {age, select, 10 {ten} 20 {twenty} other {other}} -
- {count, select, 1 {one} 2 {two} other {more than two}}
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('vingt - deux');
- });
-
- it('should handle multiple ICUs in one i18n block wrapped in HTML elements', () => {
- const template = `
-
-
- {age, select, 10 {ten} 20 {twenty} other {other}}
-
-
- {count, select, 1 {one} 2 {two} other {more than two}}
-
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- const spans = element.getElementsByTagName('span');
- expect(spans.length).toBe(2);
- expect(spans[0]).toHaveText('vingt');
- expect(spans[1]).toHaveText('deux');
- });
-
- it('should handle ICUs inside a template in i18n block', () => {
- const template = `
-
-
- {age, select, 10 {ten} 20 {twenty} other {other}}
-
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- const spans = element.getElementsByTagName('span');
- expect(spans.length).toBe(1);
- expect(spans[0]).toHaveText('vingt');
- });
-
- it('should handle nested icus', () => {
- const template = `
-
- {age, select,
- 10 {ten - {count, select, 1 {one} 2 {two} other {more than two}}}
- 20 {twenty - {count, select, 1 {one} 2 {two} other {more than two}}}
- other {other}}
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement.firstChild;
- expect(element).toHaveText('vingt - deux');
- });
-
- it('should handle ICUs inside ', () => {
- const template = `
-
- {age, select, 10 {ten} 20 {twenty} other {other}}
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText('vingt');
- });
-
- it('should handle ICUs inside ', () => {
- const template = `
-
- {age, select, 10 {ten} 20 {twenty} other {other}}
-
- `;
- const fixture = getFixtureWithOverrides({template});
-
- const element = fixture.nativeElement;
- expect(element).toHaveText('vingt');
- });
- });
-
- describe('queries', () => {
- function toHtml(element: Element): string {
- return element.innerHTML.replace(/\sng-reflect-\S*="[^"]*"/g, '')
- .replace(//g, '');
- }
-
- it('detached nodes should still be part of query', () => {
- const template = `
-
-
-
-
-
- `;
-
- @Directive({selector: '[text]', inputs: ['text'], exportAs: 'textDir'})
- class TextDirective {
- // TODO(issue/24571): remove '!'.
- text !: string;
- constructor() {}
- }
-
- @Component({selector: 'div-query', template: ''})
- class DivQuery {
- // TODO(issue/24571): remove '!'.
- @ContentChild(TemplateRef) template !: TemplateRef;
-
- // TODO(issue/24571): remove '!'.
- @ViewChild('vc', {read: ViewContainerRef})
- vc !: ViewContainerRef;
-
- // TODO(issue/24571): remove '!'.
- @ContentChildren(TextDirective, {descendants: true})
- query !: QueryList;
-
- create() { this.vc.createEmbeddedView(this.template); }
-
- destroy() { this.vc.clear(); }
- }
-
- TestBed.configureTestingModule({declarations: [TextDirective, DivQuery]});
- const fixture = getFixtureWithOverrides({template});
- const q = fixture.debugElement.children[0].references.q;
- expect(q.query.length).toEqual(0);
-
- // Create embedded view
- q.create();
- fixture.detectChanges();
- expect(q.query.length).toEqual(1);
- expect(toHtml(fixture.nativeElement))
- .toEqual(`Contenu`);
-
- // Disable ng-if
- fixture.componentInstance.visible = false;
- fixture.detectChanges();
- expect(q.query.length).toEqual(0);
- expect(toHtml(fixture.nativeElement))
- .toEqual(`Contenu`);
- });
- });
-
- it('should handle multiple i18n sections', () => {
- const template = `
- Section 1
- Section 2
- Section 3
- `;
- const fixture = getFixtureWithOverrides({template});
- expect(fixture.nativeElement.innerHTML)
- .toBe('Section 1
Section 2
Section 3
');
- });
-
- it('should handle multiple i18n sections inside of *ngFor', () => {
- const template = `
-
- - Section 1
- - Section 2
- - Section 3
-
- `;
- const fixture = getFixtureWithOverrides({template});
- const element = fixture.nativeElement;
- for (let i = 0; i < element.children.length; i++) {
- const child = element.children[i];
- expect(child.innerHTML).toBe(`Section 1Section 2Section 3`);
- }
- });
-});
diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts
index 8f47b2509a..10df144f31 100644
--- a/packages/core/test/render3/i18n_spec.ts
+++ b/packages/core/test/render3/i18n_spec.ts
@@ -6,26 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {NgForOfContext} from '@angular/common';
-
import {noop} from '../../../compiler/src/render3/view/util';
-import {Component as _Component} from '../../src/core';
-import {ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/definition';
-import {getTranslationForTemplate, ɵɵi18n, ɵɵi18nApply, ɵɵi18nAttributes, ɵɵi18nEnd, ɵɵi18nExp, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
-import {ɵɵallocHostVars, ɵɵbind, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵnextContext, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all';
-import {RenderFlags} from '../../src/render3/interfaces/definition';
-import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n} from '../../src/render3/interfaces/i18n';
-import {AttributeMarker} from '../../src/render3/interfaces/node';
+import {getTranslationForTemplate, ɵɵi18nAttributes, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
+import {ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
+import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n} from '../../src/render3/interfaces/i18n';
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
-import {getNativeByIndex, getTNode} from '../../src/render3/util/view_utils';
-
-import {NgForOf, NgIf} from './common_with_def';
-import {ComponentFixture, TemplateFixture} from './render_util';
-
-const Component: typeof _Component = function(...args: any[]): any {
- // In test we use @Component for documentation only so it's safe to mock out the implementation.
- return () => undefined;
-} as any;
+import {getNativeByIndex} from '../../src/render3/util/view_utils';
+import {TemplateFixture} from './render_util';
describe('Runtime i18n', () => {
describe('getTranslationForTemplate', () => {
@@ -83,7 +70,6 @@ describe('Runtime i18n', () => {
const index = 0;
const opCodes = getOpCodes(() => { ɵɵi18nStart(index, MSG_DIV); }, null, nbConsts, index);
-
// Check debug
const debugOps = (opCodes as any).create.debug !.operations;
expect(debugOps[0].__raw_opCode).toBe('simple text');
@@ -586,319 +572,6 @@ describe('Runtime i18n', () => {
});
});
- describe(`i18nEnd`, () => {
- it('for text', () => {
- const MSG_DIV = `simple text`;
- const fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- }, null, 2);
-
- expect(fixture.html).toEqual(`${MSG_DIV}
`);
- });
-
- it('for bindings', () => {
- const MSG_DIV = `Hello �0�!`;
- const fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- }, null, 2);
-
- // Template should be empty because there is no update template function
- expect(fixture.html).toEqual('');
-
- // But it should have created an empty text node in `viewData`
- const textTNode = fixture.hostView[HEADER_OFFSET + 2] as Node;
- expect(textTNode.nodeType).toEqual(Node.TEXT_NODE);
- });
-
- it('for elements', () => {
- const MSG_DIV = `Hello �#3�world�/#3� and �#2�universe�/#2�!`;
- let fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nStart(1, MSG_DIV);
- ɵɵelement(2, 'div');
- ɵɵelement(3, 'span');
- ɵɵi18nEnd();
- ɵɵelementEnd();
- }, null, 4);
-
- expect(fixture.html).toEqual('Hello
world and
universe
!
');
- });
-
- it('for translations without top level element', () => {
- // When it's the first node
- let MSG_DIV = `Hello world`;
- let fixture = prepareFixture(() => { ɵɵi18n(0, MSG_DIV); }, null, 1);
-
- expect(fixture.html).toEqual('Hello world');
-
- // When the first node is a text node
- MSG_DIV = ` world`;
- fixture = prepareFixture(() => {
- ɵɵtext(0, 'Hello');
- ɵɵi18n(1, MSG_DIV);
- }, null, 2);
-
- expect(fixture.html).toEqual('Hello world');
-
- // When the first node is an element
- fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵtext(1, 'Hello');
- ɵɵelementEnd();
- ɵɵi18n(2, MSG_DIV);
- }, null, 3);
-
- expect(fixture.html).toEqual('Hello
world');
-
- // When there is a node after
- MSG_DIV = `Hello `;
- fixture = prepareFixture(() => {
- ɵɵi18n(0, MSG_DIV);
- ɵɵtext(1, 'world');
- }, null, 2);
-
- expect(fixture.html).toEqual('Hello world');
- });
-
- it('for deleted placeholders', () => {
- const MSG_DIV = `Hello �#3�world�/#3�`;
- let fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- {
- ɵɵi18nStart(1, MSG_DIV);
- {
- ɵɵelement(2, 'div'); // Will be removed
- ɵɵelement(3, 'span');
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- ɵɵelementStart(4, 'div');
- { ɵɵtext(5, '!'); }
- ɵɵelementEnd();
- }, null, 6);
-
- expect(fixture.html).toEqual('Hello world
!
');
- });
-
- it('for sub-templates', () => {
- // Template: `Content:
beforemiddleafter
!
`;
- const MSG_DIV =
- `Content: �*2:1��#1:1�before�*2:2��#1:2�middle�/#1:2��/*2:2�after�/#1:1��/*2:1�!`;
-
- function subTemplate_1(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵi18nStart(0, MSG_DIV, 1);
- ɵɵelementStart(1, 'div');
- ɵɵtemplate(2, subTemplate_2, 2, 0, 'span', [AttributeMarker.Template, 'ngIf']);
- ɵɵelementEnd();
- ɵɵi18nEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(2, 'ngIf', ɵɵbind(true));
- }
- }
-
- function subTemplate_2(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵi18nStart(0, MSG_DIV, 2);
- ɵɵelement(1, 'span');
- ɵɵi18nEnd();
- }
- }
-
- class MyApp {
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- directives: [NgIf],
- factory: () => new MyApp(),
- consts: 3,
- vars: 1,
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- ɵɵi18nStart(1, MSG_DIV);
- ɵɵtemplate(2, subTemplate_1, 3, 1, 'div', [AttributeMarker.Template, 'ngIf']);
- ɵɵi18nEnd();
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(2, 'ngIf', true);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- expect(fixture.html)
- .toEqual('Content:
beforemiddleafter
!
');
- });
-
- it('for ICU expressions', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- }`;
- const fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- }, null, 2);
-
- // Template should be empty because there is no update template function
- expect(fixture.html).toEqual('');
- });
-
- it('for multiple ICU expressions', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- } - {�0�, select,
- other {(�0�)}
- }`;
- const fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- }, null, 2);
-
- // Template should be empty because there is no update template function
- expect(fixture.html).toEqual(' -
');
- });
-
- it('for multiple ICU expressions inside html', () => {
- const MSG_DIV = `�#2�{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- }�/#2��#3�{�0�, select,
- other {(�0�)}
- }�/#3�`;
- const fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nStart(1, MSG_DIV);
- ɵɵelement(2, 'span');
- ɵɵelement(3, 'span');
- ɵɵi18nEnd();
- ɵɵelementEnd();
- }, null, 4);
-
- // Template should be empty because there is no update template function
- expect(fixture.html).toEqual('
');
- });
-
- it('for ICU expressions inside templates', () => {
- const MSG_DIV = `�*2:1��#1:1�{�0:1�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0:1� emails}
- }�/#1:1��/*2:1�`;
-
- function subTemplate_1(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵi18nStart(0, MSG_DIV, 1);
- ɵɵelement(1, 'span');
- ɵɵi18nEnd();
- }
- if (rf & RenderFlags.Update) {
- const ctx = ɵɵnextContext();
- ɵɵi18nExp(ɵɵbind(ctx.value0));
- ɵɵi18nExp(ɵɵbind(ctx.value1));
- ɵɵi18nApply(0);
- }
- }
-
- class MyApp {
- value0 = 0;
- value1 = 'emails label';
-
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- directives: [NgIf],
- factory: () => new MyApp(),
- consts: 3,
- vars: 1,
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- ɵɵi18nStart(1, MSG_DIV);
- ɵɵtemplate(2, subTemplate_1, 2, 2, 'span', [AttributeMarker.Template, 'ngIf']);
- ɵɵi18nEnd();
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(2, 'ngIf', true);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- expect(fixture.html)
- .toEqual('no emails!
');
-
- // Update the value
- fixture.component.value0 = 3;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '3 emails
');
- });
-
- it('for ICU expressions inside ', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- }`;
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- {
- ɵɵelementContainerStart(1);
- { ɵɵi18n(2, MSG_DIV); }
- ɵɵelementContainerEnd();
- }
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(0));
- ɵɵi18nExp(ɵɵbind('more than one'));
- ɵɵi18nApply(2);
- },
- 3, 2);
-
- expect(fixture.html).toEqual('no emails!
');
- });
-
- it('for nested ICU expressions', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {zero}
- other {�0� {�1�, select,
- cat {cats}
- dog {dogs}
- other {animals}
- }!}
- }`;
- const fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- }, null, 2);
-
- // Template should be empty because there is no update template function
- expect(fixture.html).toEqual('');
- });
- });
-
describe(`i18nAttribute`, () => {
it('for text', () => {
const MSG_title = `Hello world!`;
@@ -975,1140 +648,6 @@ describe('Runtime i18n', () => {
});
});
- describe(`i18nExp & i18nApply`, () => {
- it('for text bindings', () => {
- const MSG_DIV = `Hello �0�!`;
- const ctx = {value: 'world'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value));
- ɵɵi18nApply(1);
- },
- 2, 1);
-
- // Template should be empty because there is no update template function
- expect(fixture.html).toEqual('Hello world!
');
- });
-
- it('for attribute bindings', () => {
- const MSG_title = `Hello �0�!`;
- const MSG_div_attr = ['title', MSG_title];
- const ctx = {value: 'world'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nAttributes(1, MSG_div_attr);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value));
- ɵɵi18nApply(1);
- },
- 2, 1);
-
- expect(fixture.html).toEqual('');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html).toEqual('');
-
- ctx.value = 'universe';
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('for attributes with no bindings', () => {
- const MSG_title = `Hello world!`;
- const MSG_div_attr = ['title', MSG_title];
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nAttributes(1, MSG_div_attr);
- ɵɵelementEnd();
- },
- () => { ɵɵi18nApply(1); }, 2, 1);
-
- expect(fixture.html).toEqual('');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('for multiple attribute bindings', () => {
- const MSG_title = `Hello �0� and �1�, again �0�!`;
- const MSG_div_attr = ['title', MSG_title];
- const ctx = {value0: 'world', value1: 'universe'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nAttributes(1, MSG_div_attr);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value0));
- ɵɵi18nExp(ɵɵbind(ctx.value1));
- ɵɵi18nApply(1);
- },
- 2, 2);
-
- expect(fixture.html).toEqual('');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html).toEqual('');
-
- ctx.value0 = 'earth';
- fixture.update();
- expect(fixture.html).toEqual('');
-
- ctx.value0 = 'earthlings';
- ctx.value1 = 'martians';
- fixture.update();
- expect(fixture.html)
- .toEqual('');
- });
-
- it('for bindings of multiple attributes', () => {
- const MSG_title = `Hello �0�!`;
- const MSG_div_attr = ['title', MSG_title, 'aria-label', MSG_title];
- const ctx = {value: 'world'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nAttributes(1, MSG_div_attr);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value));
- ɵɵi18nApply(1);
- },
- 2, 1);
-
- expect(fixture.html).toEqual('');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html).toEqual('');
-
- ctx.value = 'universe';
- fixture.update();
- expect(fixture.html)
- .toEqual('');
- });
-
- it('for ICU expressions', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- }`;
- const ctx = {value0: 0, value1: 'emails label'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value0));
- ɵɵi18nExp(ɵɵbind(ctx.value1));
- ɵɵi18nApply(1);
- },
- 2, 2);
- expect(fixture.html).toEqual('no emails!
');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html).toEqual('no emails!
');
-
- ctx.value0 = 1;
- fixture.update();
- expect(fixture.html).toEqual('one email
');
-
- ctx.value0 = 10;
- fixture.update();
- expect(fixture.html)
- .toEqual('10 emails
');
-
- ctx.value1 = '10 emails';
- fixture.update();
- expect(fixture.html)
- .toEqual('10 emails
');
-
- ctx.value0 = 0;
- fixture.update();
- expect(fixture.html).toEqual('no emails!
');
- });
-
- it('for multiple ICU expressions', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- } - {�0�, select,
- other {(�0�)}
- }`;
- const ctx = {value0: 0, value1: 'emails label'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value0));
- ɵɵi18nExp(ɵɵbind(ctx.value1));
- ɵɵi18nApply(1);
- },
- 2, 2);
- expect(fixture.html)
- .toEqual('no emails! - (0)
');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html)
- .toEqual('no emails! - (0)
');
-
- ctx.value0 = 1;
- fixture.update();
- expect(fixture.html).toEqual('one email - (1)
');
-
- ctx.value0 = 10;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '10 emails - (10)
');
-
- ctx.value1 = '10 emails';
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '10 emails - (10)
');
-
- ctx.value0 = 0;
- fixture.update();
- expect(fixture.html)
- .toEqual('no emails! - (0)
');
- });
-
- it('for multiple ICU expressions', () => {
- const MSG_DIV = `�#2�{�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- }�/#2��#3�{�0�, select,
- other {(�0�)}
- }�/#3�`;
- const ctx = {value0: 0, value1: 'emails label'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18nStart(1, MSG_DIV);
- ɵɵelement(2, 'span');
- ɵɵelement(3, 'span');
- ɵɵi18nEnd();
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value0));
- ɵɵi18nExp(ɵɵbind(ctx.value1));
- ɵɵi18nApply(1);
- },
- 4, 2);
- expect(fixture.html)
- .toEqual(
- 'no emails!(0)
');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'no emails!(0)
');
-
- ctx.value0 = 1;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'one email(1)
');
-
- ctx.value0 = 10;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '10 emails(10)
');
-
- ctx.value1 = '10 emails';
- fixture.update();
- expect(fixture.html)
- .toEqual(
- '10 emails(10)
');
-
- ctx.value0 = 0;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- 'no emails!(0)
');
- });
-
- it('for nested ICU expressions', () => {
- const MSG_DIV = `{�0�, plural,
- =0 {zero}
- other {�0� {�1�, select,
- cat {cats}
- dog {dogs}
- other {animals}
- }!}
- }`;
- const ctx = {value0: 0, value1: 'cat'};
-
- const fixture = prepareFixture(
- () => {
- ɵɵelementStart(0, 'div');
- ɵɵi18n(1, MSG_DIV);
- ɵɵelementEnd();
- },
- () => {
- ɵɵi18nExp(ɵɵbind(ctx.value0));
- ɵɵi18nExp(ɵɵbind(ctx.value1));
- ɵɵi18nApply(1);
- },
- 2, 2);
-
- expect(fixture.html).toEqual('zero
');
-
- // Change detection cycle, no model changes
- fixture.update();
- expect(fixture.html).toEqual('zero
');
-
- ctx.value0 = 10;
- fixture.update();
- expect(fixture.html).toEqual('10 cats!
');
-
- ctx.value1 = 'squirrel';
- fixture.update();
- expect(fixture.html).toEqual('10 animals!
');
-
- ctx.value0 = 0;
- fixture.update();
- expect(fixture.html).toEqual('zero
');
- });
- });
-
- describe('integration', () => {
- it('should support multiple i18n blocks', () => {
- // Translated template:
- //
-
- const MSG_DIV_1 = `trad �0�`;
- const MSG_DIV_2_ATTR = ['title', `start �1� middle �0� end`];
- const MSG_DIV_2 = `�#9��/#9��#7�trad�/#7�`;
-
- class MyApp {
- exp1 = '1';
- exp2 = '2';
-
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- factory: () => new MyApp(),
- consts: 10,
- vars: 2,
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵelementStart(1, 'a');
- { ɵɵi18n(2, MSG_DIV_1); }
- ɵɵelementEnd();
- ɵɵtext(3, 'hello');
- ɵɵelementStart(4, 'b');
- {
- ɵɵi18nAttributes(5, MSG_DIV_2_ATTR);
- ɵɵi18nStart(6, MSG_DIV_2);
- {
- ɵɵelement(7, 'c');
- ɵɵelement(8, 'd'); // will be removed
- ɵɵelement(9, 'e'); // will be moved before `c`
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nApply(2);
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nExp(ɵɵbind(ctx.exp2));
- ɵɵi18nApply(5);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- expect(fixture.html)
- .toEqual(
- ``);
- });
-
- it('should support multiple sibling i18n blocks', () => {
- // Translated template:
- //
- //
Section 1
- //
Section 2
- //
Section 3
- //
-
- const MSG_DIV_1 = `Section 1`;
- const MSG_DIV_2 = `Section 2`;
- const MSG_DIV_3 = `Section 3`;
-
- class MyApp {
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- factory: () => new MyApp(),
- consts: 7,
- vars: 0,
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵelementStart(1, 'div');
- { ɵɵi18n(2, MSG_DIV_1); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'div');
- { ɵɵi18n(4, MSG_DIV_2); }
- ɵɵelementEnd();
- ɵɵelementStart(5, 'div');
- { ɵɵi18n(6, MSG_DIV_3); }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nApply(2);
- ɵɵi18nApply(4);
- ɵɵi18nApply(6);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- expect(fixture.html)
- .toEqual(`Section 1
Section 2
Section 3
`);
- });
-
- it('should support multiple sibling i18n blocks inside of *ngFor', () => {
- // Translated template:
- //
- // - Section 1
- // - Section 2
- // - Section 3
- //
-
- const MSG_DIV_1 = `Section 1`;
- const MSG_DIV_2 = `Section 2`;
- const MSG_DIV_3 = `Section 3`;
-
- function liTemplate(rf: RenderFlags, ctx: NgForOfContext) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'ul');
- ɵɵelementStart(1, 'li');
- { ɵɵi18n(2, MSG_DIV_1); }
- ɵɵelementEnd();
- ɵɵelementStart(3, 'li');
- { ɵɵi18n(4, MSG_DIV_2); }
- ɵɵelementEnd();
- ɵɵelementStart(5, 'li');
- { ɵɵi18n(6, MSG_DIV_3); }
- ɵɵelementEnd();
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nApply(2);
- ɵɵi18nApply(4);
- ɵɵi18nApply(6);
- }
- }
-
- class MyApp {
- items: string[] = ['1', '2', '3'];
-
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- factory: () => new MyApp(),
- consts: 2,
- vars: 1,
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵtemplate(
- 1, liTemplate, 7, 0, 'ul', [AttributeMarker.Template, 'ngFor', 'ngForOf']);
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(1, 'ngForOf', ɵɵbind(ctx.items));
- }
- },
- directives: () => [NgForOf]
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- expect(fixture.html)
- .toEqual(
- `- Section 1
- Section 2
- Section 3
- Section 1
- Section 2
- Section 3
- Section 1
- Section 2
- Section 3
`);
- });
-
- it('should support attribute translations on removed elements', () => {
- // Translated template:
- //
- // trad {{exp1}}
- //
-
- const MSG_DIV_1 = `trad �0�`;
- const MSG_DIV_1_ATTR_1 = ['title', `start �1� middle �0� end`];
-
- class MyApp {
- exp1 = '1';
- exp2 = '2';
-
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- factory: () => new MyApp(),
- consts: 5,
- vars: 5,
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵi18nAttributes(1, MSG_DIV_1_ATTR_1);
- ɵɵi18nStart(2, MSG_DIV_1);
- {
- ɵɵelementStart(3, 'b'); // Will be removed
- { ɵɵi18nAttributes(4, MSG_DIV_1_ATTR_1); }
- ɵɵelementEnd();
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nExp(ɵɵbind(ctx.exp2));
- ɵɵi18nApply(1);
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nApply(2);
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nExp(ɵɵbind(ctx.exp2));
- ɵɵi18nApply(4);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- expect(fixture.html).toEqual(`trad 1
`);
- });
-
- it('should work with directives and host bindings', () => {
- let directiveInstances: Directive[] = [];
-
- class Directive {
- // @HostBinding('className')
- klass = 'foo';
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: Directive,
- selectors: [['', 'dir', '']],
- factory: () => {
- const instance = new Directive();
- directiveInstances.push(instance);
- return instance;
- },
- hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => {
- if (rf & RenderFlags.Create) {
- ɵɵallocHostVars(1);
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(elementIndex, 'className', ɵɵbind(ctx.klass), null, true);
- }
- }
- });
- }
-
- // Translated template:
- //
- // trad {�0�, plural,
- // =0 {no emails!}
- // =1 {one email}
- // other {�0� emails}
- // }
- //
-
- const MSG_DIV_1 = `trad {�0�, plural,
- =0 {no emails!}
- =1 {one email}
- other {�0� emails}
- }`;
- const MSG_DIV_1_ATTR_1 = ['title', `start �1� middle �0� end`];
-
- class MyApp {
- exp1 = 1;
- exp2 = 2;
-
- static ngComponentDef = ɵɵdefineComponent({
- type: MyApp,
- selectors: [['my-app']],
- factory: () => new MyApp(),
- consts: 6,
- vars: 5,
- directives: [Directive],
- template: (rf: RenderFlags, ctx: MyApp) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', [AttributeMarker.Bindings, 'dir']);
- {
- ɵɵi18nAttributes(1, MSG_DIV_1_ATTR_1);
- ɵɵi18nStart(2, MSG_DIV_1);
- {
- ɵɵelementStart(3, 'b', [AttributeMarker.Bindings, 'dir']); // Will be removed
- { ɵɵi18nAttributes(4, MSG_DIV_1_ATTR_1); }
- ɵɵelementEnd();
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- ɵɵelement(5, 'div', [AttributeMarker.Bindings, 'dir']);
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nExp(ɵɵbind(ctx.exp2));
- ɵɵi18nApply(1);
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nApply(2);
- ɵɵi18nExp(ɵɵbind(ctx.exp1));
- ɵɵi18nExp(ɵɵbind(ctx.exp2));
- ɵɵi18nApply(4);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(MyApp);
- // the "test" attribute should not be reflected in the DOM as it is here only for directive
- // matching purposes
- expect(fixture.html)
- .toEqual(
- `trad one email
`);
-
- directiveInstances.forEach(instance => instance.klass = 'bar');
- fixture.component.exp1 = 2;
- fixture.component.exp2 = 3;
- fixture.update();
- expect(fixture.html)
- .toEqual(
- `trad 2 emails
`);
- });
-
- it('should fix the links when adding/moving/removing nodes', () => {
- const MSG_DIV = `�#2��/#2��#8��/#8��#4��/#4��#5��/#5�Hello World�#3��/#3��#7��/#7�`;
- let fixture = prepareFixture(() => {
- ɵɵelementStart(0, 'div');
- {
- ɵɵi18nStart(1, MSG_DIV);
- {
- ɵɵelement(2, 'div2');
- ɵɵelement(3, 'div3');
- ɵɵelement(4, 'div4');
- ɵɵelement(5, 'div5');
- ɵɵelement(6, 'div6');
- ɵɵelement(7, 'div7');
- ɵɵelement(8, 'div8');
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }, null, 9);
-
- expect(fixture.html)
- .toEqual(
- '');
-
- const div0 = getTNode(0, fixture.hostView);
- const div2 = getTNode(2, fixture.hostView);
- const div3 = getTNode(3, fixture.hostView);
- const div4 = getTNode(4, fixture.hostView);
- const div5 = getTNode(5, fixture.hostView);
- const div7 = getTNode(7, fixture.hostView);
- const div8 = getTNode(8, fixture.hostView);
- const text = getTNode(9, fixture.hostView);
- expect(div0.child).toEqual(div2);
- expect(div0.next).toBeNull();
- expect(div2.next).toEqual(div8);
- expect(div8.next).toEqual(div4);
- expect(div4.next).toEqual(div5);
- expect(div5.next).toEqual(text);
- expect(text.next).toEqual(div3);
- expect(div3.next).toEqual(div7);
- expect(div7.next).toBeNull();
- });
-
- describe('projection', () => {
- it('should project the translations', () => {
- @Component({selector: 'child', template: '
'})
- class Child {
- static ngComponentDef = ɵɵdefineComponent({
- type: Child,
- selectors: [['child']],
- factory: () => new Child(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'p');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const MSG_DIV_SECTION_1 = `�#2�Je suis projeté depuis �#3��0��/#3��/#2�`;
- const MSG_ATTR_1 = ['title', `Enfant de �0�`];
-
- @Component({
- selector: 'parent',
- template: `
-
-
- I am projected from
- {{name}}
-
-
-
-
-
-
`
- // Translated to:
- //
- //
- //
- // Je suis projeté depuis {{name}}
- //
- //
- //
- })
- class Parent {
- name: string = 'Parent';
- static ngComponentDef = ɵɵdefineComponent({
- type: Parent,
- selectors: [['parent']],
- directives: [Child],
- factory: () => new Parent(),
- consts: 8,
- vars: 2,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵi18nStart(1, MSG_DIV_SECTION_1);
- {
- ɵɵelementStart(2, 'child');
- {
- ɵɵelementStart(3, 'b');
- {
- ɵɵi18nAttributes(4, MSG_ATTR_1);
- ɵɵelement(5, 'remove-me-1');
- }
- ɵɵelementEnd();
- ɵɵelement(6, 'remove-me-2');
- }
- ɵɵelementEnd();
- ɵɵelement(7, 'remove-me-3');
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nExp(ɵɵbind(cmp.name));
- ɵɵi18nApply(1);
- ɵɵi18nExp(ɵɵbind(cmp.name));
- ɵɵi18nApply(4);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual(
- 'Je suis projeté depuis Parent
');
- //
- // Je suis projeté depuis Parent
- });
-
- it('should project a translated i18n block', () => {
- @Component({selector: 'child', template: '
'})
- class Child {
- static ngComponentDef = ɵɵdefineComponent({
- type: Child,
- selectors: [['child']],
- factory: () => new Child(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'p');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const MSG_DIV_SECTION_1 = `Je suis projeté depuis �0�`;
- const MSG_ATTR_1 = ['title', `Enfant de �0�`];
-
- @Component({
- selector: 'parent',
- template: `
-
-
-
- I am projected from {{name}}
-
-
-
`
- // Translated to:
- //
- //
- //
- // Je suis projeté depuis {{name}}
- //
- //
- //
- })
- class Parent {
- name: string = 'Parent';
- static ngComponentDef = ɵɵdefineComponent({
- type: Parent,
- selectors: [['parent']],
- directives: [Child],
- factory: () => new Parent(),
- consts: 7,
- vars: 2,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div');
- {
- ɵɵelementStart(1, 'child');
- {
- ɵɵelement(2, 'any');
- ɵɵelementStart(3, 'b');
- {
- ɵɵi18nAttributes(4, MSG_ATTR_1);
- ɵɵi18n(5, MSG_DIV_SECTION_1);
- }
- ɵɵelementEnd();
- ɵɵelement(6, 'any');
- }
- ɵɵelementEnd();
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵi18nExp(ɵɵbind(cmp.name));
- ɵɵi18nApply(4);
- ɵɵi18nExp(ɵɵbind(cmp.name));
- ɵɵi18nApply(5);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual(
- 'Je suis projeté depuis Parent
');
-
- // it should be able to render a new component with the same template code
- const fixture2 = new ComponentFixture(Parent);
- expect(fixture2.html).toEqual(fixture.html);
-
- // Updating the fixture should work
- fixture2.component.name = 'Parent 2';
- fixture.update();
- fixture2.update();
- expect(fixture2.html)
- .toEqual(
- 'Je suis projeté depuis Parent 2
');
-
- // The first fixture should not have changed
- expect(fixture.html)
- .toEqual(
- 'Je suis projeté depuis Parent
');
- });
-
- it('should re-project translations when multiple projections', () => {
- @Component({selector: 'grand-child', template: '
'})
- class GrandChild {
- static ngComponentDef = ɵɵdefineComponent({
- type: GrandChild,
- selectors: [['grand-child']],
- factory: () => new GrandChild(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- @Component(
- {selector: 'child', template: ''})
- class Child {
- static ngComponentDef = ɵɵdefineComponent({
- type: Child,
- selectors: [['child']],
- directives: [GrandChild],
- factory: () => new Child(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'grand-child');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const MSG_DIV_SECTION_1 = `�#2�Bonjour�/#2� Monde!`;
-
- @Component({
- selector: 'parent',
- template: `Hello World!`
- // Translated to:
- // Bonjour Monde!
- })
- class Parent {
- name: string = 'Parent';
- static ngComponentDef = ɵɵdefineComponent({
- type: Parent,
- selectors: [['parent']],
- directives: [Child],
- factory: () => new Parent(),
- consts: 3,
- vars: 0,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵi18nStart(1, MSG_DIV_SECTION_1);
- { ɵɵelement(2, 'b'); }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual('Bonjour Monde!
');
- // Bonjour
- // Bonjour Monde!
- });
-
- xit('should re-project translations when removed placeholders', () => {
- @Component({selector: 'grand-child', template: '
'})
- class GrandChild {
- static ngComponentDef = ɵɵdefineComponent({
- type: GrandChild,
- selectors: [['grand-child']],
- factory: () => new GrandChild(),
- consts: 3,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'div');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- @Component(
- {selector: 'child', template: ''})
- class Child {
- static ngComponentDef = ɵɵdefineComponent({
- type: Child,
- selectors: [['child']],
- directives: [GrandChild],
- factory: () => new Child(),
- consts: 2,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef();
- ɵɵelementStart(0, 'grand-child');
- { ɵɵprojection(1); }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const MSG_DIV_SECTION_1 = `Bonjour Monde!`;
-
- @Component({
- selector: 'parent',
- template: `Hello World!`
- // Translated to:
- // Bonjour Monde!
- })
- class Parent {
- name: string = 'Parent';
- static ngComponentDef = ɵɵdefineComponent({
- type: Parent,
- selectors: [['parent']],
- directives: [Child],
- factory: () => new Parent(),
- consts: 3,
- vars: 0,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵi18nStart(1, MSG_DIV_SECTION_1);
- {
- ɵɵelement(2, 'b'); // will be removed
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html)
- .toEqual('Bonjour Monde!
');
- });
-
- it('should project translations with selectors', () => {
- @Component({
- selector: 'child',
- template: `
-
- `
- })
- class Child {
- static ngComponentDef = ɵɵdefineComponent({
- type: Child,
- selectors: [['child']],
- factory: () => new Child(),
- consts: 1,
- vars: 0,
- template: (rf: RenderFlags, cmp: Child) => {
- if (rf & RenderFlags.Create) {
- ɵɵprojectionDef([[['span']]]);
- ɵɵprojection(0, 1);
- }
- }
- });
- }
-
- const MSG_DIV_SECTION_1 = `�#2�Contenu�/#2�`;
-
- @Component({
- selector: 'parent',
- template: `
-
-
-
-
- `
- // Translated to:
- // Contenu
- })
- class Parent {
- static ngComponentDef = ɵɵdefineComponent({
- type: Parent,
- selectors: [['parent']],
- directives: [Child],
- factory: () => new Parent(),
- consts: 4,
- vars: 0,
- template: (rf: RenderFlags, cmp: Parent) => {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'child');
- {
- ɵɵi18nStart(1, MSG_DIV_SECTION_1);
- {
- ɵɵelement(2, 'span', ['title', 'keepMe']);
- ɵɵelement(3, 'span', ['title', 'deleteMe']);
- }
- ɵɵi18nEnd();
- }
- ɵɵelementEnd();
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(Parent);
- expect(fixture.html).toEqual('Contenu');
- });
- });
- });
-
describe('i18nPostprocess', () => {
it('should handle valid cases', () => {
const arr = ['�*1:1��#2:1�', '�#4:1�', '�6:1�', '�/#2:1��/*1:1�'];