Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * This provides the class IntlMessage to represent an occurence of | |
| 7 * [Intl.message] in a program. It is used when parsing sources to extract | |
| 8 * messages or to generate code for message substitution. | |
| 9 */ | |
| 10 library intl_message; | |
| 11 | |
| 12 /** | |
| 13 * 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?
| |
| 14 * assemble it into an object that can be used to write out some translation | |
| 15 * format and can also print itself into code. | |
| 16 */ | |
| 17 class IntlMessage { | |
| 18 | |
| 19 /** | |
| 20 * The message, which may be just one string, or may be several strings, | |
| 21 * some of which are interpolation expressions. The output format must | |
| 22 * 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.
| |
| 23 */ | |
| 24 List<String> messagePieces; | |
| 25 | |
| 26 String description; | |
| 27 | |
| 28 /** The examples from the Intl.message call */ | |
| 29 String examples; | |
| 30 | |
| 31 /** 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.
| |
| 32 * to Intl.message, or we may just re-use the message | |
| 33 */ | |
| 34 String _name; | |
| 35 | |
| 36 /** The arguments parameter from the Intl.message call */ | |
| 37 List<String> arguments; | |
| 38 | |
| 39 /** | |
| 40 * A placeholder for any other identifier that the translation format | |
| 41 * may want to use. | |
| 42 */ | |
| 43 String id; | |
| 44 | |
| 45 /** | |
| 46 * When generating code, we store translations for each locale | |
| 47 * associated with the original message. | |
| 48 */ | |
| 49 Map<String, String> translations = new Map(); | |
| 50 | |
| 51 IntlMessage(); | |
| 52 | |
| 53 /** | |
| 54 * If the message was not given a name, we use the entire message string as | |
| 55 * the name. | |
| 56 */ | |
| 57 get name => _name == null ? computeName() : _name; | |
| 58 set name(x) => _name = x; | |
| 59 computeName() => name = fullMessage((msg, chunk) => ""); | |
| 60 | |
| 61 /** | |
| 62 * Return the full message, with any interpolation expressions transformed | |
| 63 * by [f] and all the results concatenated. | |
| 64 */ | |
| 65 String fullMessage([Function f]) { | |
| 66 var transform = f == null ? (msg, chunk) => chunk : f; | |
| 67 var out = new StringBuffer(); | |
| 68 messagePieces.map((chunk) => transform(this, chunk)).forEach(out.write); | |
| 69 return out.toString(); | |
| 70 } | |
| 71 | |
| 72 /** | |
| 73 * The node will have the attribute names as strings, so we translate | |
| 74 * between those and the fields of the class. | |
| 75 */ | |
| 76 operator []=(attributeName, value) { | |
| 77 switch (attributeName) { | |
| 78 case "desc" : description = value; return; | |
| 79 case "examples" : examples = value; return; | |
| 80 case "name" : name = value; return; | |
| 81 // We use the actual args from the parser rather than what's given in the | |
| 82 // arguments to Intl.message. | |
| 83 case "args" : return; | |
| 84 default: return; | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 /** | |
| 89 * Record the translation for this message in the given locale, after | |
| 90 * suitably escaping it. | |
| 91 */ | |
| 92 addTranslation(locale, value) => | |
| 93 translations[locale] = escapeAndValidate(locale, value); | |
| 94 | |
| 95 /** | |
| 96 * Escape the string and validate that it doesn't contain any interpolations | |
| 97 * more complex than including a simple variable value. | |
| 98 */ | |
| 99 escapeAndValidate(String locale, String s) { | |
| 100 const escapes = const { | |
| 101 r"\" : r"\\", | |
| 102 '"' : r'\"', | |
| 103 "\b" : r"\b", | |
| 104 "\f" : r"\f", | |
| 105 "\n" : r"\n", | |
| 106 "\r" : r"\r", | |
| 107 "\t" : r"\t", | |
| 108 "\v" : r"\v" | |
| 109 }; | |
| 110 | |
| 111 _escape(String s) => (escapes[s] == null) ? s : escapes[s]; | |
| 112 | |
| 113 // We know that we'll be enclosing the string in double-quotes, so we need | |
| 114 // to escape those, but not single-quotes. In addition we must escape | |
| 115 // backslashes, newlines, and other formatting characters. | |
| 116 var escaped = s.splitMapJoin("", onNonMatch: _escape); | |
| 117 print(escaped); | |
| 118 | |
| 119 // We don't allow any ${} expressions, only $variable to avoid malicious | |
| 120 // code. Disallow any usage of "${". If that makes a false positive | |
| 121 // on a translation that legitimate contains "\\${" or other variations, | |
| 122 // we'll live with that rather than risk a false negative. | |
| 123 var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})"); | |
| 124 var validMatches = validInterpolations.allMatches(escaped); | |
| 125 escapeInvalidMatches(Match m) { | |
| 126 var valid = validMatches.any((x) => x.start == m.start); | |
| 127 if (valid) { | |
| 128 return m.group(0); | |
| 129 } else { | |
| 130 return "\\${m.group(0)}"; | |
| 131 } | |
| 132 } | |
| 133 return escaped.replaceAllMapped("\$", escapeInvalidMatches); | |
| 134 } | |
| 135 | |
| 136 /** | |
| 137 * Generate code for this message, expecting it to be part of a map | |
| 138 * keyed by name with values the function that calls Intl.message. | |
| 139 */ | |
| 140 toCode(String locale) { | |
|
Emily Fortuna
2013/03/13 18:54:43
return types, please
Alan Knight
2013/03/14 17:49:05
Done.
| |
| 141 var out = new StringBuffer(); | |
| 142 // These are statics because we want to closurize them into a map and | |
| 143 // that doesn't work for instance methods. | |
| 144 out.write('static $name('); | |
| 145 out.write(arguments.join(", ")); | |
| 146 out.write(') => Intl.message("${translations[locale]}");'); | |
| 147 return out.toString(); | |
| 148 } | |
| 149 | |
| 150 /** | |
| 151 * Escape the string to be used in the name, as a map key. So no double quotes | |
| 152 * and no interpolation. Assumes that the string has no existing escaping. | |
| 153 */ | |
| 154 escapeForName(String s) { | |
| 155 var escaped1 = s.replaceAll('"', r'\"'); | |
| 156 var escaped2 = escaped1.replaceAll('\$', r'\$'); | |
| 157 return escaped2; | |
| 158 } | |
| 159 | |
| 160 toString() => | |
| 161 "Intl.message(${fullMessage()}, $name, $description, $examples, " | |
| 162 "$arguments)"; | |
| 163 } | |
| OLD | NEW |