| 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..4a1f6cd178e301c99fee80b3865262a0233ada66
|
| --- /dev/null
|
| +++ b/pkg/intl/lib/src/intl_message.dart
|
| @@ -0,0 +1,169 @@
|
| +// 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
|
| + * assemble it into an object that can be used to write out some translation
|
| + * format and can also print itself into code.
|
| + */
|
| +class IntlMessage {
|
| +
|
| + /**
|
| + * This holds either Strings or ints representing the message. Literal
|
| + * parts of the message are stored as strings. Interpolations are represented
|
| + * by the index of the function parameter that they represent. When writing
|
| + * out to a translation file format the interpolations must be turned
|
| + * into the appropriate syntax, and the non-interpolated sections
|
| + * may be modified. See [fullMessage].
|
| + */
|
| + // TODO(alanknight): This will need to be changed for plural support.
|
| + List messagePieces;
|
| +
|
| + String description;
|
| +
|
| + /** The examples from the Intl.message call */
|
| + String examples;
|
| +
|
| + /**
|
| + * 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;
|
| +
|
| + /** 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.
|
| + */
|
| + String get name => _name == null ? computeName() : _name;
|
| + void set name(x) {_name = x;}
|
| + String computeName() => name = fullMessage((msg, chunk) => "");
|
| +
|
| + /**
|
| + * Return the full message, with any interpolation expressions transformed
|
| + * by [f] and all the results concatenated. The argument to [f] may be
|
| + * either a String or an int representing the index of a function parameter
|
| + * that's being interpolated. See [messagePieces].
|
| + */
|
| + 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.
|
| + */
|
| + void 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.
|
| + */
|
| + String 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.
|
| + */
|
| + String 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);
|
| +
|
| + // 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.
|
| + */
|
| + String toCode(String locale) {
|
| + 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.
|
| + */
|
| + String escapeForName(String s) {
|
| + var escaped1 = s.replaceAll('"', r'\"');
|
| + var escaped2 = escaped1.replaceAll('\$', r'\$');
|
| + return escaped2;
|
| + }
|
| +
|
| + String toString() =>
|
| + "Intl.message(${fullMessage()}, $name, $description, $examples, "
|
| + "$arguments)";
|
| +}
|
|
|