feat(compiler-cli): private i18n API for the CLI (#13536)
Also change the Extractor API to align with the Codegen API (internal APIs)
This commit is contained in:
parent
0e3981afc1
commit
6b65fc1286
|
@ -1,5 +1,5 @@
|
|||
<div>
|
||||
<h1>hello world</h1>
|
||||
<h1 i18n>hello world</h1>
|
||||
<a [routerLink]="['lazy']">lazy</a>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,6 @@ import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompiler
|
|||
|
||||
const glob = require('glob');
|
||||
|
||||
|
||||
/**
|
||||
* Main method.
|
||||
* Standalone program that executes codegen using the ngtools API and tests that files were
|
||||
|
@ -30,6 +29,7 @@ function main() {
|
|||
|
||||
Promise.resolve()
|
||||
.then(() => codeGenTest())
|
||||
.then(() => i18nTest())
|
||||
.then(() => lazyRoutesTest())
|
||||
.then(() => {
|
||||
console.log('All done!');
|
||||
|
@ -42,7 +42,6 @@ function main() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
function codeGenTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
|
@ -109,6 +108,67 @@ function codeGenTest() {
|
|||
});
|
||||
}
|
||||
|
||||
function i18nTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
const readResources: string[] = [];
|
||||
const wroteFiles: string[] = [];
|
||||
|
||||
const config = tsc.readConfiguration(project, basePath);
|
||||
const hostContext = new NodeCompilerHostContext();
|
||||
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
|
||||
const host: ts.CompilerHost = Object.assign(
|
||||
{}, delegateHost,
|
||||
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
|
||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||
|
||||
config.ngOptions.basePath = basePath;
|
||||
|
||||
console.log(`>>> running i18n extraction for ${project}`);
|
||||
return __NGTOOLS_PRIVATE_API_2
|
||||
.extractI18n({
|
||||
basePath,
|
||||
compilerOptions: config.parsed.options, program, host,
|
||||
angularCompilerOptions: config.ngOptions,
|
||||
i18nFormat: 'xlf',
|
||||
readResource: (fileName: string) => {
|
||||
readResources.push(fileName);
|
||||
return hostContext.readResource(fileName);
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`>>> i18n extraction done, asserting read and wrote files`);
|
||||
|
||||
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
||||
|
||||
assert(wroteFiles.length == 1, `Expected a single message bundle file.`);
|
||||
|
||||
assert(
|
||||
wroteFiles[0].endsWith('/ngtools_src/messages.xlf'),
|
||||
`Expected the bundle file to be "message.xlf".`);
|
||||
|
||||
allFiles.forEach((fileName: string) => {
|
||||
// Skip tsconfig.
|
||||
if (fileName.match(/tsconfig-build.json$/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that file was read.
|
||||
if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
|
||||
assert(
|
||||
readResources.indexOf(fileName) != -1,
|
||||
`Expected resource "${fileName}" to be read.`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.error(e.stack);
|
||||
console.error('Extraction failed');
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
function lazyRoutesTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
|
|
|
@ -14,41 +14,15 @@
|
|||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Extractor} from './extractor';
|
||||
|
||||
function extract(
|
||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
||||
program: ts.Program, host: ts.CompilerHost) {
|
||||
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
|
||||
|
||||
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
|
||||
|
||||
return (bundlePromise).then(messageBundle => {
|
||||
let ext: string;
|
||||
let serializer: compiler.Serializer;
|
||||
const format = (cliOptions.i18nFormat || 'xlf').toLowerCase();
|
||||
|
||||
switch (format) {
|
||||
case 'xmb':
|
||||
ext = 'xmb';
|
||||
serializer = new compiler.Xmb();
|
||||
break;
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
ext = 'xlf';
|
||||
serializer = new compiler.Xliff();
|
||||
break;
|
||||
}
|
||||
|
||||
const dstPath = path.join(ngOptions.genDir, `messages.${ext}`);
|
||||
host.writeFile(dstPath, messageBundle.write(serializer), false);
|
||||
});
|
||||
program: ts.Program, host: ts.CompilerHost): Promise<void> {
|
||||
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
|
|
|
@ -15,27 +15,70 @@ import 'reflect-metadata';
|
|||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost,
|
||||
private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
|
||||
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
|
||||
private program: ts.Program) {}
|
||||
|
||||
extract(): Promise<compiler.MessageBundle> {
|
||||
return this.ngExtractor.extract(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
|
||||
extract(formatName: string): Promise<void> {
|
||||
// Checks the format and returns the extension
|
||||
const ext = this.getExtension(formatName);
|
||||
|
||||
const files = this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
|
||||
|
||||
const promiseBundle = this.ngExtractor.extract(files);
|
||||
|
||||
return promiseBundle.then(bundle => {
|
||||
const content = this.serialize(bundle, ext);
|
||||
const dstPath = path.join(this.options.genDir, `messages.${ext}`);
|
||||
this.host.writeFile(dstPath, content, false);
|
||||
});
|
||||
}
|
||||
|
||||
serialize(bundle: compiler.MessageBundle, ext: string): string {
|
||||
let serializer: compiler.Serializer;
|
||||
|
||||
switch (ext) {
|
||||
case 'xmb':
|
||||
serializer = new compiler.Xmb();
|
||||
break;
|
||||
case 'xlf':
|
||||
default:
|
||||
serializer = new compiler.Xliff();
|
||||
}
|
||||
|
||||
return bundle.write(serializer);
|
||||
}
|
||||
|
||||
getExtension(formatName: string): string {
|
||||
const format = (formatName || 'xlf').toLowerCase();
|
||||
|
||||
if (format === 'xmb') return 'xmb';
|
||||
if (format === 'xlf' || format === 'xlif') return 'xlf';
|
||||
|
||||
throw new Error('Unsupported format "${formatName}"');
|
||||
}
|
||||
|
||||
static create(
|
||||
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
||||
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor {
|
||||
if (!ngCompilerHost)
|
||||
ngCompilerHost =
|
||||
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost));
|
||||
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
|
||||
compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
|
||||
if (!ngCompilerHost) {
|
||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
||||
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
||||
new CompilerHost(program, options, context);
|
||||
}
|
||||
|
||||
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost);
|
||||
return new Extractor(ngExtractor, ngCompilerHost, program);
|
||||
|
||||
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ import * as ts from 'typescript';
|
|||
|
||||
import {CodeGenerator} from './codegen';
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {Extractor} from './extractor';
|
||||
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
|
||||
|
||||
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||
basePath: string;
|
||||
compilerOptions: ts.CompilerOptions;
|
||||
|
@ -50,9 +50,18 @@ export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
|
|||
// Every new property under this line should be optional.
|
||||
}
|
||||
|
||||
|
||||
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
||||
|
||||
export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
|
||||
basePath: string;
|
||||
compilerOptions: ts.CompilerOptions;
|
||||
program: ts.Program;
|
||||
host: ts.CompilerHost;
|
||||
angularCompilerOptions: AngularCompilerOptions;
|
||||
i18nFormat: string;
|
||||
readResource: (fileName: string) => Promise<string>;
|
||||
// Every new property under this line should be optional.
|
||||
}
|
||||
|
||||
/**
|
||||
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
||||
|
@ -94,7 +103,6 @@ export class NgTools_InternalApi_NG_2 {
|
|||
return codeGenerator.codegen();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
|
@ -124,4 +132,19 @@ export class NgTools_InternalApi_NG_2 {
|
|||
},
|
||||
{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise<void> {
|
||||
const hostContext: CompilerHostContext =
|
||||
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
||||
|
||||
// Create the i18n extractor.
|
||||
const extractor = Extractor.create(
|
||||
options.angularCompilerOptions, options.program, options.host, hostContext);
|
||||
|
||||
return extractor.extract(options.i18nFormat);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
|||
import {NgModuleResolver} from '../ng_module_resolver';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {PipeResolver} from '../pipe_resolver';
|
||||
import {Console} from '../private_import_core';
|
||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
||||
|
||||
|
|
Loading…
Reference in New Issue