angular-cn/modules/@angular/compiler-cli/src/extract_i18n.ts

214 lines
8.7 KiB
TypeScript
Raw Normal View History

2016-06-01 17:58:11 -04:00
#!/usr/bin/env node
/**
* @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
*/
2016-06-01 17:58:11 -04:00
/**
* Extract i18n messages from source code
2016-07-21 16:56:58 -04:00
*
* TODO(vicb): factorize code with the CodeGenerator
2016-06-01 17:58:11 -04:00
*/
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
2016-07-21 16:56:58 -04:00
import * as compiler from '@angular/compiler';
import {ComponentMetadata, NgModuleMetadata, ViewEncapsulation} from '@angular/core';
import * as path from 'path';
2016-06-01 17:58:11 -04:00
import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
2016-07-21 16:56:58 -04:00
import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleCompiler, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler, ParseError} from './compiler_private';
import {Console} from './core_private';
2016-07-21 16:56:58 -04:00
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
2016-06-01 17:58:11 -04:00
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
2016-07-21 16:56:58 -04:00
import {StaticReflector, StaticSymbol} from './static_reflector';
2016-06-01 17:58:11 -04:00
function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) {
2016-08-13 01:13:54 -04:00
const htmlParser = new compiler.i18n.HtmlParser(new HtmlParser());
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, htmlParser);
2016-07-21 16:56:58 -04:00
const bundlePromise: Promise<compiler.i18n.MessageBundle> = extractor.extract();
return (bundlePromise).then(messageBundle => {
2016-08-13 01:13:54 -04:00
let ext: string;
let serializer: compiler.i18n.Serializer;
const format = (cliOptions.i18nFormat || 'xlf').toLowerCase();
switch (format) {
case 'xmb':
ext = 'xmb';
serializer = new compiler.i18n.Xmb();
break;
case 'xliff':
case 'xlf':
default:
ext = 'xlf';
serializer = new compiler.i18n.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
break;
}
const dstPath = path.join(ngOptions.genDir, `messages.${ext}`);
2016-07-21 16:56:58 -04:00
host.writeFile(dstPath, messageBundle.write(serializer), false);
});
2016-06-01 17:58:11 -04:00
}
2016-07-21 16:56:58 -04:00
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
2016-07-21 16:56:58 -04:00
export class Extractor {
constructor(
2016-07-21 16:56:58 -04:00
private program: ts.Program, public host: ts.CompilerHost,
private staticReflector: StaticReflector, private messageBundle: compiler.i18n.MessageBundle,
private reflectorHost: ReflectorHost, private metadataResolver: CompileMetadataResolver,
private directiveNormalizer: DirectiveNormalizer,
private compiler: compiler.OfflineCompiler) {}
private readFileMetadata(absSourcePath: string): FileMetadata {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath};
if (!moduleMetadata) {
2016-06-01 17:58:11 -04:00
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
}
2016-07-21 16:56:58 -04:00
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
2016-06-01 17:58:11 -04:00
if (!symbols || !symbols.length) {
return result;
}
for (const symbol of symbols) {
2016-07-21 16:56:58 -04:00
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
2016-06-01 17:58:11 -04:00
}
2016-07-21 16:56:58 -04:00
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
const annotations = this.staticReflector.annotations(staticType);
annotations.forEach((annotation) => {
if (annotation instanceof NgModuleMetadata) {
result.ngModules.push(staticType);
} else if (annotation instanceof ComponentMetadata) {
result.components.push(staticType);
}
});
2016-06-01 17:58:11 -04:00
}
return result;
}
2016-07-21 16:56:58 -04:00
extract(): Promise<compiler.i18n.MessageBundle> {
const filePaths =
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
ngModules.push(...fileMeta.ngModules);
return ngModules;
}, <StaticSymbol[]>[]);
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
const errors: ParseError[] = [];
let bundlePromise =
Promise
.all(fileMetas.map((fileMeta) => {
const url = fileMeta.fileUrl;
return Promise.all(fileMeta.components.map(compType => {
const compMeta = this.metadataResolver.getDirectiveMetadata(<any>compType);
const ngModule = analyzedNgModules.ngModuleByComponent.get(compType);
if (!ngModule) {
throw new Error(
`Cannot determine the module for component ${compMeta.type.name}!`);
}
return Promise
.all([compMeta, ...ngModule.transitiveModule.directives].map(
dirMeta =>
this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
.then((normalizedCompWithDirectives) => {
const compMeta = normalizedCompWithDirectives[0];
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, url, interpolationConfig));
});
}));
}))
.then(_ => this.messageBundle)
.catch((e) => { console.error(e.stack); });
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
2016-06-01 17:58:11 -04:00
2016-07-21 16:56:58 -04:00
return bundlePromise;
2016-06-01 17:58:11 -04:00
}
static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
2016-08-13 01:13:54 -04:00
compilerHost: ts.CompilerHost, htmlParser: compiler.i18n.HtmlParser,
reflectorHostContext?: ReflectorHostContext): Extractor {
const resourceLoader: compiler.ResourceLoader = {
get: (s: string) => {
if (!compilerHost.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(compilerHost.readFile(s));
}
};
2016-07-21 16:56:58 -04:00
2016-06-01 17:58:11 -04:00
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
2016-07-21 16:56:58 -04:00
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
2016-06-01 17:58:11 -04:00
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
2016-07-21 16:56:58 -04:00
const config = new compiler.CompilerConfig({
2016-07-21 16:56:58 -04:00
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
});
2016-07-21 16:56:58 -04:00
const normalizer = new DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
2016-06-24 17:31:35 -04:00
const expressionParser = new Parser(new Lexer());
const elementSchemaRegistry = new DomElementSchemaRegistry();
const console = new Console();
2016-07-21 16:56:58 -04:00
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
2016-06-01 17:58:11 -04:00
const resolver = new CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
config, console, elementSchemaRegistry, staticReflector);
2016-07-21 16:56:58 -04:00
const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost), null, null);
2016-06-01 17:58:11 -04:00
2016-07-21 16:56:58 -04:00
// TODO(vicb): implicit tags & attributes
let messageBundle = new compiler.i18n.MessageBundle(htmlParser, [], {});
2016-06-01 17:58:11 -04:00
return new Extractor(
2016-07-21 16:56:58 -04:00
program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, normalizer,
offlineCompiler);
2016-06-01 17:58:11 -04:00
}
}
2016-07-21 16:56:58 -04:00
interface FileMetadata {
fileUrl: string;
components: StaticSymbol[];
ngModules: StaticSymbol[];
}
2016-06-01 17:58:11 -04:00
// Entry point
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2));
const project = args.p || args.project || '.';
const cliOptions = new tsc.I18nExtractionCliOptions(args);
tsc.main(project, cliOptions, extract)
.then((exitCode: any) => process.exit(exitCode))
.catch((e: any) => {
console.error(e.stack);
2016-07-21 16:56:58 -04:00
console.error('Extraction failed');
process.exit(1);
});
}