@@ -15,12 +15,18 @@ use intl_pluralrules::{IntlPluralRules, PluralRuleType};
15
15
use crate :: entry:: Entry ;
16
16
use crate :: entry:: GetEntry ;
17
17
use crate :: errors:: FluentError ;
18
- use crate :: resolve:: { resolve_value_for_entry , Scope } ;
18
+ use crate :: resolve:: { resolve_pattern_for_entry , Scope } ;
19
19
use crate :: resource:: FluentResource ;
20
20
use crate :: types:: FluentValue ;
21
21
22
22
#[ derive( Debug , PartialEq ) ]
23
23
pub struct Message < ' m > {
24
+ pub value : Option < & ' m ast:: Pattern < ' m > > ,
25
+ pub attributes : HashMap < & ' m str , & ' m ast:: Pattern < ' m > > ,
26
+ }
27
+
28
+ #[ derive( Debug , PartialEq ) ]
29
+ pub struct FormattedMessage < ' m > {
24
30
pub value : Option < Cow < ' m , str > > ,
25
31
pub attributes : HashMap < & ' m str , Cow < ' m , str > > ,
26
32
}
@@ -246,6 +252,43 @@ impl<'bundle, R> FluentBundle<'bundle, R> {
246
252
self . get_message ( id) . is_some ( )
247
253
}
248
254
255
+ pub fn get_message2 ( & ' bundle self , id : & str ) -> Option < Message >
256
+ where
257
+ R : Borrow < FluentResource > ,
258
+ {
259
+ let message = self . get_message ( id) ?;
260
+ let value = message. value . as_ref ( ) ;
261
+ let mut attributes = if message. attributes . is_empty ( ) {
262
+ HashMap :: new ( )
263
+ } else {
264
+ HashMap :: with_capacity ( message. attributes . len ( ) )
265
+ } ;
266
+
267
+ for attr in message. attributes . iter ( ) {
268
+ attributes. insert ( attr. id . name , & attr. value ) ;
269
+ }
270
+ return Some ( Message { value, attributes } ) ;
271
+ }
272
+
273
+ pub fn format_pattern (
274
+ & ' bundle self ,
275
+ pattern : & ' bundle ast:: Pattern ,
276
+ args : Option < & ' bundle HashMap < & str , FluentValue > > ,
277
+ errors : & mut Vec < FluentError > ,
278
+ ) -> Cow < ' bundle , str >
279
+ where
280
+ R : Borrow < FluentResource > ,
281
+ {
282
+ let mut scope = Scope :: new ( self , args) ;
283
+ let result = resolve_pattern_for_entry ( pattern, & mut scope) . to_string ( ) ;
284
+
285
+ for err in scope. errors {
286
+ errors. push ( err. into ( ) ) ;
287
+ }
288
+
289
+ result
290
+ }
291
+
249
292
/// Makes the provided rust function available to messages with the name `id`. See
250
293
/// the [FTL syntax guide] to learn how these are used in messages.
251
294
///
@@ -295,83 +338,6 @@ impl<'bundle, R> FluentBundle<'bundle, R> {
295
338
}
296
339
}
297
340
298
- /// Formats the message value identified by `path` using `args` to
299
- /// provide variables. `path` is either a message id ("hello"), or
300
- /// message id plus attribute ("hello.tooltip").
301
- ///
302
- /// # Examples
303
- ///
304
- /// ```
305
- /// use fluent_bundle::{FluentBundle, FluentResource, FluentValue};
306
- /// use std::collections::HashMap;
307
- ///
308
- /// let ftl_string = String::from("intro = Welcome, { $name }.");
309
- /// let resource = FluentResource::try_new(ftl_string)
310
- /// .expect("Could not parse an FTL string.");
311
- /// let mut bundle = FluentBundle::new(&["en-US"]);
312
- /// bundle.add_resource(resource)
313
- /// .expect("Failed to add FTL resources to the bundle.");
314
- ///
315
- /// let mut args = HashMap::new();
316
- /// args.insert("name", FluentValue::from("Rustacean"));
317
- ///
318
- /// let (value, _) = bundle.format("intro", Some(&args))
319
- /// .expect("Failed to format a message.");
320
- /// assert_eq!(&value, "Welcome, \u{2068}Rustacean\u{2069}.");
321
- ///
322
- /// ```
323
- ///
324
- /// An example with attributes and no args:
325
- ///
326
- /// ```
327
- /// use fluent_bundle::{FluentBundle, FluentResource};
328
- ///
329
- /// let ftl_string = String::from("
330
- /// hello =
331
- /// .title = Hi!
332
- /// .tooltip = This says 'Hi!'
333
- /// ");
334
- /// let resource = FluentResource::try_new(ftl_string)
335
- /// .expect("Could not parse an FTL string.");
336
- /// let mut bundle = FluentBundle::new(&["en-US"]);
337
- /// bundle.add_resource(resource)
338
- /// .expect("Failed to add FTL resources to the bundle.");
339
- ///
340
- /// let (value, _) = bundle.format("hello.title", None)
341
- /// .expect("Failed to format a message.");
342
- /// assert_eq!(&value, "Hi!");
343
- /// ```
344
- ///
345
- /// # Errors
346
- ///
347
- /// For some cases where `format` can't find a message it will return `None`.
348
- ///
349
- /// In all other cases `format` returns a string even if it
350
- /// encountered errors. Generally, during partial errors `format` will
351
- /// use ids to replace parts of the formatted message that it could
352
- /// not successfuly build. For more fundamental errors `format` will return
353
- /// the path itself as the translation.
354
- ///
355
- /// The second term of the tuple will contain any extra error information
356
- /// gathered during formatting. A caller may safely ignore the extra errors
357
- /// if the fallback formatting policies are acceptable.
358
- ///
359
- /// ```
360
- /// use fluent_bundle::{FluentBundle, FluentResource};
361
- ///
362
- /// // Create a message with bad cyclic reference
363
- /// let ftl_string = String::from("foo = a { foo } b");
364
- /// let resource = FluentResource::try_new(ftl_string)
365
- /// .expect("Could not parse an FTL string.");
366
- /// let mut bundle = FluentBundle::new(&["en-US"]);
367
- /// bundle.add_resource(resource)
368
- /// .expect("Failed to add FTL resources to the bundle.");
369
- ///
370
- /// // The result falls back to "a foo b"
371
- /// let (value, _) = bundle.format("foo", None)
372
- /// .expect("Failed to format a message.");
373
- /// assert_eq!(&value, "a foo b");
374
- /// ```
375
341
pub fn format (
376
342
& ' bundle self ,
377
343
path : & str ,
@@ -380,113 +346,47 @@ impl<'bundle, R> FluentBundle<'bundle, R> {
380
346
where
381
347
R : Borrow < FluentResource > ,
382
348
{
383
- let mut scope = Scope :: new ( self , args) ;
384
-
385
349
let mut errors = vec ! [ ] ;
386
350
387
- let string = if let Some ( ptr_pos) = path. find ( '.' ) {
351
+ let val = if let Some ( ptr_pos) = path. find ( '.' ) {
388
352
let message_id = & path[ ..ptr_pos] ;
389
- let message = self . get_message ( message_id) ?;
353
+ let message = self . get_message2 ( message_id) ?;
390
354
let attr_name = & path[ ( ptr_pos + 1 ) ..] ;
391
- let attr = message
392
- . attributes
393
- . iter ( )
394
- . find ( |attr| attr. id . name == attr_name) ?;
395
- resolve_value_for_entry ( & attr. value , ( message, attr) . into ( ) , & mut scope) . to_string ( )
355
+ let attr = message. attributes . get ( attr_name) ?;
356
+ self . format_pattern ( attr, args, & mut errors)
396
357
} else {
397
358
let message_id = path;
398
- let message = self . get_message ( message_id) ?;
399
- message
400
- . value
401
- . as_ref ( )
402
- . map ( |value| resolve_value_for_entry ( value, message. into ( ) , & mut scope) ) ?
403
- . to_string ( )
359
+ let message = self . get_message2 ( message_id) ?;
360
+ self . format_pattern ( message. value ?, args, & mut errors)
404
361
} ;
405
362
406
- for err in scope. errors {
407
- errors. push ( err. into ( ) ) ;
408
- }
409
-
410
- Some ( ( string, errors) )
363
+ return Some ( ( val, errors) ) ;
411
364
}
412
365
413
- /// Formats both the message value and attributes identified by `message_id`
414
- /// using `args` to provide variables. This is useful for cases where a UI
415
- /// element requires multiple related text fields, such as a button that has
416
- /// both display text and assistive text.
417
- ///
418
- /// # Examples
419
- ///
420
- /// ```
421
- /// use fluent_bundle::{FluentBundle, FluentResource, FluentValue};
422
- /// use std::collections::HashMap;
423
- ///
424
- /// let ftl_string = String::from("
425
- /// login-input = Predefined value
426
- /// .placeholder = [email protected]
427
- /// .aria-label = Login input value
428
- /// .title = Type your login email
429
- /// ");
430
- /// let resource = FluentResource::try_new(ftl_string)
431
- /// .expect("Could not parse an FTL string.");
432
- /// let mut bundle = FluentBundle::new(&["en-US"]);
433
- /// bundle.add_resource(resource)
434
- /// .expect("Failed to add FTL resources to the bundle.");
435
- ///
436
- /// let (message, _) = bundle.compound("login-input", None)
437
- /// .expect("Failed to format a message.");
438
- /// assert_eq!(message.value, Some("Predefined value".into()));
439
- /// assert_eq!(message.attributes.get("title"), Some(&"Type your login email".into()));
440
- /// ```
441
- ///
442
- /// # Errors
443
- ///
444
- /// For some cases where `compound` can't find a message it will return `None`.
445
- ///
446
- /// In all other cases `compound` returns a message even if it
447
- /// encountered errors. Generally, during partial errors `compound` will
448
- /// use ids to replace parts of the formatted message that it could
449
- /// not successfuly build. For more fundamental errors `compound` will return
450
- /// the path itself as the translation.
451
- ///
452
- /// The second term of the tuple will contain any extra error information
453
- /// gathered during formatting. A caller may safely ignore the extra errors
454
- /// if the fallback formatting policies are acceptable.
455
366
pub fn compound (
456
367
& ' bundle self ,
457
368
message_id : & str ,
458
369
args : Option < & ' bundle HashMap < & str , FluentValue > > ,
459
- ) -> Option < ( Message < ' bundle > , Vec < FluentError > ) >
370
+ ) -> Option < ( FormattedMessage < ' bundle > , Vec < FluentError > ) >
460
371
where
461
372
R : Borrow < FluentResource > ,
462
373
{
463
- let mut scope = Scope :: new ( self , args) ;
464
374
let mut errors = vec ! [ ] ;
465
- let message = self . get_message ( message_id) ?;
466
-
375
+ let message = self . get_message2 ( message_id) ?;
467
376
let value = message
468
377
. value
469
- . as_ref ( )
470
- . map ( |value| resolve_value_for_entry ( value, message. into ( ) , & mut scope) . to_string ( ) ) ;
378
+ . map ( |pattern| self . format_pattern ( pattern, args, & mut errors) ) ;
471
379
472
- // Setting capacity helps performance for cases with attributes,
473
- // but is slower than `::new` for cases without.
474
- // Maybe one day this will be fixed but for now let's use the trick.
475
380
let mut attributes = if message. attributes . is_empty ( ) {
476
381
HashMap :: new ( )
477
382
} else {
478
- HashMap :: with_capacity ( message. attributes . len ( ) )
383
+ HashMap :: with_capacity ( message. attributes . keys ( ) . len ( ) )
479
384
} ;
480
385
481
- for attr in message. attributes . iter ( ) {
482
- let val = resolve_value_for_entry ( & attr. value , ( message, attr) . into ( ) , & mut scope) ;
483
- attributes. insert ( attr. id . name , val. to_string ( ) ) ;
484
- }
485
-
486
- for err in scope. errors {
487
- errors. push ( err. into ( ) ) ;
386
+ for ( key, pattern) in message. attributes {
387
+ attributes. insert ( key, self . format_pattern ( pattern, args, & mut errors) ) ;
488
388
}
489
389
490
- Some ( ( Message { value, attributes } , errors) )
390
+ return Some ( ( FormattedMessage { value, attributes } , errors) ) ;
491
391
}
492
392
}
0 commit comments