From c3169ae7d2a68405e061453d041934a7d3a7dc76 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Thu, 10 Oct 2019 21:39:00 +0200 Subject: [PATCH] New i18n schema (#15760) * feat(@angular-devkit/core): update schema to support new i18n options "projects": { "my-app": { "projectType": "application", "schematics": {}, "root": "", "i18n": { "sourceLocale": "en-US", "locales": { "fr": "src/locale/messages.fr.xlf" } }, "sourceRoot": "src", ... } } * feat(@angular-devkit/build-angular): add new i18n options to browser and server builders With this change we add `translateLocales` as new options for i18n in browser and server builders. We also deprecate the following options; * i18nLocale * i18nFormat * i18nFile * feat(@angular-devkit/build-angular): deprecate `i18nFormat` and `i18nLocale` options of `extract-i18n` builder Option `i18nFormat` has been deprecated in favor of `format` and `i18nLocale` option has been deprecated in favor of the `sourceLocale` sub option of the `i18n` project level option. * feat(@angular/cli): add alias of `i18n-extract` for `x18n` command * refactor: rename `translateLocales` to `localize` --- .../angular_devkit/core/src/_golden-api.d.ts | 6 ++ packages/angular/cli/commands/xi18n.json | 2 +- packages/angular/cli/lib/config/schema.json | 101 +++++++++++++++--- .../angular-cli-files/models/build-options.ts | 15 ++- .../build_angular/src/browser/schema.json | 30 +++++- .../build_angular/src/extract-i18n/index.ts | 22 +++- .../src/extract-i18n/schema.json | 17 ++- .../build_angular/src/server/schema.json | 30 +++++- .../test/extract-i18n/works_spec_large.ts | 2 +- .../workspace/workspace-schema.json | 41 ++++++- .../workspace/workspace-schema.ts | 15 +++ .../experimental/workspace/workspace_spec.ts | 8 +- .../legacy-cli/e2e/tests/i18n/extract-xmb.ts | 2 +- 13 files changed, 256 insertions(+), 35 deletions(-) diff --git a/etc/api/angular_devkit/core/src/_golden-api.d.ts b/etc/api/angular_devkit/core/src/_golden-api.d.ts index 6419d836f3bc..c9226acff2e1 100644 --- a/etc/api/angular_devkit/core/src/_golden-api.d.ts +++ b/etc/api/angular_devkit/core/src/_golden-api.d.ts @@ -1251,6 +1251,7 @@ export declare class WorkspaceNotYetLoadedException extends BaseException { export interface WorkspaceProject { architect?: WorkspaceTool; cli?: WorkspaceTool; + i18n?: WorkspaceProjectI18n; prefix: string; projectType: "application" | "library"; root: string; @@ -1259,6 +1260,11 @@ export interface WorkspaceProject { targets?: WorkspaceTool; } +export interface WorkspaceProjectI18n { + locales: Record; + sourceLocale?: string; +} + export interface WorkspaceSchema { $schema?: string; architect?: WorkspaceTool; diff --git a/packages/angular/cli/commands/xi18n.json b/packages/angular/cli/commands/xi18n.json index 082ae7ca1ba2..c48d958440fa 100644 --- a/packages/angular/cli/commands/xi18n.json +++ b/packages/angular/cli/commands/xi18n.json @@ -4,7 +4,7 @@ "description": "Extracts i18n messages from source code.", "$longDescription": "", - "$aliases": [], + "$aliases": ["i18n-extract"], "$scope": "in", "$type": "architect", "$impl": "./xi18n-impl#Xi18nCommand", diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 1958147b6079..1fde7a4b6541 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -342,6 +342,9 @@ "type": "string", "description": "Root of the project files." }, + "i18n": { + "$ref": "#/definitions/project/definitions/i18n" + }, "sourceRoot": { "type": "string", "description": "The root of the source files, assets and index.html file structure." @@ -398,6 +401,28 @@ "^[a-z]{1,3}-.*": {} }, "definitions": { + "i18n": { + "description": "Project i18n options", + "type": "object", + "properties": { + "sourceLocale": { + "type": "string", + "description": "Specifies the source language of the application.", + "default": "en-US" + }, + "locales": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-z]{2}(-[a-zA-Z]{2,})?$": { + "type": "string", + "description": "Localization file to use for i18n" + } + } + } + }, + "additionalProperties": false + }, "target": { "oneOf": [ { @@ -756,21 +781,26 @@ "description": "Log progress to the console while building.", "default": true }, + "localize": { + "$ref": "#/definitions/buildersOptions/localize" + }, + "i18nMissingTranslation": { + "$ref": "#/definitions/buildersOptions/missingTranslation" + }, "i18nFile": { "type": "string", - "description": "Localization file to use for i18n." + "description": "Localization file to use for i18n.", + "x-deprecated": "Deprecated since 9.0" }, "i18nFormat": { "type": "string", - "description": "Format of the localization file specified with --i18n-file." + "description": "Format of the localization file specified with --i18n-file.", + "x-deprecated": "Deprecated since 9.0" }, "i18nLocale": { "type": "string", - "description": "Locale to use for i18n." - }, - "i18nMissingTranslation": { - "type": "string", - "description": "How to handle missing translations for i18n." + "description": "Locale to use for i18n.", + "x-deprecated": "Deprecated since 9.0" }, "extractCss": { "type": "boolean", @@ -1281,10 +1311,24 @@ "type": "string", "description": "Target to extract from." }, + "format": { + "type": "string", + "description": "Output format for the generated file.", + "default": "xlf", + "enum": [ + "xmb", + "xlf", + "xlif", + "xliff", + "xlf2", + "xliff2" + ] + }, "i18nFormat": { "type": "string", "description": "Output format for the generated file.", "default": "xlf", + "x-deprecated": "Use 'format' option instead.", "enum": [ "xmb", "xlf", @@ -1296,7 +1340,8 @@ }, "i18nLocale": { "type": "string", - "description": "Specifies the source language of the application." + "description": "Specifies the source language of the application.", + "x-deprecated": "Use 'i18n' project level sub-option 'sourceLocale' instead." }, "progress": { "type": "boolean", @@ -1761,21 +1806,26 @@ "description": "Log progress to the console while building.", "default": true }, + "localize": { + "$ref": "#/definitions/buildersOptions/localize" + }, "i18nFile": { "type": "string", - "description": "Localization file to use for i18n." + "description": "Localization file to use for i18n.", + "x-deprecated": "Deprecated since 9.0" }, "i18nFormat": { "type": "string", - "description": "Format of the localization file specified with --i18n-file." + "description": "Format of the localization file specified with --i18n-file.", + "x-deprecated": "Deprecated since 9.0" }, "i18nLocale": { "type": "string", - "description": "Locale to use for i18n." + "description": "Locale to use for i18n.", + "x-deprecated": "Deprecated since 9.0" }, "i18nMissingTranslation": { - "type": "string", - "description": "How to handle missing translations for i18n." + "$ref": "#/definitions/buildersOptions/missingTranslation" }, "outputHashing": { "type": "string", @@ -1965,6 +2015,31 @@ "additionalProperties": false } } + }, + "buildersOptions": { + "missingTranslation": { + "type": "string", + "description": "How to handle missing translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "localize": { + "oneOf": [ + { + "type": "boolean", + "description": "Translate all locales." + }, + { + "type": "array", + "description": "List of locales ID's to translate.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^[a-z]{2}(-[a-zA-Z]{2,})?$" + } + } + ] + } } } } diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts index 0f92cacb4a83..d7c5a73b3500 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts @@ -15,6 +15,8 @@ import { AssetPatternClass, Budget, ExtraEntryPoint, + I18NMissingTranslation, + Localize, OptimizationClass, SourceMapClass, } from '../../browser/schema'; @@ -27,9 +29,9 @@ export interface BuildOptions { resourcesOutputPath?: string; aot?: boolean; sourceMap: SourceMapClass; - /** @deprecated use sourceMap instead */ + /** @deprecated since version 8. use sourceMap instead. */ vendorSourceMap?: boolean; - /** @deprecated */ + /** @deprecated since version 8 */ evalSourceMap?: boolean; vendorChunk?: boolean; commonChunk?: boolean; @@ -37,16 +39,19 @@ export interface BuildOptions { deployUrl?: string; verbose?: boolean; progress?: boolean; + /** @deprecated since version 9. Use 'locales' object in the project metadata instead.*/ i18nFile?: string; + /** @deprecated since version 9. No longer needed as the format will be determined automatically.*/ i18nFormat?: string; + /** @deprecated since version 9. Use 'localize' instead.*/ i18nLocale?: string; - i18nMissingTranslation?: string; + localize?: Localize; + i18nMissingTranslation?: I18NMissingTranslation; extractCss?: boolean; bundleDependencies?: 'none' | 'all'; watch?: boolean; outputHashing?: string; poll?: number; - app?: string; deleteOutputPath?: boolean; preserveSymlinks?: boolean; extractLicenses?: boolean; @@ -56,10 +61,12 @@ export interface BuildOptions { subresourceIntegrity?: boolean; serviceWorker?: boolean; webWorkerTsConfig?: string; + /** @deprecated since version 8 **/ skipAppShell?: boolean; statsJson: boolean; forkTypeChecker: boolean; profile?: boolean; + /** @deprecated since version 8 **/ es5BrowserSupport?: boolean; main: string; diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 5d37643ec1ed..e0665a4bc5c7 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -180,19 +180,41 @@ }, "i18nFile": { "type": "string", - "description": "Localization file to use for i18n." + "description": "Localization file to use for i18n.", + "x-deprecated": "Use 'locales' object in the project metadata instead." }, "i18nFormat": { "type": "string", - "description": "Format of the localization file specified with --i18n-file." + "description": "Format of the localization file specified with --i18n-file.", + "x-deprecated": "No longer needed as the format will be determined automatically." }, "i18nLocale": { "type": "string", - "description": "Locale to use for i18n." + "description": "Locale to use for i18n.", + "x-deprecated": "Use 'localize' instead." }, "i18nMissingTranslation": { "type": "string", - "description": "How to handle missing translations for i18n." + "description": "How to handle missing translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "localize": { + "oneOf": [ + { + "type": "boolean", + "description": "Translate all locales." + }, + { + "type": "array", + "description": "List of locales ID's to translate.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^[a-z]{2}(-[a-zA-Z]{2,})?$" + } + } + ] }, "extractCss": { "type": "boolean", diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/index.ts b/packages/angular_devkit/build_angular/src/extract-i18n/index.ts index ae4076b8b939..563ef3fcd83d 100644 --- a/packages/angular_devkit/build_angular/src/extract-i18n/index.ts +++ b/packages/angular_devkit/build_angular/src/extract-i18n/index.ts @@ -22,9 +22,10 @@ import { } from '../angular-cli-files/models/webpack-configs'; import { statsErrorsToString, statsWarningsToString } from '../angular-cli-files/utilities/stats'; import { Schema as BrowserBuilderOptions } from '../browser/schema'; +import { createI18nOptions } from '../utils/i18n-options'; import { assertCompatibleAngularVersion } from '../utils/version'; import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config'; -import { Schema as ExtractI18nBuilderOptions } from './schema'; +import { Format, Schema as ExtractI18nBuilderOptions } from './schema'; function getI18nOutfile(format: string | undefined) { switch (format) { @@ -58,13 +59,26 @@ async function execute(options: ExtractI18nBuilderOptions, context: BuilderConte await context.getBuilderNameForTarget(browserTarget), ); + if (options.i18nFormat !== Format.Xlf) { + options.format = options.i18nFormat; + } + // We need to determine the outFile name so that AngularCompiler can retrieve it. - let outFile = options.outFile || getI18nOutfile(options.i18nFormat); + let outFile = options.outFile || getI18nOutfile(options.format); if (options.outputPath) { // AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead. outFile = path.join(options.outputPath, outFile); } + const projectName = context.target && context.target.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + // target is verified in the above call + // tslint:disable-next-line: no-non-null-assertion + const metadata = await context.getProjectMetadata(context.target!); + const i18n = createI18nOptions(metadata); + const { config } = await generateBrowserWebpackConfigFromContext( { ...browserOptions, @@ -73,8 +87,8 @@ async function execute(options: ExtractI18nBuilderOptions, context: BuilderConte styles: false, }, buildOptimizer: false, - i18nLocale: options.i18nLocale, - i18nFormat: options.i18nFormat, + i18nLocale: options.i18nLocale || i18n.sourceLocale, + i18nFormat: options.format, i18nFile: outFile, aot: true, progress: options.progress, diff --git a/packages/angular_devkit/build_angular/src/extract-i18n/schema.json b/packages/angular_devkit/build_angular/src/extract-i18n/schema.json index a40af39254db..3212818ff580 100644 --- a/packages/angular_devkit/build_angular/src/extract-i18n/schema.json +++ b/packages/angular_devkit/build_angular/src/extract-i18n/schema.json @@ -8,10 +8,24 @@ "type": "string", "description": "Target to extract from." }, + "format": { + "type": "string", + "description": "Output format for the generated file.", + "default": "xlf", + "enum": [ + "xmb", + "xlf", + "xlif", + "xliff", + "xlf2", + "xliff2" + ] + }, "i18nFormat": { "type": "string", "description": "Output format for the generated file.", "default": "xlf", + "x-deprecated": "Use 'format' option instead.", "enum": [ "xmb", "xlf", @@ -23,7 +37,8 @@ }, "i18nLocale": { "type": "string", - "description": "Specifies the source language of the application." + "description": "Specifies the source language of the application.", + "x-deprecated": "Use 'i18n' project level sub-option 'sourceLocale' instead." }, "progress": { "type": "boolean", diff --git a/packages/angular_devkit/build_angular/src/server/schema.json b/packages/angular_devkit/build_angular/src/server/schema.json index b67f552e99c9..041654fa7990 100644 --- a/packages/angular_devkit/build_angular/src/server/schema.json +++ b/packages/angular_devkit/build_angular/src/server/schema.json @@ -142,19 +142,41 @@ }, "i18nFile": { "type": "string", - "description": "Localization file to use for i18n." + "description": "Localization file to use for i18n.", + "x-deprecated": "Use 'locales' object in the project metadata instead." }, "i18nFormat": { "type": "string", - "description": "Format of the localization file specified with --i18n-file." + "description": "Format of the localization file specified with --i18n-file.", + "x-deprecated": "No longer needed as the format will be determined automatically." }, "i18nLocale": { "type": "string", - "description": "Locale to use for i18n." + "description": "Locale to use for i18n.", + "x-deprecated": "Use 'localize' instead." }, "i18nMissingTranslation": { "type": "string", - "description": "How to handle missing translations for i18n." + "description": "How to handle missing translations for i18n.", + "enum": ["warning", "error", "ignore"], + "default": "warning" + }, + "localize": { + "oneOf": [ + { + "type": "boolean", + "description": "Translate all locales." + }, + { + "type": "array", + "description": "List of locales ID's to translate.", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^[a-z]{2}(-[a-zA-Z]{2,})?$" + } + } + ] }, "outputHashing": { "type": "string", diff --git a/packages/angular_devkit/build_angular/test/extract-i18n/works_spec_large.ts b/packages/angular_devkit/build_angular/test/extract-i18n/works_spec_large.ts index f22e39932655..a721e03e06f1 100644 --- a/packages/angular_devkit/build_angular/test/extract-i18n/works_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/extract-i18n/works_spec_large.ts @@ -122,7 +122,7 @@ describe('Extract i18n Target', () => { it('supports i18n format', async () => { host.appendToFile('src/app/app.component.html', '

i18n test

'); const extractionFile = join(normalize('src'), 'messages.xmb'); - const overrides = { i18nFormat: 'xmb' }; + const overrides = { format: 'xmb' }; const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides); diff --git a/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.json b/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.json index a030a6753d55..de7b5abec537 100644 --- a/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.json +++ b/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.json @@ -90,13 +90,31 @@ "targets": { "$ref": "#/definitions/tool", "default": {} + }, + "i18n": { + "$ref": "#/definitions/i18n", + "default": {} } }, "additionalProperties": false, "required": [ "root", "projectType" - ] + ], + "if": { + "properties": { + "projectType": { + "const": "library" + } + } + }, + "then": { + "not": { + "required": [ + "i18n" + ] + } + } }, "tool": { "type": "object", @@ -108,6 +126,27 @@ } }, "additionalProperties": true + }, + "i18n": { + "type": "object", + "properties": { + "sourceLocale": { + "type": "string", + "description": "Specifies the source language of the application.", + "default": "en-US" + }, + "locales": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-z]{2}(-[a-zA-Z]{2,})?$": { + "type": "string", + "description": "Localization file to use for i18n." + } + } + } + }, + "additionalProperties": false } } } diff --git a/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.ts b/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.ts index f10dbc18a688..84769ee96389 100644 --- a/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.ts +++ b/packages/angular_devkit/core/src/experimental/workspace/workspace-schema.ts @@ -82,6 +82,10 @@ export interface WorkspaceProject { * Tool options. */ targets?: WorkspaceTool; + /** + * i18n options. + */ + i18n?: WorkspaceProjectI18n; } /** * Architect options. @@ -93,3 +97,14 @@ export interface WorkspaceTool { $schema?: string; [k: string]: any; } + +export interface WorkspaceProjectI18n { + /** + * Specifies the source language of the application. + */ + sourceLocale?: string; + /** + * Localization file to use for i18n. + */ + locales: Record; +} diff --git a/packages/angular_devkit/core/src/experimental/workspace/workspace_spec.ts b/packages/angular_devkit/core/src/experimental/workspace/workspace_spec.ts index 98032715d4cf..a4e725990be9 100644 --- a/packages/angular_devkit/core/src/experimental/workspace/workspace_spec.ts +++ b/packages/angular_devkit/core/src/experimental/workspace/workspace_spec.ts @@ -17,7 +17,7 @@ import { Workspace, WorkspaceNotYetLoadedException, } from './workspace'; -import { WorkspaceProject, WorkspaceSchema, WorkspaceTool } from './workspace-schema'; +import { WorkspaceSchema, WorkspaceTool } from './workspace-schema'; describe('Workspace', () => { @@ -57,6 +57,12 @@ describe('Workspace', () => { projectType: 'application', prefix: 'app', cli: {}, + i18n: { + sourceLocale: 'en-US', + locales: { + 'fr': 'src/locale/messages.fr.xlf', + }, + }, schematics: { '@schematics/angular': { '*': { diff --git a/tests/legacy-cli/e2e/tests/i18n/extract-xmb.ts b/tests/legacy-cli/e2e/tests/i18n/extract-xmb.ts index e931ec8657ec..c5dd1c5a85f2 100644 --- a/tests/legacy-cli/e2e/tests/i18n/extract-xmb.ts +++ b/tests/legacy-cli/e2e/tests/i18n/extract-xmb.ts @@ -13,7 +13,7 @@ export default function() { .then(() => writeFile(join('src/app/i18n-test', 'i18n-test.component.html'), '

Hello world

'), ) - .then(() => ng('xi18n', '--i18n-format', 'xmb')) + .then(() => ng('xi18n', '--format', 'xmb')) .then(() => expectFileToExist('messages.xmb')) .then(() => expectFileToMatch('messages.xmb', /Hello world/)); }