Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * This provides the class IntlMessage to represent an occurence of | 6 * This provides the class IntlMessage to represent an occurence of |
|
Emily Fortuna
2013/07/03 17:52:33
IntlMessage -> MainMessage?
Alan Knight
2013/07/03 18:41:07
Yes. Rephrased the whole thing, as this now provid
| |
| 7 * [Intl.message] in a program. It is used when parsing sources to extract | 7 * Intl.message in a program. It is used when parsing sources to extract |
| 8 * messages or to generate code for message substitution. | 8 * messages or to generate code for message substitution. Normal programs |
| 9 * using Intl would not import this library. | |
| 10 * | |
| 11 * While it's written | |
| 12 * in a somewhat abstract way, it has some assumptions about ICU-style | |
| 13 * message syntax for parameter substitutions, choices, selects, etc. | |
| 9 */ | 14 */ |
| 10 library intl_message; | 15 library intl_message; |
| 11 | 16 |
| 12 /** | 17 /** A default function for [IntlMessageSend.expanded]. */ |
| 13 * Represents an occurence of Intl.message in the program's source text. We | 18 _nullTransform(msg, chunk) => chunk; |
| 14 * assemble it into an object that can be used to write out some translation | 19 |
| 15 * format and can also print itself into code. | 20 /** |
| 16 */ | 21 * An abstract superclass for Intl.message/plural/gender calls in the |
| 17 class IntlMessage { | 22 * program's source text. We |
| 18 | 23 * assemble these into objects that can be used to write out some translation |
| 19 /** | 24 * format and can also print themselves into code. |
| 20 * This holds either Strings or ints representing the message. Literal | 25 */ |
| 21 * parts of the message are stored as strings. Interpolations are represented | 26 abstract class Message { |
| 22 * by the index of the function parameter that they represent. When writing | 27 |
| 23 * out to a translation file format the interpolations must be turned | 28 /** |
| 24 * into the appropriate syntax, and the non-interpolated sections | 29 * All [Message]s except a [MainMessage] are contained inside some parent, |
| 25 * may be modified. See [fullMessage]. | 30 * terminating at an Intl.message call which supplies the arguments we |
| 26 */ | 31 * use for variable substitutions. |
| 27 // TODO(alanknight): This will need to be changed for plural support. | 32 */ |
| 28 List messagePieces; | 33 Message parent; |
| 29 | 34 |
| 35 Message(this.parent); | |
| 36 | |
| 37 /** | |
| 38 * We find the arguments from the top-level [MainMessage] and use those to | |
| 39 * do variable substitutions. | |
| 40 */ | |
| 41 get arguments => parent == null ? const [] : parent.arguments; | |
| 42 | |
| 43 /** | |
| 44 * Turn a value, typically read from a translation file or created out of an | |
| 45 * AST for a source program, into the appropriate | |
| 46 * subclass. We expect to get literal Strings, variable substitutions | |
| 47 * represented by integers, things that are already MessageChunks and | |
| 48 * lists of the same. | |
| 49 */ | |
| 50 static Message from(value, Message parent) { | |
| 51 if (value is String) return new LiteralString(value, parent); | |
| 52 if (value is int) return new VariableSubstitution(value, parent); | |
| 53 if (value is Iterable) { | |
| 54 var result = new CompositeMessage([], parent); | |
| 55 var items = value.map((x) => from(x, result)).toList(); | |
| 56 result.pieces.addAll(items); | |
| 57 return result; | |
| 58 } | |
| 59 // We assume this is already a Message. | |
| 60 value.parent = parent; | |
| 61 return value; | |
| 62 } | |
| 63 | |
| 64 /** | |
| 65 * Return a string representation of this message for use in generated Dart | |
| 66 * code. | |
| 67 */ | |
| 68 String toCode(); | |
| 69 | |
| 70 /** | |
| 71 * Escape the string for use in generated Dart code and validate that it | |
| 72 * doesn't doesn't contain any illegal interpolations. We only allow | |
| 73 * simple variables ("$foo", but not "${foo}") and Intl.gender/plural | |
| 74 * calls. | |
| 75 */ | |
| 76 String escapeAndValidateString(String value) { | |
| 77 const escapes = const { | |
| 78 r"\" : r"\\", | |
| 79 '"' : r'\"', | |
| 80 "\b" : r"\b", | |
| 81 "\f" : r"\f", | |
| 82 "\n" : r"\n", | |
| 83 "\r" : r"\r", | |
| 84 "\t" : r"\t", | |
| 85 "\v" : r"\v", | |
| 86 "'" : r"\'", | |
| 87 }; | |
| 88 | |
| 89 _escape(String s) => (escapes[s] == null) ? s : escapes[s]; | |
| 90 | |
| 91 var escaped = value.splitMapJoin("", onNonMatch: _escape); | |
| 92 | |
| 93 // We don't allow any ${} expressions, only $variable to avoid malicious | |
| 94 // code. Disallow any usage of "${". If that makes a false positive | |
| 95 // on a translation that legitimately contains "\\${" or other variations, | |
| 96 // we'll live with that rather than risk a false negative. | |
| 97 var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})"); | |
| 98 var validMatches = validInterpolations.allMatches(escaped); | |
| 99 escapeInvalidMatches(Match m) { | |
| 100 var valid = validMatches.any((x) => x.start == m.start); | |
| 101 if (valid) { | |
| 102 return m.group(0); | |
| 103 } else { | |
| 104 return "\\${m.group(0)}"; | |
| 105 } | |
| 106 } | |
| 107 return escaped.replaceAllMapped("\$", escapeInvalidMatches); | |
| 108 } | |
| 109 | |
| 110 /** | |
| 111 * Expand this string out into a printed form. The function [f] will be | |
| 112 * applied to any sub-messages, allowing this to be used to generate a form | |
| 113 * suitable for a wide variety of translation file formats. | |
| 114 */ | |
| 115 String expanded([Function f]); | |
| 116 } | |
| 117 | |
| 118 /** | |
| 119 * Abstract class for messages with internal structure, representing the | |
| 120 * main Intl.message call, plurals, and genders. | |
|
Emily Fortuna
2013/07/03 17:52:33
can you give an example in the documentation of ea
Alan Knight
2013/07/03 18:41:07
Done.
| |
| 121 */ | |
| 122 abstract class ComplexMessage extends Message { | |
| 123 | |
| 124 ComplexMessage(parent) : super(parent); | |
| 125 | |
| 126 /** | |
| 127 * When we create these from strings or from AST nodes, we want to look up and | |
| 128 * set their attributes by string names, so we override the indexing operators | |
| 129 * so that they behave like maps with respect to those attribute names. | |
| 130 */ | |
| 131 operator [](x); | |
| 132 | |
| 133 /** | |
| 134 * When we create these from strings or from AST nodes, we want to look up and | |
| 135 * set their attributes by string names, so we override the indexing operators | |
| 136 * so that they behave like maps with respect to those attribute names. | |
| 137 */ | |
| 138 operator []=(x, y); | |
| 139 | |
| 140 List<String> get attributeNames; | |
| 141 | |
| 142 /** | |
| 143 * Return the name of the message type, as it will be generated into an | |
| 144 * ICU-type format. e.g. choice, select | |
| 145 */ | |
| 146 String get icuMessageName; | |
| 147 | |
| 148 /** | |
| 149 * Return the message name we would use for this when doing Dart code | |
| 150 * generation, e.g. "Intl.plural". | |
| 151 */ | |
| 152 String get dartMessageName; | |
| 153 } | |
| 154 | |
| 155 /** | |
| 156 * This represents a message chunk that is a list of multiple sub-pieces, | |
| 157 * each of which is in turn a [Message]. | |
| 158 */ | |
| 159 class CompositeMessage extends Message { | |
| 160 List<Message> pieces; | |
| 161 | |
| 162 CompositeMessage.parent(parent) : super(parent); | |
| 163 CompositeMessage(this.pieces, ComplexMessage parent) : super(parent) { | |
| 164 pieces.forEach((x) => x.parent = this); | |
| 165 } | |
| 166 toCode() => pieces.map((each) => each.toCode()).join(''); | |
| 167 toString() => "CompositeMessage(" + pieces.toString() + ")"; | |
| 168 String expanded([Function f = _nullTransform]) => | |
| 169 pieces.map((chunk) => f(this, chunk)).join(""); | |
|
Emily Fortuna
2013/07/03 17:52:33
+2 spaces
Alan Knight
2013/07/03 18:41:07
Done.
| |
| 170 } | |
| 171 | |
| 172 /** Represents a simple constant string with no dynamic elements. */ | |
| 173 class LiteralString extends Message { | |
| 174 String string; | |
| 175 LiteralString(this.string, Message parent) : super(parent); | |
| 176 toCode() => escapeAndValidateString(string); | |
| 177 toString() => "Literal($string)"; | |
| 178 String expanded([Function f = _nullTransform]) => f(this, string); | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Represents an interpolation of a variable value in a message. We expect | |
| 183 * this to be specified as an [index] into the list of variables, and we will | |
| 184 * compute the variable name for the interpolation based on that. | |
| 185 */ | |
| 186 class VariableSubstitution extends Message { | |
| 187 VariableSubstitution(this.index, Message parent) : super(parent); | |
| 188 | |
| 189 /** The index in the list of parameters of the containing function. */ | |
| 190 int index; | |
| 191 | |
| 192 /** | |
| 193 * The name of the variable in the parameter list of the containing function. | |
| 194 * Used when generating code for the interpolation. | |
| 195 */ | |
| 196 String get variableName => | |
| 197 _variableName == null ? _variableName = arguments[index] : _variableName; | |
| 198 String _variableName; | |
| 199 // Although we only allow simple variable references, we always enclose them | |
| 200 // in curly braces so that there's no possibility of ambiguity with | |
| 201 // surrounding text. | |
| 202 toCode() => "\${${variableName}}"; | |
| 203 toString() => "VariableSubstitution($index)"; | |
| 204 String expanded([Function f = _nullTransform]) => f(this, index); | |
| 205 } | |
| 206 | |
| 207 class MainMessage extends ComplexMessage { | |
| 208 | |
| 209 MainMessage() : super(null); | |
| 210 | |
| 211 /** | |
| 212 * All the pieces of the message. When we go to print, these will | |
| 213 * all be expanded appropriately. The exact form depends on what we're | |
| 214 * printing it for See [expanded], [toCode]. | |
| 215 */ | |
| 216 List<Message> messagePieces = []; | |
| 217 | |
| 218 void addPieces(List<Message> messages) { | |
| 219 for (var each in messages) { | |
| 220 messagePieces.add(Message.from(each, this)); | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 /** The description provided in the Intl.message call. */ | |
| 30 String description; | 225 String description; |
| 31 | 226 |
| 32 /** The examples from the Intl.message call */ | 227 /** The examples from the Intl.message call */ |
| 33 String examples; | 228 String examples; |
| 34 | 229 |
| 35 /** | 230 /** |
| 36 * The name, which may come from the function name, from the arguments | 231 * The name, which may come from the function name, from the arguments |
| 37 * to Intl.message, or we may just re-use the message. | 232 * to Intl.message, or we may just re-use the message. |
| 38 */ | 233 */ |
| 39 String _name; | 234 String _name; |
| 40 | 235 |
| 41 /** The arguments parameter from the Intl.message call. */ | |
| 42 List<String> arguments; | |
| 43 | |
| 44 /** | 236 /** |
| 45 * A placeholder for any other identifier that the translation format | 237 * A placeholder for any other identifier that the translation format |
| 46 * may want to use. | 238 * may want to use. |
| 47 */ | 239 */ |
| 48 String id; | 240 String id; |
| 49 | 241 |
| 242 /** The arguments list from the Intl.message call. */ | |
| 243 List arguments; | |
| 244 | |
| 50 /** | 245 /** |
| 51 * When generating code, we store translations for each locale | 246 * When generating code, we store translations for each locale |
| 52 * associated with the original message. | 247 * associated with the original message. |
| 53 */ | 248 */ |
| 54 Map<String, String> translations = new Map(); | 249 Map<String, String> translations = new Map(); |
| 55 | 250 |
| 56 IntlMessage(); | |
| 57 | |
| 58 /** | 251 /** |
| 59 * If the message was not given a name, we use the entire message string as | 252 * If the message was not given a name, we use the entire message string as |
| 60 * the name. | 253 * the name. |
| 61 */ | 254 */ |
| 62 String get name => _name == null ? computeName() : _name; | 255 String get name => _name == null ? computeName() : _name; |
| 63 void set name(x) {_name = x;} | 256 void set name(x) {_name = x;} |
| 64 String computeName() => name = fullMessage((msg, chunk) => ""); | 257 |
| 258 String computeName() => name = expanded((msg, chunk) => ""); | |
| 65 | 259 |
| 66 /** | 260 /** |
| 67 * Return the full message, with any interpolation expressions transformed | 261 * Return the full message, with any interpolation expressions transformed |
| 68 * by [f] and all the results concatenated. The argument to [f] may be | 262 * by [f] and all the results concatenated. The chunk argument to [f] may be |
| 69 * either a String or an int representing the index of a function parameter | 263 * either a String, an int or an object representing a more complex |
| 70 * that's being interpolated. See [messagePieces]. | 264 * message entity. |
| 265 * See [messagePieces]. | |
| 71 */ | 266 */ |
| 72 String fullMessage([Function f]) { | 267 String expanded([Function f = _nullTransform]) => |
| 73 var transform = f == null ? (msg, chunk) => chunk : f; | 268 messagePieces.map((chunk) => f(this, chunk)).join(""); |
|
Emily Fortuna
2013/07/03 17:52:33
+2 spaces
Alan Knight
2013/07/03 18:41:07
Done.
| |
| 74 var out = new StringBuffer(); | 269 |
| 75 messagePieces.map((chunk) => transform(this, chunk)).forEach(out.write); | 270 /** |
| 271 * Record the translation for this message in the given locale, after | |
| 272 * suitably escaping it. | |
| 273 */ | |
| 274 String addTranslation(String locale, Message translated) { | |
| 275 translated.parent = this; | |
| 276 translations[locale] = translated.toCode(); | |
| 277 } | |
| 278 | |
| 279 toCode() => throw | |
| 280 new UnsupportedError("MainMessage.toCode requires a locale"); | |
| 281 | |
| 282 /** | |
| 283 * Generate code for this message, expecting it to be part of a map | |
| 284 * keyed by name with values the function that calls Intl.message. | |
| 285 */ | |
| 286 String toCodeForLocale(String locale) { | |
| 287 var out = new StringBuffer() | |
| 288 ..write('static $name(') | |
| 289 ..write(arguments.join(", ")) | |
| 290 ..write(') => Intl.$dartMessageName("') | |
| 291 ..write(translations[locale]) | |
| 292 ..write('");'); | |
| 76 return out.toString(); | 293 return out.toString(); |
| 77 } | 294 } |
| 78 | 295 |
| 79 /** | 296 /** |
| 80 * The node will have the attribute names as strings, so we translate | 297 * The AST node will have the attribute names as strings, so we translate |
| 81 * between those and the fields of the class. | 298 * between those and the fields of the class. |
| 82 */ | 299 */ |
| 83 void operator []=(attributeName, value) { | 300 void operator []=(attributeName, value) { |
| 84 switch (attributeName) { | 301 switch (attributeName) { |
| 85 case "desc" : description = value; return; | 302 case "desc" : description = value; return; |
| 86 case "examples" : examples = value; return; | 303 case "examples" : examples = value; return; |
| 87 case "name" : name = value; return; | 304 case "name" : name = value; return; |
| 88 // We use the actual args from the parser rather than what's given in the | 305 // We use the actual args from the parser rather than what's given in the |
| 89 // arguments to Intl.message. | 306 // arguments to Intl.message. |
| 90 case "args" : return; | 307 case "args" : return; |
| 91 default: return; | 308 default: return; |
| 92 } | 309 } |
| 93 } | 310 } |
| 94 | 311 |
| 95 /** | 312 /** |
| 96 * Record the translation for this message in the given locale, after | 313 * The AST node will have the attribute names as strings, so we translate |
| 97 * suitably escaping it. | 314 * between those and the fields of the class. |
| 98 */ | 315 */ |
| 99 String addTranslation(locale, value) => | 316 operator [](attributeName) { |
| 100 translations[locale] = escapeAndValidate(locale, value); | 317 switch (attributeName) { |
| 318 case "desc" : return description; | |
| 319 case "examples" : return examples; | |
| 320 case "name" : return name; | |
| 321 // We use the actual args from the parser rather than what's given in the | |
| 322 // arguments to Intl.message. | |
| 323 case "args" : return []; | |
| 324 default: return null; | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 // This is the top-level construct, so there's no meaningful ICU name. | |
| 329 get icuMessageName => ''; | |
| 330 | |
| 331 get dartMessageName => "message"; | |
| 332 | |
| 333 /** The parameters that the Intl.message call may provide. */ | |
| 334 get attributeNames => const ["name", "desc", "examples", "args"]; | |
| 335 | |
| 336 String toString() => | |
| 337 "Intl.message(${expanded()}, $name, $description, $examples, $arguments)"; | |
| 338 } | |
| 339 | |
| 340 /** | |
| 341 * An abstract class to represent sub-sections of a message, primarily | |
| 342 * plurals and genders. | |
| 343 */ | |
| 344 abstract class SubMessage extends ComplexMessage { | |
| 345 | |
| 346 SubMessage() : super(null); | |
| 101 | 347 |
| 102 /** | 348 /** |
| 103 * Escape the string and validate that it doesn't contain any interpolations | 349 * Creates the sub-message, given a list of [clauses] in the sort of form |
| 104 * more complex than including a simple variable value. | 350 * that we're likely to get them from parsing a translation file format, |
| 351 * as a list of [key, value] where value may in turn be a list. | |
| 105 */ | 352 */ |
| 106 String escapeAndValidate(String locale, String s) { | 353 SubMessage.from(this.mainArgument, List clauses, parent) : super(parent) { |
| 107 const escapes = const { | 354 for (var clause in clauses) { |
| 108 r"\" : r"\\", | 355 this[clause.first] = (clause.last is List) ? clause.last : [clause.last]; |
| 109 '"' : r'\"', | |
| 110 "\b" : r"\b", | |
| 111 "\f" : r"\f", | |
| 112 "\n" : r"\n", | |
| 113 "\r" : r"\r", | |
| 114 "\t" : r"\t", | |
| 115 "\v" : r"\v" | |
| 116 }; | |
| 117 | |
| 118 _escape(String s) => (escapes[s] == null) ? s : escapes[s]; | |
| 119 | |
| 120 // We know that we'll be enclosing the string in double-quotes, so we need | |
| 121 // to escape those, but not single-quotes. In addition we must escape | |
| 122 // backslashes, newlines, and other formatting characters. | |
| 123 var escaped = s.splitMapJoin("", onNonMatch: _escape); | |
| 124 | |
| 125 // We don't allow any ${} expressions, only $variable to avoid malicious | |
| 126 // code. Disallow any usage of "${". If that makes a false positive | |
| 127 // on a translation that legitimate contains "\\${" or other variations, | |
| 128 // we'll live with that rather than risk a false negative. | |
| 129 var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})"); | |
| 130 var validMatches = validInterpolations.allMatches(escaped); | |
| 131 escapeInvalidMatches(Match m) { | |
| 132 var valid = validMatches.any((x) => x.start == m.start); | |
| 133 if (valid) { | |
| 134 return m.group(0); | |
| 135 } else { | |
| 136 return "\\${m.group(0)}"; | |
| 137 } | |
| 138 } | 356 } |
| 139 return escaped.replaceAllMapped("\$", escapeInvalidMatches); | |
| 140 } | 357 } |
| 141 | 358 |
| 359 toString() => expanded(); | |
| 360 | |
| 142 /** | 361 /** |
| 143 * Generate code for this message, expecting it to be part of a map | 362 * The name of the main argument, which is expected to have the value |
| 144 * keyed by name with values the function that calls Intl.message. | 363 * which is one of [attributeNames] and is used to decide which clause to use. |
| 145 */ | 364 */ |
| 146 String toCode(String locale) { | 365 String mainArgument; |
| 366 | |
| 367 /** | |
| 368 * Return the list of attribute names to use when generating code. This | |
| 369 * may be different from [attributeNames] if there are multiple aliases | |
| 370 * that map to the same clause. | |
| 371 */ | |
| 372 List<String> get codeAttributeNames; | |
| 373 | |
| 374 String expanded([Function transform = _nullTransform]) { | |
| 375 fullMessageForClause(key) => key + '{' + transform(parent, this[key]) + '}'; | |
| 376 var clauses = attributeNames | |
| 377 .where((key) => this[key] != null) | |
| 378 .map(fullMessageForClause); | |
| 379 return "{$mainArgument,$icuMessageName, ${clauses.join("")}}"; | |
| 380 } | |
| 381 | |
| 382 String toCode() { | |
| 147 var out = new StringBuffer(); | 383 var out = new StringBuffer(); |
| 148 // These are statics because we want to closurize them into a map and | 384 out.write('\${'); |
| 149 // that doesn't work for instance methods. | 385 out.write(dartMessageName); |
| 150 out.write('static $name('); | 386 out.write('('); |
| 151 out.write(arguments.join(", ")); | 387 out.write(mainArgument); |
| 152 out.write(') => Intl.message("${translations[locale]}");'); | 388 var args = codeAttributeNames.where( |
| 389 (attribute) => this[attribute] != null); | |
| 390 args.fold(out, (buffer, arg) => buffer..write( | |
| 391 ", $arg: '${this[arg].toCode()}'")); | |
| 392 out.write(")}"); | |
| 153 return out.toString(); | 393 return out.toString(); |
| 154 } | 394 } |
| 395 } | |
| 396 | |
| 397 /** | |
| 398 * Represents a message send of [Intl.gender] inside a message that is to | |
| 399 * be internationalized. This corresponds to an ICU message syntax "select" | |
| 400 * with "male", "female", and "other" as the possible options. | |
| 401 */ | |
| 402 class Gender extends SubMessage { | |
| 403 | |
| 404 Gender(); | |
| 405 /** | |
| 406 * Create a new IntlGender providing [mainArgument] and the list of possible | |
| 407 * clauses. Each clause is expected to be a list whose first element is a | |
| 408 * variable name and whose second element is either a String or | |
| 409 * a list of strings and IntlMessageSends or IntlVariableSubstitution. | |
| 410 */ | |
| 411 Gender.from(mainArgument, List clauses, parent) : | |
| 412 super.from(mainArgument, clauses, parent); | |
| 413 | |
| 414 Message female; | |
| 415 Message male; | |
| 416 Message other; | |
| 417 | |
| 418 String get icuMessageName => "select"; | |
| 419 String get dartMessageName => 'Intl.gender'; | |
| 420 | |
| 421 get attributeNames => ["female", "male", "other"]; | |
| 422 get codeAttributeNames => attributeNames; | |
| 155 | 423 |
| 156 /** | 424 /** |
| 157 * Escape the string to be used in the name, as a map key. So no double quotes | 425 * The node will have the attribute names as strings, so we translate |
| 158 * and no interpolation. Assumes that the string has no existing escaping. | 426 * between those and the fields of the class. |
| 159 */ | 427 */ |
| 160 String escapeForName(String s) { | 428 void operator []=(attributeName, rawValue) { |
| 161 var escaped1 = s.replaceAll('"', r'\"'); | 429 var value = Message.from(rawValue, this); |
| 162 var escaped2 = escaped1.replaceAll('\$', r'\$'); | 430 switch (attributeName) { |
| 163 return escaped2; | 431 case "female" : female = value; return; |
| 432 case "male" : male = value; return; | |
| 433 case "other" : other = value; return; | |
| 434 default: return; | |
| 435 } | |
| 164 } | 436 } |
| 437 Message operator [](String attributeName) { | |
| 438 switch (attributeName) { | |
| 439 case "female" : return female; | |
| 440 case "male" : return male; | |
| 441 case "other" : return other; | |
| 442 default: return other; | |
| 443 } | |
| 444 } | |
| 445 } | |
| 165 | 446 |
| 166 String toString() => | 447 class Plural extends SubMessage { |
| 167 "Intl.message(${fullMessage()}, $name, $description, $examples, " | 448 |
| 168 "$arguments)"; | 449 Plural(); |
| 169 } | 450 Plural.from(mainArgument, clauses, parent) : |
| 451 super.from(mainArgument, clauses, parent); | |
| 452 | |
| 453 Message zero; | |
| 454 Message one; | |
| 455 Message two; | |
| 456 Message few; | |
| 457 Message many; | |
| 458 Message other; | |
| 459 | |
| 460 String get icuMessageName => "plural"; | |
| 461 String get dartMessageName => "Intl.plural"; | |
| 462 | |
| 463 get attributeNames => ["=0", "=1", "=2", "few", "many", "other"]; | |
| 464 get codeAttributeNames => ["zero", "one", "two", "few", "many", "other"]; | |
| 465 | |
| 466 /** | |
| 467 * The node will have the attribute names as strings, so we translate | |
| 468 * between those and the fields of the class. | |
| 469 */ | |
| 470 void operator []=(String attributeName, rawValue) { | |
| 471 var value = Message.from(rawValue, this); | |
| 472 switch (attributeName) { | |
| 473 case "zero" : zero = value; return; | |
| 474 case "=0" : zero = value; return; | |
| 475 case "one" : one = value; return; | |
| 476 case "=1" : one = value; return; | |
| 477 case "two" : two = value; return; | |
| 478 case "=2" : two = value; return; | |
| 479 case "few" : few = value; return; | |
| 480 case "many" : many = value; return; | |
| 481 case "other" : other = value; return; | |
| 482 default: return; | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 Message operator [](String attributeName) { | |
| 487 switch (attributeName) { | |
| 488 case "zero" : return zero; | |
| 489 case "=0" : return zero; | |
| 490 case "one" : return one; | |
| 491 case "=1" : return one; | |
| 492 case "two" : return two; | |
| 493 case "=2" : return two; | |
| 494 case "few" : return few; | |
| 495 case "many" : return many; | |
| 496 case "other" : return other; | |
| 497 default: return other; | |
| 498 } | |
| 499 } | |
| 500 } | |
| 501 | |
| OLD | NEW |