X Tutup
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
8 changes: 8 additions & 0 deletions modules/angular2/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @module
* @description
* Entry point to i18n
*/
export * from './src/i18n/message';
export * from './src/i18n/xmb_serializer';
export * from './src/i18n/message_extractor';
1 change: 1 addition & 0 deletions modules/angular2/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies:
protobuf: '^0.5.0'
source_span: '^1.0.0'
stack_trace: '^1.1.1'
build: '>=0.0.1'
dev_dependencies:
transformer_test: '^0.2.0'
guinness: '^0.1.18'
Expand Down
6 changes: 5 additions & 1 deletion modules/angular2/src/facade/lang.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
library angular.core.facade.lang;

export 'dart:core' show Type, RegExp, print, DateTime;
export 'dart:core' show Type, RegExp, print, DateTime, Uri;
import 'dart:math' as math;
import 'dart:convert' as convert;
import 'dart:async' show Future;
Expand Down Expand Up @@ -372,3 +372,7 @@ num bitWiseAnd(List values) {
var val = values.reduce((num a, num b) => (a as int) & (b as int));
return val as num;
}

String escape(String s) {
return Uri.encodeComponent(s);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that importing this was expensive. Check with @yjbanov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just importing, no. If it's only used in the compiler and tree-shaken off before going to production, it won't do anything bad. If pulled into production, it will eat ~15kb as it implements the full Uri spec.

}
5 changes: 5 additions & 0 deletions modules/angular2/src/facade/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface BrowserNodeGlobal {
clearTimeout: Function;
setInterval: Function;
clearInterval: Function;
encodeURI: Function;
}

// TODO(jteplitz602): Load WorkerGlobalScope from lib.webworker.d.ts file #3492
Expand Down Expand Up @@ -481,3 +482,7 @@ export function bitWiseOr(values: number[]): number {
export function bitWiseAnd(values: number[]): number {
return values.reduce((a, b) => { return a & b; });
}

export function escape(s: string): string {
return _global.encodeURI(s);
}
21 changes: 21 additions & 0 deletions modules/angular2/src/i18n/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {isPresent, escape} from 'angular2/src/facade/lang';

/**
* A message extracted from a template.
*
* The identity of a message is comprised of `content` and `meaning`.
*
* `description` is additional information provided to the translator.
*/
export class Message {
constructor(public content: string, public meaning: string, public description: string) {}
}

/**
* Computes the id of a message
*/
export function id(m: Message): string {
let meaning = isPresent(m.meaning) ? m.meaning : "";
let content = isPresent(m.content) ? m.content : "";
return escape(`$ng|${meaning}|${content}`);
}
17 changes: 3 additions & 14 deletions modules/angular2/src/i18n/message_extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,11 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
import {Interpolation} from 'angular2/src/core/change_detection/parser/ast';
import {Message, id} from './message';

const I18N_ATTR = "i18n";
const I18N_ATTR_PREFIX = "i18n-";

/**
* A message extracted from a template.
*
* The identity of a message is comprised of `content` and `meaning`.
*
* `description` is additional information provided to the translator.
*/
export class Message {
constructor(public content: string, public meaning: string, public description: string) {}
}

/**
* All messages extracted from a template.
*/
Expand Down Expand Up @@ -56,9 +46,8 @@ export class I18nExtractionError extends ParseError {
export function removeDuplicates(messages: Message[]): Message[] {
let uniq: {[key: string]: Message} = {};
messages.forEach(m => {
let key = `$ng__${m.meaning}__|${m.content}`;
if (!StringMapWrapper.contains(uniq, key)) {
uniq[key] = m;
if (!StringMapWrapper.contains(uniq, id(m))) {
uniq[id(m)] = m;
}
});
return StringMapWrapper.values(uniq);
Expand Down
12 changes: 12 additions & 0 deletions modules/angular2/src/i18n/xmb_serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {isPresent} from 'angular2/src/facade/lang';
import {Message, id} from './message';

export function serialize(messages: Message[]): string {
let ms = messages.map((m) => _serializeMessage(m)).join("");
return `<message-bundle>${ms}</message-bundle>`;
}

function _serializeMessage(m: Message): string {
let desc = isPresent(m.description) ? ` desc='${m.description}'` : "";
return `<msg id='${id(m)}'${desc}>${m.content}</msg>`;
}
3 changes: 2 additions & 1 deletion modules/angular2/test/i18n/message_extractor_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
} from 'angular2/testing_internal';

import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {MessageExtractor, Message, removeDuplicates} from 'angular2/src/i18n/message_extractor';
import {MessageExtractor, removeDuplicates} from 'angular2/src/i18n/message_extractor';
import {Message} from 'angular2/src/i18n/message';
import {Parser} from 'angular2/src/core/change_detection/parser/parser';
import {Lexer} from 'angular2/src/core/change_detection/parser/lexer';

Expand Down
27 changes: 27 additions & 0 deletions modules/angular2/test/i18n/message_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xdescribe,
xit
} from 'angular2/testing_internal';

import {Message, id} from 'angular2/src/i18n/message';

export function main() {
ddescribe('Message', () => {
describe("id", () => {
it("should return a different id for messages with and without the meaning", () => {
let m1 = new Message("content", "meaning", null);
let m2 = new Message("content", null, null);
expect(id(m1)).toEqual(id(m1));
expect(id(m1)).not.toEqual(id(m2));
});
});
});
}
35 changes: 35 additions & 0 deletions modules/angular2/test/i18n/xmb_serializer_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xdescribe,
xit
} from 'angular2/testing_internal';

import {Message, id} from 'angular2/src/i18n/message';
import {serialize} from 'angular2/src/i18n/xmb_serializer';

export function main() {
describe('Xmb Serialization', () => {
it("should return an empty message bundle for an empty list of messages",
() => { expect(serialize([])).toEqual("<message-bundle></message-bundle>"); });

it("should serialize messages without desc", () => {
let m = new Message("content", "meaning", null);
let expected = `<message-bundle><msg id='${id(m)}'>content</msg></message-bundle>`;
expect(serialize([m])).toEqual(expected);
});

it("should serialize messages with desc", () => {
let m = new Message("content", "meaning", "description");
let expected =
`<message-bundle><msg id='${id(m)}' desc='description'>content</msg></message-bundle>`;
expect(serialize([m])).toEqual(expected);
});
});
}
2 changes: 1 addition & 1 deletion modules/benchmarks_external/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: benchmarks_external
environment:
sdk: '>=1.4.0'
dependencies:
angular: '>=1.0.0 <2.0.0'
angular: '>=1.1.2+2 <2.0.0'
browser: '>=0.10.0 <0.11.0'
dev_dependencies:
angular2:
Expand Down
119 changes: 119 additions & 0 deletions modules_dart/transform/lib/extract_messages.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'package:build/build.dart';
import 'package:analyzer/src/generated/element.dart';
import 'src/transform/common/url_resolver.dart';
import 'dart:async';
import 'package:angular2/i18n.dart';
import 'package:angular2/src/core/change_detection/parser/parser.dart';
import 'package:angular2/src/core/change_detection/parser/lexer.dart';
import 'package:angular2/src/core/reflection/reflector.dart';
import 'package:angular2/src/core/reflection/reflection_capabilities.dart';
import 'package:angular2/src/compiler/html_parser.dart';

/**
* An command-line utility extracting i18n messages from an application.
*
* For instance, the following command will extract all the messages from the 'my-app-package' package, where
* index.dart is the entry point, and will serialize them into i18n-messages.xml.
*
* pub run packages/angular2/extract_messages.dart 'my-app-package' 'web/src/index.dart' 'i18n-messages.xml'
*/
main(List<String> args) async {
final input = new InputSet(args[0], [args[1]]);
final output = new AssetId(args[0], args[2]);

await build(new PhaseGroup.singleAction(new I18nMessageExtractorBuilder(output), input));
}

class I18nMessageExtractorBuilder implements Builder {
final AssetId outputAssetId;

I18nMessageExtractorBuilder(this.outputAssetId);

Future build(BuildStep buildStep) async {
final resolver = await buildStep.resolve(buildStep.input.id);
final entryLib = resolver.getLibrary(buildStep.input.id);

final extractor = new I18nMessageExtractor((path) => buildStep.readAsString(path));
await extractor.processLibrary(entryLib);
resolver.release();

if (extractor.errors.length > 0) {
print("Errors:");
extractor.errors.forEach(print);
throw "Failed to extract messages";

} else {
await buildStep.writeAsString(new Asset(outputAssetId, extractor.output));
}
}

List<AssetId> declareOutputs(AssetId inputId) => [outputAssetId];
}

class I18nMessageExtractor {
final TransformerUrlResolver urlResovler = new TransformerUrlResolver();
final List<Message> messages = [];
final List errors = [];
final HtmlParser htmlParser = new HtmlParser();
final Parser parser = new Parser(new Lexer(), new Reflector(new ReflectionCapabilities()));

final Function readInput;

I18nMessageExtractor(this.readInput);

String get output => serialize(removeDuplicates(messages));

Future processLibrary(LibraryElement el) async {
return Future.wait(el.units.map(processCompilationUnit));
}

Future processCompilationUnit(CompilationUnitElement el) async {
return Future.wait(el.types.map(processClass));
}

Future processClass(ClassElement el) async {
final baseUrl = (el.source as dynamic).assetId;
final filtered = el.metadata.where((m) {
if (m.element is ConstructorElement) {
final isComponent = m.element.enclosingElement.name == "Component" &&
m.element.library.displayName == "angular2.src.core.metadata";

final isView = m.element.enclosingElement.name == "View" &&
m.element.library.displayName == "angular2.src.core.metadata";

return isComponent || isView;
} else {
return false;
}
});

return Future.wait(filtered.map((m) => processAnnotation(el, m, baseUrl)));
}

Future processAnnotation(ClassElement el, ElementAnnotation m, baseUrl) async {
final fields = (m.constantValue as dynamic).fields["(super)"].fields;
final template = fields["template"];
final templateUrl = fields["templateUrl"];

if (template != null && !template.isNull) {
processTemplate(template.toStringValue(), baseUrl.toString());
}

if (templateUrl != null && !templateUrl.isNull) {
final value = templateUrl.toStringValue();
final resolvedPath = urlResovler.resolve(toAssetUri(baseUrl), value);
final template = await readInput(fromUri(resolvedPath));
processTemplate(template.toStringValue(), baseUrl.toString());
}
}

void processTemplate(String template, String sourceUrl) {
final m = new MessageExtractor(htmlParser, parser);
final res = m.extract(template, sourceUrl);
if (res.errors.isNotEmpty) {
errors.addAll(res.errors);
} else {
messages.addAll(res.messages);
}
}
}
X Tutup