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""",