feat(i18n): pass translation config directly into ngc (#10622)

This commit is contained in:
Matias Niemelä
2016-08-12 14:45:36 -07:00
committed by vikerman
parent 04c6b2fe85
commit 6580d67875
20 changed files with 187 additions and 56 deletions
@@ -2,3 +2,4 @@
<form><input type="button" [(ngModel)]="ctxProp" name="first"/></form>
<my-comp *ngIf="ctxBool"></my-comp>
<div *ngFor="let x of ctxArr" [attr.value]="x"></div>
<p id="welcomeMessage"><!--i18n-->Welcome<!--/i18n--></p>
@@ -7,7 +7,7 @@
*/
import {NgFor, NgIf} from '@angular/common';
import {Component, Inject} from '@angular/core';
import {Component, Inject, LOCALE_ID, TRANSLATIONS_FORMAT} from '@angular/core';
import {FORM_DIRECTIVES} from '@angular/forms';
import {MultipleComponentsMyComp} from './a/multiple_components';
@@ -23,5 +23,9 @@ export class BasicComp {
ctxProp: string;
ctxBool: boolean;
ctxArr: any[] = [];
constructor() { this.ctxProp = 'initialValue'; }
constructor(
@Inject(LOCALE_ID) public localeId: string,
@Inject(TRANSLATIONS_FORMAT) public translationsFormat: string) {
this.ctxProp = 'initialValue';
}
}
@@ -0,0 +1,4 @@
<translationbundle>
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
</translationbundle>
@@ -44,24 +44,45 @@ describe('template codegen output', () => {
it('should support ngIf', () => {
var compFixture = createComponent(BasicComp);
var debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(2);
expect(debugElement.children.length).toBe(3);
compFixture.componentInstance.ctxBool = true;
compFixture.detectChanges();
expect(debugElement.children.length).toBe(3);
expect(debugElement.children.length).toBe(4);
expect(debugElement.children[2].injector.get(MultipleComponentsMyComp)).toBeTruthy();
});
it('should support ngFor', () => {
var compFixture = createComponent(BasicComp);
var debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(2);
expect(debugElement.children.length).toBe(3);
// test NgFor
compFixture.componentInstance.ctxArr = [1, 2];
compFixture.detectChanges();
expect(debugElement.children.length).toBe(4);
expect(debugElement.children.length).toBe(5);
expect(debugElement.children[2].attributes['value']).toBe('1');
expect(debugElement.children[3].attributes['value']).toBe('2');
});
describe('i18n', () => {
it('should inject the locale into the component', () => {
const compFixture = createComponent(BasicComp);
expect(compFixture.componentInstance.localeId).toEqual('fi');
});
it('should inject the translations format into the component', () => {
const compFixture = createComponent(BasicComp);
expect(compFixture.componentInstance.translationsFormat).toEqual('xtb');
});
it('should support i18n for content tags', () => {
const compFixture = createComponent(BasicComp);
const debugElement = compFixture.debugElement;
const containerElement = <any>debugElement.nativeElement;
const pElement = <any>containerElement.children.find((c: any) => c.name == 'p');
const pText = <string>pElement.children.map((c: any) => c.data).join('').trim();
expect(pText).toBe('tervetuloa');
});
});
});
@@ -39,6 +39,7 @@ describe('template i18n extraction output', () => {
]>
<messagebundle>
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
</messagebundle>`;
const xmbOutput = path.join(outDir, 'messages.xmb');
+16 -5
View File
@@ -12,7 +12,7 @@
*/
import * as compiler from '@angular/compiler';
import {ComponentMetadata, NgModuleMetadata, ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions} from '@angular/tsc-wrapped';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
@@ -22,6 +22,8 @@ import {GENERATED_FILES, ReflectorHost, ReflectorHostContext} from './reflector_
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
const nodeFs = require('fs');
const PREAMBLE = `/**
* This file is generated by the Angular 2 template compiler.
* Do not edit.
@@ -117,8 +119,9 @@ export class CodeGenerator {
}
static create(
options: AngularCompilerOptions, program: ts.Program, compilerHost: ts.CompilerHost,
reflectorHostContext?: ReflectorHostContext, xhr?: compiler.XHR): CodeGenerator {
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext,
xhr?: compiler.XHR): CodeGenerator {
xhr = xhr || {
get: (s: string) => {
if (!compilerHost.fileExists(s)) {
@@ -128,11 +131,18 @@ export class CodeGenerator {
return Promise.resolve(compilerHost.readFile(s));
}
};
const transFile = cliOptions.i18nFile;
const locale = cliOptions.locale;
let transContent: string = '';
if (transFile && locale) {
transContent = nodeFs.readFileSync(transFile, 'utf8');
}
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new compiler.i18n.HtmlParser(new HtmlParser());
const htmlParser = new compiler.i18n.HtmlParser(new HtmlParser(), transContent);
const config = new compiler.CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
@@ -151,7 +161,8 @@ export class CodeGenerator {
config, console, elementSchemaRegistry, staticReflector);
const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost));
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost), cliOptions.locale,
cliOptions.i18nFormat);
return new CodeGenerator(
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
@@ -28,8 +28,9 @@ import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabi
import {StaticReflector, StaticSymbol} from './static_reflector';
function extract(
ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
const extractor = Extractor.create(ngOptions, program, host);
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.i18n.MessageBundle> = extractor.extract();
return (bundlePromise).then(messageBundle => {
@@ -126,8 +127,8 @@ export class Extractor {
}
static create(
options: tsc.AngularCompilerOptions, program: ts.Program, compilerHost: ts.CompilerHost,
reflectorHostContext?: ReflectorHostContext): Extractor {
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext): Extractor {
const xhr: compiler.XHR = {
get: (s: string) => {
if (!compilerHost.fileExists(s)) {
@@ -163,7 +164,7 @@ export class Extractor {
config, console, elementSchemaRegistry, staticReflector);
const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost));
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost), null, null);
// TODO(vicb): implicit tags & attributes
let messageBundle = new compiler.i18n.MessageBundle(htmlParser, [], {});
@@ -183,11 +184,13 @@ interface FileMetadata {
// Entry point
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2));
tsc.main(args.p || args.project || '.', args.basePath, extract)
.then(exitCode => process.exit(exitCode))
.catch(e => {
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);
console.error('Extraction failed');
process.exit(1);
});
}
}
+10 -9
View File
@@ -17,18 +17,19 @@ import * as tsc from '@angular/tsc-wrapped';
import {CodeGenerator} from './codegen';
function codegen(
ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, program, host).codegen();
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
}
// CLI entry point
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2));
tsc.main(args.p || args.project || '.', args.basePath, codegen)
.then(exitCode => process.exit(exitCode))
.catch(e => {
console.error(e.stack);
console.error('Compilation failed');
process.exit(1);
});
const project = args.p || args.project || '.';
const cliOptions = new tsc.NgcCliOptions(args);
tsc.main(project, cliOptions, codegen).then(exitCode => process.exit(exitCode)).catch(e => {
console.error(e.stack);
console.error('Compilation failed');
process.exit(1);
});
}