Localizations for Material (#11832)

diff --git a/dev/tools/gen_localizations.dart b/dev/tools/gen_localizations.dart
new file mode 100644
index 0000000..0a7a838
--- /dev/null
+++ b/dev/tools/gen_localizations.dart
@@ -0,0 +1,127 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Given a directory that contains localized ".arb" (application resource
+// bundle) files, generates a Dart "localizations" Map definition that combines
+// the contents of the arb files. The map can be used to lookup a localized
+// string: localizations[localeString][resourceId].
+//
+// See *.arb and localizations.dart in packages/flutter/lib/src/material/i18n/.
+//
+// The arb (JSON) format files must contain a single map indexed by locale.
+// Each map value is itself a map with resource identifier keys and localized
+// resource string values.
+//
+// The arb filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb",
+// where prefix is the 2nd command line argument, lc is a language code and cc
+// is the country code. In most cases both codes are just two characters. A typical
+// filename would be "material_en.arb".
+//
+// This app is typically run by hand when a module's .arb files have been
+// updated.
+//
+// Usage: dart gen_localizations.dart directory prefix
+
+import 'dart:convert' show JSON;
+import 'dart:io';
+
+const String outputHeader = '''
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file has been automatically generated.  Please do not edit it manually.
+// To regenerate the file, use:
+// @(regenerate)
+''';
+
+final Map<String, Map<String, String>> localeToResources = <String, Map<String, String>>{};
+
+// Return s as a Dart-parseable raw string in double quotes. Expand double quotes:
+// foo => r"foo"
+// foo "bar" => r"foo " '"' r"bar" '"'
+String generateString(String s) {
+  if (!s.contains('"'))
+    return 'r"$s"';
+
+  final StringBuffer output = new StringBuffer();
+  bool started = false; // Have we started writing a raw string.
+  for (int i = 0; i < s.length; i++) {
+    if (s[i] == '"') {
+      if (started)
+        output.write('"');
+      output.write(' \'"\' ');
+      started = false;
+    } else if (!started) {
+      output.write('r"${s[i]}');
+      started = true;
+    } else {
+      output.write(s[i]);
+    }
+  }
+  if (started)
+    output.write('"');
+  return output.toString();
+}
+
+String generateLocalizationsMap() {
+  final StringBuffer output = new StringBuffer();
+
+  output.writeln('const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {');
+
+  final String lastLocale = localeToResources.keys.last;
+  for (String locale in localeToResources.keys) {
+    output.writeln('  "$locale": const <String, String>{');
+
+    final Map<String, String> resources = localeToResources[locale];
+    final String lastName = resources.keys.last;
+    for (String name in resources.keys) {
+      final String comma = name == lastName ? "" : ",";
+      final String value = generateString(resources[name]);
+      output.writeln('    "$name": $value$comma');
+    }
+    final String comma = locale == lastLocale ? "" : ",";
+    output.writeln('  }$comma');
+  }
+
+  output.writeln('};');
+  return output.toString();
+}
+
+void processBundle(File file, String locale) {
+  localeToResources[locale] ??= <String, String>{};
+  final Map<String, String> resources = localeToResources[locale];
+  final Map<String, dynamic> bundle = JSON.decode(file.readAsStringSync());
+  for (String key in bundle.keys) {
+    // The ARB file resource "attributes" for foo are called @foo.
+    if (key.startsWith('@'))
+      continue;
+    resources[key] = bundle[key];
+  }
+}
+
+void main(List<String> args) {
+  if (args.length != 2)
+    stderr.writeln('Usage: dart gen_localizations.dart directory prefix');
+
+  // filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb", where prefix
+  // is the 2nd command line argument, lc is a language code and cc is the country
+  // code. In most cases both codes are just two characters.
+
+  final Directory directory = new Directory(args[0]);
+  final String prefix = args[1];
+  final RegExp filenameRE = new RegExp('${prefix}_(\\w+)\\.arb\$');
+
+  for (FileSystemEntity entity in directory.listSync()) {
+    final String path = entity.path;
+    if (FileSystemEntity.isFileSync(path) && filenameRE.hasMatch(path)) {
+      final String locale = filenameRE.firstMatch(path)[1];
+      processBundle(new File(path), locale);
+    }
+  }
+
+  final String regenerate = 'dart gen_localizations ${directory.path} ${args[1]}';
+  print(outputHeader.replaceFirst('@(regenerate)', regenerate));
+  print(generateLocalizationsMap());
+}