[dart] Add //third_party/dart-pkg/pub/intl_translation

Approved in OSRB-120.

IN-870: #progress
Change-Id: If542c96f0eca76d0a8d2af3454408aceffbd5d53
diff --git a/intl_translation/.gitignore b/intl_translation/.gitignore
new file mode 100644
index 0000000..0c764b5
--- /dev/null
+++ b/intl_translation/.gitignore
@@ -0,0 +1,4 @@
+# Don't commit the following directories created by pub.
+.dart_tool
+.packages
+pubspec.lock
diff --git a/intl_translation/.status b/intl_translation/.status
new file mode 100644
index 0000000..f781ae8
--- /dev/null
+++ b/intl_translation/.status
@@ -0,0 +1,31 @@
+# Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Don't run any test-like files that show up in packages directories.
+*/packages/*/*: Skip
+*/*/packages/*/*: Skip
+*/*/*/packages/*/*: Skip
+
+[ $compiler == dart2js ]
+test/*: Skip  # raw tests only meant to be run in dartium. Other browsers run
+              # the output of pub-build
+
+[ $runtime == vm ]
+build/*: Skip
+
+[ $browser ]
+test/message_extraction/examples_parsing_test: Skip # Users dart:io
+test/message_extraction/failed_extraction_test: Skip # Users dart:io
+test/message_extraction/message_extraction_test: Skip # Uses dart:io.
+test/message_extraction/message_extraction_no_deferred_test: Skip # Uses dart:io.
+test/message_extraction/really_fail_extraction_test: Skip # Users dart:io
+test/message_extraction/embedded_plural_text_after_test: Skip # Uses dart:io.
+test/message_extraction/embedded_plural_text_before_test: Skip # Uses dart:io.
+build/test/message_extraction/examples_parsing_test: Skip # Users dart:io
+build/test/message_extraction/failed_extraction_test: Skip # Users dart:io
+build/test/message_extraction/message_extraction_test: Skip # Uses dart:io.
+build/test/message_extraction/message_extraction_no_deferred_test: Skip # Uses dart:io.
+build/test/message_extraction/really_fail_extraction_test: Skip # Users dart:io
+build/test/message_extraction/embedded_plural_text_after_test: Skip # Uses dart:io.
+build/test/message_extraction/embedded_plural_text_before_test: Skip # Uses dart:io.
diff --git a/intl_translation/.travis.yml b/intl_translation/.travis.yml
new file mode 100644
index 0000000..c24e901
--- /dev/null
+++ b/intl_translation/.travis.yml
@@ -0,0 +1,16 @@
+language: dart
+
+dart:
+  - dev
+
+dart_task:
+  - test: -p vm
+  - dartanalyzer
+
+# Only building master means that we don't run two builds for each pull request.
+branches:
+  only: [master]
+
+cache:
+  directories:
+    - $HOME/.pub-cache
diff --git a/intl_translation/AUTHORS b/intl_translation/AUTHORS
new file mode 100644
index 0000000..0617765
--- /dev/null
+++ b/intl_translation/AUTHORS
@@ -0,0 +1,9 @@
+# Names should be added to this file with this pattern:
+#
+# For individuals:
+#   Name <email address>
+#
+# For organizations:
+#   Organization <fnmatch pattern>
+#
+Google Inc. <*@google.com>
diff --git a/intl_translation/BUILD.gn b/intl_translation/BUILD.gn
new file mode 100644
index 0000000..2d1b6b0
--- /dev/null
+++ b/intl_translation/BUILD.gn
@@ -0,0 +1,22 @@
+# This file is generated by importer.py for intl_translation-0.17.2
+
+import("//build/dart/dart_library.gni")
+
+dart_library("intl_translation") {
+  package_name = "intl_translation"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart/third_party/pkg/intl",
+    "//third_party/dart-pkg/pub/petitparser",
+    "//third_party/dart-pkg/pub/args",
+    "//third_party/dart/pkg/analyzer",
+    "//third_party/dart-pkg/pub/dart_style",
+  ]
+}
diff --git a/intl_translation/CHANGELOG.md b/intl_translation/CHANGELOG.md
new file mode 100644
index 0000000..62e4b19
--- /dev/null
+++ b/intl_translation/CHANGELOG.md
@@ -0,0 +1,107 @@
+## 0.17.2
+  * Changes to support new mixin syntax.
+
+## 0.17.1
+  * Added --suppress-last-modified flag to suppress output of the
+    @@last_modified entry in output file.
+  * Add a "package" field in MessageGeneration that can be useful for emitting
+    additional information about e.g. which locales are available and which
+    package we're generating for. Also makes libraryName public.
+  * Silence unnecessary_new lint warnings in generated code.
+  * Add --require_description command line option to message extraction.
+
+## 0.17.0
+  * Fully move to Dart 2.0
+  * Delete the transformer and related code.
+  * Minor update to analyzer API.
+  * Update pubspec version requirements
+
+## 0.16.8
+  * Allow message extraction to find messages from prefixed uses of Intl.
+  * Move analyzer dependency up to 0.33.0
+
+## 0.16.7
+  * Allow message extraction to find messages in class field declarations
+    and top-level declarations.
+  * Fix incorrect name and parameters propagation during extraction phase.
+  * Still more uppercase constant removal.
+
+## 0.16.6
+  * More uppercase constant removal.
+
+## 0.16.5
+  * Replace uses of JSON constant for Dart 2 compatibility.
+
+## 0.16.4
+  * Update Intl compatibility requirements. This requires at least 0.15.3 of
+    Intl, because the tests contain messages with the new "skip" parameter.
+
+## 0.16.3
+  * Fix https://github.com/flutter/flutter/issues/15458 - specify concrete type
+    for generated map.
+
+## 0.16.2
+ * Handle fallback better when we provide translations for locale "xx" but
+   initialize "xx_YY", initializing "xx". Previously we would do nothing.
+ * Skip extracting messages that pass the 'skip' argument to Intl calls.
+ * Move analyzer dependency up to 0.32.0
+
+## 0.16.1
+ * Add @@last_modified to extracted ARB files.
+ * Handle @@locale in translated ARB files properly, and adds a --locale
+   parameter to specify the locale.
+ * Adds a --output-file parameter to extract_to_arb
+ * Indent the output file for ARB for better readability.
+ * A couple of tweaks to satisfy Flutter's default linter rules when run on the
+   generated code.
+
+## 0.16.0
+  * BREAKING CHANGE: Require that the examples to message/plural/gender/select
+    calls be const. DDC does not optimize non-const maps well, so it's a
+    significant performance issue if these are non-const.
+  * Added a utility to convert examples in calls to be const. See
+    bin/make_examples_const.dart
+  * Add a codegen_mode flag, which can be either release or debug. In release
+    mode a missing translation throws an exception, in debug mode it returns the
+    original text, which was the previous behavior.
+  * Add support for generating translated messages as JSON rather than
+    methods. This can significantly improve dart2js compile times for
+    applications with many translations. The JSON is a literal string in the
+    deferred library, so usage doesn't change at all.
+
+## 0.15.0
+  * Change non-transformer message rewriting to preserve the original message as
+    much as possible. Adds --useStringSubstitution command-line arg.
+  * Change non-transformer message rewriting to allow multiple input files to be
+    specified on the command line. Adds --replace flag to ignore --output option
+    and just replace files.
+  * Make non-transformer message rewriting also run dartfmt on the output.
+  * Make message extraction more robust: error message instead of stack trace
+    when an Intl call is made outside a method, when a prefixed expression is
+    used in an interpolation, and when a non-required example Map is not a
+    literal.
+  * Make message extraction more robust: if parsing triggers an exception then
+    report it as an error instead of exiting.
+  * Move barback to being a normal rather than a dev dependency.
+  * Add a check for invalid select keywords.
+  * Added a post-message construction validate, moved
+    IntlMessageExtractionException into intl_message.dart
+  * Make use of analyzer's new AstFactory class (requires analyzer version
+    0.29.1).
+  * Fix error in transformer, pass the path instead of the asset id.
+  * Prefer an explicit =0/=1/=2 to a ZERO/ONE/TWO if both are present. We don't
+    distinguish the two as Intl.message arguments, we just have the "one"
+    parameter, which we confusingly write out as =1. Tools interpret these
+    differently, and in particular, a ONE clause is used for the zero case if
+    there's no explicit zero. Translation tools may implement this by filling in
+    both ZERO and ONE values with the OTHER clause when there's no ZERO
+    provided, resulting in a translation with both =1 and ONE clauses which are
+    different. We should prefer the explicit =1 in that case. In future we may
+    distinguish the different forms, but that would probably break existing
+    translations.
+  * Switch to using package:test
+  * Give a more specific type in the generated code to keep lints happy.
+
+## 0.14.0
+  * Split message extraction and code generation out into a separate
+    package. Versioned to match the corresponding Intl version.
diff --git a/intl_translation/LICENSE b/intl_translation/LICENSE
new file mode 100644
index 0000000..ee99930
--- /dev/null
+++ b/intl_translation/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2013, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/intl_translation/PATENTS b/intl_translation/PATENTS
new file mode 100644
index 0000000..6954196
--- /dev/null
+++ b/intl_translation/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/intl_translation/README.md b/intl_translation/README.md
new file mode 100644
index 0000000..54eee58
--- /dev/null
+++ b/intl_translation/README.md
@@ -0,0 +1,51 @@
+Intl_translation
+====
+
+This package provides message extraction and code generation from translated
+messages for the [Intl][Intl] package. It's a separate package so as to not
+require a dependency on analyzer for all users.
+
+## Extracting And Using Translated Messages
+
+When your program contains messages that need translation, these must
+be extracted from the program source, sent to human translators, and the
+results need to be incorporated.
+
+To extract messages, run the `extract_to_arb.dart` program.
+
+      pub run intl_translation:extract_to_arb --output-dir=target/directory
+          my_program.dart more_of_my_program.dart
+
+This will produce a file `intl_messages.arb` with the messages from
+all of these programs. an [ARB]
+(https://code.google.com/p/arb/wiki/ApplicationResourceBundleSpecification)
+format file which can be used for input to translation tools like
+[Google Translator Toolkit](https://translate.google.com/toolkit/)
+The resulting translations can be used to generate a set of libraries
+using the `generate_from_arb.dart` program.
+
+This expects to receive a series of files, one per
+locale.
+
+```
+pub run intl_translation:generate_from_arb --generated-file-prefix=<prefix>
+    <my_dart_files> <translated_ARB_files>
+```
+
+This will generate Dart libraries, one per locale, which contain the
+translated versions. Your Dart libraries can import the primary file,
+named `<prefix>messages_all.dart`, and then call the initialization
+for a specific locale. Once that's done, any
+[Intl.message][Intl.message] calls made in the context of that locale
+will automatically print the translated version instead of the
+original.
+
+      import "my_prefix_messages_all.dart";
+      ...
+      initializeMessages("dk").then(printSomeMessages);
+
+Once the future returned from the initialization call returns, the
+message data is available.
+
+[Intl]: https://www.dartdocs.org/documentation/intl/latest
+[Intl.message]: https://www.dartdocs.org/documentation/intl/latest/intl/Intl/message.html
diff --git a/intl_translation/bin/extract_to_arb.dart b/intl_translation/bin/extract_to_arb.dart
new file mode 100644
index 0000000..ec89645
--- /dev/null
+++ b/intl_translation/bin/extract_to_arb.dart
@@ -0,0 +1,161 @@
+#!/usr/bin/env dart
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This script uses the extract_messages.dart library to find the Intl.message
+/// calls in the target dart files and produces ARB format output. See
+/// https://code.google.com/p/arb/wiki/ApplicationResourceBundleSpecification
+library extract_to_arb;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+
+import 'package:intl_translation/extract_messages.dart';
+import 'package:intl_translation/src/intl_message.dart';
+
+main(List<String> args) {
+  var targetDir;
+  var outputFilename;
+  bool transformer;
+  var parser = new ArgParser();
+  var extraction = new MessageExtraction();
+  String locale;
+  parser.addFlag("suppress-last-modified",
+      defaultsTo: false,
+      callback: (x) => extraction.suppressLastModified = x,
+      help: 'Suppress @@last_modified entry.');
+  parser.addFlag("suppress-warnings",
+      defaultsTo: false,
+      callback: (x) => extraction.suppressWarnings = x,
+      help: 'Suppress printing of warnings.');
+  parser.addFlag("warnings-are-errors",
+      defaultsTo: false,
+      callback: (x) => extraction.warningsAreErrors = x,
+      help: 'Treat all warnings as errors, stop processing ');
+  parser.addFlag("embedded-plurals",
+      defaultsTo: true,
+      callback: (x) => extraction.allowEmbeddedPluralsAndGenders = x,
+      help: 'Allow plurals and genders to be embedded as part of a larger '
+          'string, otherwise they must be at the top level.');
+  parser.addFlag("transformer",
+      defaultsTo: false,
+      callback: (x) => transformer = x,
+      help: "Assume that the transformer is in use, so name and args "
+          "don't need to be specified for messages.");
+  parser.addOption("locale",
+      defaultsTo: null,
+      callback: (value) => locale = value,
+      help: 'Specify the locale set inside the arb file.');
+  parser.addOption("output-dir",
+      defaultsTo: '.',
+      callback: (value) => targetDir = value,
+      help: 'Specify the output directory.');
+  parser.addOption("output-file",
+      defaultsTo: 'intl_messages.arb',
+      callback: (value) => outputFilename = value,
+      help: 'Specify the output file.');
+  parser.addFlag("require_descriptions",
+      defaultsTo: false,
+      help: "Fail for messages that don't have a description.",
+      callback: (val) => extraction.descriptionRequired = val);
+
+  parser.parse(args);
+  if (args.length == 0) {
+    print('Accepts Dart files and produces $outputFilename');
+    print('Usage: extract_to_arb [options] [files.dart]');
+    print(parser.usage);
+    exit(0);
+  }
+  var allMessages = {};
+  if (locale != null) {
+    allMessages["@@locale"] = locale;
+  }
+  if (!extraction.suppressLastModified) {
+    allMessages["@@last_modified"] = new DateTime.now().toIso8601String();
+  }
+  for (var arg in args.where((x) => x.contains(".dart"))) {
+    var messages = extraction.parseFile(new File(arg), transformer);
+    messages.forEach((k, v) => allMessages.addAll(toARB(v)));
+  }
+  var file = new File(path.join(targetDir, outputFilename));
+  var encoder = new JsonEncoder.withIndent("  ");
+  file.writeAsStringSync(encoder.convert(allMessages));
+  if (extraction.hasWarnings && extraction.warningsAreErrors) {
+    exit(1);
+  }
+}
+
+/// This is a placeholder for transforming a parameter substitution from
+/// the translation file format into a Dart interpolation. In our case we
+/// store it to the file in Dart interpolation syntax, so the transformation
+/// is trivial.
+String leaveTheInterpolationsInDartForm(MainMessage msg, chunk) {
+  if (chunk is String) return chunk;
+  if (chunk is int) return "\$${msg.arguments[chunk]}";
+  return chunk.toCode();
+}
+
+/// Convert the [MainMessage] to a trivial JSON format.
+Map toARB(MainMessage message) {
+  if (message.messagePieces.isEmpty) return null;
+  var out = {};
+  out[message.name] = icuForm(message);
+  out["@${message.name}"] = arbMetadata(message);
+  return out;
+}
+
+Map arbMetadata(MainMessage message) {
+  var out = {};
+  var desc = message.description;
+  if (desc != null) {
+    out["description"] = desc;
+  }
+  out["type"] = "text";
+  var placeholders = {};
+  for (var arg in message.arguments) {
+    addArgumentFor(message, arg, placeholders);
+  }
+  out["placeholders"] = placeholders;
+  return out;
+}
+
+void addArgumentFor(MainMessage message, String arg, Map result) {
+  var extraInfo = {};
+  if (message.examples != null && message.examples[arg] != null) {
+    extraInfo["example"] = message.examples[arg];
+  }
+  result[arg] = extraInfo;
+}
+
+/// Return a version of the message string with with ICU parameters "{variable}"
+/// rather than Dart interpolations "$variable".
+String icuForm(MainMessage message) =>
+    message.expanded(turnInterpolationIntoICUForm);
+
+String turnInterpolationIntoICUForm(Message message, chunk,
+    {bool shouldEscapeICU: false}) {
+  if (chunk is String) {
+    return shouldEscapeICU ? escape(chunk) : chunk;
+  }
+  if (chunk is int && chunk >= 0 && chunk < message.arguments.length) {
+    return "{${message.arguments[chunk]}}";
+  }
+  if (chunk is SubMessage) {
+    return chunk.expanded((message, chunk) =>
+        turnInterpolationIntoICUForm(message, chunk, shouldEscapeICU: true));
+  }
+  if (chunk is Message) {
+    return chunk.expanded((message, chunk) => turnInterpolationIntoICUForm(
+        message, chunk,
+        shouldEscapeICU: shouldEscapeICU));
+  }
+  throw new FormatException("Illegal interpolation: $chunk");
+}
+
+String escape(String s) {
+  return s.replaceAll("'", "''").replaceAll("{", "'{'").replaceAll("}", "'}'");
+}
diff --git a/intl_translation/bin/generate_from_arb.dart b/intl_translation/bin/generate_from_arb.dart
new file mode 100644
index 0000000..8dc592e
--- /dev/null
+++ b/intl_translation/bin/generate_from_arb.dart
@@ -0,0 +1,176 @@
+#!/usr/bin/env dart
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A main program that takes as input a source Dart file and a number
+/// of ARB files representing translations of messages from the corresponding
+/// Dart file. See extract_to_arb.dart and make_hardcoded_translation.dart.
+///
+/// If the ARB file has an @@locale or _locale value, that will be used as
+/// the locale. If not, we will try to figure out the locale from the end of
+/// the file name, e.g. foo_en_GB.arb will be assumed to be in en_GB locale.
+///
+/// This produces a series of files named
+/// "messages_<locale>.dart" containing messages for a particular locale
+/// and a main import file named "messages_all.dart" which has imports all of
+/// them and provides an initializeMessages function.
+
+library generate_from_arb;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+
+import 'package:intl_translation/extract_messages.dart';
+import 'package:intl_translation/generate_localized.dart';
+import 'package:intl_translation/src/intl_message.dart';
+import 'package:intl_translation/src/icu_parser.dart';
+
+/// Keeps track of all the messages we have processed so far, keyed by message
+/// name.
+Map<String, List<MainMessage>> messages;
+
+const jsonDecoder = const JsonCodec();
+
+main(List<String> args) {
+  var targetDir;
+  var parser = new ArgParser();
+  var extraction = new MessageExtraction();
+  var generation = new MessageGeneration();
+  var transformer;
+  parser.addFlag('json', defaultsTo: false, callback: (useJson) {
+    generation =
+        useJson ? new JsonMessageGeneration() : new MessageGeneration();
+  }, help: 'Generate translations as a JSON string rather than as functions.');
+  parser.addFlag("suppress-warnings",
+      defaultsTo: false,
+      callback: (x) => extraction.suppressWarnings = x,
+      help: 'Suppress printing of warnings.');
+  parser.addOption('output-dir',
+      defaultsTo: '.',
+      callback: (x) => targetDir = x,
+      help: 'Specify the output directory.');
+  parser.addOption("generated-file-prefix",
+      defaultsTo: '',
+      callback: (x) => generation.generatedFilePrefix = x,
+      help: 'Specify a prefix to be used for the generated file names.');
+  parser.addFlag("use-deferred-loading",
+      defaultsTo: true,
+      callback: (x) => generation.useDeferredLoading = x,
+      help: 'Generate message code that must be loaded with deferred loading. '
+          'Otherwise, all messages are eagerly loaded.');
+  parser.addOption('codegen_mode',
+      allowed: ['release', 'debug'],
+      defaultsTo: 'debug',
+      callback: (x) => generation.codegenMode = x,
+      help: 'What mode to run the code generator in. Either release or debug.');
+  parser.addFlag("transformer",
+      defaultsTo: false,
+      callback: (x) => transformer = x,
+      help: "Assume that the transformer is in use, so name and args "
+          "don't need to be specified for messages.");
+
+  parser.parse(args);
+  var dartFiles = args.where((x) => x.endsWith("dart")).toList();
+  var jsonFiles = args.where((x) => x.endsWith(".arb")).toList();
+  if (dartFiles.length == 0 || jsonFiles.length == 0) {
+    print('Usage: generate_from_arb [options]'
+        ' file1.dart file2.dart ...'
+        ' translation1_<languageTag>.arb translation2.arb ...');
+    print(parser.usage);
+    exit(0);
+  }
+
+  // TODO(alanknight): There is a possible regression here. If a project is
+  // using the transformer and expecting it to provide names for messages with
+  // parameters, we may report those names as missing. We now have two distinct
+  // mechanisms for providing names: the transformer and just using the message
+  // text if there are no parameters. Previously this was always acting as if
+  // the transformer was in use, but that breaks the case of using the message
+  // text. The intent is to deprecate the transformer, but if this is an issue
+  // for real projects we could provide a command-line flag to indicate which
+  // sort of automated name we're using.
+  extraction.suppressWarnings = true;
+  var allMessages = dartFiles
+      .map((each) => extraction.parseFile(new File(each), transformer));
+
+  messages = new Map();
+  for (var eachMap in allMessages) {
+    eachMap.forEach(
+        (key, value) => messages.putIfAbsent(key, () => []).add(value));
+  }
+  for (var arg in jsonFiles) {
+    var file = new File(arg);
+    generateLocaleFile(file, targetDir, generation);
+  }
+
+  var mainImportFile = new File(path.join(
+      targetDir, '${generation.generatedFilePrefix}messages_all.dart'));
+  mainImportFile.writeAsStringSync(generation.generateMainImportFile());
+}
+
+/// Create the file of generated code for a particular locale. We read the ARB
+/// data and create [BasicTranslatedMessage] instances from everything,
+/// excluding only the special _locale attribute that we use to indicate the
+/// locale. If that attribute is missing, we try to get the locale from the last
+/// section of the file name.
+void generateLocaleFile(
+    File file, String targetDir, MessageGeneration generation) {
+  var src = file.readAsStringSync();
+  var data = jsonDecoder.decode(src);
+  var locale = data["@@locale"] ?? data["_locale"];
+  if (locale == null) {
+    // Get the locale from the end of the file name. This assumes that the file
+    // name doesn't contain any underscores except to begin the language tag
+    // and to separate language from country. Otherwise we can't tell if
+    // my_file_fr.arb is locale "fr" or "file_fr".
+    var name = path.basenameWithoutExtension(file.path);
+    locale = name.split("_").skip(1).join("_");
+    print("No @@locale or _locale field found in $name, "
+        "assuming '$locale' based on the file name.");
+  }
+  generation.allLocales.add(locale);
+
+  List<TranslatedMessage> translations = [];
+  data.forEach((id, messageData) {
+    TranslatedMessage message = recreateIntlObjects(id, messageData);
+    if (message != null) {
+      translations.add(message);
+    }
+  });
+  generation.generateIndividualMessageFile(locale, translations, targetDir);
+}
+
+/// Regenerate the original IntlMessage objects from the given [data]. For
+/// things that are messages, we expect [id] not to start with "@" and
+/// [data] to be a String. For metadata we expect [id] to start with "@"
+/// and [data] to be a Map or null. For metadata we return null.
+BasicTranslatedMessage recreateIntlObjects(String id, data) {
+  if (id.startsWith("@")) return null;
+  if (data == null) return null;
+  var parsed = pluralAndGenderParser.parse(data).value;
+  if (parsed is LiteralString && parsed.string.isEmpty) {
+    parsed = plainParser.parse(data).value;
+  }
+  return new BasicTranslatedMessage(id, parsed);
+}
+
+/// A TranslatedMessage that just uses the name as the id and knows how to look
+/// up its original messages in our [messages].
+class BasicTranslatedMessage extends TranslatedMessage {
+  BasicTranslatedMessage(String name, translated) : super(name, translated);
+
+  List<MainMessage> get originalMessages => (super.originalMessages == null)
+      ? _findOriginals()
+      : super.originalMessages;
+
+  // We know that our [id] is the name of the message, which is used as the
+  //key in [messages].
+  List<MainMessage> _findOriginals() => originalMessages = messages[id];
+}
+
+final pluralAndGenderParser = new IcuParser().message;
+final plainParser = new IcuParser().nonIcuMessage;
diff --git a/intl_translation/bin/make_examples_const.dart b/intl_translation/bin/make_examples_const.dart
new file mode 100644
index 0000000..12df6f1
--- /dev/null
+++ b/intl_translation/bin/make_examples_const.dart
@@ -0,0 +1,74 @@
+#!/usr/bin/env dart
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Converts the examples parameter for Intl messages to be const.
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:dart_style/dart_style.dart';
+import 'package:intl_translation/src/message_rewriter.dart';
+
+main(List<String> args) {
+  var parser = new ArgParser();
+  var rest = parser.parse(args).rest;
+  if (rest.length == 0) {
+    print('Accepts Dart file paths and rewrites the examples to be const '
+        'in Intl.message calls.');
+    print('Usage: make_examples_const [options] [file.dart]...');
+    print(parser.usage);
+    exit(0);
+  }
+
+  var formatter = new DartFormatter();
+  for (var inputFile in rest) {
+    var outputFile = inputFile;
+    var file = new File(inputFile);
+    var content = file.readAsStringSync();
+    var newSource = rewriteMessages(content, '$file');
+    if (content == newSource) {
+      print('No changes to $outputFile');
+    } else {
+      print('Writing new source to $outputFile');
+      var out = new File(outputFile);
+      out.writeAsStringSync(formatter.format(newSource));
+    }
+  }
+}
+
+/// Rewrite all Intl.message/plural/etc. calls in [source] which have
+/// examples, making them be const.
+///
+/// Return the modified source code. If there are errors parsing, list
+/// [sourceName] in the error message.
+String rewriteMessages(String source, String sourceName) {
+  var messages = findMessages(source, sourceName);
+  messages.sort((a, b) => a.sourcePosition.compareTo(b.sourcePosition));
+  var start = 0;
+  var newSource = new StringBuffer();
+  for (var message in messages) {
+    if (message.examples != null) {
+      newSource.write(source.substring(start, message.sourcePosition));
+      rewrite(newSource, source, start, message);
+      start = message.endPosition;
+    }
+  }
+  newSource.write(source.substring(start));
+  return newSource.toString();
+}
+
+void rewrite(StringBuffer newSource, String source, int start, message) {
+  var originalSource =
+      source.substring(message.sourcePosition, message.endPosition);
+  var examples = nonConstExamples.firstMatch(originalSource);
+  if (examples == null) {
+    newSource.write(originalSource);
+  } else {
+    var modifiedSource = originalSource.replaceFirst(
+        examples.group(1), examples.group(1) + 'const');
+    newSource.write(modifiedSource);
+  }
+}
+
+final RegExp nonConstExamples = new RegExp('([\\n,]\\s+examples\: ){');
diff --git a/intl_translation/bin/rewrite_intl_messages.dart b/intl_translation/bin/rewrite_intl_messages.dart
new file mode 100644
index 0000000..0b55cd8
--- /dev/null
+++ b/intl_translation/bin/rewrite_intl_messages.dart
@@ -0,0 +1,73 @@
+#!/usr/bin/env dart
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A main program that imitates the action of the transformer, adding
+/// name and args parameters to Intl.message calls automatically.
+///
+/// This is mainly intended to test the transformer logic outside of barback.
+/// It takes as input a single source Dart file and rewrites any
+/// Intl.message or related calls to automatically include the name and args
+/// parameters and writes the result to stdout.
+///
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+import 'package:intl_translation/src/message_rewriter.dart';
+import 'package:dart_style/dart_style.dart';
+
+String outputFileOption = 'transformed_output.dart';
+
+bool useStringSubstitution = true;
+bool replace = false;
+
+main(List<String> args) {
+  var parser = new ArgParser();
+  parser.addOption('output',
+      defaultsTo: 'transformed_output.dart',
+      callback: (x) => outputFileOption = x,
+      help: 'Specify the output file.');
+  parser.addFlag('replace',
+      defaultsTo: false,
+      callback: (x) => replace = x,
+      help: 'Overwrite the input file; ignore --output option.');
+  parser.addFlag('useStringSubstitution',
+      defaultsTo: true,
+      callback: (x) => useStringSubstitution = x,
+      help: 'If true, in rewriting, try to leave the text of the message'
+          ' as close to the original as possible. This is slightly less reliable,'
+          ' because it relies on string matching, but better for updating'
+          ' source code to move away from the transformer. If false,'
+          ' behave like the transformer, regenerating the message code'
+          ' from our internal representation. This is more reliable, but'
+          ' produces less readable code.');
+  print(args);
+  var rest = parser.parse(args).rest;
+  if (rest.length == 0) {
+    print('Accepts Dart file paths and adds "name" and "args" parameters '
+        ' to Intl.message calls.');
+    print('Primarily useful for exercising the transformer logic or '
+        'for rewriting programs to not require the transformer.');
+    print('Usage: rewrite_intl_messages [options] [file.dart]...');
+    print(parser.usage);
+    exit(0);
+  }
+
+  var formatter = new DartFormatter();
+  for (var inputFile in rest) {
+    var outputFile = replace ? inputFile : outputFileOption;
+    var file = new File(inputFile);
+    var content = file.readAsStringSync();
+    var newSource = rewriteMessages(content, '$file',
+        useStringSubstitution: useStringSubstitution);
+    if (content == newSource) {
+      print('No changes to $outputFile');
+    } else {
+      print('Writing new source to $outputFile');
+      var out = new File(outputFile);
+      out.writeAsStringSync(formatter.format(newSource));
+    }
+  }
+}
diff --git a/intl_translation/codereview.settings b/intl_translation/codereview.settings
new file mode 100644
index 0000000..318c9c5
--- /dev/null
+++ b/intl_translation/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/intl_translation/commit/
+CC_LIST: reviews@dartlang.org
diff --git a/intl_translation/lib/extract_messages.dart b/intl_translation/lib/extract_messages.dart
new file mode 100644
index 0000000..166f5b7
--- /dev/null
+++ b/intl_translation/lib/extract_messages.dart
@@ -0,0 +1,644 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This is for use in extracting messages from a Dart program
+/// using the Intl.message() mechanism and writing them to a file for
+/// translation. This provides only the stub of a mechanism, because it
+/// doesn't define how the file should be written. It provides an
+/// [IntlMessage] class that holds the extracted data and [parseString]
+/// and [parseFile] methods which
+/// can extract messages that conform to the expected pattern:
+///       (parameters) => Intl.message("Message $parameters", desc: ...);
+/// It uses the analyzer package to do the parsing, so may
+/// break if there are changes to the API that it provides.
+/// An example can be found in test/message_extraction/extract_to_json.dart
+///
+/// Note that this does not understand how to follow part directives, so it
+/// has to explicitly be given all the files that it needs. A typical use case
+/// is to run it on all .dart files in a directory.
+library extract_messages;
+
+import 'dart:io';
+
+import 'package:analyzer/analyzer.dart';
+import 'package:analyzer/dart/ast/standard_ast_factory.dart';
+import 'package:intl_translation/src/intl_message.dart';
+
+/// A function that takes a message and does something useful with it.
+typedef void OnMessage(String message);
+
+/// A particular message extraction run.
+///
+///  This encapsulates all the state required for message extraction so that
+///  it can be run inside a persistent process.
+class MessageExtraction {
+  /// What to do when a message is encountered, defaults to [print].
+  OnMessage onMessage = print;
+
+  /// If this is true, the @@last_modified entry is not output.
+  bool suppressLastModified = false;
+
+  /// If this is true, print warnings for skipped messages. Otherwise, warnings
+  /// are suppressed.
+  bool suppressWarnings = false;
+
+  /// If this is true, then treat all warnings as errors.
+  bool warningsAreErrors = false;
+
+  /// This accumulates a list of all warnings/errors we have found. These are
+  /// saved as strings right now, so all that can really be done is print and
+  /// count them.
+  List<String> warnings = [];
+
+  /// Were there any warnings or errors in extracting messages.
+  bool get hasWarnings => warnings.isNotEmpty;
+
+  /// Are plural and gender expressions required to be at the top level
+  /// of an expression, or are they allowed to be embedded in string literals.
+  ///
+  /// For example, the following expression
+  ///     'There are ${Intl.plural(...)} items'.
+  /// is legal if [allowEmbeddedPluralsAndGenders] is true, but illegal
+  /// if [allowEmbeddedPluralsAndGenders] is false.
+  bool allowEmbeddedPluralsAndGenders = true;
+
+  /// Are examples required on all messages.
+  bool examplesRequired = false;
+
+  bool descriptionRequired = false;
+
+  /// Parse the source of the Dart program file [file] and return a Map from
+  /// message names to [IntlMessage] instances.
+  ///
+  /// If [transformer] is true, assume the transformer will supply any "name"
+  /// and "args" parameters required in Intl.message calls.
+  Map<String, MainMessage> parseFile(File file, [transformer = false]) {
+    // Optimization to avoid parsing files we're sure don't contain any messages.
+    String contents = file.readAsStringSync();
+    origin = file.path;
+    if (contents.contains("Intl.")) {
+      root = _parseCompilationUnit(contents, origin);
+    } else {
+      return {};
+    }
+    var visitor = new MessageFindingVisitor(this);
+    visitor.generateNameAndArgs = transformer;
+    root.accept(visitor);
+    return visitor.messages;
+  }
+
+  CompilationUnit _parseCompilationUnit(String contents, String origin) {
+    var parsed;
+    try {
+      parsed = parseCompilationUnit(contents);
+    } on AnalyzerErrorGroup {
+      print("Error in parsing $origin, no messages extracted.");
+      rethrow;
+    }
+    return parsed;
+  }
+
+  /// The root of the compilation unit, and the first node we visit. We hold
+  /// on to this for error reporting, as it can give us line numbers of other
+  /// nodes.
+  CompilationUnit root;
+
+  /// An arbitrary string describing where the source code came from. Most
+  /// obviously, this could be a file path. We use this when reporting
+  /// invalid messages.
+  String origin;
+
+  String _reportErrorLocation(AstNode node) {
+    var result = new StringBuffer();
+    if (origin != null) result.write("    from $origin");
+    var info = root.lineInfo;
+    if (info != null) {
+      var line = info.getLocation(node.offset);
+      result
+          .write("    line: ${line.lineNumber}, column: ${line.columnNumber}");
+    }
+    return result.toString();
+  }
+}
+
+/// This visits the program source nodes looking for Intl.message uses
+/// that conform to its pattern and then creating the corresponding
+/// IntlMessage objects. We have to find both the enclosing function, and
+/// the Intl.message invocation.
+class MessageFindingVisitor extends GeneralizingAstVisitor {
+  MessageFindingVisitor(this.extraction);
+
+  /// The message extraction in which we are running.
+  final MessageExtraction extraction;
+
+  /// Accumulates the messages we have found, keyed by name.
+  final Map<String, MainMessage> messages = new Map<String, MainMessage>();
+
+  /// Should we generate the name and arguments from the function definition,
+  /// meaning we're running in the transformer.
+  bool generateNameAndArgs = false;
+
+  /// We keep track of the data from the last MethodDeclaration,
+  /// FunctionDeclaration or FunctionExpression that we saw on the way down,
+  /// as that will be the nearest parent of the Intl.message invocation.
+  FormalParameterList parameters;
+  String name;
+
+  final FormalParameterList _emptyParameterList =
+      astFactory.formalParameterList(null, [], null, null, null);
+
+  /// Return true if [node] matches the pattern we expect for Intl.message()
+  bool looksLikeIntlMessage(MethodInvocation node) {
+    const validNames = const ["message", "plural", "gender", "select"];
+    if (!validNames.contains(node.methodName.name)) return false;
+    final target = node.target;
+    if (target is SimpleIdentifier) {
+      return target.token.toString() == 'Intl';
+    } else if (target is PrefixedIdentifier) {
+      return target.identifier.token.toString() == 'Intl';
+    }
+    return false;
+  }
+
+  Message _expectedInstance(String type) {
+    switch (type) {
+      case 'message':
+        return new MainMessage();
+      case 'plural':
+        return new Plural();
+      case 'gender':
+        return new Gender();
+      case 'select':
+        return new Select();
+      default:
+        return null;
+    }
+  }
+
+  /// Returns a String describing why the node is invalid, or null if no
+  /// reason is found, so it's presumed valid.
+  String checkValidity(MethodInvocation node) {
+    if (parameters == null) {
+      return "Calls to Intl must be inside a method, field declaration or "
+          "top level declaration.";
+    }
+    // The containing function cannot have named parameters.
+    if (parameters.parameters.any((each) => each.isNamed)) {
+      return "Named parameters on message functions are not supported.";
+    }
+    var arguments = node.argumentList.arguments;
+    var instance = _expectedInstance(node.methodName.name);
+    return instance.checkValidity(node, arguments, name, parameters,
+        nameAndArgsGenerated: generateNameAndArgs,
+        examplesRequired: extraction.examplesRequired);
+  }
+
+  /// Record the parameters of the function or method declaration we last
+  /// encountered before seeing the Intl.message call.
+  void visitMethodDeclaration(MethodDeclaration node) {
+    name = node.name.name;
+    parameters = node.parameters ?? _emptyParameterList;
+    super.visitMethodDeclaration(node);
+    name = null;
+    parameters = null;
+  }
+
+  /// Record the parameters of the function or method declaration we last
+  /// encountered before seeing the Intl.message call.
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    name = node.name.name;
+    parameters = node.functionExpression.parameters ?? _emptyParameterList;
+    super.visitFunctionDeclaration(node);
+    name = null;
+    parameters = null;
+  }
+
+  /// Record the name of field declaration we last
+  /// encountered before seeing the Intl.message call.
+  void visitFieldDeclaration(FieldDeclaration node) {
+    // We don't support names in list declarations,
+    // e.g. String first, second = Intl.message(...);
+    if (node.fields.variables.length == 1) {
+      name = node.fields.variables.first.name.name;
+    } else {
+      name = null;
+    }
+    parameters = _emptyParameterList;
+    super.visitFieldDeclaration(node);
+    name = null;
+    parameters = null;
+  }
+
+  /// Record the name of the top level variable declaration we last
+  /// encountered before seeing the Intl.message call.
+  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+    // We don't support names in list declarations,
+    // e.g. String first, second = Intl.message(...);
+    if (node.variables.variables.length == 1) {
+      name = node.variables.variables.first.name.name;
+    } else {
+      name = null;
+    }
+    parameters = _emptyParameterList;
+    super.visitTopLevelVariableDeclaration(node);
+    name = null;
+    parameters = null;
+  }
+
+  /// Examine method invocations to see if they look like calls to Intl.message.
+  /// If we've found one, stop recursing. This is important because we can have
+  /// Intl.message(...Intl.plural...) and we don't want to treat the inner
+  /// plural as if it was an outermost message.
+  void visitMethodInvocation(MethodInvocation node) {
+    if (!addIntlMessage(node)) {
+      super.visitMethodInvocation(node);
+    }
+  }
+
+  /// Check that the node looks like an Intl.message invocation, and create
+  /// the [IntlMessage] object from it and store it in [messages]. Return true
+  /// if we successfully extracted a message and should stop looking. Return
+  /// false if we didn't, so should continue recursing.
+  bool addIntlMessage(MethodInvocation node) {
+    if (!looksLikeIntlMessage(node)) return false;
+    var reason = checkValidity(node) ?? _extractMessage(node);
+
+    if (reason != null) {
+      if (!extraction.suppressWarnings) {
+        var err = new StringBuffer()
+          ..write("Skipping invalid Intl.message invocation\n    <$node>\n")
+          ..writeAll(
+              ["    reason: $reason\n", extraction._reportErrorLocation(node)]);
+        var errString = err.toString();
+        extraction.warnings.add(errString);
+        extraction.onMessage(errString);
+      }
+    }
+
+    // We found a message, valid or not. Stop recursing.
+    return true;
+  }
+
+  /// Try to extract a message. On failure, return a String error message.
+  String _extractMessage(MethodInvocation node) {
+    var message;
+    try {
+      if (node.methodName.name == "message") {
+        message = messageFromIntlMessageCall(node);
+      } else {
+        message = messageFromDirectPluralOrGenderCall(node);
+      }
+    } catch (e, s) {
+      return "Unexpected exception: $e, $s";
+    }
+    return message == null ? null : _validateMessage(message);
+  }
+
+  /// Perform any post-construction validations on the message and
+  /// ensure that it's not a duplicate.
+  // TODO(alanknight): This is still ugly and may lead to duplicate reporting
+  // of the same error. Refactor to consistently throw
+  // IntlMessageExtractionException instead of returning strings and centralize
+  // the reporting.
+  String _validateMessage(MainMessage message) {
+    try {
+      message.validate();
+      if (extraction.descriptionRequired) {
+        message.validateDescription();
+      }
+    } on IntlMessageExtractionException catch (e) {
+      return e.message;
+    }
+    var existing = messages[message.name];
+    if (existing != null) {
+      // TODO(alanknight): We may want to require the descriptions to match.
+      var existingCode =
+          existing.toOriginalCode(includeDesc: false, includeExamples: false);
+      var messageCode =
+          message.toOriginalCode(includeDesc: false, includeExamples: false);
+      if (existingCode != messageCode) {
+        return "WARNING: Duplicate message name:\n"
+            "'${message.name}' occurs more than once in ${extraction.origin}";
+      }
+    } else {
+      if (!message.skip) {
+        messages[message.name] = message;
+      }
+      return null;
+    }
+    return null; // Placate the analyzer
+  }
+
+  /// Create a MainMessage from [node] using the name and
+  /// parameters of the last function/method declaration we encountered,
+  /// and the values we get by calling [extract]. We set those values
+  /// by calling [setAttribute]. This is the common parts between
+  /// [messageFromIntlMessageCall] and [messageFromDirectPluralOrGenderCall].
+  MainMessage _messageFromNode(
+      MethodInvocation node,
+      MainMessage extract(MainMessage message, List<AstNode> arguments),
+      void setAttribute(
+          MainMessage message, String fieldName, Object fieldValue)) {
+    var message = new MainMessage();
+    message.sourcePosition = node.offset;
+    message.endPosition = node.end;
+    message.arguments =
+        parameters.parameters.map((x) => x.identifier.name).toList();
+    var arguments = node.argumentList.arguments;
+    var extractionResult = extract(message, arguments);
+    if (extractionResult == null) return null;
+
+    for (NamedExpression namedArgument
+        in arguments.where((x) => x is NamedExpression)) {
+      var name = namedArgument.name.label.name;
+      var exp = namedArgument.expression;
+      var evaluator = new ConstantEvaluator();
+      var basicValue = exp.accept(evaluator);
+      var value = basicValue == ConstantEvaluator.NOT_A_CONSTANT
+          ? exp.toString()
+          : basicValue;
+      setAttribute(message, name, value);
+    }
+    // We only rewrite messages with parameters, otherwise we use the literal
+    // string as the name and no arguments are necessary.
+    if (!message.hasName) {
+      if (generateNameAndArgs && message.arguments.isNotEmpty) {
+        // Always try for class_method if this is a class method and
+        // generating names/args.
+        message.name = Message.classPlusMethodName(node, name) ?? name;
+      } else if (arguments.first is SimpleStringLiteral ||
+          arguments.first is AdjacentStrings) {
+        // If there's no name, and the message text is a simple string, compute
+        // a name based on that plus meaning, if present.
+        var simpleName = (arguments.first as StringLiteral).stringValue;
+        message.name =
+            computeMessageName(message.name, simpleName, message.meaning);
+      }
+    }
+    return message;
+  }
+
+  /// Find the message pieces from a Dart interpolated string.
+  List _extractFromIntlCallWithInterpolation(
+      MainMessage message, List<AstNode> arguments) {
+    var interpolation = new InterpolationVisitor(message, extraction);
+    arguments.first.accept(interpolation);
+    if (interpolation.pieces.any((x) => x is Plural || x is Gender) &&
+        !extraction.allowEmbeddedPluralsAndGenders) {
+      if (interpolation.pieces.any((x) => x is String && x.isNotEmpty)) {
+        throw new IntlMessageExtractionException(
+            "Plural and gender expressions must be at the top level, "
+            "they cannot be embedded in larger string literals.\n");
+      }
+    }
+    return interpolation.pieces;
+  }
+
+  /// Create a MainMessage from [node] using the name and
+  /// parameters of the last function/method declaration we encountered
+  /// and the parameters to the Intl.message call.
+  MainMessage messageFromIntlMessageCall(MethodInvocation node) {
+    MainMessage extractFromIntlCall(
+        MainMessage message, List<AstNode> arguments) {
+      try {
+        // The pieces of the message, either literal strings, or integers
+        // representing the index of the argument to be substituted.
+        List extracted;
+        extracted = _extractFromIntlCallWithInterpolation(message, arguments);
+        message.addPieces(extracted);
+      } on IntlMessageExtractionException catch (e) {
+        message = null;
+        var err = new StringBuffer()
+          ..writeAll(["Error ", e, "\nProcessing <", node, ">\n"])
+          ..write(extraction._reportErrorLocation(node));
+        var errString = err.toString();
+        extraction.onMessage(errString);
+        extraction.warnings.add(errString);
+      }
+      return message;
+    }
+
+    void setValue(MainMessage message, String fieldName, Object fieldValue) {
+      message[fieldName] = fieldValue;
+    }
+
+    return _messageFromNode(node, extractFromIntlCall, setValue);
+  }
+
+  /// Create a MainMessage from [node] using the name and
+  /// parameters of the last function/method declaration we encountered
+  /// and the parameters to the Intl.plural or Intl.gender call.
+  MainMessage messageFromDirectPluralOrGenderCall(MethodInvocation node) {
+    MainMessage extractFromPluralOrGender(MainMessage message, _) {
+      var visitor = new PluralAndGenderVisitor(
+          message.messagePieces, message, extraction);
+      node.accept(visitor);
+      return message;
+    }
+
+    void setAttribute(MainMessage msg, String fieldName, fieldValue) {
+      if (msg.attributeNames.contains(fieldName)) {
+        msg[fieldName] = fieldValue;
+      }
+    }
+
+    return _messageFromNode(node, extractFromPluralOrGender, setAttribute);
+  }
+}
+
+/// Given an interpolation, find all of its chunks, validate that they are only
+/// simple variable substitutions or else Intl.plural/gender calls,
+/// and keep track of the pieces of text so that other parts
+/// of the program can deal with the simple string sections and the generated
+/// parts separately. Note that this is a SimpleAstVisitor, so it only
+/// traverses one level of children rather than automatically recursing. If we
+/// find a plural or gender, which requires recursion, we do it with a separate
+/// special-purpose visitor.
+class InterpolationVisitor extends SimpleAstVisitor {
+  final Message message;
+
+  /// The message extraction in which we are running.
+  final MessageExtraction extraction;
+
+  InterpolationVisitor(this.message, this.extraction);
+
+  List pieces = [];
+  String get extractedMessage => pieces.join();
+
+  void visitAdjacentStrings(AdjacentStrings node) {
+    node.visitChildren(this);
+    super.visitAdjacentStrings(node);
+  }
+
+  void visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    super.visitStringInterpolation(node);
+  }
+
+  void visitSimpleStringLiteral(SimpleStringLiteral node) {
+    pieces.add(node.value);
+    super.visitSimpleStringLiteral(node);
+  }
+
+  void visitInterpolationString(InterpolationString node) {
+    pieces.add(node.value);
+    super.visitInterpolationString(node);
+  }
+
+  void visitInterpolationExpression(InterpolationExpression node) {
+    if (node.expression is SimpleIdentifier) {
+      return handleSimpleInterpolation(node);
+    } else {
+      return lookForPluralOrGender(node);
+    }
+    // Note that we never end up calling super.
+  }
+
+  lookForPluralOrGender(InterpolationExpression node) {
+    var visitor = new PluralAndGenderVisitor(pieces, message, extraction);
+    node.accept(visitor);
+    if (!visitor.foundPluralOrGender) {
+      throw new IntlMessageExtractionException(
+          "Only simple identifiers and Intl.plural/gender/select expressions "
+          "are allowed in message "
+          "interpolation expressions.\nError at $node");
+    }
+  }
+
+  void handleSimpleInterpolation(InterpolationExpression node) {
+    var index = arguments.indexOf(node.expression.toString());
+    if (index == -1) {
+      throw new IntlMessageExtractionException(
+          "Cannot find argument ${node.expression}");
+    }
+    pieces.add(index);
+  }
+
+  List get arguments => message.arguments;
+}
+
+/// A visitor to extract information from Intl.plural/gender sends. Note that
+/// this is a SimpleAstVisitor, so it doesn't automatically recurse. So this
+/// needs to be called where we expect a plural or gender immediately below.
+class PluralAndGenderVisitor extends SimpleAstVisitor {
+  /// The message extraction in which we are running.
+  final MessageExtraction extraction;
+
+  /// A plural or gender always exists in the context of a parent message,
+  /// which could in turn also be a plural or gender.
+  final ComplexMessage parent;
+
+  /// The pieces of the message. We are given an initial version of this
+  /// from our parent and we add to it as we find additional information.
+  List pieces;
+
+  /// This will be set to true if we find a plural or gender.
+  bool foundPluralOrGender = false;
+
+  PluralAndGenderVisitor(this.pieces, this.parent, this.extraction) : super();
+
+  visitInterpolationExpression(InterpolationExpression node) {
+    // TODO(alanknight): Provide better errors for malformed expressions.
+    if (!looksLikePluralOrGender(node.expression)) return;
+    var reason = checkValidity(node.expression);
+    if (reason != null) throw reason;
+    var message = messageFromMethodInvocation(node.expression);
+    foundPluralOrGender = true;
+    pieces.add(message);
+    super.visitInterpolationExpression(node);
+  }
+
+  visitMethodInvocation(MethodInvocation node) {
+    pieces.add(messageFromMethodInvocation(node));
+    super.visitMethodInvocation(node);
+  }
+
+  /// Return true if [node] matches the pattern for plural or gender message.
+  bool looksLikePluralOrGender(Expression expression) {
+    if (expression is! MethodInvocation) return false;
+    final node = expression as MethodInvocation;
+    if (!["plural", "gender", "select"].contains(node.methodName.name)) {
+      return false;
+    }
+    if (!(node.target is SimpleIdentifier)) return false;
+    SimpleIdentifier target = node.target;
+    return target.token.toString() == "Intl";
+  }
+
+  /// Returns a String describing why the node is invalid, or null if no
+  /// reason is found, so it's presumed valid.
+  String checkValidity(MethodInvocation node) {
+    // TODO(alanknight): Add reasonable validity checks.
+    return null;
+  }
+
+  /// Create a MainMessage from [node] using the name and
+  /// parameters of the last function/method declaration we encountered
+  /// and the parameters to the Intl.message call.
+  Message messageFromMethodInvocation(MethodInvocation node) {
+    var message;
+    switch (node.methodName.name) {
+      case "gender":
+        message = new Gender();
+        break;
+      case "plural":
+        message = new Plural();
+        break;
+      case "select":
+        message = new Select();
+        break;
+      default:
+        throw new IntlMessageExtractionException(
+            "Invalid plural/gender/select message ${node.methodName.name} "
+            "in $node");
+    }
+    message.parent = parent;
+
+    var arguments = message.argumentsOfInterestFor(node);
+    arguments.forEach((key, value) {
+      try {
+        var interpolation = new InterpolationVisitor(message, extraction);
+        value.accept(interpolation);
+        // Might be null due to previous errors.
+        // Continue collecting errors, but don't build message.
+        if (message != null) {
+          message[key] = interpolation.pieces;
+        }
+      } on IntlMessageExtractionException catch (e) {
+        message = null;
+        var err = new StringBuffer()
+          ..writeAll(["Error ", e, "\nProcessing <", node, ">"])
+          ..write(extraction._reportErrorLocation(node));
+        var errString = err.toString();
+        extraction.onMessage(errString);
+        extraction.warnings.add(errString);
+      }
+    });
+    var mainArg = node.argumentList.arguments
+        .firstWhere((each) => each is! NamedExpression);
+    if (mainArg is SimpleStringLiteral) {
+      message.mainArgument = mainArg.toString();
+    } else if (mainArg is SimpleIdentifier) {
+      message.mainArgument = mainArg.name;
+    } else {
+      var err = new StringBuffer()
+        ..write("Error (Invalid argument to plural/gender/select, "
+            "must be simple variable reference) "
+            "\nProcessing <$node>")
+        ..write(extraction._reportErrorLocation(node));
+      var errString = err.toString();
+      extraction.onMessage(errString);
+      extraction.warnings.add(errString);
+    }
+    return message;
+  }
+}
+
+/// If a message is a string literal without interpolation, compute
+/// a name based on that and the meaning, if present.
+// NOTE: THIS LOGIC IS DUPLICATED IN intl AND THE TWO MUST MATCH.
+String computeMessageName(String name, String text, String meaning) {
+  if (name != null && name != "") return name;
+  return meaning == null ? text : "${text}_${meaning}";
+}
diff --git a/intl_translation/lib/generate_localized.dart b/intl_translation/lib/generate_localized.dart
new file mode 100644
index 0000000..f64ad54
--- /dev/null
+++ b/intl_translation/lib/generate_localized.dart
@@ -0,0 +1,467 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This provides utilities for generating localized versions of
+/// messages. It does not stand alone, but expects to be given
+/// TranslatedMessage objects and generate code for a particular locale
+/// based on them.
+///
+/// An example of usage can be found
+/// in test/message_extract/generate_from_json.dart
+library generate_localized;
+
+import 'package:intl/intl.dart';
+import 'src/intl_message.dart';
+import 'dart:convert';
+import 'dart:io';
+import 'package:path/path.dart' as path;
+
+class MessageGeneration {
+  /// If the import path following package: is something else, modify the
+  /// [intlImportPath] variable to change the import directives in the generated
+  /// code.
+  var intlImportPath = 'intl';
+
+  /// If the path to the generated files is something other than the current
+  /// directory, update the [generatedImportPath] variable to change the import
+  /// directives in the generated code.
+  var generatedImportPath = '';
+
+  /// Given a base file, return the file prefixed with the path to import it.
+  /// By default, that is in the current directory, but if [generatedImportPath]
+  /// has been set, then use that as a prefix.
+  String importForGeneratedFile(String file) =>
+      generatedImportPath.isEmpty ? file : "$generatedImportPath/$file";
+
+  /// A list of all the locales for which we have translations. Code that does
+  /// the reading of translations should add to this.
+  List<String> allLocales = [];
+
+  /// If we have more than one set of messages to generate in a particular
+  /// directory we may want to prefix some to distinguish them.
+  String generatedFilePrefix = '';
+
+  /// Should we use deferred loading for the generated libraries.
+  bool useDeferredLoading = true;
+
+  /// The mode to generate in - either 'release' or 'debug'.
+  ///
+  /// In release mode, a missing translation is an error. In debug mode, it
+  /// falls back to the original string.
+  String codegenMode;
+
+  /// What is the path to the package for which we are generating.
+  ///
+  /// The exact format of this string depends on the generation mechanism,
+  /// so it's left undefined.
+  String package;
+
+  get releaseMode => codegenMode == 'release';
+
+  bool get jsonMode => false;
+
+  /// Holds the generated translations.
+  StringBuffer output = new StringBuffer();
+
+  void clearOutput() {
+    output = new StringBuffer();
+  }
+
+  /// Generate a file <[generated_file_prefix]>_messages_<[locale]>.dart
+  /// for the [translations] in [locale] and put it in [targetDir].
+  void generateIndividualMessageFile(String basicLocale,
+      Iterable<TranslatedMessage> translations, String targetDir) {
+    clearOutput();
+    var locale = new MainMessage()
+        .escapeAndValidateString(Intl.canonicalizedLocale(basicLocale));
+    output.write(prologue(locale));
+    // Exclude messages with no translation and translations with no matching
+    // original message (e.g. if we're using some messages from a larger
+    // catalog)
+    var usableTranslations = translations
+        .where((each) => each.originalMessages != null && each.message != null)
+        .toList();
+    for (var each in usableTranslations) {
+      for (var original in each.originalMessages) {
+        original.addTranslation(locale, each.message);
+      }
+    }
+    usableTranslations.sort((a, b) =>
+        a.originalMessages.first.name.compareTo(b.originalMessages.first.name));
+
+    writeTranslations(usableTranslations, locale);
+
+    // To preserve compatibility, we don't use the canonical version of the
+    // locale in the file name.
+    var filename = path.join(
+        targetDir, "${generatedFilePrefix}messages_$basicLocale.dart");
+    new File(filename).writeAsStringSync(output.toString());
+  }
+
+  /// Write out the translated forms.
+  void writeTranslations(
+      Iterable<TranslatedMessage> usableTranslations, String locale) {
+    for (var translation in usableTranslations) {
+      // Some messages we generate as methods in this class. Simpler ones
+      // we inline in the map from names to messages.
+      var messagesThatNeedMethods = translation.originalMessages
+          .where((each) => _hasArguments(each))
+          .toSet()
+          .toList();
+      for (var original in messagesThatNeedMethods) {
+        output
+          ..write("  ")
+          ..write(
+              original.toCodeForLocale(locale, _methodNameFor(original.name)))
+          ..write("\n\n");
+      }
+    }
+    output.write(messagesDeclaration);
+
+    // Now write the map of names to either the direct translation or to a
+    // method.
+    var entries = (usableTranslations
+            .expand((translation) => translation.originalMessages)
+            .toSet()
+            .toList()
+              ..sort((a, b) => a.name.compareTo(b.name)))
+        .map((original) =>
+            '    "${original.escapeAndValidateString(original.name)}" '
+            ': ${_mapReference(original, locale)}');
+    output..write(entries.join(",\n"))..write("\n  };\n}\n");
+  }
+
+  /// Any additional imports the individual message files need.
+  String get extraImports => '';
+
+  String get messagesDeclaration =>
+      // Includes some gyrations to prevent parts of the deferred libraries from
+      // being inlined into the main one, defeating the space savings. Issue
+      // 24356
+      """
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+""";
+
+  /// [generateIndividualMessageFile] for the beginning of the file,
+  /// parameterized by [locale].
+  String prologue(String locale) =>
+      """
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a $locale locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+import 'package:$intlImportPath/intl.dart';
+import 'package:$intlImportPath/message_lookup_by_library.dart';
+$extraImports
+// ignore: unnecessary_new
+final messages = new MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  get localeName => '$locale';
+
+""" +
+      (releaseMode ? overrideLookup : "");
+
+  String overrideLookup = """
+  String lookupMessage(
+      String message_str, String locale, String name, List args, String meaning,
+      {MessageIfAbsent ifAbsent}) {
+    String failedLookup(String message_str, List args) {
+      // If there's no message_str, then we are an internal lookup, e.g. an
+      // embedded plural, and shouldn't fail.
+      if (message_str == null) return null;
+      // ignore: unnecessary_new
+      throw new UnsupportedError(
+          "No translation found for message '\$name',\\n"
+          "  original text '\$message_str'");
+    }
+    return super.lookupMessage(message_str, locale, name, args, meaning,
+        ifAbsent: ifAbsent ?? failedLookup);
+  }
+
+""";
+
+  /// This section generates the messages_all.dart file based on the list of
+  /// [allLocales].
+  String generateMainImportFile() {
+    clearOutput();
+    output.write(mainPrologue);
+    for (var locale in allLocales) {
+      var baseFile = '${generatedFilePrefix}messages_$locale.dart';
+      var file = importForGeneratedFile(baseFile);
+      output.write("import '$file' ");
+      if (useDeferredLoading) output.write("deferred ");
+      output.write("as ${libraryName(locale)};\n");
+    }
+    output.write("\n");
+    output.write("typedef Future<dynamic> LibraryLoader();\n");
+    output.write("Map<String, LibraryLoader> _deferredLibraries = {\n");
+    for (var rawLocale in allLocales) {
+      var locale = Intl.canonicalizedLocale(rawLocale);
+      var loadOperation = (useDeferredLoading)
+          ? "  '$locale': () => ${libraryName(locale)}.loadLibrary(),\n"
+          : "// ignore: unnecessary_new\n"
+              + "  '$locale': () => new Future.value(null),\n";
+      output.write(loadOperation);
+    }
+    output.write("};\n");
+    output.write("\nMessageLookupByLibrary _findExact(localeName) {\n"
+        "  switch (localeName) {\n");
+    for (var rawLocale in allLocales) {
+      var locale = Intl.canonicalizedLocale(rawLocale);
+      output.write(
+          "    case '$locale':\n      return ${libraryName(locale)}.messages;\n");
+    }
+    output.write(closing);
+    return output.toString();
+  }
+
+  /// Constant string used in [generateMainImportFile] for the beginning of the
+  /// file.
+  get mainPrologue => """
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that looks up messages for specific locales by
+// delegating to the appropriate library.
+
+import 'dart:async';
+
+import 'package:$intlImportPath/intl.dart';
+import 'package:$intlImportPath/message_lookup_by_library.dart';
+// ignore: implementation_imports
+import 'package:$intlImportPath/src/intl_helpers.dart';
+
+""";
+
+  /// Constant string used in [generateMainImportFile] as the end of the file.
+  get closing => """
+    default:\n      return null;
+  }
+}
+
+/// User programs should call this before using [localeName] for messages.
+Future<bool> initializeMessages(String localeName) async {
+  var availableLocale = Intl.verifiedLocale(
+    localeName,
+    (locale) => _deferredLibraries[locale] != null,
+    onFailure: (_) => null);
+  if (availableLocale == null) {
+    // ignore: unnecessary_new
+    return new Future.value(false);
+  }
+  var lib = _deferredLibraries[availableLocale];
+  // ignore: unnecessary_new
+  await (lib == null ? new Future.value(false) : lib());
+  // ignore: unnecessary_new
+  initializeInternalMessageLookup(() => new CompositeMessageLookup());
+  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
+  // ignore: unnecessary_new
+  return new Future.value(true);
+}
+
+bool _messagesExistFor(String locale) {
+  try {
+    return _findExact(locale) != null;
+  } catch (e) {
+    return false;
+  }
+}
+
+MessageLookupByLibrary _findGeneratedMessagesFor(locale) {
+  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
+      onFailure: (_) => null);
+  if (actualLocale == null) return null;
+  return _findExact(actualLocale);
+}
+""";
+}
+
+class JsonMessageGeneration extends MessageGeneration {
+  /// We import the main file so as to get the shared code to evaluate
+  /// the JSON data.
+  String get extraImports => '''
+import 'dart:convert';
+import '${generatedFilePrefix}messages_all.dart' show evaluateJsonTemplate;
+''';
+
+  String prologue(locale) =>
+      super.prologue(locale) +
+      '''
+  String evaluateMessage(translation, List args) {
+    return evaluateJsonTemplate(translation, args);
+  }
+''';
+
+  void writeTranslations(
+      Iterable<TranslatedMessage> usableTranslations, String locale) {
+    output.write(r"""
+  var _messages;
+  // ignore: unnecessary_new
+  get messages => _messages ??= new JsonDecoder().convert(messageText);
+""");
+
+    output.write("  static final messageText = ");
+    var entries = usableTranslations
+        .expand((translation) => translation.originalMessages);
+    var map = {};
+    for (var original in entries) {
+      map[original.name] = original.toJsonForLocale(locale);
+    }
+    output.write(
+        "r'''\n" + new JsonEncoder.withIndent('  ').convert(map) + "''';\n}");
+  }
+
+  get closing =>
+      super.closing +
+      '''
+/// Turn the JSON template into a string.
+///
+/// We expect one of the following forms for the template.
+/// * null -> null
+/// * String s -> s
+/// * int n -> '\${args[n]}'
+/// * List list, one of
+///   * \['Intl.plural', int howMany, (templates for zero, one, ...)\]
+///   * \['Intl.gender', String gender, (templates for female, male, other)\]
+///   * \['Intl.select', String choice, { 'case' : template, ...} \]
+///   * \['text alternating with ', 0 , ' indexes in the argument list'\]
+String evaluateJsonTemplate(Object input, List args) {
+  if (input == null) return null;
+  if (input is String) return input;
+  if (input is int) {
+    return "\${args[input]}";
+  }
+
+  List template = input;
+  var messageName = template.first;
+  if (messageName == "Intl.plural") {
+     var howMany = args[template[1]];
+     return evaluateJsonTemplate(
+         Intl.pluralLogic(
+             howMany,
+             zero: template[2],
+             one: template[3],
+             two: template[4],
+             few: template[5],
+             many: template[6],
+             other: template[7]),
+         args);
+   }
+   if (messageName == "Intl.gender") {
+     var gender = args[template[1]];
+     return evaluateJsonTemplate(
+         Intl.genderLogic(
+             gender,
+             female: template[2],
+             male: template[3],
+             other: template[4]),
+         args);
+   }
+   if (messageName == "Intl.select") {
+     var select = args[template[1]];
+     var choices = template[2];
+     return evaluateJsonTemplate(Intl.selectLogic(select, choices), args);
+   }
+
+   // If we get this far, then we are a basic interpolation, just strings and
+   // ints.
+   // ignore: unnecessary_new
+   var output = new StringBuffer();
+   for (var entry in template) {
+     if (entry is int) {
+       output.write("\${args[entry]}");
+     } else {
+       output.write("\$entry");
+     }
+   }
+   return output.toString();
+  }
+
+ ''';
+}
+
+/// This represents a message and its translation. We assume that the
+/// translation has some identifier that allows us to figure out the original
+/// message it corresponds to, and that it may want to transform the translated
+/// text in some way, e.g. to turn whatever format the translation uses for
+/// variables into a Dart string interpolation. Specific translation mechanisms
+/// are expected to subclass this.
+abstract class TranslatedMessage {
+  /// The identifier for this message. In the simplest case, this is the name
+  /// parameter from the Intl.message call,
+  /// but it can be any identifier that this program and the output of the
+  /// translation can agree on as identifying a message.
+  final String id;
+
+  /// Our translated version of all the [originalMessages].
+  final Message translated;
+
+  /// The original messages that we are a translation of. There can
+  ///  be more than one original message for the same translation.
+  List<MainMessage> _originalMessages;
+
+  List<MainMessage> get originalMessages => _originalMessages;
+  set originalMessages(List<MainMessage> x) {
+    _originalMessages = x;
+  }
+
+  /// For backward compatibility, we still have the originalMessage API.
+  MainMessage get originalMessage => originalMessages.first;
+  set originalMessage(MainMessage m) {
+    originalMessages = [m];
+  }
+
+  TranslatedMessage(this.id, this.translated);
+
+  Message get message => translated;
+
+  toString() => id.toString();
+
+  operator ==(x) => x is TranslatedMessage && x.id == id;
+
+  get hashCode => id.hashCode;
+}
+
+/// We can't use a hyphen in a Dart library name, so convert the locale
+/// separator to an underscore.
+String libraryName(String x) => 'messages_' + x.replaceAll('-', '_');
+
+bool _hasArguments(MainMessage message) => message.arguments.length != 0;
+
+///  Simple messages are printed directly in the map of message names to
+///  functions as a call that returns a lambda. e.g.
+///
+///        "foo" : simpleMessage("This is foo"),
+///
+///  This is helpful for the compiler.
+/// */
+String _mapReference(MainMessage original, String locale) {
+  if (!_hasArguments(original)) {
+    // No parameters, can be printed simply.
+    return 'MessageLookupByLibrary.simpleMessage("'
+        '${original.translations[locale]}")';
+  } else {
+    return _methodNameFor(original.name);
+  }
+}
+
+/// Generated method counter for use in [_methodNameFor].
+int _methodNameCounter = 0;
+
+/// A map from Intl message names to the generated method names
+/// for their translated versions.
+Map<String, String> _internalMethodNames = {};
+
+/// Generate a Dart method name of the form "m<number>".
+String _methodNameFor(String name) {
+  return _internalMethodNames.putIfAbsent(
+      name, () => "m${_methodNameCounter++}");
+}
diff --git a/intl_translation/lib/src/icu_parser.dart b/intl_translation/lib/src/icu_parser.dart
new file mode 100644
index 0000000..4e55b12
--- /dev/null
+++ b/intl_translation/lib/src/icu_parser.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Contains a parser for ICU format plural/gender/select format for localized
+/// messages. See extract_to_arb.dart and make_hardcoded_translation.dart.
+library icu_parser;
+
+import 'package:intl_translation/src/intl_message.dart';
+import 'package:petitparser/petitparser.dart';
+
+/// This defines a grammar for ICU MessageFormat syntax. Usage is
+///       new IcuParser.message.parse(<string>).value;
+/// The "parse" method will return a Success or Failure object which responds
+/// to "value".
+class IcuParser {
+  get openCurly => char("{");
+
+  get closeCurly => char("}");
+  get quotedCurly => (string("'{'") | string("'}'")).map((x) => x[1]);
+
+  get icuEscapedText => quotedCurly | twoSingleQuotes;
+  get curly => (openCurly | closeCurly);
+  get notAllowedInIcuText => curly | char("<");
+  get icuText => notAllowedInIcuText.neg();
+  get notAllowedInNormalText => char("{");
+  get normalText => notAllowedInNormalText.neg();
+  get messageText => (icuEscapedText | icuText).plus().map((x) => x.join());
+  get nonIcuMessageText => normalText.plus().map((x) => x.join());
+  get twoSingleQuotes => string("''").map((x) => "'");
+  get number => digit().plus().flatten().trim().map(int.parse);
+  get id => (letter() & (word() | char("_")).star()).flatten().trim();
+  get comma => char(",").trim();
+
+  /// Given a list of possible keywords, return a rule that accepts any of them.
+  /// e.g., given ["male", "female", "other"], accept any of them.
+  asKeywords(list) => list.map(string).reduce((a, b) => a | b).flatten().trim();
+
+  get pluralKeyword => asKeywords(
+      ["=0", "=1", "=2", "zero", "one", "two", "few", "many", "other"]);
+  get genderKeyword => asKeywords(["female", "male", "other"]);
+
+  var interiorText = undefined();
+
+  get preface => (openCurly & id & comma).map((values) => values[1]);
+
+  get pluralLiteral => string("plural");
+  get pluralClause => (pluralKeyword & openCurly & interiorText & closeCurly)
+      .trim()
+      .map((result) => [result[0], result[2]]);
+  get plural =>
+      preface & pluralLiteral & comma & pluralClause.plus() & closeCurly;
+  get intlPlural =>
+      plural.map((values) => new Plural.from(values.first, values[3], null));
+
+  get selectLiteral => string("select");
+  get genderClause => (genderKeyword & openCurly & interiorText & closeCurly)
+      .trim()
+      .map((result) => [result[0], result[2]]);
+  get gender =>
+      preface & selectLiteral & comma & genderClause.plus() & closeCurly;
+  get intlGender =>
+      gender.map((values) => new Gender.from(values.first, values[3], null));
+  get selectClause =>
+      (id & openCurly & interiorText & closeCurly).map((x) => [x.first, x[2]]);
+  get generalSelect =>
+      preface & selectLiteral & comma & selectClause.plus() & closeCurly;
+  get intlSelect => generalSelect
+      .map((values) => new Select.from(values.first, values[3], null));
+
+  get pluralOrGenderOrSelect => intlPlural | intlGender | intlSelect;
+
+  get contents => pluralOrGenderOrSelect | parameter | messageText;
+  get simpleText => (nonIcuMessageText | parameter | openCurly).plus();
+  get empty => epsilon().map((_) => '');
+
+  get parameter => (openCurly & id & closeCurly)
+      .map((param) => new VariableSubstitution.named(param[1], null));
+
+  /// The primary entry point for parsing. Accepts a string and produces
+  /// a parsed representation of it as a Message.
+  Parser get message => (pluralOrGenderOrSelect | empty)
+      .map((chunk) => Message.from(chunk, null));
+
+  /// Represents an ordinary message, i.e. not a plural/gender/select, although
+  /// it may have parameters.
+  Parser get nonIcuMessage =>
+      (simpleText | empty).map((chunk) => Message.from(chunk, null));
+
+  get stuff => (pluralOrGenderOrSelect | empty)
+      .map((chunk) => Message.from(chunk, null));
+
+  IcuParser() {
+    // There is a cycle here, so we need the explicit set to avoid
+    // infinite recursion.
+    interiorText.set(contents.plus() | empty);
+  }
+}
diff --git a/intl_translation/lib/src/intl_message.dart b/intl_translation/lib/src/intl_message.dart
new file mode 100644
index 0000000..6a636b8
--- /dev/null
+++ b/intl_translation/lib/src/intl_message.dart
@@ -0,0 +1,963 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This provides classes to represent the internal structure of the
+/// arguments to `Intl.message`. It is used when parsing sources to extract
+/// messages or to generate code for message substitution. Normal programs
+/// using Intl would not import this library.
+///
+/// While it's written
+/// in a somewhat abstract way, it has some assumptions about ICU-style
+/// message syntax for parameter substitutions, choices, selects, etc.
+///
+/// For example, if we have the message
+///      plurals(num) => Intl.message("""${Intl.plural(num,
+///          zero : 'Is zero plural?',
+///          one : 'This is singular.',
+///          other : 'This is plural ($num).')
+///         }""",
+///         name: "plurals", args: [num], desc: "Basic plurals");
+/// That is represented as a MainMessage which has only one message component, a
+/// Plural, but also has a name, list of arguments, and a description.
+/// The Plural has three different clauses. The `zero` clause is
+/// a LiteralString containing 'Is zero plural?'. The `other` clause is a
+/// CompositeMessage containing three pieces, a LiteralString for
+/// 'This is plural (', a VariableSubstitution for `num`. amd a LiteralString
+/// for '.)'.
+///
+/// This representation isn't used at runtime. Rather, we read some format
+/// from a translation file, parse it into these objects, and they are then
+/// used to generate the code representation above.
+library intl_message;
+
+import 'dart:convert';
+import 'package:analyzer/analyzer.dart';
+
+/// A default function for the [Message.expanded] method.
+_nullTransform(msg, chunk) => chunk;
+
+const jsonEncoder = const JsonCodec();
+
+/// An abstract superclass for Intl.message/plural/gender calls in the
+/// program's source text. We
+/// assemble these into objects that can be used to write out some translation
+/// format and can also print themselves into code.
+abstract class Message {
+  /// All [Message]s except a [MainMessage] are contained inside some parent,
+  /// terminating at an Intl.message call which supplies the arguments we
+  /// use for variable substitutions.
+  Message parent;
+
+  Message(this.parent);
+
+  /// We find the arguments from the top-level [MainMessage] and use those to
+  /// do variable substitutions. [MainMessage] overrides this to return
+  /// the actual arguments.
+  get arguments => parent == null ? const [] : parent.arguments;
+
+  /// We find the examples from the top-level [MainMessage] and use those
+  /// when writing out variables. [MainMessage] overrides this to return
+  /// the actual examples.
+  get examples => parent == null ? const [] : parent.examples;
+
+  /// The name of the top-level [MainMessage].
+  String get name => parent == null ? '<unnamed>' : parent.name;
+
+  static final _evaluator = new ConstantEvaluator();
+
+  String _evaluateAsString(expression) {
+    var result = expression.accept(_evaluator);
+    if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! String) {
+      return null;
+    } else {
+      return result;
+    }
+  }
+
+  Map _evaluateAsMap(expression) {
+    var result = expression.accept(_evaluator);
+    if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! Map) {
+      return null;
+    } else {
+      return result;
+    }
+  }
+
+  /// Verify that the args argument matches the method parameters and
+  /// isn't, e.g. passing string names instead of the argument values.
+  bool checkArgs(NamedExpression args, List<String> parameterNames) {
+    if (args == null) return true;
+    // Detect cases where args passes invalid names, either literal strings
+    // instead of identifiers, or in the wrong order, missing values, etc.
+    ListLiteral identifiers = args.childEntities.last;
+    if (!identifiers.elements.every((each) => each is SimpleIdentifier)) {
+      return false;
+    }
+    var names = identifiers.elements
+        .map((each) => (each as SimpleIdentifier).name)
+        .toList();
+    var both;
+    try {
+      both = new Map.fromIterables(names, parameterNames);
+    } catch (e) {
+      // Most likely because sizes don't match.
+      return false;
+    }
+    var everythingMatches = true;
+    both.forEach((name, parameterName) {
+      if (name != parameterName) everythingMatches = false;
+    });
+    return everythingMatches;
+  }
+
+  /// Verify that this looks like a correct
+  /// Intl.message/plural/gender/... invocation.
+  ///
+  /// We expect an invocation like
+  ///
+  ///       outerName(x) => Intl.message("foo \$x", ...)
+  ///
+  /// The [node] parameter is the Intl.message invocation node in the AST,
+  /// [arguments] is the list of arguments to that node (also reachable as
+  /// node.argumentList.arguments), [outerName] is the name of the containing
+  /// function, e.g. "outerName" in this case and [outerArgs] is the list of
+  /// arguments to that function. Of the optional parameters
+  /// [nameAndArgsGenerated] indicates if we are generating names and arguments
+  /// while rewriting the code in the transformer or a development-time rewrite,
+  /// so we should not expect them to be present. The [examplesRequired]
+  /// parameter indicates if we will fail if parameter examples are not provided
+  /// for messages with parameters.
+  String checkValidity(MethodInvocation node, List arguments, String outerName,
+      FormalParameterList outerArgs,
+      {bool nameAndArgsGenerated: false, bool examplesRequired: false}) {
+    // If we have parameters, we must specify args and name.
+    NamedExpression args = arguments.firstWhere(
+        (each) => each is NamedExpression && each.name.label.name == 'args',
+        orElse: () => null);
+    var parameterNames =
+        outerArgs.parameters.map((x) => x.identifier.name).toList();
+    var hasArgs = args != null;
+    var hasParameters = !outerArgs.parameters.isEmpty;
+    if (!nameAndArgsGenerated && !hasArgs && hasParameters) {
+      return "The 'args' argument for Intl.message must be specified for "
+          "messages with parameters. Consider using rewrite_intl_messages.dart";
+    }
+    if (!checkArgs(args, parameterNames)) {
+      return "The 'args' argument must match the message arguments,"
+          " e.g. args: ${parameterNames}";
+    }
+    var messageNameArgument = arguments.firstWhere(
+        (eachArg) =>
+            eachArg is NamedExpression && eachArg.name.label.name == 'name',
+        orElse: () => null);
+    var nameExpression = messageNameArgument?.expression;
+    String messageName;
+    String givenName;
+
+    //TODO(alanknight): If we generalize this to messages with parameters
+    // this check will need to change.
+    if (nameExpression == null) {
+      if (!hasParameters) {
+        // No name supplied, no parameters. Use the message as the name.
+        messageName = _evaluateAsString(arguments[0]);
+        outerName = messageName;
+      } else {
+        // We have no name and parameters, but the transformer generates the
+        // name.
+        if (nameAndArgsGenerated) {
+          givenName = outerName;
+          messageName = givenName;
+        } else {
+          return "The 'name' argument for Intl.message must be supplied for "
+              "messages with parameters. Consider using "
+              "rewrite_intl_messages.dart";
+        }
+      }
+    } else {
+      // Name argument is supplied, use it.
+      givenName = _evaluateAsString(nameExpression);
+      messageName = givenName;
+    }
+
+    if (messageName == null) {
+      return "The 'name' argument for Intl.message must be a string literal";
+    }
+
+    var hasOuterName = outerName != null;
+    var simpleMatch = outerName == givenName || givenName == null;
+
+    var classPlusMethod = Message.classPlusMethodName(node, outerName);
+    var classMatch = classPlusMethod != null && (givenName == classPlusMethod);
+    if (!(hasOuterName && (simpleMatch || classMatch))) {
+      return "The 'name' argument for Intl.message must match either "
+          "the name of the containing function or <ClassName>_<methodName> ("
+          "was '$givenName' but must be '$outerName'  or '$classPlusMethod')";
+    }
+
+    var simpleArguments = arguments.where((each) =>
+        each is NamedExpression &&
+        ["desc", "name"].contains(each.name.label.name));
+    var values = simpleArguments.map((each) => each.expression).toList();
+    for (var arg in values) {
+      if (_evaluateAsString(arg) == null) {
+        return ("Intl.message arguments must be string literals: $arg");
+      }
+    }
+
+    if (hasParameters) {
+      var exampleArg = arguments.where((each) =>
+          each is NamedExpression && each.name.label.name == "examples");
+      var examples = exampleArg.map((each) => each.expression).toList();
+      if (examples.isEmpty && examplesRequired) {
+        return "Examples must be provided for messages with parameters";
+      }
+      if (examples.isNotEmpty) {
+        var example = examples.first;
+        var map = _evaluateAsMap(example);
+        if (map == null) {
+          return "Examples must be a const Map literal.";
+        }
+        if (example.constKeyword == null) {
+          return "Examples must be const.";
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /// Verify that a constructed message is valid.
+  ///
+  /// This is called after the message has already been built, as opposed
+  /// to checkValidity which is called before creation. It can be used to
+  /// validate conditions that can just be checked against the result,
+  /// and/or are simpler to check there than on the AST nodes. For example,
+  /// is a required clause like "other" included, or are there examples
+  /// for all of the parameters. It should throw an
+  /// IntlMessageExtractionException for errors.
+  void validate() {}
+
+  /// Return the name of the enclosing class (if any) plus method name, or null
+  /// if there's no enclosing class.
+  ///
+  /// For a method foo in class Bar we allow either "foo" or "Bar_Foo" as the
+  /// name.
+  static String classPlusMethodName(MethodInvocation node, String outerName) {
+    ClassOrMixinDeclaration classNode(n) {
+      if (n == null) return null;
+      if (n is ClassOrMixinDeclaration) return n;
+      return classNode(n.parent);
+    }
+
+    var classDeclaration = classNode(node);
+    return classDeclaration == null
+        ? null
+        : "${classDeclaration.name.token}_$outerName";
+  }
+
+  /// Turn a value, typically read from a translation file or created out of an
+  /// AST for a source program, into the appropriate
+  /// subclass. We expect to get literal Strings, variable substitutions
+  /// represented by integers, things that are already MessageChunks and
+  /// lists of the same.
+  static Message from(Object value, Message parent) {
+    if (value is String) return new LiteralString(value, parent);
+    if (value is int) return new VariableSubstitution(value, parent);
+    if (value is List) {
+      if (value.length == 1) return Message.from(value[0], parent);
+      var result = new CompositeMessage([], parent);
+      var items = value.map((x) => from(x, result)).toList();
+      result.pieces.addAll(items);
+      return result;
+    }
+    // We assume this is already a Message.
+    Message mustBeAMessage = value;
+    mustBeAMessage.parent = parent;
+    return mustBeAMessage;
+  }
+
+  /// Return a string representation of this message for use in generated Dart
+  /// code.
+  String toCode();
+
+  /// Return a JSON-storable representation of this message which can be
+  /// interpolated at runtime.
+  Object toJson();
+
+  /// Escape the string for use in generated Dart code.
+  String escapeAndValidateString(String value) {
+    const Map<String, String> escapes = const {
+      r"\": r"\\",
+      '"': r'\"',
+      "\b": r"\b",
+      "\f": r"\f",
+      "\n": r"\n",
+      "\r": r"\r",
+      "\t": r"\t",
+      "\v": r"\v",
+      "'": r"\'",
+      r"$": r"\$"
+    };
+
+    String _escape(String s) => escapes[s] ?? s;
+
+    var escaped = value.splitMapJoin("", onNonMatch: _escape);
+    return escaped;
+  }
+
+  /// Expand this string out into a printed form. The function [f] will be
+  /// applied to any sub-messages, allowing this to be used to generate a form
+  /// suitable for a wide variety of translation file formats.
+  String expanded([Function f]);
+}
+
+/// Abstract class for messages with internal structure, representing the
+/// main Intl.message call, plurals, and genders.
+abstract class ComplexMessage extends Message {
+  ComplexMessage(parent) : super(parent);
+
+  /// When we create these from strings or from AST nodes, we want to look up
+  /// and set their attributes by string names, so we override the indexing
+  /// operators so that they behave like maps with respect to those attribute
+  /// names.
+  operator [](String x);
+
+  /// When we create these from strings or from AST nodes, we want to look up
+  /// and set their attributes by string names, so we override the indexing
+  /// operators so that they behave like maps with respect to those attribute
+  /// names.
+  operator []=(String x, y);
+
+  List<String> get attributeNames;
+
+  /// Return the name of the message type, as it will be generated into an
+  /// ICU-type format. e.g. choice, select
+  String get icuMessageName;
+
+  /// Return the message name we would use for this when doing Dart code
+  /// generation, e.g. "Intl.plural".
+  String get dartMessageName;
+}
+
+/// This represents a message chunk that is a list of multiple sub-pieces,
+/// each of which is in turn a [Message].
+class CompositeMessage extends Message {
+  List<Message> pieces;
+
+  CompositeMessage.withParent(parent) : super(parent);
+  CompositeMessage(this.pieces, ComplexMessage parent) : super(parent) {
+    pieces.forEach((x) => x.parent = this);
+  }
+  toCode() => pieces.map((each) => each.toCode()).join('');
+  toJson() => pieces.map((each) => each.toJson()).toList();
+  toString() => "CompositeMessage(" + pieces.toString() + ")";
+  String expanded([Function f = _nullTransform]) =>
+      pieces.map((chunk) => f(this, chunk)).join("");
+}
+
+/// Represents a simple constant string with no dynamic elements.
+class LiteralString extends Message {
+  String string;
+  LiteralString(this.string, Message parent) : super(parent);
+  toCode() => escapeAndValidateString(string);
+  toJson() => string;
+  toString() => "Literal($string)";
+  String expanded([Function f = _nullTransform]) => f(this, string);
+}
+
+/// Represents an interpolation of a variable value in a message. We expect
+/// this to be specified as an [index] into the list of variables, or else
+/// as the name of a variable that exists in [arguments] and we will
+/// compute the variable name or the index based on the value of the other.
+class VariableSubstitution extends Message {
+  VariableSubstitution(this._index, Message parent) : super(parent);
+
+  /// Create a substitution based on the name rather than the index. The name
+  /// may have been used as all upper-case in the translation tool, so we
+  /// save it separately and look it up case-insensitively once the parent
+  /// (and its arguments) are definitely available.
+  VariableSubstitution.named(String name, Message parent) : super(parent) {
+    _variableNameUpper = name.toUpperCase();
+  }
+
+  /// The index in the list of parameters of the containing function.
+  int _index;
+  int get index {
+    if (_index != null) return _index;
+    if (arguments.isEmpty) return null;
+    // We may have been given an all-uppercase version of the name, so compare
+    // case-insensitive.
+    _index = arguments
+        .map((x) => x.toUpperCase())
+        .toList()
+        .indexOf(_variableNameUpper);
+    if (_index == -1) {
+      throw new ArgumentError(
+          "Cannot find parameter named '$_variableNameUpper' in "
+          "message named '$name'. Available "
+          "parameters are $arguments");
+    }
+    return _index;
+  }
+
+  /// The variable name we get from parsing. This may be an all uppercase
+  /// version of the Dart argument name.
+  String _variableNameUpper;
+
+  /// The name of the variable in the parameter list of the containing function.
+  /// Used when generating code for the interpolation.
+  String get variableName =>
+      _variableName == null ? _variableName = arguments[index] : _variableName;
+  String _variableName;
+  // Although we only allow simple variable references, we always enclose them
+  // in curly braces so that there's no possibility of ambiguity with
+  // surrounding text.
+  toCode() => "\${${variableName}}";
+  toJson() => index;
+  toString() => "VariableSubstitution($index)";
+  String expanded([Function f = _nullTransform]) => f(this, index);
+}
+
+class MainMessage extends ComplexMessage {
+  MainMessage() : super(null);
+
+  /// All the pieces of the message. When we go to print, these will
+  /// all be expanded appropriately. The exact form depends on what we're
+  /// printing it for See [expanded], [toCode].
+  List<Message> messagePieces = [];
+
+  /// The position in the source at which this message starts.
+  int sourcePosition;
+
+  /// The position in the source at which this message ends.
+  int endPosition;
+
+  /// Verify that this looks like a correct Intl.message invocation.
+  String checkValidity(MethodInvocation node, List arguments, String outerName,
+      FormalParameterList outerArgs,
+      {bool nameAndArgsGenerated: false, bool examplesRequired: false}) {
+    if (arguments.first is! StringLiteral) {
+      return "Intl.message messages must be string literals";
+    }
+
+    return super.checkValidity(node, arguments, outerName, outerArgs,
+        nameAndArgsGenerated: nameAndArgsGenerated,
+        examplesRequired: examplesRequired);
+  }
+
+  void addPieces(List<Object> messages) {
+    for (var each in messages) {
+      messagePieces.add(Message.from(each, this));
+    }
+  }
+
+  void validateDescription() {
+    if (description == null) {
+      throw new IntlMessageExtractionException(
+          "Missing description for message $this");
+    }
+  }
+
+  /// The description provided in the Intl.message call.
+  String description;
+
+  /// The examples from the Intl.message call
+  Map<String, dynamic> examples;
+
+  /// A field to disambiguate two messages that might have exactly the
+  /// same text. The two messages will also need different names, but
+  /// this can be used by machine translation tools to distinguish them.
+  String meaning;
+
+  /// The name, which may come from the function name, from the arguments
+  /// to Intl.message, or we may just re-use the message.
+  String _name;
+
+  /// A placeholder for any other identifier that the translation format
+  /// may want to use.
+  String id;
+
+  /// The arguments list from the Intl.message call.
+  List<String> arguments;
+
+  /// The locale argument from the Intl.message call
+  String locale;
+
+  /// Whether extraction skip outputting this message.
+  ///
+  /// For example, this could be used to define messages whose purpose is known,
+  /// but whose text isn't final yet and shouldn't be sent for translation.
+  bool skip = false;
+
+  /// When generating code, we store translations for each locale
+  /// associated with the original message.
+  Map<String, String> translations = {};
+  Map<String, Object> jsonTranslations = {};
+
+  /// If the message was not given a name, we use the entire message string as
+  /// the name.
+  String get name => _name ?? "";
+  set name(String newName) {
+    _name = newName;
+  }
+
+  /// Does this message have an assigned name.
+  bool get hasName => _name != null;
+
+  /// Return the full message, with any interpolation expressions transformed
+  /// by [f] and all the results concatenated. The chunk argument to [f] may be
+  /// either a String, an int or an object representing a more complex
+  /// message entity.
+  /// See [messagePieces].
+  String expanded([Function f = _nullTransform]) =>
+      messagePieces.map((chunk) => f(this, chunk)).join("");
+
+  /// Record the translation for this message in the given locale, after
+  /// suitably escaping it.
+  void addTranslation(String locale, Message translated) {
+    translated.parent = this;
+    translations[locale] = translated.toCode();
+    jsonTranslations[locale] = translated.toJson();
+  }
+
+  toCode() =>
+      throw new UnsupportedError("MainMessage.toCode requires a locale");
+  toJson() =>
+      throw new UnsupportedError("MainMessage.toJson requires a locale");
+
+  /// Generate code for this message, expecting it to be part of a map
+  /// keyed by name with values the function that calls Intl.message.
+  String toCodeForLocale(String locale, String name) {
+    var out = new StringBuffer()
+      ..write('static $name(')
+      ..write(arguments.join(", "))
+      ..write(') => "')
+      ..write(translations[locale])
+      ..write('";');
+    return out.toString();
+  }
+
+  /// Return a JSON string representation of this message.
+  toJsonForLocale(String locale) {
+    return jsonTranslations[locale];
+  }
+
+  turnInterpolationBackIntoStringForm(Message message, chunk) {
+    if (chunk is String) return escapeAndValidateString(chunk);
+    if (chunk is int) return r"${" + message.arguments[chunk] + "}";
+    if (chunk is Message) return chunk.toCode();
+    throw new ArgumentError.value(chunk, "Unexpected value in Intl.message");
+  }
+
+  /// Create a string that will recreate this message, optionally
+  /// including the compile-time only information desc and examples.
+  String toOriginalCode({bool includeDesc: true, includeExamples: true}) {
+    var out = new StringBuffer()..write("Intl.message('");
+    out.write(expanded(turnInterpolationBackIntoStringForm));
+    out.write("', ");
+    out.write("name: '$name', ");
+    out.write(locale == null ? "" : "locale: '$locale', ");
+    if (includeDesc) {
+      out.write(description == null
+          ? ""
+          : "desc: '${escapeAndValidateString(description)}', ");
+    }
+    if (includeExamples) {
+      // json is already mostly-escaped, but we need to handle interpolations.
+      var json = jsonEncoder.encode(examples).replaceAll(r"$", r"\$");
+      out.write(examples == null ? "" : "examples: const ${json}, ");
+    }
+    out.write(meaning == null
+        ? ""
+        : "meaning: '${escapeAndValidateString(meaning)}', ");
+    out.write("args: [${arguments.join(', ')}]");
+    out.write(")");
+    return out.toString();
+  }
+
+  /// The AST node will have the attribute names as strings, so we translate
+  /// between those and the fields of the class.
+  void operator []=(String attributeName, value) {
+    switch (attributeName) {
+      case "desc":
+        description = value;
+        return;
+      case "examples":
+        examples = value as Map<String, dynamic>;
+        return;
+      case "name":
+        name = value;
+        return;
+      // We use the actual args from the parser rather than what's given in the
+      // arguments to Intl.message.
+      case "args":
+        return;
+      case "meaning":
+        meaning = value;
+        return;
+      case "locale":
+        locale = value;
+        return;
+      case "skip":
+        skip = value as bool;
+        return;
+      default:
+        return;
+    }
+  }
+
+  /// The AST node will have the attribute names as strings, so we translate
+  /// between those and the fields of the class.
+  operator [](String attributeName) {
+    switch (attributeName) {
+      case "desc":
+        return description;
+      case "examples":
+        return examples;
+      case "name":
+        return name;
+      // We use the actual args from the parser rather than what's given in the
+      // arguments to Intl.message.
+      case "args":
+        return [];
+      case "meaning":
+        return meaning;
+      case "skip":
+        return skip;
+      default:
+        return null;
+    }
+  }
+
+  // This is the top-level construct, so there's no meaningful ICU name.
+  get icuMessageName => '';
+
+  get dartMessageName => "message";
+
+  /// The parameters that the Intl.message call may provide.
+  get attributeNames =>
+      const ["name", "desc", "examples", "args", "meaning", "skip"];
+
+  String toString() =>
+      "Intl.message(${expanded()}, $name, $description, $examples, $arguments)";
+}
+
+/// An abstract class to represent sub-sections of a message, primarily
+/// plurals and genders.
+abstract class SubMessage extends ComplexMessage {
+  SubMessage() : super(null);
+
+  /// Creates the sub-message, given a list of [clauses] in the sort of form
+  /// that we're likely to get them from parsing a translation file format,
+  /// as a list of [key, value] where value may in turn be a list.
+  SubMessage.from(this.mainArgument, List clauses, parent) : super(parent) {
+    for (var clause in clauses) {
+      this[clause.first] = (clause.last is List) ? clause.last : [clause.last];
+    }
+  }
+
+  toString() => expanded();
+
+  /// The name of the main argument, which is expected to have the value which
+  /// is one of [attributeNames] and is used to decide which clause to use.
+  String mainArgument;
+
+  /// Return the arguments that affect this SubMessage as a map of
+  /// argument names and values.
+  Map argumentsOfInterestFor(MethodInvocation node) {
+    var basicArguments = node.argumentList.arguments;
+    var others = basicArguments.where((each) => each is NamedExpression);
+    return new Map.fromIterable(others,
+        key: (node) => node.name.label.token.value(),
+        value: (node) => node.expression);
+  }
+
+  /// Return the list of attribute names to use when generating code. This
+  ///  may be different from [attributeNames] if there are multiple aliases
+  ///  that map to the same clause.
+  List<String> get codeAttributeNames;
+
+  String expanded([Function transform = _nullTransform]) {
+    fullMessageForClause(String key) =>
+        key + '{' + transform(parent, this[key]).toString() + '}';
+    var clauses = attributeNames
+        .where((key) => this[key] != null)
+        .map(fullMessageForClause)
+        .toList();
+    return "{$mainArgument,$icuMessageName, ${clauses.join("")}}";
+  }
+
+  String toCode() {
+    var out = new StringBuffer();
+    out.write('\${');
+    out.write(dartMessageName);
+    out.write('(');
+    out.write(mainArgument);
+    var args = codeAttributeNames.where((attribute) => this[attribute] != null);
+    args.fold(
+        out, (buffer, arg) => buffer..write(", $arg: '${this[arg].toCode()}'"));
+    out.write(")}");
+    return out.toString();
+  }
+
+  /// We represent this in JSON as a list with [dartMessageName], the index in
+  /// the arguments list at which we will find the main argument (e.g. howMany
+  /// for a plural), and then the values of all the possible arguments, in the
+  /// order that they appear in codeAttributeNames. Any missing arguments are
+  /// saved as an explicit null.
+  toJson() {
+    var json = [];
+    json.add(dartMessageName);
+    json.add(arguments.indexOf(mainArgument));
+    for (var arg in codeAttributeNames) {
+      json.add(this[arg]?.toJson());
+    }
+    return json;
+  }
+}
+
+/// Represents a message send of [Intl.gender] inside a message that is to
+/// be internationalized. This corresponds to an ICU message syntax "select"
+/// with "male", "female", and "other" as the possible options.
+class Gender extends SubMessage {
+  Gender();
+
+  /// Create a new Gender providing [mainArgument] and the list of possible
+  /// clauses. Each clause is expected to be a list whose first element is a
+  /// variable name and whose second element is either a [String] or
+  /// a list of strings and [Message] or [VariableSubstitution].
+  Gender.from(String mainArgument, List clauses, Message parent)
+      : super.from(mainArgument, clauses, parent);
+
+  Message female;
+  Message male;
+  Message other;
+
+  String get icuMessageName => "select";
+  String get dartMessageName => 'Intl.gender';
+
+  get attributeNames => ["female", "male", "other"];
+  get codeAttributeNames => attributeNames;
+
+  /// The node will have the attribute names as strings, so we translate
+  /// between those and the fields of the class.
+  void operator []=(String attributeName, rawValue) {
+    var value = Message.from(rawValue, this);
+    switch (attributeName) {
+      case "female":
+        female = value;
+        return;
+      case "male":
+        male = value;
+        return;
+      case "other":
+        other = value;
+        return;
+      default:
+        return;
+    }
+  }
+
+  Message operator [](String attributeName) {
+    switch (attributeName) {
+      case "female":
+        return female;
+      case "male":
+        return male;
+      case "other":
+        return other;
+      default:
+        return other;
+    }
+  }
+}
+
+class Plural extends SubMessage {
+  Plural();
+  Plural.from(String mainArgument, List clauses, Message parent)
+      : super.from(mainArgument, clauses, parent);
+
+  Message zero;
+  Message one;
+  Message two;
+  Message few;
+  Message many;
+  Message other;
+
+  String get icuMessageName => "plural";
+  String get dartMessageName => "Intl.plural";
+
+  get attributeNames => ["=0", "=1", "=2", "few", "many", "other"];
+  get codeAttributeNames => ["zero", "one", "two", "few", "many", "other"];
+
+  /// The node will have the attribute names as strings, so we translate
+  /// between those and the fields of the class.
+  void operator []=(String attributeName, rawValue) {
+    var value = Message.from(rawValue, this);
+    switch (attributeName) {
+      case "zero":
+        // We prefer an explicit "=0" clause to a "ZERO"
+        // if both are present.
+        if (zero == null) zero = value;
+        return;
+      case "=0":
+        zero = value;
+        return;
+      case "one":
+        // We prefer an explicit "=1" clause to a "ONE"
+        // if both are present.
+        if (one == null) one = value;
+        return;
+      case "=1":
+        one = value;
+        return;
+      case "two":
+        // We prefer an explicit "=2" clause to a "TWO"
+        // if both are present.
+        if (two == null) two = value;
+        return;
+      case "=2":
+        two = value;
+        return;
+      case "few":
+        few = value;
+        return;
+      case "many":
+        many = value;
+        return;
+      case "other":
+        other = value;
+        return;
+      default:
+        return;
+    }
+  }
+
+  Message operator [](String attributeName) {
+    switch (attributeName) {
+      case "zero":
+        return zero;
+      case "=0":
+        return zero;
+      case "one":
+        return one;
+      case "=1":
+        return one;
+      case "two":
+        return two;
+      case "=2":
+        return two;
+      case "few":
+        return few;
+      case "many":
+        return many;
+      case "other":
+        return other;
+      default:
+        return other;
+    }
+  }
+}
+
+/// Represents a message send of [Intl.select] inside a message that is to
+/// be internationalized. This corresponds to an ICU message syntax "select"
+/// with arbitrary options.
+class Select extends SubMessage {
+  Select();
+
+  /// Create a new [Select] providing [mainArgument] and the list of possible
+  /// clauses. Each clause is expected to be a list whose first element is a
+  /// variable name and whose second element is either a String or
+  /// a list of strings and [Message]s or [VariableSubstitution]s.
+  Select.from(String mainArgument, List clauses, Message parent)
+      : super.from(mainArgument, clauses, parent);
+
+  Map<String, Message> cases = new Map<String, Message>();
+
+  String get icuMessageName => "select";
+  String get dartMessageName => 'Intl.select';
+
+  get attributeNames => cases.keys.toList();
+  get codeAttributeNames => attributeNames;
+
+  // Check for valid select keys.
+  // See http://site.icu-project.org/design/formatting/select
+  static const selectPattern = '[a-zA-Z][a-zA-Z0-9_-]*';
+  static final validSelectKey = new RegExp(selectPattern);
+
+  void operator []=(String attributeName, rawValue) {
+    var value = Message.from(rawValue, this);
+    if (validSelectKey.stringMatch(attributeName) == attributeName) {
+      cases[attributeName] = value;
+    } else {
+      throw new IntlMessageExtractionException(
+          "Invalid select keyword: '$attributeName', must "
+          "match '$selectPattern'");
+    }
+  }
+
+  Message operator [](String attributeName) {
+    var exact = cases[attributeName];
+    return exact == null ? cases["other"] : exact;
+  }
+
+  /// Return the arguments that we care about for the select. In this
+  /// case they will all be passed in as a Map rather than as the named
+  /// arguments used in Plural/Gender.
+  Map argumentsOfInterestFor(MethodInvocation node) {
+    MapLiteral casesArgument = node.argumentList.arguments[1];
+    return new Map.fromIterable(casesArgument.entries,
+        key: (node) => node.key.value, value: (node) => node.value);
+  }
+
+  void validate() {
+    if (this["other"] == null) {
+      throw new IntlMessageExtractionException(
+          "Missing keyword other for Intl.select $this");
+    }
+  }
+
+  /// Write out the generated representation of this message. This differs
+  /// from Plural/Gender in that it prints a literal map rather than
+  /// named arguments.
+  String toCode() {
+    var out = new StringBuffer();
+    out.write('\${');
+    out.write(dartMessageName);
+    out.write('(');
+    out.write(mainArgument);
+    var args = codeAttributeNames;
+    out.write(", {");
+    args.fold(out,
+        (buffer, arg) => buffer..write("'$arg': '${this[arg].toCode()}', "));
+    out.write("})}");
+    return out.toString();
+  }
+
+  /// We represent this in JSON as a List with the name of the message
+  /// (e.g. Intl.select), the index in the arguments list of the main argument,
+  /// and then a Map from the cases to the List of strings or sub-messages.
+  toJson() {
+    var json = [];
+    json.add(dartMessageName);
+    json.add(arguments.indexOf(mainArgument));
+    var attributes = {};
+    for (var arg in codeAttributeNames) {
+      attributes[arg] = this[arg].toJson();
+    }
+    json.add(attributes);
+    return json;
+  }
+}
+
+/// Exception thrown when we cannot process a message properly.
+class IntlMessageExtractionException implements Exception {
+  /// A message describing the error.
+  final String message;
+
+  /// Creates a new exception with an optional error [message].
+  const IntlMessageExtractionException([this.message = ""]);
+
+  String toString() => "IntlMessageExtractionException: $message";
+}
diff --git a/intl_translation/lib/src/message_rewriter.dart b/intl_translation/lib/src/message_rewriter.dart
new file mode 100644
index 0000000..470386a
--- /dev/null
+++ b/intl_translation/lib/src/message_rewriter.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Code to rewrite Intl.message calls adding the name and args parameters
+/// automatically, primarily used by the transformer.
+import 'package:analyzer/analyzer.dart';
+
+import 'package:intl_translation/extract_messages.dart';
+
+/// Rewrite all Intl.message/plural/etc. calls in [source], adding "name"
+/// and "args" parameters if they are not provided.
+///
+/// Return the modified source code. If there are errors parsing, list
+/// [sourceName] in the error message.
+String rewriteMessages(String source, String sourceName,
+    {useStringSubstitution: false}) {
+  var messages = findMessages(source, sourceName);
+  messages.sort((a, b) => a.sourcePosition.compareTo(b.sourcePosition));
+
+  var start = 0;
+  var newSource = new StringBuffer();
+  for (var message in messages) {
+    if (message.arguments.isNotEmpty) {
+      newSource.write(source.substring(start, message.sourcePosition));
+      if (useStringSubstitution) {
+        rewriteWithStringSubstitution(newSource, source, start, message);
+      } else {
+        rewriteRegenerating(newSource, source, start, message);
+      }
+      start = message.endPosition;
+    }
+  }
+  newSource.write(source.substring(start));
+  return newSource.toString();
+}
+
+/// Rewrite the message by regenerating from our internal representation.
+///
+/// This may produce uglier source, but is more reliable.
+rewriteRegenerating(StringBuffer newSource, String source, int start, message) {
+  // TODO(alanknight): We could generate more efficient code than the
+  // original here, dispatching more directly to the MessageLookup.
+  newSource.write(message.toOriginalCode());
+}
+
+rewriteWithStringSubstitution(
+    StringBuffer newSource, String source, int start, message) {
+  var originalSource =
+      source.substring(message.sourcePosition, message.endPosition);
+  var closingParen = originalSource.lastIndexOf(')');
+  // This is very ugly, checking to see if name/args is already there by
+  // examining the source string. But at least the failure mode should
+  // be very direct if we end up omitting name or args.
+  var hasName = originalSource.contains(nameCheck);
+  var hasArgs = originalSource.contains(argsCheck);
+  var withName = hasName ? '' : ",\nname: '${message.name}'";
+  var withArgs = hasArgs ? '' : ",\nargs: ${message.arguments}";
+  var nameAndArgs = "$withName$withArgs)";
+  newSource.write(originalSource.substring(0, closingParen));
+  newSource.write(nameAndArgs);
+  // We normally don't have anything after the closing paren, but
+  // be safe.
+  newSource.write(originalSource.substring(closingParen + 1));
+}
+
+final RegExp nameCheck = new RegExp('[\\n,]\\s+name\:');
+final RegExp argsCheck = new RegExp('[\\n,]\\s+args\:');
+
+/// Find all the messages in the [source] text.
+///
+/// Report errors as coming from [sourceName]
+List findMessages(String source, String sourceName,
+    [MessageExtraction extraction]) {
+  extraction = extraction ?? new MessageExtraction();
+  try {
+    extraction.root = parseCompilationUnit(source, name: sourceName);
+  } on AnalyzerErrorGroup catch (e) {
+    extraction
+        .onMessage("Error in parsing $sourceName, no messages extracted.");
+    extraction.onMessage("  $e");
+    return [];
+  }
+  extraction.origin = sourceName;
+  var visitor = new MessageFindingVisitor(extraction);
+  visitor.generateNameAndArgs = true;
+  extraction.root.accept(visitor);
+  return visitor.messages.values.toList();
+}
diff --git a/intl_translation/pubspec.yaml b/intl_translation/pubspec.yaml
new file mode 100644
index 0000000..824c8b7
--- /dev/null
+++ b/intl_translation/pubspec.yaml
@@ -0,0 +1,21 @@
+name: intl_translation
+version: 0.17.2
+author: Dart Team <misc@dartlang.org>
+description: >-
+  Contains code to deal with internationalized/localized messages,
+  date and number formatting and parsing, bi-directional text, and
+  other internationalization issues.
+
+homepage: https://github.com/dart-lang/intl_translation
+environment:
+  sdk: '>=2.0.0-dev.33.0 <3.0.0'
+
+dependencies:
+  analyzer: '>=0.33.0-alpha.0 <0.34.0'
+  args: '>=0.12.1 <2.0.0'
+  dart_style: ^1.0.0
+  intl: '>=0.15.3 <0.16.0'
+  path: '>=0.9.0 <2.0.0'
+  petitparser: '>=1.1.3 < 3.0.0'
+dev_dependencies:
+  test: ^1.2.0