@@ -15,12 +15,18 @@ use intl_pluralrules::{IntlPluralRules, PluralRuleType};
1515use crate :: entry:: Entry ;
1616use crate :: entry:: GetEntry ;
1717use crate :: errors:: FluentError ;
18- use crate :: resolve:: { resolve_value_for_entry , Scope } ;
18+ use crate :: resolve:: { resolve_pattern_for_entry , Scope } ;
1919use crate :: resource:: FluentResource ;
2020use crate :: types:: FluentValue ;
2121
2222#[ derive( Debug , PartialEq ) ]
2323pub 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 > {
2430 pub value : Option < Cow < ' m , str > > ,
2531 pub attributes : HashMap < & ' m str , Cow < ' m , str > > ,
2632}
@@ -246,6 +252,43 @@ impl<'bundle, R> FluentBundle<'bundle, R> {
246252 self . get_message ( id) . is_some ( )
247253 }
248254
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+
249292 /// Makes the provided rust function available to messages with the name `id`. See
250293 /// the [FTL syntax guide] to learn how these are used in messages.
251294 ///
@@ -295,83 +338,6 @@ impl<'bundle, R> FluentBundle<'bundle, R> {
295338 }
296339 }
297340
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- /// ```
375341 pub fn format (
376342 & ' bundle self ,
377343 path : & str ,
@@ -380,113 +346,47 @@ impl<'bundle, R> FluentBundle<'bundle, R> {
380346 where
381347 R : Borrow < FluentResource > ,
382348 {
383- let mut scope = Scope :: new ( self , args) ;
384-
385349 let mut errors = vec ! [ ] ;
386350
387- let string = if let Some ( ptr_pos) = path. find ( '.' ) {
351+ let val = if let Some ( ptr_pos) = path. find ( '.' ) {
388352 let message_id = & path[ ..ptr_pos] ;
389- let message = self . get_message ( message_id) ?;
353+ let message = self . get_message2 ( message_id) ?;
390354 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)
396357 } else {
397358 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)
404361 } ;
405362
406- for err in scope. errors {
407- errors. push ( err. into ( ) ) ;
408- }
409-
410- Some ( ( string, errors) )
363+ return Some ( ( val, errors) ) ;
411364 }
412365
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.
455366 pub fn compound (
456367 & ' bundle self ,
457368 message_id : & str ,
458369 args : Option < & ' bundle HashMap < & str , FluentValue > > ,
459- ) -> Option < ( Message < ' bundle > , Vec < FluentError > ) >
370+ ) -> Option < ( FormattedMessage < ' bundle > , Vec < FluentError > ) >
460371 where
461372 R : Borrow < FluentResource > ,
462373 {
463- let mut scope = Scope :: new ( self , args) ;
464374 let mut errors = vec ! [ ] ;
465- let message = self . get_message ( message_id) ?;
466-
375+ let message = self . get_message2 ( message_id) ?;
467376 let value = message
468377 . 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) ) ;
471379
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.
475380 let mut attributes = if message. attributes . is_empty ( ) {
476381 HashMap :: new ( )
477382 } else {
478- HashMap :: with_capacity ( message. attributes . len ( ) )
383+ HashMap :: with_capacity ( message. attributes . keys ( ) . len ( ) )
479384 } ;
480385
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) ) ;
488388 }
489389
490- Some ( ( Message { value, attributes } , errors) )
390+ return Some ( ( FormattedMessage { value, attributes } , errors) ) ;
491391 }
492392}
0 commit comments