diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 58704a1369..3c92295a6d 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -233,8 +233,8 @@ function transformIvySourceFile( // Generate the constant statements first, as they may involve adding additional imports // to the ImportManager. const constants = constantPool.statements.map( - stmt => - translateStatement(stmt, importManager, defaultImportRecorder, ts.ScriptTarget.ES2015)); + stmt => translateStatement( + stmt, importManager, defaultImportRecorder, getLocalizeCompileTarget(context))); // Preserve @fileoverview comments required by Closure, since the location might change as a // result of adding extra imports and constant pool statements. @@ -250,6 +250,22 @@ function transformIvySourceFile( return sf; } +/** + * Compute the correct target output for `$localize` messages generated by Angular + * + * In some versions of TypeScript, the transformation of synthetic `$localize` tagged template + * literals is broken. See https://github.com/microsoft/TypeScript/issues/38485 + * + * Here we compute what the expected final output target of the compilation will + * be so that we can generate ES5 compliant `$localize` calls instead of relying upon TS to do the + * downleveling for us. + */ +function getLocalizeCompileTarget(context: ts.TransformationContext): + Exclude { + const target = context.getCompilerOptions().target || ts.ScriptTarget.ES2015; + return target !== ts.ScriptTarget.JSON ? target : ts.ScriptTarget.ES2015; +} + function getFileOverviewComment(statements: ts.NodeArray): FileOverviewMeta|null { if (statements.length > 0) { const host = statements[0]; 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 cfb73846a3..d7cd299ada 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 @@ -5,9 +5,9 @@ * 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 {AttributeMarker} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; +import * as ts from 'typescript'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler'; import {decimalDigest} from '../../../compiler/src/i18n/digest'; @@ -3761,6 +3761,23 @@ $` + String.raw`{$I18N_4$}:ICU:\`; })); }); + describe('es5 support', () => { + it('should generate ES5 compliant localized messages if the target is ES5', () => { + const input = ` +
Content A
+ `; + + const output = String.raw` + var $I18N_0$; + … + $I18N_0$ = $localize(…__makeTemplateObject([":meaning:A|descA@@idA:Content A"], [":meaning\\:A|descA@@idA:Content A"])…); + `; + + verify( + input, output, {skipIdBasedCheck: true, compilerOptions: {target: ts.ScriptTarget.ES5}}); + }); + }); + describe('errors', () => { const verifyNestedSectionsError = (errorThrown: any, expectedErrorText: string) => { expect(errorThrown.ngParseErrors.length).toBe(1);