From f16f6a290b7c5ea9694d7cb01c2f15dfd8d956f2 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 3 Dec 2019 08:36:38 +0000 Subject: [PATCH] fix(ngcc): render legacy i18n message ids by default (#34135) By ensuring that legacy i18n message ids are rendered into the templates of components for packages processed by ngcc, we ensure that these packages can be used in an application that may provide translations in a legacy format. Fixes #34056 PR Close #34135 --- packages/compiler-cli/ngcc/main-ngcc.ts | 13 +++++ packages/compiler-cli/ngcc/src/main.ts | 19 +++++- .../ngcc/test/integration/ngcc_spec.ts | 58 ++++++++++++++++++- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/compiler-cli/ngcc/main-ngcc.ts b/packages/compiler-cli/ngcc/main-ngcc.ts index fe33eb9565..3864c70496 100644 --- a/packages/compiler-cli/ngcc/main-ngcc.ts +++ b/packages/compiler-cli/ngcc/main-ngcc.ts @@ -52,6 +52,17 @@ if (require.main === module) { 'The Angular CLI does this already, so it is safe to use this option if the project is being built via the CLI.', type: 'boolean', }) + .option('legacy-message-ids', { + describe: 'Render `$localize` messages with legacy format ids.\n' + + 'The default value is `true`. Only set this to `false` if you do not want legacy message ids to\n' + + 'be rendered. For example, if you are not using legacy message ids in your translation files\n' + + 'AND are not doing compile-time inlining of translations, in which case the extra message ids\n' + + 'would add unwanted size to the final source bundle.\n' + + 'It is safe to leave this set to true if you are doing compile-time inlining because the extra\n' + + 'legacy message ids will all be stripped during translation.', + type: 'boolean', + default: true, + }) .option('async', { describe: 'Whether to compile asynchronously. This is enabled by default as it allows compilations to be parallelized.\n' + @@ -81,6 +92,7 @@ if (require.main === module) { const compileAllFormats = !options['first-only']; const createNewEntryPointFormats = options['create-ivy-entry-points']; const logLevel = options['l'] as keyof typeof LogLevel | undefined; + const enableI18nLegacyMessageIdFormat = options['legacy-message-ids']; (async() => { try { @@ -93,6 +105,7 @@ if (require.main === module) { compileAllFormats, createNewEntryPointFormats, logger, + enableI18nLegacyMessageIdFormat, async: options['async'], }); diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index 1937eda373..0eafd2d247 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -97,6 +97,19 @@ export interface SyncNgccOptions { * Default: `false` (i.e. run synchronously) */ async?: false; + + /** + * Render `$localize` messages with legacy format ids. + * + * The default value is `true`. Only set this to `false` if you do not want legacy message ids to + * be rendered. For example, if you are not using legacy message ids in your translation files + * AND are not doing compile-time inlining of translations, in which case the extra message ids + * would add unwanted size to the final source bundle. + * + * It is safe to leave this set to true if you are doing compile-time inlining because the extra + * legacy message ids will all be stripped during translation. + */ + enableI18nLegacyMessageIdFormat?: boolean; } /** @@ -124,8 +137,8 @@ export function mainNgcc(options: SyncNgccOptions): void; export function mainNgcc( {basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, compileAllFormats = true, createNewEntryPointFormats = false, - logger = new ConsoleLogger(LogLevel.info), pathMappings, async = false}: NgccOptions): void| - Promise { + logger = new ConsoleLogger(LogLevel.info), pathMappings, async = false, + enableI18nLegacyMessageIdFormat = true}: NgccOptions): void|Promise { // Execute in parallel, if async execution is acceptable and there are more than 1 CPU cores. const inParallel = async && (os.cpus().length > 1); @@ -242,7 +255,7 @@ export function mainNgcc( const bundle = makeEntryPointBundle( fileSystem, entryPoint, formatPath, isCore, format, processDts, pathMappings, true, - false); + enableI18nLegacyMessageIdFormat); logger.info(`Compiling ${entryPoint.name} : ${formatProperty} as ${format}`); diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index 13fe6d0a5d..0f7496d273 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -312,7 +312,7 @@ runInEachFileSystem(() => { expect(jsContents).not.toMatch(/\$localize\s*`/); expect(jsContents) .toMatch( - /\$localize\(ɵngcc\d+\.__makeTemplateObject\(\[":some:`description`:A message"], \[":some\\\\:\\\\`description\\\\`:A message"]\)\);/); + /\$localize\(ɵngcc\d+\.__makeTemplateObject\(\[":some:`description`\\u241Fefc92f285b3c24b083a8a594f62c7fccf3118766\\u241F3806630072763809030:A message"], \[":some\\\\:\\\\`description\\\\`\\u241Fefc92f285b3c24b083a8a594f62c7fccf3118766\\u241F3806630072763809030:A message"]\)\);/); }); describe('in async mode', () => { @@ -1219,6 +1219,62 @@ runInEachFileSystem(() => { }); }); + describe('legacy message ids', () => { + it('should render legacy message ids when compiling i18n tags in component templates', () => { + compileIntoApf('test-package', { + '/index.ts': ` + import {Component} from '@angular/core'; + + @Component({ + selector: '[base]', + template: '
Some message
' + }) + export class AppComponent {} + `, + }); + + mainNgcc({ + basePath: '/node_modules', + targetEntryPointPath: 'test-package', + propertiesToConsider: ['esm2015'], + }); + + + const jsContents = fs.readFile(_(`/node_modules/test-package/esm2015/src/index.js`)); + expect(jsContents) + .toContain( + '$localize `:␟888aea0e46f7e9dddbd95fc1ef380a3ff70ada9d␟1812794354835616626:Some message'); + }); + + it('should not render legacy message ids when compiling i18n tags in component templates if `enableI18nLegacyMessageIdFormat` is false', + () => { + compileIntoApf('test-package', { + '/index.ts': ` + import {Component} from '@angular/core'; + + @Component({ + selector: '[base]', + template: '
Some message
' + }) + export class AppComponent {} + `, + }); + + mainNgcc({ + basePath: '/node_modules', + targetEntryPointPath: 'test-package', + propertiesToConsider: ['esm2015'], + enableI18nLegacyMessageIdFormat: false, + }); + + + const jsContents = fs.readFile(_(`/node_modules/test-package/esm2015/src/index.js`)); + expect(jsContents).not.toContain('␟888aea0e46f7e9dddbd95fc1ef380a3ff70ada9d'); + expect(jsContents).not.toContain('␟1812794354835616626'); + expect(jsContents).not.toContain('␟'); + }); + }); + function loadPackage( packageName: string, basePath: AbsoluteFsPath = _('/node_modules')): EntryPointPackageJson { return JSON.parse(fs.readFile(fs.resolve(basePath, packageName, 'package.json')));