From 8326ab3240cf657c9315e93d8ade4f139fc2e50b Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 16 Mar 2016 11:58:43 -0700 Subject: [PATCH] feat(i18n): add a simple dart script extracting all i18n messages from a package Closes #7620 --- modules/angular2/pubspec.yaml | 3 +- modules/benchmarks_external/pubspec.yaml | 2 +- .../transform/lib/extract_messages.dart | 119 ++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 modules_dart/transform/lib/extract_messages.dart diff --git a/modules/angular2/pubspec.yaml b/modules/angular2/pubspec.yaml index 008fa2d8ef..8dbd0125d1 100644 --- a/modules/angular2/pubspec.yaml +++ b/modules/angular2/pubspec.yaml @@ -20,8 +20,7 @@ dependencies: protobuf: '^0.5.0' source_span: '^1.0.0' stack_trace: '^1.1.1' - build: - git: git@github.com:dart-lang/build.git + build: '>=0.0.1' dev_dependencies: transformer_test: '^0.2.0' guinness: '^0.1.18' diff --git a/modules/benchmarks_external/pubspec.yaml b/modules/benchmarks_external/pubspec.yaml index e7cf10f476..a488a5d87b 100644 --- a/modules/benchmarks_external/pubspec.yaml +++ b/modules/benchmarks_external/pubspec.yaml @@ -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: diff --git a/modules_dart/transform/lib/extract_messages.dart b/modules_dart/transform/lib/extract_messages.dart new file mode 100644 index 0000000000..e4cdbdc71c --- /dev/null +++ b/modules_dart/transform/lib/extract_messages.dart @@ -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 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 declareOutputs(AssetId inputId) => [outputAssetId]; +} + +class I18nMessageExtractor { + final TransformerUrlResolver urlResovler = new TransformerUrlResolver(); + final List 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); + } + } +} \ No newline at end of file