From 27535bfb79bdf029f3cf2fe9388e2f36ecb08acb Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Fri, 24 Sep 2021 19:19:11 +0200 Subject: [PATCH] refactor: expose new `@angular/localize/tools` entry-point for CLI usage (#43431) This wires up the `@angular/localize/tools` entry-point. For context: This entry-point is being created to avoid deep imports into `@angular/localize/src/tools/<..>` like the CLI relies on. Deep imports do not play well with strict ESM, and now that all APF packages are strict ESM, the tool code needs to be either strict ESM as well. We use ESBuild to create individual bundles for the CLI entry-points, and the actual tool entry-point. We use a bundler because this enables the localize code be ESM compatible. Without a bundler, all relative imports within the `tools` entry-point would need to explicitly have the `.js` extension. This would be cumbersome and hard to maintain/enforce or validate. One might wonder why this is not a standard APF entry-point then. The answer is that the APF entry-points do not support exposing the CLI binaries (like `yarn localize-translate`). This could be done through tertiary entry-points, but using ESBuild directly gives us more control for now. We might want to revisit this in the future again. PR Close #43431 --- .pullapprove.yml | 3 +- packages/compiler-cli/BUILD.bazel | 2 +- packages/localize/BUILD.bazel | 2 +- packages/localize/package.json | 15 +- packages/localize/src/tools/BUILD.bazel | 40 ------ .../localize/src/tools/tsconfig-build.json | 23 --- packages/localize/tools/BUILD.bazel | 72 ++++++++++ packages/localize/tools/README.md | 4 + packages/localize/tools/esbuild.config.js | 21 +++ packages/localize/tools/index.ts | 32 +++++ packages/localize/tools/src/extract/cli.ts | 116 ++++++++++++++++ .../tools/src/extract/{main.ts => index.ts} | 115 +-------------- packages/localize/tools/src/migrate/cli.ts | 51 +++++++ .../tools/src/migrate/{main.ts => index.ts} | 46 +----- packages/localize/tools/src/translate/cli.ts | 131 ++++++++++++++++++ .../tools/src/translate/{main.ts => index.ts} | 125 +---------------- .../translation_parsers/translation_parser.ts | 2 +- packages/localize/tools/test/BUILD.bazel | 6 +- .../test/extract/integration/BUILD.bazel | 12 +- .../test/extract/integration/main_spec.ts | 6 +- .../integration/test_files/BUILD.bazel | 2 +- .../localize/tools/test/helpers/BUILD.bazel | 2 +- .../test/migrate/integration/BUILD.bazel | 6 +- .../test/migrate/integration/main_spec.ts | 2 +- .../test/translate/integration/BUILD.bazel | 8 +- .../translate/integration/locales/BUILD.bazel | 2 +- .../test/translate/integration/main_spec.ts | 2 +- .../integration/test_files/BUILD.bazel | 2 +- .../extract_typings_rule.bzl | 1 + 29 files changed, 478 insertions(+), 373 deletions(-) delete mode 100644 packages/localize/src/tools/BUILD.bazel delete mode 100644 packages/localize/src/tools/tsconfig-build.json create mode 100644 packages/localize/tools/BUILD.bazel create mode 100644 packages/localize/tools/README.md create mode 100644 packages/localize/tools/esbuild.config.js create mode 100644 packages/localize/tools/index.ts create mode 100644 packages/localize/tools/src/extract/cli.ts rename packages/localize/tools/src/extract/{main.ts => index.ts} (52%) create mode 100644 packages/localize/tools/src/migrate/cli.ts rename packages/localize/tools/src/migrate/{main.ts => index.ts} (51%) create mode 100644 packages/localize/tools/src/translate/cli.ts rename packages/localize/tools/src/translate/{main.ts => index.ts} (50%) rename {packages/compiler-cli => tools}/extract_typings_rule.bzl (95%) diff --git a/.pullapprove.yml b/.pullapprove.yml index a4fd7d26a44..789600040b2 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -1028,7 +1028,8 @@ groups: - *can-be-global-docs-approved - > contains_any_globs(files, [ - 'packages/compiler-cli/src/tooling.ts' + 'packages/compiler-cli/src/tooling.ts', + 'packages/localize/tools/index.ts' ]) reviewers: users: diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 7ed45e5dca5..19d68a68018 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -1,6 +1,6 @@ load("//tools:defaults.bzl", "api_golden_test", "pkg_npm", "ts_config", "ts_library") load("@npm//@bazel/esbuild:index.bzl", "esbuild", "esbuild_config") -load(":extract_typings_rule.bzl", "extract_typings") +load("//tools:extract_typings_rule.bzl", "extract_typings") # Load ng_perf_flag explicitly from ng_perf.bzl as it's private API, and not exposed to other # consumers of @angular/bazel. diff --git a/packages/localize/BUILD.bazel b/packages/localize/BUILD.bazel index 7214aa8a6df..42066b3b98c 100644 --- a/packages/localize/BUILD.bazel +++ b/packages/localize/BUILD.bazel @@ -25,7 +25,7 @@ ng_package( ], nested_packages = [ "//packages/localize/schematics:npm_package", - "//packages/localize/src/tools:npm_package", + "//packages/localize/tools:npm_package", ], tags = [ "release-with-framework", diff --git a/packages/localize/package.json b/packages/localize/package.json index 27daca069ac..1d437e09b99 100644 --- a/packages/localize/package.json +++ b/packages/localize/package.json @@ -3,9 +3,15 @@ "version": "0.0.0-PLACEHOLDER", "description": "Angular - library for localizing messages", "bin": { - "localize-translate": "./src/tools/src/translate/main.js", - "localize-extract": "./src/tools/src/extract/main.js", - "localize-migrate": "./src/tools/src/migrate/main.js" + "localize-translate": "./tools/bundles/src/translate/cli.js", + "localize-extract": "./tools/bundles/src/extract/cli.js", + "localize-migrate": "./tools/bundles/src/migrate/cli.js" + }, + "exports": { + "./tools": { + "types": "./tools/index.d.ts", + "default": "./tools/bundles/index.js" + } }, "author": "angular", "license": "MIT", @@ -28,7 +34,8 @@ "**/init.mjs" ], "dependencies": { - "@babel/core": "7.8.3", + "@babel/core": "7.8.6", + "@babel/types": "7.8.6", "glob": "7.2.0", "yargs": "^17.0.0" }, diff --git a/packages/localize/src/tools/BUILD.bazel b/packages/localize/src/tools/BUILD.bazel deleted file mode 100644 index a7b42679670..00000000000 --- a/packages/localize/src/tools/BUILD.bazel +++ /dev/null @@ -1,40 +0,0 @@ -load("//tools:defaults.bzl", "pkg_npm", "ts_config", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -ts_config( - name = "tsconfig", - src = "tsconfig-build.json", - deps = ["//packages:tsconfig-build.json"], -) - -ts_library( - name = "tools", - srcs = glob( - [ - "**/*.ts", - ], - ), - tsconfig = ":tsconfig", - deps = [ - "//packages/compiler", - "//packages/compiler-cli/private", - "//packages/localize", - "@npm//@babel/core", - "@npm//@babel/types", - "@npm//@types/babel__core", - "@npm//@types/babel__traverse", - "@npm//@types/glob", - "@npm//@types/node", - "@npm//@types/yargs", - ], -) - -pkg_npm( - name = "npm_package", - srcs = [ - ], - deps = [ - ":tools", - ], -) diff --git a/packages/localize/src/tools/tsconfig-build.json b/packages/localize/src/tools/tsconfig-build.json deleted file mode 100644 index d72ef484361..00000000000 --- a/packages/localize/src/tools/tsconfig-build.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "../../../tsconfig-build.json", - - "compilerOptions": { - "module": "commonjs", - "stripInternal": false, - "target": "es2015", - "lib": [ - "es2015", - "es2017.object" - ], - "paths": { - "@angular/*": ["./packages/*"] - }, - "strict": true, - "types": [ - "node" - ] - }, - "bazelOptions": { - "suppressTsconfigOverrideWarnings": true - } -} \ No newline at end of file diff --git a/packages/localize/tools/BUILD.bazel b/packages/localize/tools/BUILD.bazel new file mode 100644 index 00000000000..07231f7355c --- /dev/null +++ b/packages/localize/tools/BUILD.bazel @@ -0,0 +1,72 @@ +load("@npm//@bazel/esbuild:index.bzl", "esbuild", "esbuild_config") +load("//tools:defaults.bzl", "pkg_npm", "ts_library") +load("//tools:extract_typings_rule.bzl", "extract_typings") + +ts_library( + name = "tools", + srcs = glob( + [ + "**/*.ts", + ], + ), + visibility = ["//packages/localize/tools:__subpackages__"], + deps = [ + "//packages/compiler", + "//packages/compiler-cli/private", + "//packages/localize", + "@npm//@babel/core", + "@npm//@babel/types", + "@npm//@types/babel__core", + "@npm//@types/babel__traverse", + "@npm//@types/glob", + "@npm//@types/node", + "@npm//@types/yargs", + "@npm//glob", + ], +) + +esbuild_config( + name = "esbuild_config", + config_file = "esbuild.config.js", +) + +esbuild( + name = "bundles", + config = ":esbuild_config", + entry_points = [ + ":index.ts", + ":src/extract/cli.ts", + ":src/migrate/cli.ts", + ":src/translate/cli.ts", + ], + external = [ + "@angular/localize", + "@angular/compiler", + "@angular/compiler-cli/private/localize", + "@babel/core", + "@babel/types", + "yargs", + "glob", + ], + format = "esm", + platform = "node", + target = "node14", + deps = [ + ":tools", + ], +) + +extract_typings( + name = "api_type_definitions", + deps = [":tools"], +) + +pkg_npm( + name = "npm_package", + srcs = ["README.md"], + visibility = ["//packages/localize:__pkg__"], + deps = [ + ":api_type_definitions", + ":bundles", + ], +) diff --git a/packages/localize/tools/README.md b/packages/localize/tools/README.md new file mode 100644 index 00000000000..11429306d87 --- /dev/null +++ b/packages/localize/tools/README.md @@ -0,0 +1,4 @@ +### Disclaimer + +The localize tools are consumed via the Angular CLI. The programmatic APIs are not considered officially +supported though and are not subject to the breaking change guarantees of SemVer. \ No newline at end of file diff --git a/packages/localize/tools/esbuild.config.js b/packages/localize/tools/esbuild.config.js new file mode 100644 index 00000000000..33dec5e1164 --- /dev/null +++ b/packages/localize/tools/esbuild.config.js @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +module.exports = { + resolveExtensions: ['.mjs'], + // Note: `@bazel/esbuild` has a bug and does not pass-through the format from Starlark. + format: 'esm', + banner: { + // Workaround for: https://github.com/evanw/esbuild/issues/946 + // TODO: Remove this workaround in the future once devmode is ESM as well. + js: ` + import {createRequire as __cjsCompatRequire} from 'module'; + const require = __cjsCompatRequire(import.meta.url); + `, + }, +}; diff --git a/packages/localize/tools/index.ts b/packages/localize/tools/index.ts new file mode 100644 index 00000000000..3127e2f0572 --- /dev/null +++ b/packages/localize/tools/index.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +// Note: Before changing any exports here, consult with the Angular tooling team +// as the CLI heavily relies on exports declared here. + +import {NodeJSFileSystem, setFileSystem} from '@angular/compiler-cli/private/localize'; +setFileSystem(new NodeJSFileSystem()); + +export {DiagnosticHandlingStrategy, Diagnostics} from './src/diagnostics'; +export {checkDuplicateMessages} from './src/extract/duplicates'; +export {MessageExtractor} from './src/extract/extraction'; +export {ArbTranslationSerializer} from './src/extract/translation_files/arb_translation_serializer'; +export {SimpleJsonTranslationSerializer} from './src/extract/translation_files/json_translation_serializer'; +export {LegacyMessageIdMigrationSerializer} from './src/extract/translation_files/legacy_message_id_migration_serializer'; +export {Xliff1TranslationSerializer} from './src/extract/translation_files/xliff1_translation_serializer'; +export {Xliff2TranslationSerializer} from './src/extract/translation_files/xliff2_translation_serializer'; +export {XmbTranslationSerializer} from './src/extract/translation_files/xmb_translation_serializer'; +export {buildLocalizeReplacement, isGlobalIdentifier, translate, unwrapExpressionsFromTemplateLiteral, unwrapMessagePartsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, unwrapSubstitutionsFromLocalizeCall} from './src/source_file_utils'; +export {makeEs2015TranslatePlugin} from './src/translate/source_files/es2015_translate_plugin'; +export {makeEs5TranslatePlugin} from './src/translate/source_files/es5_translate_plugin'; +export {makeLocalePlugin} from './src/translate/source_files/locale_plugin'; +export {ArbTranslationParser} from './src/translate/translation_files/translation_parsers/arb_translation_parser'; +export {SimpleJsonTranslationParser} from './src/translate/translation_files/translation_parsers/simple_json_translation_parser'; +export {Xliff1TranslationParser} from './src/translate/translation_files/translation_parsers/xliff1_translation_parser'; +export {Xliff2TranslationParser} from './src/translate/translation_files/translation_parsers/xliff2_translation_parser'; +export {XtbTranslationParser} from './src/translate/translation_files/translation_parsers/xtb_translation_parser'; diff --git a/packages/localize/tools/src/extract/cli.ts b/packages/localize/tools/src/extract/cli.ts new file mode 100644 index 00000000000..1422a7238bc --- /dev/null +++ b/packages/localize/tools/src/extract/cli.ts @@ -0,0 +1,116 @@ +#!/usr/bin/env node +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {ConsoleLogger, LogLevel, NodeJSFileSystem, setFileSystem} from '@angular/compiler-cli/private/localize'; +import * as glob from 'glob'; +import * as yargs from 'yargs'; + +import {DiagnosticHandlingStrategy} from '../diagnostics'; +import {parseFormatOptions} from './translation_files/format_options'; +import {extractTranslations} from './index'; + +process.title = 'Angular Localization Message Extractor (localize-extract)'; +const args = process.argv.slice(2); +const options = + yargs + .option('l', { + alias: 'locale', + describe: 'The locale of the source being processed', + default: 'en', + type: 'string', + }) + .option('r', { + alias: 'root', + default: '.', + describe: 'The root path for other paths provided in these options.\n' + + 'This should either be absolute or relative to the current working directory.', + type: 'string', + }) + .option('s', { + alias: 'source', + required: true, + describe: + 'A glob pattern indicating what files to search for translations, e.g. `./dist/**/*.js`.\n' + + 'This should be relative to the root path.', + type: 'string', + }) + .option('f', { + alias: 'format', + required: true, + choices: + ['xmb', 'xlf', 'xlif', 'xliff', 'xlf2', 'xlif2', 'xliff2', 'json', 'legacy-migrate'], + describe: 'The format of the translation file.', + type: 'string', + }) + .option('formatOptions', { + describe: + 'Additional options to pass to the translation file serializer, in the form of JSON formatted key-value string pairs:\n' + + 'For example: `--formatOptions {"xml:space":"preserve"}.\n' + + 'The meaning of the options is specific to the format being serialized.', + type: 'string' + }) + .option('o', { + alias: 'outputPath', + required: true, + describe: + 'A path to where the translation file will be written. This should be relative to the root path.', + type: 'string', + }) + .option('loglevel', { + describe: 'The lowest severity logging message that should be output.', + choices: ['debug', 'info', 'warn', 'error'], + type: 'string', + }) + .option('useSourceMaps', { + type: 'boolean', + default: true, + describe: + 'Whether to generate source information in the output files by following source-map mappings found in the source files', + }) + .option('useLegacyIds', { + type: 'boolean', + default: true, + describe: + 'Whether to use the legacy id format for messages that were extracted from Angular templates.', + }) + .option('d', { + alias: 'duplicateMessageHandling', + describe: 'How to handle messages with the same id but different text.', + choices: ['error', 'warning', 'ignore'], + default: 'warning', + type: 'string', + }) + .strict() + .help() + .parse(args); + +const fileSystem = new NodeJSFileSystem(); +setFileSystem(fileSystem); + +const rootPath = options.r; +const sourceFilePaths = glob.sync(options.s, {cwd: rootPath, nodir: true}); +const logLevel = options.loglevel as (keyof typeof LogLevel) | undefined; +const logger = new ConsoleLogger(logLevel ? LogLevel[logLevel] : LogLevel.warn); +const duplicateMessageHandling = options.d as DiagnosticHandlingStrategy; +const formatOptions = parseFormatOptions(options.formatOptions); +const format = options.f; + +extractTranslations({ + rootPath, + sourceFilePaths, + sourceLocale: options.l, + format, + outputPath: options.o, + logger, + useSourceMaps: options.useSourceMaps, + useLegacyIds: format === 'legacy-migrate' || options.useLegacyIds, + duplicateMessageHandling, + formatOptions, + fileSystem, +}); diff --git a/packages/localize/tools/src/extract/main.ts b/packages/localize/tools/src/extract/index.ts similarity index 52% rename from packages/localize/tools/src/extract/main.ts rename to packages/localize/tools/src/extract/index.ts index 426595acdc7..a36b20e6729 100644 --- a/packages/localize/tools/src/extract/main.ts +++ b/packages/localize/tools/src/extract/index.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node /** * @license * Copyright Google LLC All Rights Reserved. @@ -6,126 +5,22 @@ * 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 {setFileSystem, NodeJSFileSystem, AbsoluteFsPath, FileSystem, PathManipulation, ConsoleLogger, Logger, LogLevel} from '@angular/compiler-cli/private/localize'; +import {AbsoluteFsPath, FileSystem, Logger, PathManipulation} from '@angular/compiler-cli/private/localize'; import {ɵParsedMessage} from '@angular/localize'; -import * as glob from 'glob'; -import * as yargs from 'yargs'; -import {Diagnostics, DiagnosticHandlingStrategy} from '../diagnostics'; +import {DiagnosticHandlingStrategy, Diagnostics} from '../diagnostics'; import {checkDuplicateMessages} from './duplicates'; import {MessageExtractor} from './extraction'; -import {TranslationSerializer} from './translation_files/translation_serializer'; import {ArbTranslationSerializer} from './translation_files/arb_translation_serializer'; +import {FormatOptions} from './translation_files/format_options'; import {SimpleJsonTranslationSerializer} from './translation_files/json_translation_serializer'; +import {LegacyMessageIdMigrationSerializer} from './translation_files/legacy_message_id_migration_serializer'; +import {TranslationSerializer} from './translation_files/translation_serializer'; import {Xliff1TranslationSerializer} from './translation_files/xliff1_translation_serializer'; import {Xliff2TranslationSerializer} from './translation_files/xliff2_translation_serializer'; import {XmbTranslationSerializer} from './translation_files/xmb_translation_serializer'; -import {FormatOptions, parseFormatOptions} from './translation_files/format_options'; -import {LegacyMessageIdMigrationSerializer} from './translation_files/legacy_message_id_migration_serializer'; -if (require.main === module) { - process.title = 'Angular Localization Message Extractor (localize-extract)'; - const args = process.argv.slice(2); - const options = - yargs - .option('l', { - alias: 'locale', - describe: 'The locale of the source being processed', - default: 'en', - type: 'string', - }) - .option('r', { - alias: 'root', - default: '.', - describe: 'The root path for other paths provided in these options.\n' + - 'This should either be absolute or relative to the current working directory.', - type: 'string', - }) - .option('s', { - alias: 'source', - required: true, - describe: - 'A glob pattern indicating what files to search for translations, e.g. `./dist/**/*.js`.\n' + - 'This should be relative to the root path.', - type: 'string', - }) - .option('f', { - alias: 'format', - required: true, - choices: [ - 'xmb', 'xlf', 'xlif', 'xliff', 'xlf2', 'xlif2', 'xliff2', 'json', 'legacy-migrate' - ], - describe: 'The format of the translation file.', - type: 'string', - }) - .option('formatOptions', { - describe: - 'Additional options to pass to the translation file serializer, in the form of JSON formatted key-value string pairs:\n' + - 'For example: `--formatOptions {"xml:space":"preserve"}.\n' + - 'The meaning of the options is specific to the format being serialized.', - type: 'string' - }) - .option('o', { - alias: 'outputPath', - required: true, - describe: - 'A path to where the translation file will be written. This should be relative to the root path.', - type: 'string', - }) - .option('loglevel', { - describe: 'The lowest severity logging message that should be output.', - choices: ['debug', 'info', 'warn', 'error'], - type: 'string', - }) - .option('useSourceMaps', { - type: 'boolean', - default: true, - describe: - 'Whether to generate source information in the output files by following source-map mappings found in the source files', - }) - .option('useLegacyIds', { - type: 'boolean', - default: true, - describe: - 'Whether to use the legacy id format for messages that were extracted from Angular templates.', - }) - .option('d', { - alias: 'duplicateMessageHandling', - describe: 'How to handle messages with the same id but different text.', - choices: ['error', 'warning', 'ignore'], - default: 'warning', - type: 'string', - }) - .strict() - .help() - .parse(args); - - const fileSystem = new NodeJSFileSystem(); - setFileSystem(fileSystem); - - const rootPath = options.r; - const sourceFilePaths = glob.sync(options.s, {cwd: rootPath, nodir: true}); - const logLevel = options.loglevel as (keyof typeof LogLevel) | undefined; - const logger = new ConsoleLogger(logLevel ? LogLevel[logLevel] : LogLevel.warn); - const duplicateMessageHandling = options.d as DiagnosticHandlingStrategy; - const formatOptions = parseFormatOptions(options.formatOptions); - const format = options.f; - - extractTranslations({ - rootPath, - sourceFilePaths, - sourceLocale: options.l, - format, - outputPath: options.o, - logger, - useSourceMaps: options.useSourceMaps, - useLegacyIds: format === 'legacy-migrate' || options.useLegacyIds, - duplicateMessageHandling, - formatOptions, - fileSystem, - }); -} export interface ExtractTranslationsOptions { /** diff --git a/packages/localize/tools/src/migrate/cli.ts b/packages/localize/tools/src/migrate/cli.ts new file mode 100644 index 00000000000..c0528662cab --- /dev/null +++ b/packages/localize/tools/src/migrate/cli.ts @@ -0,0 +1,51 @@ +#!/usr/bin/env node +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {ConsoleLogger, LogLevel, NodeJSFileSystem, setFileSystem} from '@angular/compiler-cli/private/localize'; +import * as glob from 'glob'; +import * as yargs from 'yargs'; +import {migrateFiles} from './index'; + +const args = process.argv.slice(2); +const options = + yargs + .option('r', { + alias: 'root', + default: '.', + describe: 'The root path for other paths provided in these options.\n' + + 'This should either be absolute or relative to the current working directory.', + type: 'string', + }) + .option('f', { + alias: 'files', + required: true, + describe: + 'A glob pattern indicating what files to migrate. This should be relative to the root path', + type: 'string', + }) + .option('m', { + alias: 'mapFile', + required: true, + describe: + 'Path to the migration mapping file generated by `localize-extract`. This should be relative to the root path.', + type: 'string', + }) + .strict() + .help() + .parse(args); + +const fs = new NodeJSFileSystem(); +setFileSystem(fs); + +const rootPath = options.r; +const translationFilePaths = glob.sync(options.f, {cwd: rootPath, nodir: true}); +const logger = new ConsoleLogger(LogLevel.warn); + +migrateFiles({rootPath, translationFilePaths, mappingFilePath: options.m, logger}); +process.exit(0); diff --git a/packages/localize/tools/src/migrate/main.ts b/packages/localize/tools/src/migrate/index.ts similarity index 51% rename from packages/localize/tools/src/migrate/main.ts rename to packages/localize/tools/src/migrate/index.ts index 8fa81cc14f4..c05cdef9d63 100644 --- a/packages/localize/tools/src/migrate/main.ts +++ b/packages/localize/tools/src/migrate/index.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node /** * @license * Copyright Google LLC All Rights Reserved. @@ -6,51 +5,10 @@ * 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 {getFileSystem, NodeJSFileSystem, setFileSystem, ConsoleLogger, Logger, LogLevel} from '@angular/compiler-cli/private/localize'; -import * as glob from 'glob'; -import * as yargs from 'yargs'; + +import {getFileSystem, Logger} from '@angular/compiler-cli/private/localize'; import {migrateFile, MigrationMapping} from './migrate'; -if (require.main === module) { - const args = process.argv.slice(2); - const options = - yargs - .option('r', { - alias: 'root', - default: '.', - describe: 'The root path for other paths provided in these options.\n' + - 'This should either be absolute or relative to the current working directory.', - type: 'string', - }) - .option('f', { - alias: 'files', - required: true, - describe: - 'A glob pattern indicating what files to migrate. This should be relative to the root path', - type: 'string', - }) - .option('m', { - alias: 'mapFile', - required: true, - describe: - 'Path to the migration mapping file generated by `localize-extract`. This should be relative to the root path.', - type: 'string', - }) - .strict() - .help() - .parse(args); - - const fs = new NodeJSFileSystem(); - setFileSystem(fs); - - const rootPath = options.r; - const translationFilePaths = glob.sync(options.f, {cwd: rootPath, nodir: true}); - const logger = new ConsoleLogger(LogLevel.warn); - - migrateFiles({rootPath, translationFilePaths, mappingFilePath: options.m, logger}); - process.exit(0); -} - export interface MigrateFilesOptions { /** * The base path for other paths provided in these options. diff --git a/packages/localize/tools/src/translate/cli.ts b/packages/localize/tools/src/translate/cli.ts new file mode 100644 index 00000000000..c1724552bd1 --- /dev/null +++ b/packages/localize/tools/src/translate/cli.ts @@ -0,0 +1,131 @@ +#!/usr/bin/env node +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {NodeJSFileSystem, setFileSystem} from '@angular/compiler-cli/private/localize'; +import * as glob from 'glob'; +import * as yargs from 'yargs'; + +import {DiagnosticHandlingStrategy, Diagnostics} from '../diagnostics'; +import {getOutputPathFn} from './output_path'; +import {translateFiles} from './index'; + +process.title = 'Angular Localization Message Translator (localize-translate)'; +const args = process.argv.slice(2); +const options = + yargs + .option('r', { + alias: 'root', + required: true, + describe: + 'The root path of the files to translate, either absolute or relative to the current working directory. E.g. `dist/en`.', + type: 'string', + }) + .option('s', { + alias: 'source', + required: true, + describe: + 'A glob pattern indicating what files to translate, relative to the `root` path. E.g. `bundles/**/*`.', + type: 'string', + }) + + .option('l', { + alias: 'source-locale', + describe: + 'The source locale of the application. If this is provided then a copy of the application will be created with no translation but just the `$localize` calls stripped out.', + type: 'string', + }) + + .option('t', { + alias: 'translations', + required: true, + array: true, + describe: + 'A list of paths to the translation files to load, either absolute or relative to the current working directory.\n' + + 'E.g. `-t src/locale/messages.en.xlf src/locale/messages.fr.xlf src/locale/messages.de.xlf`.\n' + + 'If you want to merge multiple translation files for each locale, then provide the list of files in an array.\n' + + 'Note that the arrays must be in double quotes if you include any whitespace within the array.\n' + + 'E.g. `-t "[src/locale/messages.en.xlf, src/locale/messages-2.en.xlf]" [src/locale/messages.fr.xlf,src/locale/messages-2.fr.xlf]`', + type: 'string', + }) + + .option('target-locales', { + array: true, + describe: + 'A list of target locales for the translation files, which will override any target locale parsed from the translation file.\n' + + 'E.g. "-t en fr de".', + type: 'string', + }) + + .option('o', { + alias: 'outputPath', + required: true, + describe: 'A output path pattern to where the translated files will be written.\n' + + 'The path must be either absolute or relative to the current working directory.\n' + + 'The marker `{{LOCALE}}` will be replaced with the target locale. E.g. `dist/{{LOCALE}}`.', + type: 'string', + }) + + .option('m', { + alias: 'missingTranslation', + describe: 'How to handle missing translations.', + choices: ['error', 'warning', 'ignore'], + default: 'warning', + type: 'string', + }) + + .option('d', { + alias: 'duplicateTranslation', + describe: 'How to handle duplicate translations.', + choices: ['error', 'warning', 'ignore'], + default: 'warning', + type: 'string', + }) + + .strict() + .help() + .parse(args); + +const fs = new NodeJSFileSystem(); +setFileSystem(fs); + +const sourceRootPath = options.r; +const sourceFilePaths = glob.sync(options.s, {cwd: sourceRootPath, nodir: true}); +const translationFilePaths: (string|string[])[] = convertArraysFromArgs(options.t); +const outputPathFn = getOutputPathFn(fs, fs.resolve(options.o)); +const diagnostics = new Diagnostics(); +const missingTranslation = options.m as DiagnosticHandlingStrategy; +const duplicateTranslation = options.d as DiagnosticHandlingStrategy; +const sourceLocale: string|undefined = options.l; +const translationFileLocales: string[] = options['target-locales'] || []; + +translateFiles({ + sourceRootPath, + sourceFilePaths, + translationFilePaths, + translationFileLocales, + outputPathFn, + diagnostics, + missingTranslation, + duplicateTranslation, + sourceLocale +}); + +diagnostics.messages.forEach(m => console.warn(`${m.type}: ${m.message}`)); +process.exit(diagnostics.hasErrors ? 1 : 0); + +/** + * Parse each of the given string `args` and convert it to an array if it is of the form + * `[abc, def, ghi]`, i.e. it is enclosed in square brackets with comma delimited items. + * @param args The string to potentially convert to arrays. + */ +function convertArraysFromArgs(args: string[]): (string|string[])[] { + return args.map( + arg => (arg.startsWith('[') && arg.endsWith(']')) ? + arg.slice(1, -1).split(',').map(arg => arg.trim()) : + arg); +} diff --git a/packages/localize/tools/src/translate/main.ts b/packages/localize/tools/src/translate/index.ts similarity index 50% rename from packages/localize/tools/src/translate/main.ts rename to packages/localize/tools/src/translate/index.ts index e216705525b..019cbfc7e50 100644 --- a/packages/localize/tools/src/translate/main.ts +++ b/packages/localize/tools/src/translate/index.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node /** * @license * Copyright Google LLC All Rights Reserved. @@ -6,13 +5,11 @@ * 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 {getFileSystem, NodeJSFileSystem, setFileSystem, relativeFrom} from '@angular/compiler-cli/private/localize'; -import * as glob from 'glob'; -import * as yargs from 'yargs'; +import {getFileSystem, relativeFrom} from '@angular/compiler-cli/private/localize'; import {DiagnosticHandlingStrategy, Diagnostics} from '../diagnostics'; import {AssetTranslationHandler} from './asset_files/asset_translation_handler'; -import {getOutputPathFn, OutputPathFn} from './output_path'; +import {OutputPathFn} from './output_path'; import {SourceFileTranslationHandler} from './source_files/source_file_translation_handler'; import {TranslationLoader} from './translation_files/translation_loader'; import {ArbTranslationParser} from './translation_files/translation_parsers/arb_translation_parser'; @@ -22,112 +19,6 @@ import {Xliff2TranslationParser} from './translation_files/translation_parsers/x import {XtbTranslationParser} from './translation_files/translation_parsers/xtb_translation_parser'; import {Translator} from './translator'; -if (require.main === module) { - process.title = 'Angular Localization Message Translator (localize-translate)'; - const args = process.argv.slice(2); - const options = - yargs - .option('r', { - alias: 'root', - required: true, - describe: - 'The root path of the files to translate, either absolute or relative to the current working directory. E.g. `dist/en`.', - type: 'string', - }) - .option('s', { - alias: 'source', - required: true, - describe: - 'A glob pattern indicating what files to translate, relative to the `root` path. E.g. `bundles/**/*`.', - type: 'string', - }) - - .option('l', { - alias: 'source-locale', - describe: - 'The source locale of the application. If this is provided then a copy of the application will be created with no translation but just the `$localize` calls stripped out.', - type: 'string', - }) - - .option('t', { - alias: 'translations', - required: true, - array: true, - describe: - 'A list of paths to the translation files to load, either absolute or relative to the current working directory.\n' + - 'E.g. `-t src/locale/messages.en.xlf src/locale/messages.fr.xlf src/locale/messages.de.xlf`.\n' + - 'If you want to merge multiple translation files for each locale, then provide the list of files in an array.\n' + - 'Note that the arrays must be in double quotes if you include any whitespace within the array.\n' + - 'E.g. `-t "[src/locale/messages.en.xlf, src/locale/messages-2.en.xlf]" [src/locale/messages.fr.xlf,src/locale/messages-2.fr.xlf]`', - type: 'string', - }) - - .option('target-locales', { - array: true, - describe: - 'A list of target locales for the translation files, which will override any target locale parsed from the translation file.\n' + - 'E.g. "-t en fr de".', - type: 'string', - }) - - .option('o', { - alias: 'outputPath', - required: true, - describe: 'A output path pattern to where the translated files will be written.\n' + - 'The path must be either absolute or relative to the current working directory.\n' + - 'The marker `{{LOCALE}}` will be replaced with the target locale. E.g. `dist/{{LOCALE}}`.', - type: 'string', - }) - - .option('m', { - alias: 'missingTranslation', - describe: 'How to handle missing translations.', - choices: ['error', 'warning', 'ignore'], - default: 'warning', - type: 'string', - }) - - .option('d', { - alias: 'duplicateTranslation', - describe: 'How to handle duplicate translations.', - choices: ['error', 'warning', 'ignore'], - default: 'warning', - type: 'string', - }) - - .strict() - .help() - .parse(args); - - const fs = new NodeJSFileSystem(); - setFileSystem(fs); - - const sourceRootPath = options.r; - const sourceFilePaths = glob.sync(options.s, {cwd: sourceRootPath, nodir: true}); - const translationFilePaths: (string|string[])[] = convertArraysFromArgs(options.t); - const outputPathFn = getOutputPathFn(fs, fs.resolve(options.o)); - const diagnostics = new Diagnostics(); - const missingTranslation = options.m as DiagnosticHandlingStrategy; - const duplicateTranslation = options.d as DiagnosticHandlingStrategy; - const sourceLocale: string|undefined = options.l; - const translationFileLocales: string[] = options['target-locales'] || []; - - translateFiles({ - sourceRootPath, - sourceFilePaths, - translationFilePaths, - translationFileLocales, - outputPathFn, - diagnostics, - missingTranslation, - duplicateTranslation, - sourceLocale - }); - - diagnostics.messages.forEach(m => console.warn(`${m.type}: ${m.message}`)); - process.exit(diagnostics.hasErrors ? 1 : 0); -} - export interface TranslateFilesOptions { /** * The root path of the files to translate, either absolute or relative to the current working @@ -235,15 +126,3 @@ export function translateFiles({ sourceFilePaths.map(relativeFrom), fs.resolve(sourceRootPath), outputPathFn, translations, sourceLocale); } - -/** - * Parse each of the given string `args` and convert it to an array if it is of the form - * `[abc, def, ghi]`, i.e. it is enclosed in square brackets with comma delimited items. - * @param args The string to potentially convert to arrays. - */ -function convertArraysFromArgs(args: string[]): (string|string[])[] { - return args.map( - arg => (arg.startsWith('[') && arg.endsWith(']')) ? - arg.slice(1, -1).split(',').map(arg => arg.trim()) : - arg); -} diff --git a/packages/localize/tools/src/translate/translation_files/translation_parsers/translation_parser.ts b/packages/localize/tools/src/translate/translation_files/translation_parsers/translation_parser.ts index 514ff7bdecf..51d21009d98 100644 --- a/packages/localize/tools/src/translate/translation_files/translation_parsers/translation_parser.ts +++ b/packages/localize/tools/src/translate/translation_files/translation_parsers/translation_parser.ts @@ -5,7 +5,7 @@ * 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 {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private'; +import {ɵMessageId, ɵParsedTranslation} from '@angular/localize'; import {Diagnostics} from '../../../diagnostics'; /** diff --git a/packages/localize/tools/test/BUILD.bazel b/packages/localize/tools/test/BUILD.bazel index 1e2d6d768c9..e575065a6f6 100644 --- a/packages/localize/tools/test/BUILD.bazel +++ b/packages/localize/tools/test/BUILD.bazel @@ -6,7 +6,7 @@ ts_library( srcs = glob( ["**/*.ts"], ), - visibility = ["//packages/localize/src/tools/test:__subpackages__"], + visibility = ["//packages/localize/tools/test:__subpackages__"], deps = [ "//packages:types", "//packages/compiler", @@ -14,9 +14,9 @@ ts_library( "//packages/compiler-cli/src/ngtsc/file_system/testing", "//packages/compiler-cli/src/ngtsc/logging/testing", "//packages/localize", - "//packages/localize/src/tools", - "//packages/localize/src/tools/test/helpers", "//packages/localize/src/utils", + "//packages/localize/tools", + "//packages/localize/tools/test/helpers", "@npm//@babel/core", "@npm//@babel/generator", "@npm//@babel/template", diff --git a/packages/localize/tools/test/extract/integration/BUILD.bazel b/packages/localize/tools/test/extract/integration/BUILD.bazel index 0fbc08c302e..1464fa8e72b 100644 --- a/packages/localize/tools/test/extract/integration/BUILD.bazel +++ b/packages/localize/tools/test/extract/integration/BUILD.bazel @@ -13,9 +13,9 @@ ts_library( "//packages/compiler-cli/src/ngtsc/logging", "//packages/compiler-cli/src/ngtsc/logging/testing", "//packages/compiler-cli/src/ngtsc/testing", - "//packages/localize/src/tools", - "//packages/localize/src/tools/test:test_lib", - "//packages/localize/src/tools/test/helpers", + "//packages/localize/tools", + "//packages/localize/tools/test:test_lib", + "//packages/localize/tools/test/helpers", ], ) @@ -23,9 +23,9 @@ jasmine_node_test( name = "integration", bootstrap = ["//tools/testing:node_no_angular_es5"], data = [ - "//packages/localize/src/tools/test/extract/integration/test_files", - "//packages/localize/src/tools/test/extract/integration/test_files:compile_es2015", - "//packages/localize/src/tools/test/extract/integration/test_files:compile_es5", + "//packages/localize/tools/test/extract/integration/test_files", + "//packages/localize/tools/test/extract/integration/test_files:compile_es2015", + "//packages/localize/tools/test/extract/integration/test_files:compile_es5", ], deps = [ ":test_lib", diff --git a/packages/localize/tools/test/extract/integration/main_spec.ts b/packages/localize/tools/test/extract/integration/main_spec.ts index bc1c45b60f7..c26a90b55c2 100644 --- a/packages/localize/tools/test/extract/integration/main_spec.ts +++ b/packages/localize/tools/test/extract/integration/main_spec.ts @@ -10,7 +10,7 @@ import {InvalidFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/src import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing'; import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing'; -import {extractTranslations} from '../../../src/extract/main'; +import {extractTranslations} from '../../../src/extract/index'; import {FormatOptions} from '../../../src/extract/translation_files/format_options'; import {runInNativeFileSystem} from '../../helpers'; import {toAttributes} from '../translation_files/utils'; @@ -398,7 +398,7 @@ runInNativeFileSystem(() => { ` `, // These source file paths are due to how Bazel TypeScript compilation source-maps // work - ` ../packages/localize/src/tools/test/extract/integration/test_files/src/a.ts`, + ` ../packages/localize/tools/test/extract/integration/test_files/src/a.ts`, ` 3,${ target === 'es2015' ? 7 : 5}`, ` `, @@ -406,7 +406,7 @@ runInNativeFileSystem(() => { ` `, ` Message in !`, ` `, - ` ../packages/localize/src/tools/test/extract/integration/test_files/src/b.ts`, + ` ../packages/localize/tools/test/extract/integration/test_files/src/b.ts`, ` 3`, ` `, ` `, diff --git a/packages/localize/tools/test/extract/integration/test_files/BUILD.bazel b/packages/localize/tools/test/extract/integration/test_files/BUILD.bazel index 8ed3f1c0281..5a89e435a17 100644 --- a/packages/localize/tools/test/extract/integration/test_files/BUILD.bazel +++ b/packages/localize/tools/test/extract/integration/test_files/BUILD.bazel @@ -1,7 +1,7 @@ load("@npm//typescript:index.bzl", "tsc") load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") -package(default_visibility = ["//packages/localize/src/tools/test/extract/integration:__pkg__"]) +package(default_visibility = ["//packages/localize/tools/test/extract/integration:__pkg__"]) tsc( name = "compile_es5", diff --git a/packages/localize/tools/test/helpers/BUILD.bazel b/packages/localize/tools/test/helpers/BUILD.bazel index a54218c7402..9f9ea16d7ef 100644 --- a/packages/localize/tools/test/helpers/BUILD.bazel +++ b/packages/localize/tools/test/helpers/BUILD.bazel @@ -6,7 +6,7 @@ ts_library( srcs = glob( ["**/*.ts"], ), - visibility = ["//packages/localize/src/tools/test:__subpackages__"], + visibility = ["//packages/localize/tools/test:__subpackages__"], deps = [ "//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system/testing", diff --git a/packages/localize/tools/test/migrate/integration/BUILD.bazel b/packages/localize/tools/test/migrate/integration/BUILD.bazel index 1b3e056feca..52b652f7544 100644 --- a/packages/localize/tools/test/migrate/integration/BUILD.bazel +++ b/packages/localize/tools/test/migrate/integration/BUILD.bazel @@ -14,9 +14,9 @@ ts_library( "//packages/compiler-cli/src/ngtsc/logging", "//packages/compiler-cli/src/ngtsc/logging/testing", "//packages/compiler-cli/src/ngtsc/testing", - "//packages/localize/src/tools", - "//packages/localize/src/tools/test:test_lib", - "//packages/localize/src/tools/test/helpers", + "//packages/localize/tools", + "//packages/localize/tools/test:test_lib", + "//packages/localize/tools/test/helpers", ], ) diff --git a/packages/localize/tools/test/migrate/integration/main_spec.ts b/packages/localize/tools/test/migrate/integration/main_spec.ts index 9972cd4df92..5c331a7bb92 100644 --- a/packages/localize/tools/test/migrate/integration/main_spec.ts +++ b/packages/localize/tools/test/migrate/integration/main_spec.ts @@ -8,7 +8,7 @@ import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing'; import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing'; -import {migrateFiles} from '../../../src/migrate/main'; +import {migrateFiles} from '../../../src/migrate/index'; import {runInNativeFileSystem} from '../../helpers'; runInNativeFileSystem(() => { diff --git a/packages/localize/tools/test/translate/integration/BUILD.bazel b/packages/localize/tools/test/translate/integration/BUILD.bazel index f2fd7a622f9..c0ac0e20e27 100644 --- a/packages/localize/tools/test/translate/integration/BUILD.bazel +++ b/packages/localize/tools/test/translate/integration/BUILD.bazel @@ -11,8 +11,8 @@ ts_library( "//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system/testing", "//packages/compiler-cli/src/ngtsc/testing", - "//packages/localize/src/tools", - "//packages/localize/src/tools/test/helpers", + "//packages/localize/tools", + "//packages/localize/tools/test/helpers", ], ) @@ -20,8 +20,8 @@ jasmine_node_test( name = "integration", bootstrap = ["//tools/testing:node_no_angular_es5"], data = [ - "//packages/localize/src/tools/test/translate/integration/locales", - "//packages/localize/src/tools/test/translate/integration/test_files", + "//packages/localize/tools/test/translate/integration/locales", + "//packages/localize/tools/test/translate/integration/test_files", ], deps = [ ":test_lib", diff --git a/packages/localize/tools/test/translate/integration/locales/BUILD.bazel b/packages/localize/tools/test/translate/integration/locales/BUILD.bazel index 65ab28b4dea..115206c324d 100644 --- a/packages/localize/tools/test/translate/integration/locales/BUILD.bazel +++ b/packages/localize/tools/test/translate/integration/locales/BUILD.bazel @@ -1,6 +1,6 @@ load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") -package(default_visibility = ["//packages/localize/src/tools/test/translate/integration:__pkg__"]) +package(default_visibility = ["//packages/localize/tools/test/translate/integration:__pkg__"]) # Use copy_to_bin since filegroup doesn't seem to work on Windows. copy_to_bin( diff --git a/packages/localize/tools/test/translate/integration/main_spec.ts b/packages/localize/tools/test/translate/integration/main_spec.ts index d0912838202..d5a13760369 100644 --- a/packages/localize/tools/test/translate/integration/main_spec.ts +++ b/packages/localize/tools/test/translate/integration/main_spec.ts @@ -10,7 +10,7 @@ import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing'; import {resolve as realResolve} from 'path'; import {Diagnostics} from '../../../src/diagnostics'; -import {translateFiles} from '../../../src/translate/main'; +import {translateFiles} from '../../../src/translate/index'; import {getOutputPathFn} from '../../../src/translate/output_path'; import {runInNativeFileSystem} from '../../helpers'; diff --git a/packages/localize/tools/test/translate/integration/test_files/BUILD.bazel b/packages/localize/tools/test/translate/integration/test_files/BUILD.bazel index 16ed5b318d2..0fd58048857 100644 --- a/packages/localize/tools/test/translate/integration/test_files/BUILD.bazel +++ b/packages/localize/tools/test/translate/integration/test_files/BUILD.bazel @@ -1,6 +1,6 @@ load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") -package(default_visibility = ["//packages/localize/src/tools/test/translate/integration:__pkg__"]) +package(default_visibility = ["//packages/localize/tools/test/translate/integration:__pkg__"]) # Use copy_to_bin since filegroup doesn't seem to work on Windows. copy_to_bin( diff --git a/packages/compiler-cli/extract_typings_rule.bzl b/tools/extract_typings_rule.bzl similarity index 95% rename from packages/compiler-cli/extract_typings_rule.bzl rename to tools/extract_typings_rule.bzl index 0cb80c1d509..ee07748fdb7 100644 --- a/packages/compiler-cli/extract_typings_rule.bzl +++ b/tools/extract_typings_rule.bzl @@ -14,6 +14,7 @@ def _extract_typings_rule_impl(ctx): return [DefaultInfo(files = depset(transitive = transitive_depsets))] +# TODO: Move into shared dev-infra package. extract_typings = rule( implementation = _extract_typings_rule_impl, doc = """Rule that extracts all transitive typings of dependencies""",