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>
|
<div>
|
||||||
<h1>hello world</h1>
|
<h1 i18n>hello world</h1>
|
||||||
<a [routerLink]="['lazy']">lazy</a>
|
<a [routerLink]="['lazy']">lazy</a>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompiler
|
||||||
|
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main method.
|
* Main method.
|
||||||
* Standalone program that executes codegen using the ngtools API and tests that files were
|
* Standalone program that executes codegen using the ngtools API and tests that files were
|
||||||
|
@ -30,6 +29,7 @@ function main() {
|
||||||
|
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(() => codeGenTest())
|
.then(() => codeGenTest())
|
||||||
|
.then(() => i18nTest())
|
||||||
.then(() => lazyRoutesTest())
|
.then(() => lazyRoutesTest())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('All done!');
|
console.log('All done!');
|
||||||
|
@ -42,7 +42,6 @@ function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function codeGenTest() {
|
function codeGenTest() {
|
||||||
const basePath = path.join(__dirname, '../ngtools_src');
|
const basePath = path.join(__dirname, '../ngtools_src');
|
||||||
const project = path.join(basePath, 'tsconfig-build.json');
|
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() {
|
function lazyRoutesTest() {
|
||||||
const basePath = path.join(__dirname, '../ngtools_src');
|
const basePath = path.join(__dirname, '../ngtools_src');
|
||||||
|
|
|
@ -14,41 +14,15 @@
|
||||||
// Must be imported first, because angular2 decorators throws on load.
|
// Must be imported first, because angular2 decorators throws on load.
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
import * as compiler from '@angular/compiler';
|
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
import * as path from 'path';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Extractor} from './extractor';
|
import {Extractor} from './extractor';
|
||||||
|
|
||||||
function extract(
|
function extract(
|
||||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
||||||
program: ts.Program, host: ts.CompilerHost) {
|
program: ts.Program, host: ts.CompilerHost): Promise<void> {
|
||||||
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
|
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry point
|
// Entry point
|
||||||
|
|
|
@ -15,27 +15,70 @@ import 'reflect-metadata';
|
||||||
|
|
||||||
import * as compiler from '@angular/compiler';
|
import * as compiler from '@angular/compiler';
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
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 {
|
export class Extractor {
|
||||||
constructor(
|
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) {}
|
private program: ts.Program) {}
|
||||||
|
|
||||||
extract(): Promise<compiler.MessageBundle> {
|
extract(formatName: string): Promise<void> {
|
||||||
return this.ngExtractor.extract(this.program.getSourceFiles().map(
|
// Checks the format and returns the extension
|
||||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
|
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(
|
static create(
|
||||||
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
|
||||||
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor {
|
compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
|
||||||
if (!ngCompilerHost)
|
if (!ngCompilerHost) {
|
||||||
ngCompilerHost =
|
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||||
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost));
|
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);
|
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 {CodeGenerator} from './codegen';
|
||||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
|
import {Extractor} from './extractor';
|
||||||
import {listLazyRoutesOfModule} from './ngtools_impl';
|
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
|
|
||||||
|
|
||||||
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||||
basePath: string;
|
basePath: string;
|
||||||
compilerOptions: ts.CompilerOptions;
|
compilerOptions: ts.CompilerOptions;
|
||||||
|
@ -50,9 +50,18 @@ export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
|
||||||
// Every new property under this line should be optional.
|
// Every new property under this line should be optional.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
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
|
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
||||||
|
@ -94,7 +103,6 @@ export class NgTools_InternalApi_NG_2 {
|
||||||
return codeGenerator.codegen();
|
return codeGenerator.codegen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
* @private
|
* @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 {NgModuleResolver} from '../ng_module_resolver';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {PipeResolver} from '../pipe_resolver';
|
import {PipeResolver} from '../pipe_resolver';
|
||||||
import {Console} from '../private_import_core';
|
|
||||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||||
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue