diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index 9eab539f48d80..bcb1f76c9700e 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -3415,14 +3415,57 @@ describe('i18n support in the template compiler', () => { }); describe('errors', () => { + const verifyNestedSectionsError = (errorThrown: any, expectedErrorText: string) => { + expect(errorThrown.ngParseErrors.length).toBe(1); + const msg = errorThrown.ngParseErrors[0].toString(); + expect(msg).toContain( + 'Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.'); + expect(msg).toContain(expectedErrorText); + expect(msg).toMatch(/app\/spec\.ts\@\d+\:\d+/); + }; + it('should throw on nested i18n sections', () => { const files = getAppFilesWithTemplate(` -
Some content
+
+
Some content
+
+ `); + try { + compile(files, angularFiles); + } catch (error) { + verifyNestedSectionsError(error, '[ERROR ->]
Some content
'); + } + }); + + it('should throw on nested i18n sections with tags in between', () => { + const files = getAppFilesWithTemplate(` +
+
+
Some content
+
+
`); - expect(() => compile(files, angularFiles)) - .toThrowError( - 'Could not mark an element as translatable inside of a translatable section'); + try { + compile(files, angularFiles); + } catch (error) { + verifyNestedSectionsError(error, '[ERROR ->]
Some content
'); + } }); + it('should throw on nested i18n sections represented with s', () => { + const files = getAppFilesWithTemplate(` + +
+ Some content +
+
+ `); + try { + compile(files, angularFiles); + } catch (error) { + verifyNestedSectionsError( + error, '[ERROR ->]Some content'); + } + }); }); }); diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index 41c2452089c16..4df6909321786 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -80,11 +80,21 @@ class HtmlAstToIvyAst implements html.Visitor { errors: ParseError[] = []; styles: string[] = []; styleUrls: string[] = []; + private inI18nBlock: boolean = false; constructor(private bindingParser: BindingParser) {} // HTML visitor visitElement(element: html.Element): t.Node|null { + const isI18nRootElement = isI18nRootNode(element.i18n); + if (isI18nRootElement) { + if (this.inI18nBlock) { + this.reportError( + 'Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.', + element.sourceSpan); + } + this.inI18nBlock = true; + } const preparsedElement = preparseElement(element); if (preparsedElement.type === PreparsedElementType.SCRIPT) { return null; @@ -209,7 +219,7 @@ class HtmlAstToIvyAst implements html.Visitor { // For s with structural directives on them, avoid passing i18n information to // the wrapping template to prevent unnecessary i18n instructions from being generated. The // necessary i18n meta information will be extracted from child elements. - const i18n = isTemplateElement && isI18nRootNode(element.i18n) ? undefined : element.i18n; + const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n; // TODO(pk): test for this case parsedElement = new t.Template( @@ -218,6 +228,9 @@ class HtmlAstToIvyAst implements html.Visitor { templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, i18n); } + if (isI18nRootElement) { + this.inI18nBlock = false; + } return parsedElement; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index da3eb85fc9b5b..71e4c7078bb7a 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -535,10 +535,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const isI18nRootElement: boolean = isI18nRootNode(element.i18n) && !isSingleI18nIcu(element.i18n); - if (isI18nRootElement && this.i18n) { - throw new Error(`Could not mark an element as translatable inside of a translatable section`); - } - const i18nAttrs: (t.TextAttribute | t.BoundAttribute)[] = []; const outputAttrs: t.TextAttribute[] = []; let ngProjectAsAttr: t.TextAttribute|undefined;