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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698