diff --git a/modules/@angular/compiler-cli/integrationtest/src/basic.html b/modules/@angular/compiler-cli/integrationtest/src/basic.html
index 75cbeb7137..4d1aebaf95 100644
--- a/modules/@angular/compiler-cli/integrationtest/src/basic.html
+++ b/modules/@angular/compiler-cli/integrationtest/src/basic.html
@@ -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>
\ No newline at end of file
+<div *ngFor="let x of ctxArr" [attr.value]="x"></div>
diff --git a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
new file mode 100644
index 0000000000..23e9c10841
--- /dev/null
+++ b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
@@ -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');
+  });
+});
diff --git a/modules/@angular/compiler-cli/package.json b/modules/@angular/compiler-cli/package.json
index c85548334b..f942242171 100644
--- a/modules/@angular/compiler-cli/package.json
+++ b/modules/@angular/compiler-cli/package.json
@@ -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",
diff --git a/modules/@angular/compiler-cli/src/compiler_private.ts b/modules/@angular/compiler-cli/src/compiler_private.ts
index 7cea60bb00..1a82470bb1 100644
--- a/modules/@angular/compiler-cli/src/compiler_private.ts
+++ b/modules/@angular/compiler-cli/src/compiler_private.ts
@@ -1,10 +1,10 @@
 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;
@@ -12,6 +12,25 @@ export var CompileMetadataResolver: typeof _c.CompileMetadataResolver = _c.Compi
 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;
 
diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts
new file mode 100644
index 0000000000..4cdf9a1084
--- /dev/null
+++ b/modules/@angular/compiler-cli/src/extract_i18n.ts
@@ -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);
+    });
+}
+
diff --git a/modules/@angular/compiler-cli/tsconfig-es5.json b/modules/@angular/compiler-cli/tsconfig-es5.json
index cb01ddface..4a0ff6f020 100644
--- a/modules/@angular/compiler-cli/tsconfig-es5.json
+++ b/modules/@angular/compiler-cli/tsconfig-es5.json
@@ -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"
diff --git a/modules/@angular/compiler/private_export.ts b/modules/@angular/compiler/private_export.ts
index adb5074c37..6b5d414625 100644
--- a/modules/@angular/compiler/private_export.ts
+++ b/modules/@angular/compiler/private_export.ts
@@ -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;
@@ -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;
 
diff --git a/modules/@angular/http/http.ts b/modules/@angular/http/http.ts
index f9e9f46858..37e3831f3e 100644
--- a/modules/@angular/http/http.ts
+++ b/modules/@angular/http/http.ts
@@ -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);
 }
 
diff --git a/modules/@angular/router/src/router_providers_common.ts b/modules/@angular/router/src/router_providers_common.ts
index a25a53b42a..d90c56ddb4 100644
--- a/modules/@angular/router/src/router_providers_common.ts
+++ b/modules/@angular/router/src/router_providers_common.ts
@@ -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;
 }
diff --git a/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts b/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts
index 3d7833642b..180309efb7 100644
--- a/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts
+++ b/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts
@@ -1,5 +1,4 @@
 import {
-  provide,
   ChangeDetectorRef,
   Injector,
   OnChanges,
@@ -7,7 +6,8 @@ import {
   ComponentRef,
   SimpleChange,
   SimpleChanges,
-  ReflectiveInjector
+  ReflectiveInjector,
+  EventEmitter
 } from '@angular/core';
 import {NG1_SCOPE} from './constants';
 import {ComponentInfo} from './metadata';
@@ -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}'!`);
diff --git a/modules/playground/src/bootstrap.ts b/modules/playground/src/bootstrap.ts
index bcc91ef2a8..03460b3b4f 100644
--- a/modules/playground/src/bootstrap.ts
+++ b/modules/playground/src/bootstrap.ts
@@ -9,7 +9,7 @@ declare var System: any;
   writeScriptTag('/all/playground/vendor/system.src.js');
   writeScriptTag('/all/playground/vendor/Reflect.js');
   writeScriptTag('/all/playground/vendor/rxjs/bundles/Rx.js', 'playgroundBootstrap()');
-  global.playgroundBootstrap = playgroundBootstrap;
+  (<any>global).playgroundBootstrap = playgroundBootstrap;
 
   function playgroundBootstrap() {
     // check query param
diff --git a/scripts/ci-lite/offline_compiler_test.sh b/scripts/ci-lite/offline_compiler_test.sh
index e8ef3d1c76..666da5fa8b 100755
--- a/scripts/ci-lite/offline_compiler_test.sh
+++ b/scripts/ci-lite/offline_compiler_test.sh
@@ -32,6 +32,7 @@ cp -v package.json $TMP
 
   # Compile the compiler-cli integration tests
   ./node_modules/.bin/ngc
+  ./node_modules/.bin/ng-xi18n
 
   ./node_modules/.bin/jasmine init
   # Run compiler-cli integration tests in node