OLD | NEW |
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 /// Message/plural format library with locale support. This can have different |
6 * Message/plural format library with locale support. This can have different | 6 /// implementations based on the mechanism for finding the localized versions of |
7 * implementations based on the mechanism for finding the localized versions | 7 /// messages. This version expects them to be in a library named e.g. |
8 * of messages. This version expects them to be in a library named e.g. | 8 /// 'messages_en_US'. The prefix is set in the "initializeMessages" call, which |
9 * 'messages_en_US'. The prefix is set in the "initializeMessages" call, which | 9 /// must be made for a locale before any lookups can be done. |
10 * must be made for a locale before any lookups can be done. | 10 /// |
11 * | 11 /// See Intl class comment or `tests/message_format_test.dart` for more |
12 * See Intl class comment or `tests/message_format_test.dart` for more examples. | 12 /// examples. |
13 */ | |
14 library message_lookup_by_library; | 13 library message_lookup_by_library; |
15 | 14 |
16 import 'intl.dart'; | 15 import 'package:intl/intl.dart'; |
| 16 import 'package:intl/src/intl_helpers.dart'; |
17 | 17 |
18 /** | 18 /// This is a message lookup mechanism that delegates to one of a collection |
19 * This is a message lookup mechanism that delegates to one of a collection | 19 /// of individual [MessageLookupByLibrary] instances. |
20 * of individual [MessageLookupByLibrary] instances. | 20 class CompositeMessageLookup implements MessageLookup { |
21 */ | 21 /// A map from locale names to the corresponding lookups. |
22 class CompositeMessageLookup { | |
23 /** A map from locale names to the corresponding lookups. */ | |
24 Map<String, MessageLookupByLibrary> availableMessages = new Map(); | 22 Map<String, MessageLookupByLibrary> availableMessages = new Map(); |
25 | 23 |
26 /** Return true if we have a message lookup for [localeName]. */ | 24 /// Return true if we have a message lookup for [localeName]. |
27 bool localeExists(localeName) => availableMessages.containsKey(localeName); | 25 bool localeExists(localeName) => availableMessages.containsKey(localeName); |
28 | 26 |
29 /** | 27 /// The last locale in which we looked up messages. |
30 * Look up the message with the given [name] and [locale] and return | 28 /// |
31 * the translated version with the values in [args] interpolated. | 29 /// If this locale matches the new one then we can skip looking up the |
32 * If nothing is found, return [message_str]. The [desc] and [examples] | 30 /// messages and assume they will be the same as last time. |
33 * parameters are ignored | 31 String _lastLocale; |
34 */ | 32 |
35 String lookupMessage(String message_str, [final String desc = '', | 33 /// Caches the last messages that we found |
36 final Map examples = const {}, String locale, String name, | 34 MessageLookupByLibrary _lastLookup; |
37 List<String> args, String meaning]) { | 35 |
38 var actualLocale = (locale == null) ? Intl.getCurrentLocale() : locale; | 36 /// Look up the message with the given [name] and [locale] and return |
39 // For this usage, if the locale doesn't exist for messages, just return | 37 /// the translated version with the values in [args] interpolated. |
40 // it and we'll fall back to the original version. | 38 /// If nothing is found, return [message_str]. The [desc] and [examples] |
41 var verifiedLocale = Intl.verifiedLocale(actualLocale, localeExists, | 39 /// parameters are ignored |
42 onFailure: (locale) => locale); | 40 String lookupMessage( |
43 var messages = availableMessages[verifiedLocale]; | 41 String message_str, String locale, String name, List args, String meaning, |
44 if (messages == null) return message_str; | 42 {MessageIfAbsent ifAbsent: _useOriginal}) { |
45 return messages.lookupMessage( | 43 // If passed null, use the default. |
46 message_str, desc, examples, locale, name, args, meaning); | 44 var knownLocale = locale ?? Intl.getCurrentLocale(); |
| 45 var messages = (knownLocale == _lastLocale) |
| 46 ? _lastLookup |
| 47 : _lookupMessageCatalog(knownLocale); |
| 48 // If we didn't find any messages for this locale, use the original string, |
| 49 // faking interpolations if necessary. |
| 50 if (messages == null) { |
| 51 return ifAbsent(message_str, args); |
| 52 } |
| 53 return messages.lookupMessage(message_str, locale, name, args, meaning, |
| 54 ifAbsent: ifAbsent); |
47 } | 55 } |
48 | 56 |
49 /** | 57 /// Find the right message lookup for [locale]. |
50 * If we do not already have a locale for [localeName] then | 58 MessageLookupByLibrary _lookupMessageCatalog(String locale) { |
51 * [findLocale] will be called and the result stored as the lookup | 59 var verifiedLocale = Intl.verifiedLocale(locale, localeExists, |
52 * mechanism for that locale. | 60 onFailure: (locale) => locale); |
53 */ | 61 _lastLocale = locale; |
54 addLocale(String localeName, Function findLocale) { | 62 _lastLookup = availableMessages[verifiedLocale]; |
| 63 return _lastLookup; |
| 64 } |
| 65 |
| 66 /// If we do not already have a locale for [localeName] then |
| 67 /// [findLocale] will be called and the result stored as the lookup |
| 68 /// mechanism for that locale. |
| 69 void addLocale(String localeName, Function findLocale) { |
55 if (localeExists(localeName)) return; | 70 if (localeExists(localeName)) return; |
56 var canonical = Intl.canonicalizedLocale(localeName); | 71 var canonical = Intl.canonicalizedLocale(localeName); |
57 var newLocale = findLocale(canonical); | 72 var newLocale = findLocale(canonical); |
58 if (newLocale != null) { | 73 if (newLocale != null) { |
59 availableMessages[localeName] = newLocale; | 74 availableMessages[localeName] = newLocale; |
60 availableMessages[canonical] = newLocale; | 75 availableMessages[canonical] = newLocale; |
| 76 // If there was already a failed lookup for [newLocale], null the cache. |
| 77 if (_lastLocale == newLocale) { |
| 78 _lastLocale = null; |
| 79 _lastLookup = null; |
| 80 } |
61 } | 81 } |
62 } | 82 } |
63 } | 83 } |
64 | 84 |
65 /** | 85 /// The default ifAbsent method, just returns the message string. |
66 * This provides an abstract class for messages looked up in generated code. | 86 String _useOriginal(String message_str, List args) => message_str; |
67 * Each locale will have a separate subclass of this class with its set of | 87 |
68 * messages. See generate_localized.dart. | 88 /// This provides an abstract class for messages looked up in generated code. |
69 */ | 89 /// Each locale will have a separate subclass of this class with its set of |
| 90 /// messages. See generate_localized.dart. |
70 abstract class MessageLookupByLibrary { | 91 abstract class MessageLookupByLibrary { |
71 /** | 92 /// Return the localized version of a message. We are passed the original |
72 * Return the localized version of a message. We are passed the original | 93 /// version of the message, which consists of a |
73 * version of the message, which consists of a | 94 /// [message_str] that will be translated, and which may be interpolated |
74 * [message_str] that will be translated, and which may be interpolated | 95 /// based on one or more variables, a [desc] providing a description of usage |
75 * based on one or more variables, a [desc] providing a description of usage | 96 /// for the [message_str], and a map of [examples] for each data element to be |
76 * for the [message_str], and a map of [examples] for each data element to be | 97 /// substituted into the message. |
77 * substituted into the message. | 98 /// |
78 * | 99 /// For example, if message="Hello, $name", then |
79 * For example, if message="Hello, $name", then | 100 /// examples = {'name': 'Sparky'}. If not using the user's default locale, or |
80 * examples = {'name': 'Sparky'}. If not using the user's default locale, or | 101 /// if the locale is not easily detectable, explicitly pass [locale]. |
81 * if the locale is not easily detectable, explicitly pass [locale]. | 102 /// |
82 * | 103 /// The values of [desc] and [examples] are not used at run-time but are only |
83 * The values of [desc] and [examples] are not used at run-time but are only | 104 /// made available to the translators, so they MUST be simple Strings |
84 * made available to the translators, so they MUST be simple Strings available | 105 /// available at compile time: no String interpolation or concatenation. The |
85 * at compile time: no String interpolation or concatenation. | 106 /// expected usage of this is inside a function that takes as parameters the |
86 * The expected usage of this is inside a function that takes as parameters | 107 /// variables used in the interpolated string. |
87 * the variables used in the interpolated string. | 108 /// |
88 * | 109 /// Ultimately, the information about the enclosing function and its arguments |
89 * Ultimately, the information about the enclosing function and its arguments | 110 /// will be extracted automatically but for the time being it must be passed |
90 * will be extracted automatically but for the time being it must be passed | 111 /// explicitly in the [name] and [args] arguments. |
91 * explicitly in the [name] and [args] arguments. | 112 String lookupMessage( |
92 */ | 113 String message_str, String locale, String name, List args, String meaning, |
93 String lookupMessage(String message_str, [final String desc = '', | 114 {MessageIfAbsent ifAbsent}) { |
94 final Map examples = const {}, String locale, String name, | 115 var notFound = false; |
95 List<String> args, String meaning]) { | 116 var actualName = computeMessageName(name, message_str, meaning); |
96 if (name == null) return message_str; | 117 if (actualName == null) notFound = true; |
97 var function = this[name]; | 118 var function = this[actualName]; |
98 return function == null ? message_str : Function.apply(function, args); | 119 notFound = notFound || (function == null); |
| 120 if (notFound) { |
| 121 return ifAbsent == null ? message_str : ifAbsent(message_str, args); |
| 122 } else { |
| 123 args = args ?? []; |
| 124 return Function.apply(function, args); |
| 125 } |
99 } | 126 } |
100 | 127 |
101 /** Return our message with the given name */ | 128 /// Return our message with the given name |
102 operator [](String messageName) => messages[messageName]; | 129 operator [](String messageName) => messages[messageName]; |
103 | 130 |
104 /** | 131 /// Subclasses should override this to return a list of their message |
105 * Subclasses should override this to return a list of their message | 132 /// functions. |
106 * functions. | |
107 */ | |
108 Map<String, Function> get messages; | 133 Map<String, Function> get messages; |
109 | 134 |
110 /** Subclasses should override this to return their locale, e.g. 'en_US' */ | 135 /// Subclasses should override this to return their locale, e.g. 'en_US' |
111 String get localeName; | 136 String get localeName; |
112 | 137 |
113 toString() => localeName; | 138 toString() => localeName; |
| 139 |
| 140 /// Return a function that returns the given string. |
| 141 /// An optimization for dart2js, used from the generated code. |
| 142 static simpleMessage(translatedString) => () => translatedString; |
114 } | 143 } |
OLD | NEW |