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

Side by Side Diff: pkg/intl/lib/src/intl_message.dart

Issue 22286013: Add support for Intl.select (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Changes from review Created 7 years, 4 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 classes to represent the internal structure of the 6 * This provides classes to represent the internal structure of the
7 * arguments to `Intl.message`. It is used when parsing sources to extract 7 * arguments to `Intl.message`. It is used when parsing sources to extract
8 * messages or to generate code for message substitution. Normal programs 8 * messages or to generate code for message substitution. Normal programs
9 * using Intl would not import this library. 9 * using Intl would not import this library.
10 * 10 *
(...skipping 15 matching lines...) Expand all
26 * CompositeMessage containing three pieces, a LiteralString for 26 * CompositeMessage containing three pieces, a LiteralString for
27 * 'This is plural (', a VariableSubstitution for `num`. amd a LiteralString 27 * 'This is plural (', a VariableSubstitution for `num`. amd a LiteralString
28 * for '.)'. 28 * for '.)'.
29 * 29 *
30 * This representation isn't used at runtime. Rather, we read some format 30 * This representation isn't used at runtime. Rather, we read some format
31 * from a translation file, parse it into these objects, and they are then 31 * from a translation file, parse it into these objects, and they are then
32 * used to generate the code representation above. 32 * used to generate the code representation above.
33 */ 33 */
34 library intl_message; 34 library intl_message;
35 35
36 import 'package:analyzer_experimental/analyzer.dart';
37
36 /** A default function for the [Message.expanded] method. */ 38 /** A default function for the [Message.expanded] method. */
37 _nullTransform(msg, chunk) => chunk; 39 _nullTransform(msg, chunk) => chunk;
38 40
39 /** 41 /**
40 * An abstract superclass for Intl.message/plural/gender calls in the 42 * An abstract superclass for Intl.message/plural/gender calls in the
41 * program's source text. We 43 * program's source text. We
42 * assemble these into objects that can be used to write out some translation 44 * assemble these into objects that can be used to write out some translation
43 * format and can also print themselves into code. 45 * format and can also print themselves into code.
44 */ 46 */
45 abstract class Message { 47 abstract class Message {
46 48
47 /** 49 /**
48 * All [Message]s except a [MainMessage] are contained inside some parent, 50 * All [Message]s except a [MainMessage] are contained inside some parent,
49 * terminating at an Intl.message call which supplies the arguments we 51 * terminating at an Intl.message call which supplies the arguments we
50 * use for variable substitutions. 52 * use for variable substitutions.
51 */ 53 */
52 Message parent; 54 Message parent;
53 55
54 Message(this.parent); 56 Message(this.parent);
55 57
56 /** 58 /**
57 * We find the arguments from the top-level [MainMessage] and use those to 59 * We find the arguments from the top-level [MainMessage] and use those to
58 * do variable substitutions. 60 * do variable substitutions.
59 */ 61 */
60 get arguments => parent == null ? const [] : parent.arguments; 62 get arguments => parent == null ? const [] : parent.arguments;
61 63
64 String checkValidity(MethodInvocation node, List arguments,
65 String outerName, FormalParameterList outerArgs) {
66 var hasArgs = arguments.any(
67 (each) => each is NamedExpression && each.name.label.name == 'args');
68 var hasParameters = !outerArgs.parameters.isEmpty;
69 if (!hasArgs && hasParameters) {
70 return "The 'args' argument for Intl.message must be specified";
71 }
72
73 var messageName = arguments.firstWhere(
74 (eachArg) => eachArg is NamedExpression &&
75 eachArg.name.label.name == 'name',
76 orElse: () => null);
77 if (messageName == null) {
78 return "The 'name' argument for Intl.message must be specified";
79 }
80 if ((messageName.expression is! SimpleStringLiteral)
81 || messageName.expression.value != outerName) {
82 return "The 'name' argument for Intl.message must be a simple string "
83 "literal and match the containing function name.";
84 }
85 var simpleArguments = arguments.where(
86 (each) => each is NamedExpression
87 && ["desc", "locale", "name"].contains(each.name.label.name));
88 var values = simpleArguments.map((each) => each.expression).toList();
89 for (var arg in values) {
90 if (arg is! SimpleStringLiteral) {
91 return "Intl.message argument '${arg.name.label.name}' must be "
92 "a simple string literal";
93 }
94 }
95 }
96
62 /** 97 /**
63 * Turn a value, typically read from a translation file or created out of an 98 * Turn a value, typically read from a translation file or created out of an
64 * AST for a source program, into the appropriate 99 * AST for a source program, into the appropriate
65 * subclass. We expect to get literal Strings, variable substitutions 100 * subclass. We expect to get literal Strings, variable substitutions
66 * represented by integers, things that are already MessageChunks and 101 * represented by integers, things that are already MessageChunks and
67 * lists of the same. 102 * lists of the same.
68 */ 103 */
69 static Message from(value, Message parent) { 104 static Message from(value, Message parent) {
70 if (value is String) return new LiteralString(value, parent); 105 if (value is String) return new LiteralString(value, parent);
71 if (value is int) return new VariableSubstitution(value, parent); 106 if (value is int) return new VariableSubstitution(value, parent);
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 262
228 MainMessage() : super(null); 263 MainMessage() : super(null);
229 264
230 /** 265 /**
231 * All the pieces of the message. When we go to print, these will 266 * All the pieces of the message. When we go to print, these will
232 * all be expanded appropriately. The exact form depends on what we're 267 * all be expanded appropriately. The exact form depends on what we're
233 * printing it for See [expanded], [toCode]. 268 * printing it for See [expanded], [toCode].
234 */ 269 */
235 List<Message> messagePieces = []; 270 List<Message> messagePieces = [];
236 271
272 /** Verify that this looks like a correct Intl.message invocation. */
273 String checkValidity(MethodInvocation node, List arguments,
274 String outerName, FormalParameterList outerArgs) {
275 if (arguments.first is! StringLiteral) {
276 return "Intl.message messages must be string literals";
277 }
278
279 return super.checkValidity(node, arguments, outerName, outerArgs);
280 }
281
237 void addPieces(List<Message> messages) { 282 void addPieces(List<Message> messages) {
238 for (var each in messages) { 283 for (var each in messages) {
239 messagePieces.add(Message.from(each, this)); 284 messagePieces.add(Message.from(each, this));
240 } 285 }
241 } 286 }
242 287
243 /** The description provided in the Intl.message call. */ 288 /** The description provided in the Intl.message call. */
244 String description; 289 String description;
245 290
246 /** The examples from the Intl.message call */ 291 /** The examples from the Intl.message call */
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
377 422
378 toString() => expanded(); 423 toString() => expanded();
379 424
380 /** 425 /**
381 * The name of the main argument, which is expected to have the value 426 * The name of the main argument, which is expected to have the value
382 * which is one of [attributeNames] and is used to decide which clause to use. 427 * which is one of [attributeNames] and is used to decide which clause to use.
383 */ 428 */
384 String mainArgument; 429 String mainArgument;
385 430
386 /** 431 /**
432 * Return the arguments that affect this SubMessage as a map of
433 * argument names and values.
434 */
435 Map argumentsOfInterestFor(MethodInvocation node) {
436 var basicArguments = node.argumentList.arguments.elements;
437 var others = basicArguments.where((each) => each is NamedExpression);
438 return new Map.fromIterable(others,
439 key: (node) => node.name.label.token.value(),
440 value: (node) => node.expression);
441 }
442
443 /**
387 * Return the list of attribute names to use when generating code. This 444 * Return the list of attribute names to use when generating code. This
388 * may be different from [attributeNames] if there are multiple aliases 445 * may be different from [attributeNames] if there are multiple aliases
389 * that map to the same clause. 446 * that map to the same clause.
390 */ 447 */
391 List<String> get codeAttributeNames; 448 List<String> get codeAttributeNames;
392 449
393 String expanded([Function transform = _nullTransform]) { 450 String expanded([Function transform = _nullTransform]) {
394 fullMessageForClause(key) => key + '{' + 451 fullMessageForClause(key) => key + '{' +
395 transform(parent, this[key]).toString() + '}'; 452 transform(parent, this[key]).toString() + '}';
396 var clauses = attributeNames 453 var clauses = attributeNames
(...skipping 19 matching lines...) Expand all
416 473
417 /** 474 /**
418 * Represents a message send of [Intl.gender] inside a message that is to 475 * Represents a message send of [Intl.gender] inside a message that is to
419 * be internationalized. This corresponds to an ICU message syntax "select" 476 * be internationalized. This corresponds to an ICU message syntax "select"
420 * with "male", "female", and "other" as the possible options. 477 * with "male", "female", and "other" as the possible options.
421 */ 478 */
422 class Gender extends SubMessage { 479 class Gender extends SubMessage {
423 480
424 Gender(); 481 Gender();
425 /** 482 /**
426 * Create a new IntlGender providing [mainArgument] and the list of possible 483 * Create a new Gender providing [mainArgument] and the list of possible
427 * clauses. Each clause is expected to be a list whose first element is a 484 * clauses. Each clause is expected to be a list whose first element is a
428 * variable name and whose second element is either a String or 485 * variable name and whose second element is either a [String] or
429 * a list of strings and IntlMessageSends or IntlVariableSubstitution. 486 * a list of strings and [Message] or [VariableSubstitution].
430 */ 487 */
431 Gender.from(mainArgument, List clauses, parent) : 488 Gender.from(String mainArgument, List clauses, Message parent) :
432 super.from(mainArgument, clauses, parent); 489 super.from(mainArgument, clauses, parent);
433 490
434 Message female; 491 Message female;
435 Message male; 492 Message male;
436 Message other; 493 Message other;
437 494
438 String get icuMessageName => "select"; 495 String get icuMessageName => "select";
439 String get dartMessageName => 'Intl.gender'; 496 String get dartMessageName => 'Intl.gender';
440 497
441 get attributeNames => ["female", "male", "other"]; 498 get attributeNames => ["female", "male", "other"];
(...skipping 18 matching lines...) Expand all
460 case "male" : return male; 517 case "male" : return male;
461 case "other" : return other; 518 case "other" : return other;
462 default: return other; 519 default: return other;
463 } 520 }
464 } 521 }
465 } 522 }
466 523
467 class Plural extends SubMessage { 524 class Plural extends SubMessage {
468 525
469 Plural(); 526 Plural();
470 Plural.from(mainArgument, clauses, parent) : 527 Plural.from(String mainArgument, List clauses, Message parent) :
471 super.from(mainArgument, clauses, parent); 528 super.from(mainArgument, clauses, parent);
472 529
473 Message zero; 530 Message zero;
474 Message one; 531 Message one;
475 Message two; 532 Message two;
476 Message few; 533 Message few;
477 Message many; 534 Message many;
478 Message other; 535 Message other;
479 536
480 String get icuMessageName => "plural"; 537 String get icuMessageName => "plural";
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
512 case "two" : return two; 569 case "two" : return two;
513 case "=2" : return two; 570 case "=2" : return two;
514 case "few" : return few; 571 case "few" : return few;
515 case "many" : return many; 572 case "many" : return many;
516 case "other" : return other; 573 case "other" : return other;
517 default: return other; 574 default: return other;
518 } 575 }
519 } 576 }
520 } 577 }
521 578
579 /**
580 * Represents a message send of [Intl.select] inside a message that is to
581 * be internationalized. This corresponds to an ICU message syntax "select"
582 * with arbitrary options.
583 */
584 class Select extends SubMessage {
585
586 Select();
587 /**
588 * Create a new [Select] providing [mainArgument] and the list of possible
589 * clauses. Each clause is expected to be a list whose first element is a
590 * variable name and whose second element is either a String or
591 * a list of strings and [Message]s or [VariableSubstitution]s.
592 */
593 Select.from(String mainArgument, List clauses, Message parent) :
594 super.from(mainArgument, clauses, parent);
595
596 Map<String, Message> cases = new Map<String, Message>();
597
598 String get icuMessageName => "select";
599 String get dartMessageName => 'Intl.select';
600
601 get attributeNames => cases.keys;
602 get codeAttributeNames => attributeNames;
603
604 void operator []=(attributeName, rawValue) {
605 var value = Message.from(rawValue, this);
606 cases[attributeName] = value;
607 }
608
609 Message operator [](String attributeName) {
610 var exact = cases[attributeName];
611 return exact == null ? cases["other"] : exact;
612 }
613
614 /**
615 * Return the arguments that we care about for the select. In this
616 * case they will all be passed in as a Map rather than as the named
617 * arguments used in Plural/Gender.
618 */
619 Map argumentsOfInterestFor(MethodInvocation node) {
620 var casesArgument = node.argumentList.arguments.elements[1];
621 return new Map.fromIterable(casesArgument.entries,
622 key: (node) => node.key.value,
623 value: (node) => node.value);
624 }
625
626 /**
627 * Write out the generated representation of this message. This differs
628 * from Plural/Gender in that it prints a literal map rather than
629 * named arguments.
630 */
631 String toCode() {
632 var out = new StringBuffer();
633 out.write('\${');
634 out.write(dartMessageName);
635 out.write('(');
636 out.write(mainArgument);
637 var args = codeAttributeNames;
638 out.write(", {");
639 args.fold(out, (buffer, arg) => buffer..write(
640 "'$arg': '${this[arg].toCode()}', "));
641 out.write("})}");
642 return out.toString();
643 }
644 }
OLDNEW
« no previous file with comments | « pkg/intl/lib/intl.dart ('k') | pkg/intl/test/message_extraction/make_hardcoded_translation.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698