Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(303)

Unified Diff: pkg/intl/lib/src/intl_message.dart

Issue 12733003: Adds facilities for extracting Intl.message calls and generating code from translations (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/intl/lib/src/intl_message.dart
diff --git a/pkg/intl/lib/src/intl_message.dart b/pkg/intl/lib/src/intl_message.dart
new file mode 100644
index 0000000000000000000000000000000000000000..aadb4eefee44dfb9252da04eb1edbca16d0a1e49
--- /dev/null
+++ b/pkg/intl/lib/src/intl_message.dart
@@ -0,0 +1,163 @@
+// 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 the class IntlMessage to represent an occurence of
+ * [Intl.message] in a program. It is used when parsing sources to extract
+ * messages or to generate code for message substitution.
+ */
+library intl_message;
+
+/**
+ * Represents an occurence of Intl.message in the program's source text. We
Emily Fortuna 2013/03/13 18:54:43 nit: you have an extra space starting this line of
Alan Knight 2013/03/14 17:49:05 Done. It's 2013, where's my autoformatter?
+ * assemble it into an object that can be used to write out some translation
+ * format and can also print itself into code.
+ */
+class IntlMessage {
+
+ /**
+ * The message, which may be just one string, or may be several strings,
+ * some of which are interpolation expressions. The output format must
+ * determine what to do with the
Emily Fortuna 2013/03/13 18:54:43 unfinished comment
Alan Knight 2013/03/14 17:49:05 Done.
+ */
+ List<String> messagePieces;
+
+ String description;
+
+ /** The examples from the Intl.message call */
+ String examples;
+
+ /** The name, which may come from the function name, from the arguments
Emily Fortuna 2013/03/13 18:54:43 comment formatting. (also, end in period, here and
Alan Knight 2013/03/14 17:49:05 Done.
+ * to Intl.message, or we may just re-use the message
+ */
+ String _name;
+
+ /** The arguments parameter from the Intl.message call */
+ List<String> arguments;
+
+ /**
+ * A placeholder for any other identifier that the translation format
+ * may want to use.
+ */
+ String id;
+
+ /**
+ * When generating code, we store translations for each locale
+ * associated with the original message.
+ */
+ Map<String, String> translations = new Map();
+
+ IntlMessage();
+
+ /**
+ * If the message was not given a name, we use the entire message string as
+ * the name.
+ */
+ get name => _name == null ? computeName() : _name;
+ set name(x) => _name = x;
+ computeName() => name = fullMessage((msg, chunk) => "");
+
+ /**
+ * Return the full message, with any interpolation expressions transformed
+ * by [f] and all the results concatenated.
+ */
+ String fullMessage([Function f]) {
+ var transform = f == null ? (msg, chunk) => chunk : f;
+ var out = new StringBuffer();
+ messagePieces.map((chunk) => transform(this, chunk)).forEach(out.write);
+ return out.toString();
+ }
+
+ /**
+ * The node will have the attribute names as strings, so we translate
+ * between those and the fields of the class.
+ */
+ operator []=(attributeName, value) {
+ switch (attributeName) {
+ case "desc" : description = value; return;
+ case "examples" : examples = value; 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;
+ default: return;
+ }
+ }
+
+ /**
+ * Record the translation for this message in the given locale, after
+ * suitably escaping it.
+ */
+ addTranslation(locale, value) =>
+ translations[locale] = escapeAndValidate(locale, value);
+
+ /**
+ * Escape the string and validate that it doesn't contain any interpolations
+ * more complex than including a simple variable value.
+ */
+ escapeAndValidate(String locale, String s) {
+ const escapes = const {
+ r"\" : r"\\",
+ '"' : r'\"',
+ "\b" : r"\b",
+ "\f" : r"\f",
+ "\n" : r"\n",
+ "\r" : r"\r",
+ "\t" : r"\t",
+ "\v" : r"\v"
+ };
+
+ _escape(String s) => (escapes[s] == null) ? s : escapes[s];
+
+ // We know that we'll be enclosing the string in double-quotes, so we need
+ // to escape those, but not single-quotes. In addition we must escape
+ // backslashes, newlines, and other formatting characters.
+ var escaped = s.splitMapJoin("", onNonMatch: _escape);
+ print(escaped);
+
+ // We don't allow any ${} expressions, only $variable to avoid malicious
+ // code. Disallow any usage of "${". If that makes a false positive
+ // on a translation that legitimate contains "\\${" or other variations,
+ // we'll live with that rather than risk a false negative.
+ var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})");
+ var validMatches = validInterpolations.allMatches(escaped);
+ escapeInvalidMatches(Match m) {
+ var valid = validMatches.any((x) => x.start == m.start);
+ if (valid) {
+ return m.group(0);
+ } else {
+ return "\\${m.group(0)}";
+ }
+ }
+ return escaped.replaceAllMapped("\$", escapeInvalidMatches);
+ }
+
+ /**
+ * Generate code for this message, expecting it to be part of a map
+ * keyed by name with values the function that calls Intl.message.
+ */
+ toCode(String locale) {
Emily Fortuna 2013/03/13 18:54:43 return types, please
Alan Knight 2013/03/14 17:49:05 Done.
+ var out = new StringBuffer();
+ // These are statics because we want to closurize them into a map and
+ // that doesn't work for instance methods.
+ out.write('static $name(');
+ out.write(arguments.join(", "));
+ out.write(') => Intl.message("${translations[locale]}");');
+ return out.toString();
+ }
+
+ /**
+ * Escape the string to be used in the name, as a map key. So no double quotes
+ * and no interpolation. Assumes that the string has no existing escaping.
+ */
+ escapeForName(String s) {
+ var escaped1 = s.replaceAll('"', r'\"');
+ var escaped2 = escaped1.replaceAll('\$', r'\$');
+ return escaped2;
+ }
+
+ toString() =>
+ "Intl.message(${fullMessage()}, $name, $description, $examples, "
+ "$arguments)";
+}

Powered by Google App Engine
This is Rietveld 408576698