Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/@angular/compiler-cli/integrationtest/src/basic.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div [attr.array]="[0]" [attr.map]="{a:1}">{{ctxProp}}</div>
<div [attr.array]="[0]" [attr.map]="{a:1}" title="translate me" i18n-title="meaning|desc">{{ctxProp}}</div>
<form><input type="button" [(ngModel)]="ctxProp"/></form>
<my-comp *ngIf="ctxBool"></my-comp>
<div *ngFor="let x of ctxArr" [attr.value]="x"></div>
<div *ngFor="let x of ctxArr" [attr.value]="x"></div>
26 changes: 26 additions & 0 deletions modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Only needed to satisfy the check in core/src/util/decorators.ts
// TODO(alexeagle): maybe remove that check?
require('reflect-metadata');

require('@angular/platform-server/src/parse5_adapter.js').Parse5DomAdapter.makeCurrent();
require('zone.js/dist/zone-node.js');
require('zone.js/dist/long-stack-trace-zone.js');
let serializer = require('@angular/compiler/src/i18n/xmb_serializer.js');

import * as fs from 'fs';
import * as path from 'path';

describe("template i18n extraction output", () => {
const outDir = '';

it("should extract i18n messages", () => {
const xmbOutput = path.join(outDir, 'messages.xmb');
expect(fs.existsSync(xmbOutput)).toBeTruthy();
const xmb = fs.readFileSync(xmbOutput, {encoding: 'utf-8'});
const res = serializer.deserializeXmb(xmb);
const keys = Object.keys(res.messages);
expect(keys.length).toEqual(1);
expect(res.errors.length).toEqual(0);
expect(res.messages[keys[0]][0].value).toEqual('translate me');
});
});
3 changes: 2 additions & 1 deletion modules/@angular/compiler-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"typings": "index.d.ts",
"bin": {
"ngc": "./src/main.js"
"ngc": "./src/main.js",
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "^0.1.0",
Expand Down
23 changes: 21 additions & 2 deletions modules/@angular/compiler-cli/src/compiler_private.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import {__compiler_private__ as _c} from '@angular/compiler';

export var AssetUrl: typeof _c.AssetUrl = _c.AssetUrl;
export type AssetUrl = _c.AssetUrl;
export var AssetUrl: typeof _c.AssetUrl = _c.AssetUrl;

export var ImportGenerator: typeof _c.ImportGenerator = _c.ImportGenerator;
export type ImportGenerator = _c.ImportGenerator;
export var ImportGenerator: typeof _c.ImportGenerator = _c.ImportGenerator;

export type CompileMetadataResolver = _c.CompileMetadataResolver;
export var CompileMetadataResolver: typeof _c.CompileMetadataResolver = _c.CompileMetadataResolver;

export type HtmlParser = _c.HtmlParser;
export var HtmlParser: typeof _c.HtmlParser = _c.HtmlParser;

export type I18nHtmlParser = _c.I18nHtmlParser;
export var I18nHtmlParser: typeof _c.I18nHtmlParser = _c.I18nHtmlParser;

export type MessageExtractor = _c.MessageExtractor;
export var MessageExtractor: typeof _c.MessageExtractor = _c.MessageExtractor;

export type ExtractionResult = _c.ExtractionResult;
export var ExtractionResult: typeof _c.ExtractionResult = _c.ExtractionResult;

export type Message = _c.Message;
export var Message: typeof _c.Message = _c.Message;

export var removeDuplicates: typeof _c.removeDuplicates = _c.removeDuplicates;
export var serializeXmb: typeof _c.serializeXmb = _c.serializeXmb;
export var deserializeXmb: typeof _c.deserializeXmb = _c.deserializeXmb;

export type ParseError = _c.ParseError;
export var ParseError: typeof _c.ParseError = _c.ParseError;

export type DirectiveNormalizer = _c.DirectiveNormalizer;
export var DirectiveNormalizer: typeof _c.DirectiveNormalizer = _c.DirectiveNormalizer;

Expand Down
190 changes: 190 additions & 0 deletions modules/@angular/compiler-cli/src/extract_i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/env node

/**
* Extract i18n messages from source code
*/

// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';

import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as compiler from '@angular/compiler';

import {StaticReflector} from './static_reflector';
import {
CompileMetadataResolver,
HtmlParser,
DirectiveNormalizer,
Lexer,
Parser,
TemplateParser,
DomElementSchemaRegistry,
StyleCompiler,
ViewCompiler,
TypeScriptEmitter,
MessageExtractor,
removeDuplicates,
ExtractionResult,
Message,
ParseError,
serializeXmb,
} from './compiler_private';

import {Parse5DomAdapter} from '@angular/platform-server';

import {NodeReflectorHost} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';


function extract(ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
return Extractor.create(ngOptions, program, host).extract();
}

const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;

class Extractor {
constructor(private options: tsc.AngularCompilerOptions,
private program: ts.Program, public host: ts.CompilerHost,
private staticReflector: StaticReflector, private resolver: CompileMetadataResolver,
private compiler: compiler.OfflineCompiler,
private reflectorHost: NodeReflectorHost, private _extractor: MessageExtractor) {}

private extractCmpMessages(metadatas: compiler.CompileDirectiveMetadata[]): Promise<ExtractionResult> {
if (!metadatas || !metadatas.length) {
return null;
}

const normalize = (metadata: compiler.CompileDirectiveMetadata) => {
const directiveType = metadata.type.runtime;
const directives = this.resolver.getViewDirectivesMetadata(directiveType);
return Promise.all(directives.map(d => this.compiler.normalizeDirectiveMetadata(d)))
.then(normalizedDirectives => {
const pipes = this.resolver.getViewPipesMetadata(directiveType);
return new compiler.NormalizedComponentWithViewDirectives(metadata,
normalizedDirectives, pipes);
});
};

return Promise
.all(metadatas.map(normalize))
.then((cmps: compiler.NormalizedComponentWithViewDirectives[]) => {
let messages: Message[] = [];
let errors: ParseError[] = [];
cmps.forEach(cmp => {
// TODO(vicb): url
let result = this._extractor.extract(cmp.component.template.template, "url");
errors = errors.concat(result.errors);
messages = messages.concat(result.messages);
});

// Extraction Result might contain duplicate messages at this point
return new ExtractionResult(messages, errors);
});
}

private readComponents(absSourcePath: string) {
const result: Promise<compiler.CompileDirectiveMetadata>[] = [];
const metadata = this.staticReflector.getModuleMetadata(absSourcePath);
if (!metadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
}

const symbols = Object.keys(metadata['metadata']);
if (!symbols || !symbols.length) {
return result;
}
for (const symbol of symbols) {
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
let directive: compiler.CompileDirectiveMetadata;
directive = this.resolver.maybeGetDirectiveMetadata(<any>staticType);

if (!directive || !directive.isComponent) {
continue;
}
result.push(this.compiler.normalizeDirectiveMetadata(directive));
}
return result;
}

extract(): Promise<any> {
Parse5DomAdapter.makeCurrent();

const promises = this.program.getSourceFiles()
.map(sf => sf.fileName)
.filter(f => !GENERATED_FILES.test(f))
.map((absSourcePath:string): Promise<any> =>
Promise
.all(this.readComponents(absSourcePath))
.then(metadatas => this.extractCmpMessages(metadatas))
.catch(e => console.error(e.stack))
);

let messages: Message[] = [];
let errors: ParseError[] = [];

return Promise.all(promises)
.then(extractionResults => {
extractionResults
.filter(result => !!result)
.forEach(result => {
messages = messages.concat(result.messages);
errors = errors.concat(result.errors);
});

if (errors.length) {
throw errors;
}

messages = removeDuplicates(messages);

let genPath = path.join(this.options.genDir, 'messages.xmb');
let msgBundle = serializeXmb(messages);

this.host.writeFile(genPath, msgBundle, false);
});
}

static create(options: tsc.AngularCompilerOptions, program: ts.Program,
compilerHost: ts.CompilerHost): Extractor {
const xhr: compiler.XHR = {get: (s: string) => Promise.resolve(compilerHost.readFile(s))};
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
const reflectorHost = new NodeReflectorHost(program, compilerHost, options);
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new HtmlParser();
const config = new compiler.CompilerConfig(true, true, true);
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
const parser = new Parser(new Lexer());
const tmplParser = new TemplateParser(parser, new DomElementSchemaRegistry(), htmlParser,
/*console*/ null, []);
const offlineCompiler = new compiler.OfflineCompiler(
normalizer, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config),
new TypeScriptEmitter(reflectorHost), xhr);
const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
new compiler.ViewResolver(staticReflector), null, null, staticReflector);

// TODO(vicb): handle implicit
const extractor = new MessageExtractor(htmlParser, parser, [], {});

return new Extractor(options, program, compilerHost, staticReflector, resolver,
offlineCompiler, reflectorHost, extractor);
}
}

// 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 => {
console.error(e.stack);
console.error("Compilation failed");
process.exit(1);
});
}

1 change: 1 addition & 0 deletions modules/@angular/compiler-cli/tsconfig-es5.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"files": [
"index.ts",
"src/main.ts",
"src/extract_i18n.ts",
"../../../node_modules/@types/node/index.d.ts",
"../../../node_modules/@types/jasmine/index.d.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts"
Expand Down
23 changes: 22 additions & 1 deletion modules/@angular/compiler/private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import * as selector from './src/selector';
import * as path_util from './src/output/path_util';
import * as metadata_resolver from './src/metadata_resolver';
import * as html_parser from './src/html_parser';
import * as i18n_html_parser from './src/i18n/i18n_html_parser';
import * as directive_normalizer from './src/directive_normalizer';
import * as lexer from './src/expression_parser/lexer';
import * as parse_util from './src/parse_util';
import * as parser from './src/expression_parser/parser';
import * as template_parser from './src/template_parser';
import * as dom_element_schema_registry from './src/schema/dom_element_schema_registry';
import * as style_compiler from './src/style_compiler';
import * as view_compiler from './src/view_compiler/view_compiler';
import * as ts_emitter from './src/output/ts_emitter';
import * as i18n_extractor from './src/i18n/message_extractor';
import * as i18n_message from './src/i18n/message';
import * as xmb_serializer from './src/i18n/xmb_serializer';
import * as parse_util from './src/parse_util';

export namespace __compiler_private__ {
export type SelectorMatcher = selector.SelectorMatcher;
Expand All @@ -31,6 +35,23 @@ export namespace __compiler_private__ {
export type HtmlParser = html_parser.HtmlParser;
export var HtmlParser = html_parser.HtmlParser;

export type I18nHtmlParser = i18n_html_parser.I18nHtmlParser;
export var I18nHtmlParser = i18n_html_parser.I18nHtmlParser;

export type ExtractionResult = i18n_extractor.ExtractionResult;
export var ExtractionResult = i18n_extractor.ExtractionResult;

export type Message = i18n_message.Message;
export var Message = i18n_message.Message;

export type MessageExtractor = i18n_extractor.MessageExtractor;
export var MessageExtractor = i18n_extractor.MessageExtractor;

export var removeDuplicates = i18n_extractor.removeDuplicates;

export var serializeXmb = xmb_serializer.serializeXmb;
export var deserializeXmb = xmb_serializer.deserializeXmb;

export type DirectiveNormalizer = directive_normalizer.DirectiveNormalizer;
export var DirectiveNormalizer = directive_normalizer.DirectiveNormalizer;

Expand Down
2 changes: 1 addition & 1 deletion modules/@angular/http/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export const HTTP_PROVIDERS: any[] = [
{provide: XSRFStrategy, useValue: new CookieXSRFStrategy()},
];

function httpFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
export function httpFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
return new Http(xhrBackend, requestOptions);
}

Expand Down
12 changes: 6 additions & 6 deletions modules/@angular/router/src/router_providers_common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@ export const ROUTER_PROVIDERS_COMMON: any[] = /*@ts2dart_const*/[
provide: Router,
useFactory: routerFactory,
deps: /*@ts2dart_const*/
[ApplicationRef, ComponentResolver, RouterUrlSerializer, RouterOutletMap, Location],
[ApplicationRef, ComponentResolver, RouterUrlSerializer, RouterOutletMap, Location],
},
/*@ts2dart_Provider*/ {provide: RouteSegment, useFactory: routeSegmentFactory, deps: [Router]}
];

function routerFactory(app: ApplicationRef, componentResolver: ComponentResolver,
urlSerializer: RouterUrlSerializer, routerOutletMap: RouterOutletMap,
location: Location): Router {
export function routerFactory(app: ApplicationRef, componentResolver: ComponentResolver,
urlSerializer: RouterUrlSerializer, routerOutletMap: RouterOutletMap,
location: Location): Router {
if (app.componentTypes.length == 0) {
throw new BaseException("Bootstrap at least one component before injecting Router.");
}
// TODO: vsavkin this should not be null
let router = new Router(null, app.componentTypes[0], componentResolver, urlSerializer,
routerOutletMap, location);
routerOutletMap, location);
app.registerDisposeListener(() => router.dispose());
return router;
}

function routeSegmentFactory(router: Router): RouteSegment {
export function routeSegmentFactory(router: Router): RouteSegment {
return router.routeTree.root;
}
10 changes: 5 additions & 5 deletions modules/@angular/upgrade/src/downgrade_ng2_adapter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
provide,
ChangeDetectorRef,
Injector,
OnChanges,
ComponentFactory,
ComponentRef,
SimpleChange,
SimpleChanges,
ReflectiveInjector
ReflectiveInjector,
EventEmitter
} from '@angular/core';
import {NG1_SCOPE} from './constants';
import {ComponentInfo} from './metadata';
Expand Down Expand Up @@ -145,11 +145,11 @@ export class DowngradeNg2ComponentAdapter {
if (assignExpr && !setter) {
throw new Error(`Expression '${expr}' is not assignable!`);
}
var emitter = this.component[output.prop];
var emitter = this.component[output.prop] as EventEmitter<any>;
if (emitter) {
emitter.subscribe({
next: assignExpr ? ((setter) => (value) => setter(this.scope, value))(setter) :
((getter) => (value) => getter(this.scope, {$event: value}))(getter)
next: assignExpr ? ((setter: any) => v => setter(this.scope, v))(setter) :
((getter: any) => v => getter(this.scope, {$event: v}))(getter)
});
} else {
throw new Error(`Missing emitter '${output.prop}' on component '${this.info.selector}'!`);
Expand Down
Loading