Skip to content

Commit df42d43

Browse files
committed
Update FluentBundle to the latest API
1 parent 91bb69e commit df42d43

File tree

5 files changed

+112
-198
lines changed

5 files changed

+112
-198
lines changed

fluent-bundle/benches/resolver.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,14 @@ fn resolver_bench(c: &mut Criterion) {
9191

9292
b.iter(|| {
9393
for id in &ids {
94-
let (_msg, errors) = bundle.compound(id, args.as_ref()).expect("Message found");
94+
let msg = bundle.get_message2(id).expect("Message found");
95+
let mut errors = vec![];
96+
if let Some(value) = msg.value {
97+
let _ = bundle.format_pattern(value, args.as_ref(), &mut errors);
98+
}
99+
for (_, value) in msg.attributes {
100+
let _ = bundle.format_pattern(value, args.as_ref(), &mut errors);
101+
}
95102
assert!(errors.len() == 0, "Resolver errors: {:#?}", errors);
96103
}
97104
})

fluent-bundle/src/bundle.rs

+58-158
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ use intl_pluralrules::{IntlPluralRules, PluralRuleType};
1515
use crate::entry::Entry;
1616
use crate::entry::GetEntry;
1717
use crate::errors::FluentError;
18-
use crate::resolve::{resolve_value_for_entry, Scope};
18+
use crate::resolve::{resolve_pattern_for_entry, Scope};
1919
use crate::resource::FluentResource;
2020
use crate::types::FluentValue;
2121

2222
#[derive(Debug, PartialEq)]
2323
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> {
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

Comments
 (0)