mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-19 20:52:06 +08:00
feat(@angular-devkit/build-angular): implement stable architect API for extract-i18n
This commit is contained in:
parent
c840fcb529
commit
a011863477
@ -21,6 +21,7 @@
|
||||
},
|
||||
"extract-i18n": {
|
||||
"class": "./src/extract-i18n",
|
||||
"implementation": "./src/extract-i18n/index2",
|
||||
"schema": "./src/extract-i18n/schema.json",
|
||||
"description": "Extract i18n strings from a browser app."
|
||||
},
|
||||
|
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {
|
||||
BuilderContext,
|
||||
createBuilder,
|
||||
targetFromTargetString,
|
||||
} from '@angular-devkit/architect/src/index2';
|
||||
import { runWebpack } from '@angular-devkit/build-webpack/src/webpack/index2';
|
||||
import { JsonObject } from '@angular-devkit/core';
|
||||
import * as path from 'path';
|
||||
import * as webpack from 'webpack';
|
||||
import {
|
||||
getAotConfig,
|
||||
getCommonConfig,
|
||||
getStatsConfig,
|
||||
getStylesConfig,
|
||||
} from '../angular-cli-files/models/webpack-configs';
|
||||
import { Schema as BrowserBuilderOptions } from '../browser/schema';
|
||||
import { generateBrowserWebpackConfigFromContext } from '../utils/webpack-browser-config';
|
||||
import { Schema as ExtractI18nBuilderOptions } from './schema';
|
||||
|
||||
function getI18nOutfile(format: string | undefined) {
|
||||
switch (format) {
|
||||
case 'xmb':
|
||||
return 'messages.xmb';
|
||||
case 'xlf':
|
||||
case 'xlif':
|
||||
case 'xliff':
|
||||
case 'xlf2':
|
||||
case 'xliff2':
|
||||
return 'messages.xlf';
|
||||
default:
|
||||
throw new Error(`Unsupported format "${format}"`);
|
||||
}
|
||||
}
|
||||
|
||||
class InMemoryOutputPlugin {
|
||||
apply(compiler: webpack.Compiler): void {
|
||||
// tslint:disable-next-line:no-any
|
||||
compiler.outputFileSystem = new (webpack as any).MemoryOutputFileSystem();
|
||||
}
|
||||
}
|
||||
|
||||
export async function execute(options: ExtractI18nBuilderOptions, context: BuilderContext) {
|
||||
const browserTarget = targetFromTargetString(options.browserTarget);
|
||||
const browserOptions = await context.validateOptions<JsonObject & BrowserBuilderOptions>(
|
||||
await context.getTargetOptions(browserTarget),
|
||||
await context.getBuilderNameForTarget(browserTarget),
|
||||
);
|
||||
|
||||
// We need to determine the outFile name so that AngularCompiler can retrieve it.
|
||||
let outFile = options.outFile || getI18nOutfile(options.i18nFormat);
|
||||
if (options.outputPath) {
|
||||
// AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead.
|
||||
outFile = path.join(options.outputPath, outFile);
|
||||
}
|
||||
|
||||
const { config } = await generateBrowserWebpackConfigFromContext(
|
||||
{
|
||||
...browserOptions,
|
||||
optimization: {
|
||||
scripts: false,
|
||||
styles: false,
|
||||
},
|
||||
i18nLocale: options.i18nLocale,
|
||||
i18nFormat: options.i18nFormat,
|
||||
i18nFile: outFile,
|
||||
aot: true,
|
||||
progress: options.progress,
|
||||
assets: [],
|
||||
scripts: [],
|
||||
styles: [],
|
||||
deleteOutputPath: false,
|
||||
},
|
||||
context,
|
||||
wco => [
|
||||
{ plugins: [new InMemoryOutputPlugin()] },
|
||||
getCommonConfig(wco),
|
||||
getAotConfig(wco, true),
|
||||
getStylesConfig(wco),
|
||||
getStatsConfig(wco),
|
||||
],
|
||||
);
|
||||
|
||||
return runWebpack(config, context).toPromise();
|
||||
}
|
||||
|
||||
export default createBuilder<JsonObject & ExtractI18nBuilderOptions>(execute);
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"title": "Extract i18n Target",
|
||||
"description": "Extract i18n target options for Build Facade.",
|
||||
"type": "object",
|
||||
|
@ -5,105 +5,119 @@
|
||||
* 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 { DefaultTimeout, TestLogger, runTargetSpec } from '@angular-devkit/architect/testing';
|
||||
import { Architect } from '@angular-devkit/architect/src/index2';
|
||||
import { TestLogger } from '@angular-devkit/architect/testing';
|
||||
import { join, normalize, virtualFs } from '@angular-devkit/core';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { extractI18nTargetSpec, host } from '../utils';
|
||||
import { createArchitect, extractI18nTargetSpec, host } from '../utils';
|
||||
|
||||
|
||||
describe('Extract i18n Target', () => {
|
||||
const extractionFile = join(normalize('src'), 'messages.xlf');
|
||||
let architect: Architect;
|
||||
|
||||
beforeEach(done => host.initialize().toPromise().then(done, done.fail));
|
||||
afterEach(done => host.restore().toPromise().then(done, done.fail));
|
||||
beforeEach(async () => {
|
||||
await host.initialize().toPromise();
|
||||
architect = (await createArchitect(host.root())).architect;
|
||||
});
|
||||
|
||||
it('works', (done) => {
|
||||
afterEach(() => host.restore().toPromise());
|
||||
|
||||
it('works', async () => {
|
||||
host.appendToFile('src/app/app.component.html', '<p i18n>i18n test</p>');
|
||||
|
||||
runTargetSpec(host, extractI18nTargetSpec).pipe(
|
||||
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
|
||||
tap(() => {
|
||||
expect(host.scopedSync().exists((extractionFile))).toBe(true);
|
||||
expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)))
|
||||
.toMatch(/i18n test/);
|
||||
}),
|
||||
).toPromise().then(done, done.fail);
|
||||
const run = await architect.scheduleTarget(extractI18nTargetSpec);
|
||||
|
||||
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true }));
|
||||
|
||||
await run.stop();
|
||||
|
||||
const exists = host.scopedSync().exists(extractionFile);
|
||||
expect(exists).toBe(true);
|
||||
|
||||
if (exists) {
|
||||
const content = virtualFs.fileBufferToString(host.scopedSync().read(extractionFile));
|
||||
expect(content).toContain('i18n test');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
it('shows errors', (done) => {
|
||||
it('shows errors', async () => {
|
||||
const logger = new TestLogger('i18n-errors');
|
||||
host.appendToFile('src/app/app.component.html',
|
||||
'<p i18n>Hello world <span i18n>inner</span></p>');
|
||||
|
||||
runTargetSpec(host, extractI18nTargetSpec, {}, DefaultTimeout, logger).pipe(
|
||||
tap((buildEvent) => {
|
||||
expect(buildEvent.success).toBe(false);
|
||||
const run = await architect.scheduleTarget(extractI18nTargetSpec, undefined, { logger });
|
||||
|
||||
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false }));
|
||||
|
||||
await run.stop();
|
||||
|
||||
const msg = 'Could not mark an element as translatable inside a translatable section';
|
||||
expect(logger.includes(msg)).toBe(true);
|
||||
}),
|
||||
).toPromise().then(done, done.fail);
|
||||
}, 30000);
|
||||
|
||||
it('supports locale', (done) => {
|
||||
it('supports locale', async () => {
|
||||
host.appendToFile('src/app/app.component.html', '<p i18n>i18n test</p>');
|
||||
const overrides = { i18nLocale: 'fr' };
|
||||
|
||||
runTargetSpec(host, extractI18nTargetSpec, overrides).pipe(
|
||||
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
|
||||
tap(() => {
|
||||
const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides);
|
||||
|
||||
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true }));
|
||||
|
||||
await run.stop();
|
||||
|
||||
expect(host.scopedSync().exists((extractionFile))).toBe(true);
|
||||
expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)))
|
||||
.toContain('source-language="fr"');
|
||||
}),
|
||||
).toPromise().then(done, done.fail);
|
||||
}, 30000);
|
||||
|
||||
it('supports out file', (done) => {
|
||||
it('supports out file', async () => {
|
||||
host.appendToFile('src/app/app.component.html', '<p i18n>i18n test</p>');
|
||||
const outFile = 'messages.fr.xlf';
|
||||
const extractionFile = join(normalize('src'), outFile);
|
||||
const overrides = { outFile };
|
||||
|
||||
runTargetSpec(host, extractI18nTargetSpec, overrides).pipe(
|
||||
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
|
||||
tap(() => {
|
||||
const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides);
|
||||
|
||||
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true }));
|
||||
|
||||
await run.stop();
|
||||
|
||||
expect(host.scopedSync().exists(extractionFile)).toBe(true);
|
||||
expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)))
|
||||
.toMatch(/i18n test/);
|
||||
}),
|
||||
).toPromise().then(done, done.fail);
|
||||
}, 30000);
|
||||
|
||||
it('supports output path', (done) => {
|
||||
it('supports output path', async () => {
|
||||
host.appendToFile('src/app/app.component.html', '<p i18n>i18n test</p>');
|
||||
// Note: this folder will not be created automatically. It must exist beforehand.
|
||||
const outputPath = 'app';
|
||||
const extractionFile = join(normalize('src'), outputPath, 'messages.xlf');
|
||||
const overrides = { outputPath };
|
||||
|
||||
runTargetSpec(host, extractI18nTargetSpec, overrides).pipe(
|
||||
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
|
||||
tap(() => {
|
||||
const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides);
|
||||
|
||||
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true }));
|
||||
|
||||
await run.stop();
|
||||
|
||||
expect(host.scopedSync().exists(extractionFile)).toBe(true);
|
||||
expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)))
|
||||
.toMatch(/i18n test/);
|
||||
}),
|
||||
).toPromise().then(done, done.fail);
|
||||
}, 30000);
|
||||
|
||||
it('supports i18n format', (done) => {
|
||||
it('supports i18n format', async () => {
|
||||
host.appendToFile('src/app/app.component.html', '<p i18n>i18n test</p>');
|
||||
const extractionFile = join(normalize('src'), 'messages.xmb');
|
||||
const overrides = { i18nFormat: 'xmb' };
|
||||
|
||||
runTargetSpec(host, extractI18nTargetSpec, overrides).pipe(
|
||||
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
|
||||
tap(() => {
|
||||
const run = await architect.scheduleTarget(extractI18nTargetSpec, overrides);
|
||||
|
||||
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: true }));
|
||||
|
||||
await run.stop();
|
||||
|
||||
expect(host.scopedSync().exists(extractionFile)).toBe(true);
|
||||
expect(virtualFs.fileBufferToString(host.scopedSync().read(extractionFile)))
|
||||
.toMatch(/i18n test/);
|
||||
}),
|
||||
).toPromise().then(done, done.fail);
|
||||
}, 30000);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user