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

Side by Side Diff: packages/intl/lib/intl.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 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
« no previous file with comments | « packages/intl/lib/generate_localized.dart ('k') | packages/intl/lib/intl_browser.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 /// This library provides internationalization and localization. This includes
6 * This library provides internationalization and localization. This includes 6 /// message formatting and replacement, date and number formatting and parsing,
7 * message formatting and replacement, date and number formatting and parsing, 7 /// and utilities for working with Bidirectional text.
8 * and utilities for working with Bidirectional text. 8 ///
9 * 9 /// This is part of the [intl package]
10 * This is part of the [intl package] 10 /// (https://pub.dartlang.org/packages/intl).
11 * (https://pub.dartlang.org/packages/intl). 11 ///
12 * 12 /// For things that require locale or other data, there are multiple different
13 * For things that require locale or other data, there are multiple different 13 /// ways of making that data available, which may require importing different
14 * ways of making that data available, which may require importing different 14 /// libraries. See the class comments for more details.
15 * libraries. See the class comments for more details. 15 ///
16 * 16 /// There is also a simple example application that can be found in the
17 * There is also a simple example application that can be found in the 17 /// [example/basic](https://github.com/dart-lang/intl/tree/master/example/basic)
18 * [example/basic](https://github.com/dart-lang/intl/tree/master/example/basic) 18 /// directory.
19 * directory.
20 */
21 library intl; 19 library intl;
22 20
23 import 'dart:async'; 21 import 'dart:async';
24 import 'dart:collection'; 22 import 'dart:collection';
25 import 'dart:convert'; 23 import 'dart:convert';
26 import 'dart:math'; 24 import 'dart:math';
27 25
28 import 'date_symbols.dart'; 26 import 'date_symbols.dart';
29 import 'number_symbols.dart'; 27 import 'number_symbols.dart';
30 import 'number_symbols_data.dart'; 28 import 'number_symbols_data.dart';
31 import 'src/date_format_internal.dart'; 29 import 'src/date_format_internal.dart';
32 import 'src/intl_helpers.dart'; 30 import 'src/intl_helpers.dart';
31 import 'package:intl/src/plural_rules.dart' as plural_rules;
33 32
34 part 'src/intl/bidi_formatter.dart'; 33 part 'src/intl/bidi_formatter.dart';
35 part 'src/intl/bidi_utils.dart'; 34 part 'src/intl/bidi_utils.dart';
35
36 part 'src/intl/compact_number_format.dart';
36 part 'src/intl/date_format.dart'; 37 part 'src/intl/date_format.dart';
37 part 'src/intl/date_format_field.dart'; 38 part 'src/intl/date_format_field.dart';
38 part 'src/intl/date_format_helpers.dart'; 39 part 'src/intl/date_format_helpers.dart';
39 part 'src/intl/number_format.dart'; 40 part 'src/intl/number_format.dart';
40 41
41 /** 42 /// The Intl class provides a common entry point for internationalization
42 * The Intl class provides a common entry point for internationalization 43 /// related tasks. An Intl instance can be created for a particular locale
43 * related tasks. An Intl instance can be created for a particular locale 44 /// and used to create a date format via `anIntl.date()`. Static methods
44 * and used to create a date format via `anIntl.date()`. Static methods 45 /// on this class are also used in message formatting.
45 * on this class are also used in message formatting. 46 ///
46 * 47 /// Examples:
47 * Examples: 48 /// today(date) => Intl.message(
48 * today(date) => Intl.message( 49 /// "Today's date is $date",
49 * "Today's date is $date", 50 /// name: 'today',
50 * name: 'today', 51 /// args: [date],
51 * args: [date], 52 /// desc: 'Indicate the current date',
52 * desc: 'Indicate the current date', 53 /// examples: const {'date' : 'June 8, 2012'});
53 * examples: {'date' : 'June 8, 2012'}); 54 /// print(today(new DateTime.now().toString());
54 * print(today(new DateTime.now().toString()); 55 ///
55 * 56 /// howManyPeople(numberOfPeople, place) => Intl.plural(
56 * howManyPeople(numberOfPeople, place) => Intl.plural( 57 /// zero: 'I see no one at all',
57 * zero: 'I see no one at all', 58 /// one: 'I see one other person',
58 * one: 'I see one other person', 59 /// other: 'I see $numberOfPeople other people')} in $place.''',
59 * other: 'I see $numberOfPeople other people')} in $place.''', 60 /// name: 'msg',
60 * name: 'msg', 61 /// args: [numberOfPeople, place],
61 * args: [numberOfPeople, place], 62 /// desc: 'Description of how many people are seen in a place.',
62 * desc: 'Description of how many people are seen in a place.', 63 /// examples: const {'numberOfPeople': 3, 'place': 'London'});
63 * examples: {'numberOfPeople': 3, 'place': 'London'}); 64 ///
64 * 65 /// Calling `howManyPeople(2, 'Athens');` would
65 * Calling `howManyPeople(2, 'Athens');` would 66 /// produce "I see 2 other people in Athens." as output in the default locale.
66 * produce "I see 2 other people in Athens." as output in the default locale. 67 /// If run in a different locale it would produce appropriately translated
67 * If run in a different locale it would produce appropriately translated 68 /// output.
68 * output. 69 ///
69 * 70 /// For more detailed information on messages and localizing them see
70 * For more detailed information on messages and localizing them see 71 /// the main [package documentation](https://pub.dartlang.org/packages/intl)
71 * the main [package documentation](https://pub.dartlang.org/packages/intl) 72 ///
72 * 73 /// You can set the default locale.
73 * You can set the default locale. 74 /// Intl.defaultLocale = "pt_BR";
74 * Intl.defaultLocale = "pt_BR"; 75 ///
75 * 76 /// To temporarily use a locale other than the default, use the `withLocale`
76 * To temporarily use a locale other than the default, use the `withLocale` 77 /// function.
77 * function. 78 /// var todayString = new DateFormat("pt_BR").format(new DateTime.now());
78 * var todayString = new DateFormat("pt_BR").format(new DateTime.now()); 79 /// print(withLocale("pt_BR", () => today(todayString));
79 * print(withLocale("pt_BR", () => today(todayString)); 80 ///
80 * 81 /// See `tests/message_format_test.dart` for more examples.
81 * See `tests/message_format_test.dart` for more examples.
82 */
83 //TODO(efortuna): documentation example involving the offset parameter? 82 //TODO(efortuna): documentation example involving the offset parameter?
84 83
85 class Intl { 84 class Intl {
86 /** 85 /// String indicating the locale code with which the message is to be
87 * String indicating the locale code with which the message is to be 86 /// formatted (such as en-CA).
88 * formatted (such as en-CA).
89 */
90 String _locale; 87 String _locale;
91 88
92 /** 89 /// The default locale. This defaults to being set from systemLocale, but
93 * The default locale. This defaults to being set from systemLocale, but 90 /// can also be set explicitly, and will then apply to any new instances where
94 * can also be set explicitly, and will then apply to any new instances where 91 /// the locale isn't specified. Note that a locale parameter to
95 * the locale isn't specified. Note that a locale parameter to 92 /// [Intl.withLocale]
96 * [Intl.withLocale] 93 /// will supercede this value while that operation is active. Using
97 * will supercede this value while that operation is active. Using 94 /// [Intl.withLocale] may be preferable if you are using different locales
98 * [Intl.withLocale] may be preferable if you are using different locales 95 /// in the same application.
99 * in the same application.
100 */
101 static String get defaultLocale { 96 static String get defaultLocale {
102 var zoneLocale = Zone.current[#Intl.locale]; 97 var zoneLocale = Zone.current[#Intl.locale];
103 return zoneLocale == null ? _defaultLocale : zoneLocale; 98 return zoneLocale == null ? _defaultLocale : zoneLocale;
104 } 99 }
105 static set defaultLocale(String newLocale) => _defaultLocale = newLocale; 100
101 static set defaultLocale(String newLocale) {
102 _defaultLocale = newLocale;
103 }
104
106 static String _defaultLocale; 105 static String _defaultLocale;
107 106
108 /** 107 /// The system's locale, as obtained from the window.navigator.language
109 * The system's locale, as obtained from the window.navigator.language 108 /// or other operating system mechanism. Note that due to system limitations
110 * or other operating system mechanism. Note that due to system limitations 109 /// this is not automatically set, and must be set by importing one of
111 * this is not automatically set, and must be set by importing one of 110 /// intl_browser.dart or intl_standalone.dart and calling findSystemLocale().
112 * intl_browser.dart or intl_standalone.dart and calling findSystemLocale().
113 */
114 static String systemLocale = 'en_US'; 111 static String systemLocale = 'en_US';
115 112
116 /** 113 /// Return a new date format using the specified [pattern].
117 * Return a new date format using the specified [pattern]. 114 /// If [desiredLocale] is not specified, then we default to [locale].
118 * If [desiredLocale] is not specified, then we default to [locale].
119 */
120 DateFormat date([String pattern, String desiredLocale]) { 115 DateFormat date([String pattern, String desiredLocale]) {
121 var actualLocale = (desiredLocale == null) ? locale : desiredLocale; 116 var actualLocale = (desiredLocale == null) ? locale : desiredLocale;
122 return new DateFormat(pattern, actualLocale); 117 return new DateFormat(pattern, actualLocale);
123 } 118 }
124 119
125 /** 120 /// Constructor optionally [aLocale] for specifics of the language
126 * Constructor optionally [aLocale] for specifics of the language 121 /// locale to be used, otherwise, we will attempt to infer it (acceptable if
127 * locale to be used, otherwise, we will attempt to infer it (acceptable if 122 /// Dart is running on the client, we can infer from the browser/client
128 * Dart is running on the client, we can infer from the browser/client 123 /// preferences).
129 * preferences).
130 */
131 Intl([String aLocale]) { 124 Intl([String aLocale]) {
132 _locale = aLocale != null ? aLocale : getCurrentLocale(); 125 _locale = aLocale != null ? aLocale : getCurrentLocale();
133 } 126 }
134 127
135 /** 128 /// Use this for a message that will be translated for different locales. The
136 * Use this for a message that will be translated for different locales. The 129 /// expected usage is that this is inside an enclosing function that only
137 * expected usage is that this is inside an enclosing function that only 130 /// returns the value of this call and provides a scope for the variables that
138 * returns the value of this call and provides a scope for the variables that 131 /// will be substituted in the message.
139 * will be substituted in the message. 132 ///
140 * 133 /// The [message_str] is the string to be translated, which may be
141 * The [message_str] is the string to be translated, which may be interpolated 134 /// interpolated based on one or more variables. The [name] of the message
142 * based on one or more variables. The [name] of the message must 135 /// must match the enclosing function name. For methods, it can also be
143 * match the enclosing function name. For methods, it can also be 136 /// className_methodName. So for a method hello in class Simple, the name can
144 * className_methodName. So for a method hello in class Simple, the name 137 /// be either "hello" or "Simple_hello". The name must also be globally unique
145 * can be either "hello" or "Simple_hello". The name must also be globally 138 /// in the program, so the second form can make it easier to distinguish
146 * unique in the program, so the second form can make it easier to distinguish 139 /// messages with the same name but in different classes.
147 * messages with the same name but in different classes. 140 ///
148 * The [args] repeats the arguments of the enclosing 141 /// The [args] repeats the arguments of the enclosing
149 * function, [desc] provides a description of usage, 142 /// function, [desc] provides a description of usage,
150 * [examples] is a Map of exmaples for each interpolated variable. For example 143 /// [examples] is a Map of examples for each interpolated variable.
151 * hello(yourName) => Intl.message( 144 /// For example
152 * "Hello, $yourName", 145 ///
153 * name: "hello", 146 /// hello(yourName) => Intl.message(
154 * args: [yourName], 147 /// "Hello, $yourName",
155 * desc: "Say hello", 148 /// name: "hello",
156 * examples = {"yourName": "Sparky"}. 149 /// args: [yourName],
157 * The source code will be processed via the analyzer to extract out the 150 /// desc: "Say hello",
158 * message data, so only a subset of valid Dart code is accepted. In 151 /// examples = const {"yourName": "Sparky"}.
159 * particular, everything must be literal and cannot refer to variables 152 ///
160 * outside the scope of the enclosing function. The [examples] map must 153 /// The source code will be processed via the analyzer to extract out the
161 * be a valid const literal map. Similarly, the [desc] argument must 154 /// message data, so only a subset of valid Dart code is accepted. In
162 * be a single, simple string. These two arguments will not be used at runtime 155 /// particular, everything must be literal and cannot refer to variables
163 * but will be extracted from 156 /// outside the scope of the enclosing function. The [examples] map must be a
164 * the source code and used as additional data for translators. For more 157 /// valid const literal map. Similarly, the [desc] argument must be a single,
165 * information see the "Messages" section of the main [package documentation] 158 /// simple string. These two arguments will not be used at runtime but will be
166 * (https://pub.dartlang.org/packages/intl). 159 /// extracted from the source code and used as additional data for
167 * 160 /// translators. For more information see the "Messages" section of the main
168 * The [name] and [args] arguments are required, and are used at runtime 161 /// [package documentation] (https://pub.dartlang.org/packages/intl).
169 * to look up the localized version and pass the appropriate arguments to it. 162 ///
170 * We may in the future modify the code during compilation to make manually 163 /// The [name] and [args] arguments are required, and are used at runtime
171 * passing those arguments unnecessary. 164 /// to look up the localized version and pass the appropriate arguments to it.
172 */ 165 /// We may in the future modify the code during compilation to make manually
173 static String message(String message_str, {String desc: '', 166 /// passing those arguments unnecessary.
174 Map<String, String> examples: const {}, String locale, String name, 167 static String message(String message_str,
175 List<String> args, String meaning}) { 168 {String desc: '',
169 Map<String, dynamic> examples: const {},
170 String locale,
171 String name,
172 List args,
173 String meaning}) =>
174 _message(message_str, locale, name, args, meaning);
175
176 /// Omit the compile-time only parameters so dart2js can see to drop them.
177 static _message(String message_str, String locale, String name, List args,
178 String meaning) {
176 return messageLookup.lookupMessage( 179 return messageLookup.lookupMessage(
177 message_str, desc, examples, locale, name, args, meaning); 180 message_str, locale, name, args, meaning);
178 } 181 }
179 182
180 /** 183 /// Return the locale for this instance. If none was set, the locale will
181 * Return the locale for this instance. If none was set, the locale will 184 /// be the default.
182 * be the default.
183 */
184 String get locale => _locale; 185 String get locale => _locale;
185 186
186 /** 187 /// Given [newLocale] return a locale that we have data for that is similar
187 * Return true if the locale exists, or if it is null. The null case 188 /// to it, if possible.
188 * is interpreted to mean that we use the default locale. 189 ///
189 */ 190 /// If [newLocale] is found directly, return it. If it can't be found, look up
190 static bool _localeExists(localeName) => DateFormat.localeExists(localeName); 191 /// based on just the language (e.g. 'en_CA' -> 'en'). Also accepts '-'
191 192 /// as a separator and changes it into '_' for lookup, and changes the
192 /** 193 /// country to uppercase.
193 * Given [newLocale] return a locale that we have data for that is similar 194 ///
194 * to it, if possible. 195 /// There is a special case that if a locale named "fallback" is present
195 * 196 /// and has been initialized, this will return that name. This can be useful
196 * If [newLocale] is found directly, return it. If it can't be found, look up 197 /// for messages where you don't want to just use the text from the original
197 * based on just the language (e.g. 'en_CA' -> 'en'). Also accepts '-' 198 /// source code, but wish to have a universal fallback translation.
198 * as a separator and changes it into '_' for lookup, and changes the 199 ///
199 * country to uppercase. 200 /// Note that null is interpreted as meaning the default locale, so if
200 * 201 /// [newLocale] is null the default locale will be returned.
201 * There is a special case that if a locale named "fallback" is present
202 * and has been initialized, this will return that name. This can be useful
203 * for messages where you don't want to just use the text from the original
204 * source code, but wish to have a universal fallback translation.
205 *
206 * Note that null is interpreted as meaning the default locale, so if
207 * [newLocale] is null it will be returned.
208 */
209 static String verifiedLocale(String newLocale, Function localeExists, 202 static String verifiedLocale(String newLocale, Function localeExists,
210 {Function onFailure: _throwLocaleError}) { 203 {Function onFailure: _throwLocaleError}) {
211 // TODO(alanknight): Previously we kept a single verified locale on the Intl 204 // TODO(alanknight): Previously we kept a single verified locale on the Intl
212 // object, but with different verification for different uses, that's more 205 // object, but with different verification for different uses, that's more
213 // difficult. As a result, we call this more often. Consider keeping 206 // difficult. As a result, we call this more often. Consider keeping
214 // verified locales for each purpose if it turns out to be a performance 207 // verified locales for each purpose if it turns out to be a performance
215 // issue. 208 // issue.
216 if (newLocale == null) { 209 if (newLocale == null) {
217 return verifiedLocale(getCurrentLocale(), localeExists, 210 return verifiedLocale(getCurrentLocale(), localeExists,
218 onFailure: onFailure); 211 onFailure: onFailure);
219 } 212 }
220 if (localeExists(newLocale)) { 213 if (localeExists(newLocale)) {
221 return newLocale; 214 return newLocale;
222 } 215 }
223 for (var each in 216 for (var each in [
224 [canonicalizedLocale(newLocale), shortLocale(newLocale), "fallback"]) { 217 canonicalizedLocale(newLocale),
218 shortLocale(newLocale),
219 "fallback"
220 ]) {
225 if (localeExists(each)) { 221 if (localeExists(each)) {
226 return each; 222 return each;
227 } 223 }
228 } 224 }
229 return onFailure(newLocale); 225 return onFailure(newLocale);
230 } 226 }
231 227
232 /** 228 /// The default action if a locale isn't found in verifiedLocale. Throw
233 * The default action if a locale isn't found in verifiedLocale. Throw 229 /// an exception indicating the locale isn't correct.
234 * an exception indicating the locale isn't correct.
235 */
236 static String _throwLocaleError(String localeName) { 230 static String _throwLocaleError(String localeName) {
237 throw new ArgumentError("Invalid locale '$localeName'"); 231 throw new ArgumentError("Invalid locale '$localeName'");
238 } 232 }
239 233
240 /** Return the short version of a locale name, e.g. 'en_US' => 'en' */ 234 /// Return the short version of a locale name, e.g. 'en_US' => 'en'
241 static String shortLocale(String aLocale) { 235 static String shortLocale(String aLocale) {
242 if (aLocale.length < 2) return aLocale; 236 if (aLocale.length < 2) return aLocale;
243 return aLocale.substring(0, 2).toLowerCase(); 237 return aLocale.substring(0, 2).toLowerCase();
244 } 238 }
245 239
246 /** 240 /// Return the name [aLocale] turned into xx_YY where it might possibly be
247 * Return the name [aLocale] turned into xx_YY where it might possibly be 241 /// in the wrong case or with a hyphen instead of an underscore. If
248 * in the wrong case or with a hyphen instead of an underscore. If 242 /// [aLocale] is null, for example, if you tried to get it from IE,
249 * [aLocale] is null, for example, if you tried to get it from IE, 243 /// return the current system locale.
250 * return the current system locale.
251 */
252 static String canonicalizedLocale(String aLocale) { 244 static String canonicalizedLocale(String aLocale) {
253 // Locales of length < 5 are presumably two-letter forms, or else malformed. 245 // Locales of length < 5 are presumably two-letter forms, or else malformed.
254 // We return them unmodified and if correct they will be found. 246 // We return them unmodified and if correct they will be found.
255 // Locales longer than 6 might be malformed, but also do occur. Do as 247 // Locales longer than 6 might be malformed, but also do occur. Do as
256 // little as possible to them, but make the '-' be an '_' if it's there. 248 // little as possible to them, but make the '-' be an '_' if it's there.
257 // We treat C as a special case, and assume it wants en_ISO for formatting. 249 // We treat C as a special case, and assume it wants en_ISO for formatting.
258 // TODO(alanknight): en_ISO is probably not quite right for the C/Posix 250 // TODO(alanknight): en_ISO is probably not quite right for the C/Posix
259 // locale for formatting. Consider adding C to the formats database. 251 // locale for formatting. Consider adding C to the formats database.
260 if (aLocale == null) return getCurrentLocale(); 252 if (aLocale == null) return getCurrentLocale();
261 if (aLocale == "C") return "en_ISO"; 253 if (aLocale == "C") return "en_ISO";
262 if (aLocale.length < 5) return aLocale; 254 if (aLocale.length < 5) return aLocale;
263 if (aLocale[2] != '-' && (aLocale[2] != '_')) return aLocale; 255 if (aLocale[2] != '-' && (aLocale[2] != '_')) return aLocale;
264 var region = aLocale.substring(3); 256 var region = aLocale.substring(3);
265 // If it's longer than three it's something odd, so don't touch it. 257 // If it's longer than three it's something odd, so don't touch it.
266 if (region.length <= 3) region = region.toUpperCase(); 258 if (region.length <= 3) region = region.toUpperCase();
267 return '${aLocale[0]}${aLocale[1]}_$region'; 259 return '${aLocale[0]}${aLocale[1]}_$region';
268 } 260 }
269 261
270 /** 262 /// Format a message differently depending on [howMany]. Normally used
271 * Format a message differently depending on [howMany]. Normally used 263 /// as part of an `Intl.message` text that is to be translated.
272 * as part of an `Intl.message` text that is to be translated. 264 /// Selects the correct plural form from
273 * Selects the correct plural form from 265 /// the provided alternatives. The [other] named argument is mandatory.
274 * the provided alternatives. The [other] named argument is mandatory. 266 static String plural(int howMany,
275 */ 267 {zero,
276 static String plural(int howMany, {zero, one, two, few, many, other, 268 one,
277 String desc, Map<String, String> examples, String locale, String name, 269 two,
278 List<String> args, String meaning}) { 270 few,
271 many,
272 other,
273 String desc,
274 Map<String, dynamic> examples,
275 String locale,
276 String name,
277 List args,
278 String meaning}) {
279 // If we are passed a name and arguments, then we are operating as a 279 // If we are passed a name and arguments, then we are operating as a
280 // top-level message, so look up our translation by calling Intl.message 280 // top-level message, so look up our translation by calling Intl.message
281 // with ourselves as an argument. 281 // with ourselves as an argument.
282 if (name != null) { 282 if (name != null) {
283 return message(plural(howMany, 283 return message(
284 plural(howMany,
284 zero: zero, 285 zero: zero,
285 one: one, 286 one: one,
286 two: two, 287 two: two,
287 few: few, 288 few: few,
288 many: many, 289 many: many,
289 other: other), 290 other: other,
290 name: name, args: args, locale: locale, meaning: meaning); 291 locale: locale),
292 name: name,
293 args: args,
294 locale: locale,
295 meaning: meaning);
291 } 296 }
292 if (other == null) { 297 if (other == null) {
293 throw new ArgumentError("The 'other' named argument must be provided"); 298 throw new ArgumentError("The 'other' named argument must be provided");
294 } 299 }
295 // TODO(alanknight): This algorithm needs to be locale-dependent. 300 if (howMany == null) {
296 switch (howMany) { 301 throw new ArgumentError("The howMany argument to plural cannot be null");
297 case 0: 302 }
298 return (zero == null) ? other : zero; 303 // If there's an explicit case for the exact number, we use it. This is not
299 case 1: 304 // strictly in accord with the CLDR rules, but it seems to be the
300 return (one == null) ? other : one; 305 // expectation. At least I see e.g. Russian translations that have a zero
301 case 2: 306 // case defined. The rule for that locale will never produce a zero, and
302 return (two == null) ? ((few == null) ? other : few) : two; 307 // treats it as other. But it seems reasonable that, even if the language
308 // rules treat zero as other, we might want a special message for zero.
309 if (howMany == 0 && zero != null) return zero;
310 if (howMany == 1 && one != null) return one;
311 if (howMany == 2 && two != null) return two;
312 var pluralRule = _pluralRule(locale, howMany);
313 var pluralCase = pluralRule();
314 switch (pluralCase) {
315 case plural_rules.PluralCase.ZERO:
316 return zero ?? other;
317 case plural_rules.PluralCase.ONE:
318 return one ?? other;
319 case plural_rules.PluralCase.TWO:
320 return two ?? few ?? other;
321 case plural_rules.PluralCase.FEW:
322 return few ?? other;
323 case plural_rules.PluralCase.MANY:
324 return many ?? other;
325 case plural_rules.PluralCase.OTHER:
326 return other;
303 default: 327 default:
304 if ((howMany == 3 || howMany == 4) && few != null) return few; 328 throw new ArgumentError.value(
305 if (howMany > 10 && howMany < 100 && many != null) return many; 329 howMany, "howMany", "Invalid plural argument");
306 return other;
307 } 330 }
308 throw new ArgumentError("Invalid plural usage for $howMany");
309 } 331 }
310 332
311 /** 333 static var _cachedPluralRule;
312 * Format a message differently depending on [targetGender]. Normally used as 334 static String _cachedPluralLocale;
313 * part of an Intl.message message that is to be translated. 335
314 */ 336 static _pluralRule(String locale, int howMany) {
315 static String gender(String targetGender, {String male, String female, 337 plural_rules.startRuleEvaluation(howMany);
316 String other, String desc, Map<String, String> examples, String locale, 338 var verifiedLocale = Intl.verifiedLocale(
317 String name, List<String> args, String meaning}) { 339 locale, plural_rules.localeHasPluralRules,
340 onFailure: (locale) => 'default');
341 if (_cachedPluralLocale == verifiedLocale) {
342 return _cachedPluralRule;
343 } else {
344 _cachedPluralRule = plural_rules.pluralRules[verifiedLocale];
345 _cachedPluralLocale = verifiedLocale;
346 return _cachedPluralRule;
347 }
348 }
349
350 /// Format a message differently depending on [targetGender]. Normally used as
351 /// part of an Intl.message message that is to be translated.
352 static String gender(String targetGender,
353 {String male,
354 String female,
355 String other,
356 String desc,
357 Map<String, dynamic> examples,
358 String locale,
359 String name,
360 List args,
361 String meaning}) {
318 // If we are passed a name and arguments, then we are operating as a 362 // If we are passed a name and arguments, then we are operating as a
319 // top-level message, so look up our translation by calling Intl.message 363 // top-level message, so look up our translation by calling Intl.message
320 // with ourselves as an argument. 364 // with ourselves as an argument.
321 if (name != null) { 365 if (name != null) {
322 return message( 366 return message(
323 gender(targetGender, male: male, female: female, other: other), 367 gender(targetGender, male: male, female: female, other: other),
324 name: name, args: args, locale: locale, meaning: meaning); 368 name: name,
369 args: args,
370 locale: locale,
371 meaning: meaning);
325 } 372 }
326 373
327 if (other == null) { 374 if (other == null) {
328 throw new ArgumentError("The 'other' named argument must be specified"); 375 throw new ArgumentError("The 'other' named argument must be specified");
329 } 376 }
330 switch (targetGender) { 377 switch (targetGender) {
331 case "female": 378 case "female":
332 return female == null ? other : female; 379 return female == null ? other : female;
333 case "male": 380 case "male":
334 return male == null ? other : male; 381 return male == null ? other : male;
335 default: 382 default:
336 return other; 383 return other;
337 } 384 }
338 } 385 }
339 386
340 /** 387 /// Format a message differently depending on [choice]. We look up the value
341 * Format a message differently depending on [choice]. We look up the value 388 /// of [choice] in [cases] and return the result, or an empty string if
342 * of [choice] in [cases] and return the result, or an empty string if 389 /// it is not found. Normally used as part
343 * it is not found. Normally used as part 390 /// of an Intl.message message that is to be translated.
344 * of an Intl.message message that is to be translated. 391 static String select(Object choice, Map<String, String> cases,
345 */ 392 {String desc,
346 static String select(String choice, Map<String, String> cases, {String desc, 393 Map<String, dynamic> examples,
347 Map<String, String> examples, String locale, String name, 394 String locale,
348 List<String> args, String meaning}) { 395 String name,
396 List args,
397 String meaning}) {
398 // Allow passing non-strings, e.g. enums to a select.
399 choice = "$choice";
349 // If we are passed a name and arguments, then we are operating as a 400 // If we are passed a name and arguments, then we are operating as a
350 // top-level message, so look up our translation by calling Intl.message 401 // top-level message, so look up our translation by calling Intl.message
351 // with ourselves as an argument. 402 // with ourselves as an argument.
352 if (name != null) { 403 if (name != null) {
353 return message(select(choice, cases), 404 return message(select(choice, cases),
354 name: name, args: args, locale: locale); 405 name: name, args: args, locale: locale);
355 } 406 }
356 var exact = cases[choice]; 407 var exact = cases[choice];
357 if (exact != null) return exact; 408 if (exact != null) return exact;
358 var other = cases["other"]; 409 var other = cases["other"];
359 if (other == 410 if (other == null)
360 null) throw new ArgumentError("The 'other' case must be specified"); 411 throw new ArgumentError("The 'other' case must be specified");
361 return other; 412 return other;
362 } 413 }
363 414
364 /** 415 /// Run [function] with the default locale set to [locale] and
365 * Run [function] with the default locale set to [locale] and 416 /// return the result.
366 * return the result. 417 ///
367 * 418 /// This is run in a zone, so async operations invoked
368 * This is run in a zone, so async operations invoked 419 /// from within [function] will still have the locale set.
369 * from within [function] will still have the locale set. 420 ///
370 * 421 /// In simple usage [function] might be a single
371 * In simple usage [function] might be a single 422 /// `Intl.message()` call or number/date formatting operation. But it can
372 * `Intl.message()` call or number/date formatting operation. But it can 423 /// also be an arbitrary function that calls multiple Intl operations.
373 * also be an arbitrary function that calls multiple Intl operations. 424 ///
374 * 425 /// For example
375 * For example 426 ///
376 * 427 /// Intl.withLocale("fr", () => new NumberFormat.format(123456));
377 * Intl.withLocale("fr", () => new NumberFormat.format(123456)); 428 ///
378 * 429 /// or
379 * or 430 ///
380 * 431 /// hello(name) => Intl.message(
381 * hello(name) => Intl.message( 432 /// "Hello $name.",
382 * "Hello $name.", 433 /// name: 'hello',
383 * name: 'hello', 434 /// args: [name],
384 * args: [name], 435 /// desc: 'Say Hello');
385 * desc: 'Say Hello'); 436 /// Intl.withLocale("zh", new Timer(new Duration(milliseconds:10),
386 * Intl.withLocale("zh", new Timer(new Duration(milliseconds:10), 437 /// () => print(hello("World")));
387 * () => print(hello("World")));
388 */
389 static withLocale(String locale, function()) { 438 static withLocale(String locale, function()) {
390 var canonical = Intl.canonicalizedLocale(locale); 439 var canonical = Intl.canonicalizedLocale(locale);
391 return runZoned(function, zoneValues: {#Intl.locale: canonical}); 440 return runZoned(function, zoneValues: {#Intl.locale: canonical});
392 } 441 }
393 442
394 /** 443 /// Accessor for the current locale. This should always == the default locale,
395 * Accessor for the current locale. This should always == the default locale, 444 /// unless for some reason this gets called inside a message that resets the
396 * unless for some reason this gets called inside a message that resets the 445 /// locale.
397 * locale.
398 */
399 static String getCurrentLocale() { 446 static String getCurrentLocale() {
400 if (defaultLocale == null) defaultLocale = systemLocale; 447 if (defaultLocale == null) defaultLocale = systemLocale;
401 return defaultLocale; 448 return defaultLocale;
402 } 449 }
403 450
404 toString() => "Intl($locale)"; 451 toString() => "Intl($locale)";
405 } 452 }
453
454 /// Convert a string to beginning of sentence case, in a way appropriate to the
455 /// locale.
456 ///
457 /// Currently this just converts the first letter to uppercase, which works for
458 /// many locales, and we have the option to extend this to handle more cases
459 /// without changing the API for clients. It also hard-codes the case of
460 /// dotted i in Turkish and Azeri.
461 String toBeginningOfSentenceCase(String input, [String locale]) {
462 if (input == null || input.isEmpty) return input;
463 return "${_upperCaseLetter(input[0], locale)}${input.substring(1)}";
464 }
465
466 /// Convert the input single-letter string to upper case. A trivial
467 /// hard-coded implementation that only handles simple upper case
468 /// and the dotted i in Turkish/Azeri.
469 ///
470 /// Private to the implementation of [toBeginningOfSentenceCase].
471 // TODO(alanknight): Consider hard-coding other important cases.
472 // See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt
473 // TODO(alanknight): Alternatively, consider toLocaleUpperCase in browsers.
474 // See also https://github.com/dart-lang/sdk/issues/6706
475 String _upperCaseLetter(String input, String locale) {
476 // Hard-code the important edge case of i->İ
477 if (locale != null) {
478 if (input == "i" && locale.startsWith("tr") || locale.startsWith("az")) {
479 return "\u0130";
480 }
481 }
482 return input.toUpperCase();
483 }
OLDNEW
« no previous file with comments | « packages/intl/lib/generate_localized.dart ('k') | packages/intl/lib/intl_browser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698