Skip to content

Commit 3a340eb

Browse files
committed
feat(@angular/cli): support xi18n with Angular 5
1 parent a5fb971 commit 3a340eb

File tree

5 files changed

+125
-40
lines changed

5 files changed

+125
-40
lines changed

packages/@angular/cli/models/build-options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export interface BuildOptions {
1212
progress?: boolean;
1313
i18nFile?: string;
1414
i18nFormat?: string;
15+
i18nOutFile?: string;
16+
i18nOutFormat?: string;
1517
locale?: string;
1618
missingTranslation?: string;
1719
extractCss?: boolean;

packages/@angular/cli/models/webpack-configs/typescript.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import * as path from 'path';
22
import { stripIndent } from 'common-tags';
3-
import { AotPlugin, AngularCompilerPlugin } from '@ngtools/webpack';
3+
import {
4+
AotPlugin,
5+
AotPluginOptions,
6+
AngularCompilerPlugin,
7+
AngularCompilerPluginOptions,
8+
PLATFORM
9+
} from '@ngtools/webpack';
410
import { WebpackConfigOptions } from '../webpack-config';
511

612
const SilentError = require('silent-error');
@@ -67,22 +73,35 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
6773
};
6874
}
6975

70-
const pluginOptions = Object.assign({}, {
71-
mainPath: path.join(projectRoot, appConfig.root, appConfig.main),
72-
i18nFile: buildOptions.i18nFile,
73-
i18nFormat: buildOptions.i18nFormat,
74-
locale: buildOptions.locale,
75-
replaceExport: appConfig.platform === 'server',
76-
missingTranslation: buildOptions.missingTranslation,
77-
hostReplacementPaths,
78-
sourceMap: buildOptions.sourcemaps,
79-
// If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`.
80-
exclude: []
81-
}, options);
82-
83-
if (wco.buildOptions.experimentalAngularCompiler) {
76+
if (wco.buildOptions.experimentalAngularCompiler) {
77+
const pluginOptions: AngularCompilerPluginOptions = Object.assign({}, {
78+
mainPath: path.join(projectRoot, appConfig.root, appConfig.main),
79+
i18nInFile: buildOptions.i18nFile,
80+
i18nInFormat: buildOptions.i18nFormat,
81+
i18nOutFile: buildOptions.i18nOutFile,
82+
i18nOutFormat: buildOptions.i18nOutFormat,
83+
locale: buildOptions.locale,
84+
platform: appConfig.platform === 'server' ? PLATFORM.Server : PLATFORM.Browser,
85+
missingTranslation: buildOptions.missingTranslation,
86+
hostReplacementPaths,
87+
sourceMap: buildOptions.sourcemaps,
88+
// If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`.
89+
exclude: []
90+
}, options);
8491
return new AngularCompilerPlugin(pluginOptions);
8592
} else {
93+
const pluginOptions: AotPluginOptions = Object.assign({}, {
94+
mainPath: path.join(projectRoot, appConfig.root, appConfig.main),
95+
i18nFile: buildOptions.i18nFile,
96+
i18nFormat: buildOptions.i18nFormat,
97+
locale: buildOptions.locale,
98+
replaceExport: appConfig.platform === 'server',
99+
missingTranslation: buildOptions.missingTranslation,
100+
hostReplacementPaths,
101+
sourceMap: buildOptions.sourcemaps,
102+
// If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`.
103+
exclude: []
104+
}, options);
86105
return new AotPlugin(pluginOptions);
87106
}
88107
}

packages/@angular/cli/models/webpack-xi18n-config.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface XI18WebpackOptions {
1414
verbose?: boolean;
1515
progress?: boolean;
1616
app?: string;
17+
experimentalAngularCompiler?: boolean;
1718
}
1819
export class XI18nWebpackConfig extends NgCliWebpackConfig {
1920

@@ -25,24 +26,31 @@ export class XI18nWebpackConfig extends NgCliWebpackConfig {
2526
target: 'development',
2627
verbose: extractOptions.verbose,
2728
progress: extractOptions.progress,
28-
experimentalAngularCompiler: false,
29+
experimentalAngularCompiler: extractOptions.experimentalAngularCompiler,
30+
locale: extractOptions.locale,
31+
i18nOutFormat: extractOptions.i18nFormat,
32+
i18nOutFile: extractOptions.outFile,
33+
aot: extractOptions.experimentalAngularCompiler
2934
}, appConfig);
3035
super.buildConfig();
3136
}
3237

3338
public buildConfig() {
34-
const configPath = CliConfig.configFilePath();
35-
const projectRoot = path.dirname(configPath);
36-
37-
const extractI18nConfig =
38-
getWebpackExtractI18nConfig(projectRoot,
39-
this.appConfig,
40-
this.extractOptions.genDir,
41-
this.extractOptions.i18nFormat,
42-
this.extractOptions.locale,
43-
this.extractOptions.outFile);
44-
45-
this.config = webpackMerge([this.config, extractI18nConfig]);
39+
// The extra extraction config is only needed in Angular 2/4.
40+
if (!this.extractOptions.experimentalAngularCompiler) {
41+
const configPath = CliConfig.configFilePath();
42+
const projectRoot = path.dirname(configPath);
43+
44+
const extractI18nConfig =
45+
getWebpackExtractI18nConfig(projectRoot,
46+
this.appConfig,
47+
this.extractOptions.genDir,
48+
this.extractOptions.i18nFormat,
49+
this.extractOptions.locale,
50+
this.extractOptions.outFile);
51+
52+
this.config = webpackMerge([this.config, extractI18nConfig]);
53+
}
4654
return this.config;
4755
}
4856
}

packages/@angular/cli/tasks/extract-i18n.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { join } from 'path';
12
import * as webpack from 'webpack';
3+
import { AngularCompilerPlugin } from '@ngtools/webpack';
24
import { XI18nWebpackConfig } from '../models/webpack-xi18n-config';
35
import { getAppFromConfig } from '../utilities/app-utils';
46

@@ -9,16 +11,25 @@ const MemoryFS = require('memory-fs');
911
export const Extracti18nTask = Task.extend({
1012
run: function (runTaskOptions: any) {
1113
const appConfig = getAppFromConfig(runTaskOptions.app);
14+
const experimentalAngularCompiler = AngularCompilerPlugin.isSupported();
15+
16+
// We need to determine the outFile name so that AngularCompiler can retrieve it.
17+
let outFile = runTaskOptions.outFile || getI18nOutfile(runTaskOptions.i18nFormat);
18+
if (experimentalAngularCompiler && runTaskOptions.outputPath) {
19+
// AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead.
20+
outFile = join(runTaskOptions.outputPath, outFile);
21+
}
1222

1323
const config = new XI18nWebpackConfig({
1424
genDir: runTaskOptions.outputPath || appConfig.root,
1525
buildDir: '.tmp',
1626
i18nFormat: runTaskOptions.i18nFormat,
1727
locale: runTaskOptions.locale,
18-
outFile: runTaskOptions.outFile,
28+
outFile: outFile,
1929
verbose: runTaskOptions.verbose,
2030
progress: runTaskOptions.progress,
2131
app: runTaskOptions.app,
32+
experimentalAngularCompiler,
2233
}, appConfig).buildConfig();
2334

2435
const webpackCompiler = webpack(config);
@@ -47,3 +58,18 @@ export const Extracti18nTask = Task.extend({
4758
});
4859
}
4960
});
61+
62+
function getI18nOutfile(format: string) {
63+
switch (format) {
64+
case 'xmb':
65+
return 'messages.xmb';
66+
case 'xlf':
67+
case 'xlif':
68+
case 'xliff':
69+
case 'xlf2':
70+
case 'xliff2':
71+
return 'messages.xlf';
72+
default:
73+
throw new Error(`Unsupported format "${format}"`);
74+
}
75+
}

packages/@ngtools/webpack/src/angular_compiler_plugin.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,21 @@ export interface AngularCompilerPluginOptions {
5656
skipCodeGeneration?: boolean;
5757
hostOverrideFileSystem?: { [path: string]: string };
5858
hostReplacementPaths?: { [path: string]: string };
59-
i18nFile?: string;
60-
i18nFormat?: string;
59+
i18nInFile?: string;
60+
i18nInFormat?: string;
61+
i18nOutFile?: string;
62+
i18nOutFormat?: string;
6163
locale?: string;
6264
missingTranslation?: string;
63-
replaceExport?: boolean;
65+
platform?: PLATFORM;
6466

6567
// Use tsconfig to include path globs.
6668
exclude?: string | string[];
6769
include?: string[];
6870
compilerOptions?: ts.CompilerOptions;
6971
}
7072

71-
enum PLATFORM {
73+
export enum PLATFORM {
7274
Browser,
7375
Server
7476
}
@@ -230,11 +232,17 @@ export class AngularCompilerPlugin implements Tapable {
230232
}
231233

232234
// Process i18n options.
233-
if (options.hasOwnProperty('i18nFile')) {
234-
this._angularCompilerOptions.i18nInFile = options.i18nFile;
235+
if (options.hasOwnProperty('i18nInFile')) {
236+
this._angularCompilerOptions.i18nInFile = options.i18nInFile;
235237
}
236-
if (options.hasOwnProperty('i18nFormat')) {
237-
this._angularCompilerOptions.i18nInFormat = options.i18nFormat;
238+
if (options.hasOwnProperty('i18nInFormat')) {
239+
this._angularCompilerOptions.i18nInFormat = options.i18nInFormat;
240+
}
241+
if (options.hasOwnProperty('i18nOutFile')) {
242+
this._angularCompilerOptions.i18nOutFile = options.i18nOutFile;
243+
}
244+
if (options.hasOwnProperty('i18nOutFormat')) {
245+
this._angularCompilerOptions.i18nOutFormat = options.i18nOutFormat;
238246
}
239247
if (options.hasOwnProperty('locale') && options.locale) {
240248
this._angularCompilerOptions.i18nInLocale = this._validateLocale(options.locale);
@@ -273,8 +281,7 @@ export class AngularCompilerPlugin implements Tapable {
273281
}
274282
}
275283

276-
// TODO: consider really using platform names in the plugin options.
277-
this._platform = options.replaceExport ? PLATFORM.Server : PLATFORM.Browser;
284+
this._platform = options.platform || PLATFORM.Browser;
278285
timeEnd('AngularCompilerPlugin._setupOptions');
279286
}
280287

@@ -722,6 +729,24 @@ export class AngularCompilerPlugin implements Tapable {
722729
});
723730
}
724731

732+
writeI18nOutFile() {
733+
function _recursiveMkDir(p: string): Promise<void> {
734+
if (fs.existsSync(p)) {
735+
return Promise.resolve();
736+
} else {
737+
return _recursiveMkDir(path.dirname(p))
738+
.then(() => fs.mkdirSync(p));
739+
}
740+
}
741+
742+
// Write the extracted messages to disk.
743+
const i18nOutFilePath = path.resolve(this._basePath, this._angularCompilerOptions.i18nOutFile);
744+
const i18nOutFileContent = this._compilerHost.readFile(i18nOutFilePath);
745+
if (i18nOutFileContent) {
746+
_recursiveMkDir(path.dirname(i18nOutFilePath))
747+
.then(() => fs.writeFileSync(i18nOutFilePath, i18nOutFileContent));
748+
}
749+
}
725750

726751
getFile(fileName: string) {
727752
const outputFile = fileName.replace(/.ts$/, '.js');
@@ -792,8 +817,13 @@ export class AngularCompilerPlugin implements Tapable {
792817

793818
if (!hasErrors(allDiagnostics)) {
794819
time('AngularCompilerPlugin._emit.ng.emit');
795-
emitResult = angularProgram.emit({ emitFlags: EmitFlags.Default, customTransformers });
820+
const extractI18n = !!this._angularCompilerOptions.i18nOutFile;
821+
const emitFlags = extractI18n ? EmitFlags.I18nBundle : EmitFlags.Default;
822+
emitResult = angularProgram.emit({ emitFlags, customTransformers });
796823
allDiagnostics.push(...emitResult.diagnostics);
824+
if (extractI18n) {
825+
this.writeI18nOutFile();
826+
}
797827
timeEnd('AngularCompilerPlugin._emit.ng.emit');
798828
}
799829
}

0 commit comments

Comments
 (0)