diff --git a/modules/@angular/compiler/src/i18n/extractor_merger.ts b/modules/@angular/compiler/src/i18n/extractor_merger.ts index 45900b276b..71a1f614f3 100644 --- a/modules/@angular/compiler/src/i18n/extractor_merger.ts +++ b/modules/@angular/compiler/src/i18n/extractor_merger.ts @@ -54,8 +54,9 @@ enum _VisitorMode { */ class _Visitor implements html.Visitor { // ... - private _inI18nNode = false; - private _depth: number = 0; + private _inI18nNode: boolean; + private _depth: number; + private _inImplicitNode: boolean; // ... private _blockMeaningAndDesc: string; @@ -64,7 +65,7 @@ class _Visitor implements html.Visitor { private _inI18nBlock: boolean; // {} - private _inIcu = false; + private _inIcu: boolean; private _msgCountAtSectionStart: number; private _errors: I18nError[]; @@ -204,12 +205,15 @@ class _Visitor implements html.Visitor { this._mayBeAddBlockChildren(el); this._depth++; const wasInI18nNode = this._inI18nNode; + const wasInImplicitNode = this._inImplicitNode; let childNodes: html.Node[]; // Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU // message const i18nAttr = _getI18nAttr(el); - const isImplicitI18n = this._implicitTags.some((tag: string): boolean => el.name === tag); + const isImplicit = this._implicitTags.some((tag: string): boolean => el.name === tag); + const isTopLevelImplicit = !wasInImplicitNode && isImplicit; + this._inImplicitNode = this._inImplicitNode || isImplicit; if (!this._isInTranslatableSection && !this._inIcu) { if (i18nAttr) { @@ -217,7 +221,7 @@ class _Visitor implements html.Visitor { this._inI18nNode = true; const message = this._addMessage(el.children, i18nAttr.value); childNodes = this._translateMessage(el, message); - } else if (isImplicitI18n) { + } else if (isTopLevelImplicit) { // implicit translation this._inI18nNode = true; const message = this._addMessage(el.children); @@ -225,7 +229,7 @@ class _Visitor implements html.Visitor { } if (this._mode == _VisitorMode.Extract) { - const isTranslatable = i18nAttr || isImplicitI18n; + const isTranslatable = i18nAttr || isTopLevelImplicit; if (isTranslatable) { this._openTranslatableSection(el); } @@ -235,7 +239,7 @@ class _Visitor implements html.Visitor { } } - if (this._mode === _VisitorMode.Merge && !i18nAttr && !isImplicitI18n) { + if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) { childNodes = []; el.children.forEach(child => { const visited = child.visit(this, context); @@ -247,8 +251,7 @@ class _Visitor implements html.Visitor { }); } } else { - if (i18nAttr || isImplicitI18n) { - // TODO(vicb): we should probably allow nested implicit element (ie
) + if (i18nAttr || isTopLevelImplicit) { this._reportError( el, 'Could not mark an element as translatable inside a translatable section'); } @@ -276,6 +279,7 @@ class _Visitor implements html.Visitor { this._depth--; this._inI18nNode = wasInI18nNode; + this._inImplicitNode = wasInImplicitNode; if (this._mode === _VisitorMode.Merge) { // There are no childNodes in translatable sections - those nodes will be replace anyway @@ -299,6 +303,7 @@ class _Visitor implements html.Visitor { this._msgCountAtSectionStart = void 0; this._errors = []; this._messages = []; + this._inImplicitNode = false; this._createI18nMessage = createI18nMessageFactory(interpolationConfig); } diff --git a/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts b/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts index c46b5a9add..b09da17d4f 100644 --- a/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts +++ b/modules/@angular/compiler/test/i18n/extractor_merger_spec.ts @@ -213,6 +213,17 @@ export function main() { [['bold'], '', ''], ]); }); + + it('should allow nested implicit elements', () => { + let result: any[]; + + expect(() => {result = extract('
outer
inner
', ['div'])}).not.toThrow(); + + expect(result).toEqual([ + [['outer', 'inner'], '', ''], + ]); + }); + }); describe('implicit attributes', () => { @@ -288,12 +299,6 @@ export function main() { }); describe('implicit elements', () => { - it('should report nested implicit elements', () => { - expect(extractErrors(`

`, ['p', 'b'])).toEqual([ - ['Could not mark an element as translatable inside a translatable section', ''], - ]); - }); - it('should report implicit element in translatable element', () => { expect(extractErrors(`

`, ['b'])).toEqual([ ['Could not mark an element as translatable inside a translatable section', ''], @@ -358,7 +363,7 @@ function parseHtml(html: string): html.Node[] { const htmlParser = new HtmlParser(); const parseResult = htmlParser.parse(html, 'extractor spec', true); if (parseResult.errors.length > 1) { - throw Error(`unexpected parse errors: ${parseResult.errors.join('\n')}`); + throw new Error(`unexpected parse errors: ${parseResult.errors.join('\n')}`); } return parseResult.rootNodes; } @@ -390,13 +395,16 @@ function fakeTranslate( function extract( html: string, implicitTags: string[] = [], implicitAttrs: {[k: string]: string[]} = {}): [string[], string, string][] { - const messages = - extractMessages(parseHtml(html), DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs) - .messages; + const result = + extractMessages(parseHtml(html), DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs); + + if (result.errors.length > 0) { + throw new Error(`unexpected errors: ${result.errors.join('\n')}`); + } // clang-format off // https://github.com/angular/clang-format/issues/35 - return messages.map( + return result.messages.map( message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][]; // clang-format on }