fix(i18n): parse ICU messages while normalizing templates

Fixes:
- Inject the i18n specific HtmlParser into the directive normalizer,
- Parse ICU messages while normalizing templates,
- Normalize (visit) the content of ICU messages.

🎄🎁🎅
This commit is contained in:
Victor Berchet 2016-12-22 10:17:41 -08:00 committed by Hans Larsen
parent 881eb894bc
commit e74d8aaf92
No known key found for this signature in database
GPG Key ID: 537DD9CDA3032687
5 changed files with 46 additions and 16 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
@ -102,11 +102,12 @@ export class DirectiveNormalizer {
templateAbsUrl: string): CompileTemplateMetadata { templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation); const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse( const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), false, interpolationConfig); template, stringify(prenomData.componentType), true, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) { if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n'); const errorString = rootNodesAndErrors.errors.join('\n');
throw new SyntaxError(`Template parse errors:\n${errorString}`); throw new SyntaxError(`Template parse errors:\n${errorString}`);
} }
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({ const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles, styles: prenomData.styles,
styleUrls: prenomData.styleUrls, styleUrls: prenomData.styleUrls,
@ -228,9 +229,13 @@ class TemplatePreparseVisitor implements html.Visitor {
return null; return null;
} }
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
html.visitAll(this, ast.expression);
}
visitComment(ast: html.Comment, context: any): any { return null; } visitComment(ast: html.Comment, context: any): any { return null; }
visitAttribute(ast: html.Attribute, context: any): any { return null; } visitAttribute(ast: html.Attribute, context: any): any { return null; }
visitText(ast: html.Text, context: any): any { return null; } visitText(ast: html.Text, context: any): any { return null; }
visitExpansion(ast: html.Expansion, context: any): any { return null; }
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
} }

View File

@ -422,7 +422,7 @@ class _Visitor implements html.Visitor {
} }
/** /**
* Marks the start of a section, see `_endSection` * Marks the start of a section, see `_closeTranslatableSection`
*/ */
private _openTranslatableSection(node: html.Node): void { private _openTranslatableSection(node: html.Node): void {
if (this._isInTranslatableSection) { if (this._isInTranslatableSection) {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
@ -40,6 +40,8 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);} `No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
}; };
const baseHtmlParser = new OpaqueToken('HtmlParser');
/** /**
* A set of providers that provide `JitCompiler` and its dependencies to use for * A set of providers that provide `JitCompiler` and its dependencies to use for
* template compilation. * template compilation.
@ -52,17 +54,24 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
Console, Console,
Lexer, Lexer,
Parser, Parser,
HtmlParser, {
provide: baseHtmlParser,
useClass: HtmlParser,
},
{ {
provide: i18n.I18NHtmlParser, provide: i18n.I18NHtmlParser,
useFactory: (parser: HtmlParser, translations: string, format: string) => useFactory: (parser: HtmlParser, translations: string, format: string) =>
new i18n.I18NHtmlParser(parser, translations, format), new i18n.I18NHtmlParser(parser, translations, format),
deps: [ deps: [
HtmlParser, baseHtmlParser,
[new Optional(), new Inject(TRANSLATIONS)], [new Optional(), new Inject(TRANSLATIONS)],
[new Optional(), new Inject(TRANSLATIONS_FORMAT)], [new Optional(), new Inject(TRANSLATIONS_FORMAT)],
] ]
}, },
{
provide: HtmlParser,
useExisting: i18n.I18NHtmlParser,
},
TemplateParser, TemplateParser,
DirectiveNormalizer, DirectiveNormalizer,
CompileMetadataResolver, CompileMetadataResolver,

View File

@ -144,14 +144,25 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
<!-- /i18n --> <!-- /i18n -->
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div> <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-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> <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 -->
<div i18n="desc">{
response.getItemsList().length,
plural,
=0 {Found no results}
=1 {Found one result}
other {Found {{response.getItemsList().length}} results}
}</div>
` `
}) })
class I18nComponent { class I18nComponent {
count: number; count: number;
sex: string; sex: string;
sexB: string; sexB: string;
response: any = {getItemsList: (): any[] => []};
} }
class FrLocalization extends NgLocalization { class FrLocalization extends NgLocalization {
@ -190,6 +201,7 @@ const XTB = `
<translation id="i18n16">avec un ID explicite</translation> <translation id="i18n16">avec un ID explicite</translation>
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph <translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</translation> name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</translation>
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
</translationbundle>`; </translationbundle>`;
// unused, for reference only // unused, for reference only
@ -205,20 +217,21 @@ const XMB = `
<msg id="8670732454866344690">on translatable node</msg> <msg id="8670732454866344690">on translatable node</msg>
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg> <msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="1746565782635215"> <msg id="1746565782635215">
<ph name="ICU"/> <ph name="ICU"><ex>ICU</ex></ph>
</msg> </msg>
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg> <msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg> <msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg> <msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg> <msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
<msg id="7685649297917455806">in a translatable section</msg> <msg id="7685649297917455806">in a translatable section</msg>
<msg id="2387287228265107305"> <msg id="2387287228265107305">
<ph name="START_HEADING_LEVEL1"><ex>&lt;h1&gt;</ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex>&lt;/h1&gt;</ex></ph> <ph name="START_HEADING_LEVEL1"><ex>&lt;h1&gt;</ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex>&lt;/h1&gt;</ex></ph>
<ph name="START_TAG_DIV"><ex>&lt;div&gt;</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph> <ph name="START_TAG_DIV"><ex>&lt;div&gt;</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
<ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph> <ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"><ex>ICU</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
</msg> </msg>
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg> <msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
<msg id="i18n16">with an explicit ID</msg> <msg id="i18n16">with an explicit ID</msg>
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg> <msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="4085484936881858615">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
</messagebundle> </messagebundle>
`; `;

View File

@ -108,9 +108,12 @@ export const platformCoreDynamicTesting: (extraProviders?: any[]) => PlatformRef
provide: COMPILER_OPTIONS, provide: COMPILER_OPTIONS,
useValue: { useValue: {
providers: [ providers: [
MockPipeResolver, {provide: PipeResolver, useExisting: MockPipeResolver}, MockPipeResolver,
MockDirectiveResolver, {provide: DirectiveResolver, useExisting: MockDirectiveResolver}, {provide: PipeResolver, useExisting: MockPipeResolver},
MockNgModuleResolver, {provide: NgModuleResolver, useExisting: MockNgModuleResolver} MockDirectiveResolver,
{provide: DirectiveResolver, useExisting: MockDirectiveResolver},
MockNgModuleResolver,
{provide: NgModuleResolver, useExisting: MockNgModuleResolver},
] ]
}, },
multi: true multi: true