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

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: Fixes from review comments, also made tests more robust, removed scheduled_test dependency 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
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 * This holds either Strings or ints representing the message. Literal
21 * parts of the message are stored as strings. Interpolations are represented
22 * by the index of the function parameter that they represent. When writing
23 * out to a translation file format the interpolations must be turned
24 * into the appropriate syntax, and the non-interpolated sections
25 * may be modified. See [fullMessage].
26 */
27 // TODO(alanknight): This will need to be changed for plural support.
28 List messagePieces;
29
30 String description;
31
32 /** The examples from the Intl.message call */
33 String examples;
34
35 /**
36 * The name, which may come from the function name, from the arguments
37 * to Intl.message, or we may just re-use the message.
38 */
39 String _name;
40
41 /** The arguments parameter from the Intl.message call. */
42 List<String> arguments;
43
44 /**
45 * A placeholder for any other identifier that the translation format
46 * may want to use.
47 */
48 String id;
49
50 /**
51 * When generating code, we store translations for each locale
52 * associated with the original message.
53 */
54 Map<String, String> translations = new Map();
55
56 IntlMessage();
57
58 /**
59 * If the message was not given a name, we use the entire message string as
60 * the name.
61 */
62 String get name => _name == null ? computeName() : _name;
63 void set name(x) {_name = x;}
64 String computeName() => name = fullMessage((msg, chunk) => "");
65
66 /**
67 * Return the full message, with any interpolation expressions transformed
68 * by [f] and all the results concatenated. The argument to [f] may be
69 * either a String or an int representing the index of a function parameter
70 * that's being interpolated. See [messagePieces].
71 */
72 String fullMessage([Function f]) {
73 var transform = f == null ? (msg, chunk) => chunk : f;
74 var out = new StringBuffer();
75 messagePieces.map((chunk) => transform(this, chunk)).forEach(out.write);
76 return out.toString();
77 }
78
79 /**
80 * The node will have the attribute names as strings, so we translate
81 * between those and the fields of the class.
82 */
83 void operator []=(attributeName, value) {
84 switch (attributeName) {
85 case "desc" : description = value; return;
86 case "examples" : examples = value; return;
87 case "name" : name = value; return;
88 // We use the actual args from the parser rather than what's given in the
89 // arguments to Intl.message.
90 case "args" : return;
91 default: return;
92 }
93 }
94
95 /**
96 * Record the translation for this message in the given locale, after
97 * suitably escaping it.
98 */
99 String addTranslation(locale, value) =>
100 translations[locale] = escapeAndValidate(locale, value);
101
102 /**
103 * Escape the string and validate that it doesn't contain any interpolations
104 * more complex than including a simple variable value.
105 */
106 String escapeAndValidate(String locale, String s) {
107 const escapes = const {
108 r"\" : r"\\",
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 }
139 return escaped.replaceAllMapped("\$", escapeInvalidMatches);
140 }
141
142 /**
143 * Generate code for this message, expecting it to be part of a map
144 * keyed by name with values the function that calls Intl.message.
145 */
146 String toCode(String locale) {
147 var out = new StringBuffer();
148 // These are statics because we want to closurize them into a map and
149 // that doesn't work for instance methods.
150 out.write('static $name(');
151 out.write(arguments.join(", "));
152 out.write(') => Intl.message("${translations[locale]}");');
153 return out.toString();
154 }
155
156 /**
157 * Escape the string to be used in the name, as a map key. So no double quotes
158 * and no interpolation. Assumes that the string has no existing escaping.
159 */
160 String escapeForName(String s) {
161 var escaped1 = s.replaceAll('"', r'\"');
162 var escaped2 = escaped1.replaceAll('\$', r'\$');
163 return escaped2;
164 }
165
166 String toString() =>
167 "Intl.message(${fullMessage()}, $name, $description, $examples, "
168 "$arguments)";
169 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698