Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 Intl | |
| 2 ==== | |
| 3 | |
| 4 This package provides internationalization and localization facilities, | |
| 5 including message translation, plurals and genders, date/number formatting | |
| 6 and parsing, and bidirectional text. | |
| 7 | |
| 8 ## General | |
| 9 The most important library is [intl][intl_lib]. It defines the [Intl][Intl] | |
| 10 class, with the default locale and methods for accessing most of the | |
| 11 internationalization mechanisms. This library also defines the | |
| 12 [DateFormat][DateFormat], [NumberFormat][NumberFormat], and | |
| 13 [BidiFormatter][BidiFormatter] classes. | |
| 14 | |
| 15 ## Current locale | |
| 16 | |
| 17 The package has a single current locale, called [defaultLocale][defaultLocale]. | |
| 18 Operations will use that locale unless told to do otherwise. | |
| 19 | |
| 20 To set the global locale, you can explicitly set it, e.g. | |
| 21 | |
| 22 Intl.defaultLocale = 'pt_BR'; | |
| 23 | |
| 24 or get it from the browser by | |
| 25 | |
| 26 import "package:intl/intl_browser.dart"; | |
| 27 ... | |
| 28 findSystemLocale().then(runTheRestOfMyProgram); | |
| 29 | |
| 30 To temporarily override the current locale, pass the operation | |
| 31 to [withLocale][withLocale]. | |
| 32 | |
| 33 Intl.withLocale('fr', () => print(myLocalizedMessage()); | |
| 34 | |
| 35 To override it for a very specific operation you can create a format object in | |
| 36 a specific locale, or pass in the locale as a parameter to methods. | |
| 37 | |
| 38 var format = new DateFormat.yMd("ar"); | |
| 39 var dateString = format.format(new DateTime.now()); | |
| 40 print(myMessage(dateString, locale: 'ar'); | |
| 41 | |
| 42 ## Initialization | |
| 43 | |
| 44 All the different types of locale data require an async initialization step | |
| 45 to make | |
| 46 sure the data is available. This reduces the size of the application by only | |
| 47 loading the | |
| 48 data that is actually required. However, deferred loading does not yet work for | |
| 49 multiple | |
| 50 libraries, so currently all the code will be included anyay, increasing the code | |
| 51 size in the short term. | |
| 52 | |
| 53 Each different area of internationalization (messages, dates, numbers) requires | |
| 54 a separate initialization process. That way, if the application only needs to | |
| 55 format dates, it doesn't need to take the time or space to load up messages, | |
| 56 numbers, or other things it may not need. | |
| 57 | |
| 58 With messages, there is also a need to import a file that won't exist until | |
| 59 the code generation step has been run. This can be awkward, but can be worked | |
| 60 around by creating a stub `messages_all.dart` file, running an empty translation | |
| 61 step, or commenting out the import until translations are available. | |
| 62 See "Extracting and Using Translated Messages" | |
| 63 | |
| 64 ## Messages | |
| 65 | |
| 66 Messages to be localized are written as functions that return the result of | |
| 67 an [Intl.message][Intl.message] call. | |
| 68 | |
| 69 String continueMessage() => Intl.message( | |
| 70 "Hit any key to continue", | |
| 71 name: "continueMessage", | |
| 72 args: [], | |
| 73 desc: "Explains that we will not proceed further until " | |
| 74 "the user presses a key"); | |
| 75 print(continueMessage()); | |
| 76 | |
| 77 This provides, in addition to the basic message string, a name, a | |
| 78 description for translators, the arguments used in the message, and | |
| 79 examples. The `name` and `args` parameters are required, and much | |
|
Emily Fortuna
2013/12/11 23:15:57
much -> must
Alan Knight
2013/12/11 23:30:32
Done.
| |
| 80 match the name and arguments list of the function. In the future we | |
| 81 hope to have these provided automatically. | |
| 82 | |
| 83 This can be run in the program before any translation has been done, | |
| 84 and will just return the message string. It can also be extracted to a | |
| 85 file and then be made to return a translated version without modifying | |
| 86 the original program. See "Extracting Messages" below for more | |
| 87 details. | |
| 88 | |
| 89 The purpose of wrapping the message in a function is to allow it to | |
| 90 have parameters which can be used in the result. The message string is | |
| 91 allowed to use a restricted form of Dart string interpolation, where | |
| 92 only the function's parameters can be used, and only in simple | |
| 93 expressions. Local variables cannot be used, and neither can | |
| 94 expressions with curly braces. Only the message string can have | |
| 95 interpolation. The name, desc, args, and examples must be literals and | |
| 96 not contain interpolations. Only the args parameter can refer to | |
| 97 variables, and it should list exactly the function parameters. If you | |
| 98 are passing numbers or dates and you want them formatted, you must do | |
| 99 the formatting outside the function and pass the formatted string into | |
| 100 the message. | |
| 101 | |
| 102 greetingMessage(name) => Intl.message( | |
| 103 "Hello $name!", | |
| 104 name: "greetingMessage", | |
| 105 args: [name], | |
| 106 desc: "Greet the user as they first open the application", | |
| 107 examples: {'name': "Emily"}); | |
|
Emily Fortuna
2013/12/11 23:15:57
nice. :-)
| |
| 108 print(greetingMessage('Dan')); | |
| 109 | |
| 110 There is one special class of complex expressions allowed in the | |
| 111 message string, for plurals and genders. | |
| 112 | |
| 113 remainingEmailsMessage(int howMany, String userName) => | |
| 114 Intl.message( | |
| 115 "${Intl.plural(howMany, | |
| 116 zero: 'There are no emails left for $userName.', | |
| 117 one: 'There is one email left for $userName.', | |
| 118 other: 'There are $howMany emails left for $userName.')}", | |
| 119 name: "remainingEmailsMessage", | |
| 120 args: [howMany, userName], | |
| 121 desc: "How many emails remain after archiving.", | |
| 122 examples: {'number': 42, 'userName': 'Fred'}); | |
| 123 | |
| 124 print(remainingEmailsMessage(1, "Fred")); | |
| 125 | |
| 126 However, since the typical usage for a plural or gender is for it to | |
| 127 be at the top-level, we can also omit the [Intl.message][Intl.message] call and | |
| 128 provide its parameters to the [Intl.plural][Intl.plural] call instead. | |
| 129 | |
| 130 remainingEmailsMessage(int howMany, String userName) => | |
| 131 Intl.plural( | |
| 132 howMany, | |
| 133 zero: 'There are no emails left for $userName.', | |
| 134 one: 'There is one email left for $userName.', | |
| 135 other: 'There are $howMany emails left for $userName.'), | |
| 136 name: "remainingEmailsMessage", | |
| 137 args: [howMany, userName], | |
| 138 desc: "How many emails remain after archiving.", | |
| 139 examples: {'number': 42, 'userName': 'Fred'}); | |
| 140 | |
| 141 Similarly, there is an [Intl.gender][Intl.gender] message, and plurals | |
| 142 and genders can be nested. | |
| 143 | |
| 144 notOnlineMessage(String userName, String userGender) => | |
| 145 Intl.gender( | |
| 146 userGender, | |
| 147 male: '$userName is unavailable because he is not online.', | |
| 148 female: '$userName is unavailable because she is not online.', | |
| 149 other: '$userName is unavailable because they are not online'), | |
| 150 name: "notOnlineMessage", | |
| 151 args: [userName, userGender], | |
| 152 desc: "The user is not available to hangout.", | |
| 153 examples: {{'userGender': 'male', 'userName': 'Fred'}, | |
| 154 {'userGender': 'female', 'userName' : 'Alice'}}); | |
| 155 | |
| 156 ## Extracting And Using Translated Messages | |
| 157 | |
| 158 When your program contains messages that need translation, these must | |
| 159 be extracted from the program source, sent to translators, and the | |
|
Emily Fortuna
2013/12/11 23:15:57
"translators" -> "human translators" ?
Alan Knight
2013/12/11 23:30:32
Done.
| |
| 160 results need to be incorporated. This is still work in progress, and | |
| 161 the extraction is done to a custom JSON format that is not supported | |
| 162 by translation tools. We intend to support one or more actual | |
| 163 translation file formats. | |
| 164 | |
| 165 To extract messages, run the `pkg/intl/test/extract_to_json.dart` program. | |
| 166 | |
| 167 dart extract_to_json.dart --output-dir=target/directory | |
| 168 my_program.dart more_of_my_program.dart | |
| 169 | |
| 170 This will produce a file `intl_messages.json` with the messages from | |
| 171 all of these programs. This is in a simple JSON format with a map from | |
| 172 message names to message strings. | |
| 173 | |
| 174 The reverse step expects to receive a series of files, one per | |
| 175 locale. These consist of a map with the entry for "_locale" indicating | |
| 176 the locale, and with the function name mapped to the translated | |
| 177 string. However, plurals and genders are currently represented in an | |
| 178 opaque form, by serializing the internal objects that represent | |
| 179 them. You can see the generation of this code in the | |
| 180 `make_hardcoded_translation.dart` test file. | |
| 181 | |
| 182 If you manage to create such a set of input files, then you can run | |
| 183 | |
| 184 dart generate_from_json.dart --generated_file_prefix=<prefix> | |
| 185 <my dart files> <translated json files> | |
| 186 | |
| 187 This will generate Dart libraries, one per locale, which contain the | |
| 188 translated versions. Your Dart libraries can import the primary file, | |
| 189 named `<prefix>messages_all.dart`, and then call the initialization | |
| 190 for a specific locale. Once that's done, any | |
| 191 [Intl.message][Intl.message] calls made in the context of that locale | |
| 192 will automatically print the translated version instead of the | |
| 193 original. | |
| 194 | |
| 195 import "my_prefix_messages_all.dart"; | |
| 196 ... | |
| 197 initializeMessages("dk").then(printSomeMessages); | |
| 198 | |
| 199 Once the future returned from the initialization call returns, the | |
| 200 message data is available. | |
| 201 | |
| 202 ## Number Formatting and Parsing | |
| 203 | |
| 204 To format a number, create a NumberFormat instance. | |
| 205 | |
| 206 var f = new NumberFormat("###.0#", "en_US"); | |
| 207 print(f.format(12.345)); | |
| 208 ==> 12.34 | |
| 209 | |
| 210 The locale parameter is optional. If omitted, then it will use the | |
| 211 current locale. The format string is as described in | |
| 212 [NumberFormat][NumberFormat] | |
| 213 | |
| 214 It's also possible to access the number symbol data for the current | |
| 215 locale, which provides information as to the various separator | |
| 216 characters, patterns, and other information used for formatting, as | |
| 217 | |
| 218 f.symbols | |
| 219 | |
| 220 Current known limitations are that the currency format will only print | |
| 221 the name of the currency, and does not support currency symbols, and | |
| 222 that the scientific format does not really agree with scientific | |
|
Emily Fortuna
2013/12/11 23:15:57
heh. :-)
| |
| 223 notation. Number parsing is not yet implemented. | |
| 224 | |
| 225 Note that before doing any number formatting for a particular locale | |
| 226 you must load the appropriate data by calling | |
| 227 | |
| 228 import 'package:intl/number_symbols_data_local.dart'; | |
| 229 ... | |
| 230 initializeNumberFormatting(localeName, null).then(formatNumbers); | |
| 231 | |
| 232 Once the future returned from the initialization call returns, the | |
| 233 formatting data is available. Note that right now this includes all | |
| 234 the data for a locales. We expect to make this use deferred loading to | |
| 235 reduce code size. | |
| 236 | |
| 237 ## Date Formatting and Parsing | |
| 238 | |
| 239 To format a [DateTime][DateTime], create a [DateFormat][DateFormat] | |
| 240 instance. These can be created using a set of commonly used skeletons | |
| 241 taken from ICU/CLDR or using an explicit pattern. For details on the | |
| 242 supported skeletons and patterns see [DateFormat][DateFormat]. | |
| 243 | |
| 244 new DateFormat.yMMMMEEEEd().format(aDateTime); | |
| 245 ==> 'Wednesday, January 10, 2012' | |
| 246 new DateFormat("EEEEE", "en_US").format(aDateTime); | |
| 247 ==> 'Wednesday' | |
| 248 new DateFormat("EEEEE", "ln").format(aDateTime); | |
| 249 ==> 'mokɔlɔ mwa mísáto' | |
| 250 | |
| 251 You can also parse dates using the same skeletons or patterns. | |
| 252 | |
| 253 new DateFormat.yMd("en_US").parse("1/10/2012"); | |
| 254 new DateFormat("Hms", "en_US").parse('14:23:01'); | |
| 255 | |
| 256 Skeletons can be combined, the main use being to print a full date and | |
| 257 time, e.g. | |
| 258 | |
| 259 new DateFormat.yMEd().add_jms().format(new DateTime.now()); | |
| 260 ==> 'Thu, 5/23/2013 10:21:47 AM' | |
| 261 | |
| 262 Known limitations: Time zones are not yet supported. Dart | |
| 263 [DateTime][DateTime] objects don't have a time zone, so are either | |
| 264 local or UTC. Formatting and parsing Durations is not yet implemented. | |
| 265 | |
| 266 Note that before doing any DateTime formatting for a particular | |
| 267 locale, you must load the appropriate data by calling. | |
| 268 | |
| 269 import 'package:intl/date_symbol_data_local.dart'; | |
| 270 ... | |
| 271 initializeDateFormatting("de_DE", null).then(formatDates); | |
| 272 | |
| 273 Once the future returned from the initialization call returns, the | |
| 274 formatting data is available. | |
| 275 | |
| 276 There are other mechanisms for loading the date formatting data | |
| 277 implemented, but we expect to deprecate those in favor of having the | |
| 278 data in a library as in the above, and using deferred loading to only | |
| 279 load the portions that are needed. For the time being, this will | |
| 280 include all of the data, which will increase code size. | |
| 281 | |
| 282 ## Bidirectional Text | |
| 283 | |
| 284 The class [BidiFormatter][BidiFormatter] provides utilities for | |
| 285 working with Bidirectional text. We can wrap the string with unicode | |
| 286 directional indicator characters or with an HTML span to indicate | |
| 287 direction. The direction can be specified with the | |
| 288 [RTL][BidiFormatter.RTL] and [LTR][BidiFormatter.LTR] constructors, or | |
| 289 detected from the text. | |
| 290 | |
| 291 new BidiFormatter.RTL().wrapWithUnicode('xyz'); | |
| 292 new BidiFormatter.RTL().wrapWithSpan('xyz'); | |
| 293 | |
| 294 [intl_lib]: https://api.dartlang.org/docs/channels/stable/latest/intl.html | |
| 295 [Intl]: https://api.dartlang.org/docs/channels/stable/latest/intl/Intl.html | |
| 296 [DateFormat]: https://api.dartlang.org/docs/channels/stable/latest/intl/DateForm at.html | |
| 297 [NumberFormat]: https://api.dartlang.org/docs/channels/stable/latest/intl/Number Format.html | |
| 298 [withLocale]: https://api.dartlang.org/docs/channels/stable/latest/intl/Intl.htm l#withLocale | |
| 299 [defaultLocale]: https://api.dartlang.org/docs/channels/stable/latest/intl/Intl. html#defaultLocale | |
| 300 [Intl.message]: https://api.dartlang.org/docs/channels/stable/latest/intl/Intl.h tml#message | |
| 301 [Intl.plural]: https://api.dartlang.org/docs/channels/stable/latest/intl/Intl.ht ml#plural | |
| 302 [Intl.gender]: https://api.dartlang.org/docs/channels/stable/latest/intl/Intl.ht ml#gender | |
| 303 [DateTime]: https://api.dartlang.org/docs/channels/stable/latest/dart_core/DateT ime.html | |
| 304 [BidiFormatter]: https://api.dartlang.org/docs/channels/stable/latest/intl/BidiF ormatter.html | |
| 305 [BidiFormatter.RTL]: https://api.dartlang.org/docs/channels/stable/latest/intl/B idiFormatter.html#RTL | |
| 306 [BidiFormatter.LTR]: https://api.dartlang.org/docs/channels/stable/latest/intl/B idiFormatter.html#LTR | |
| OLD | NEW |