fluent_bundle/
args.rs

1use std::borrow::Cow;
2use std::iter::FromIterator;
3
4use crate::types::FluentValue;
5
6/// Fluent messages can use arguments in order to programmatically add values to a
7/// translated string. For instance, in a localized application you may wish to display
8/// a user's email count. This could be done with the following message.
9///
10/// `msg-key = Hello, { $user }. You have { $emailCount } messages.`
11///
12/// Here `$user` and `$emailCount` are the arguments, which can be filled with values.
13///
14/// The [`FluentArgs`] struct is the map from the argument name (for example `$user`) to
15/// the argument value (for example "John".) The logic to apply these to write these
16/// to messages is elsewhere, this struct just stores the value.
17///
18/// # Example
19///
20/// ```
21/// use fluent_bundle::{FluentArgs, FluentBundle, FluentResource};
22///
23/// let mut args = FluentArgs::new();
24/// args.set("user", "John");
25/// args.set("emailCount", 5);
26///
27/// let res = FluentResource::try_new(r#"
28///
29/// msg-key = Hello, { $user }. You have { $emailCount } messages.
30///
31/// "#.to_string())
32///     .expect("Failed to parse FTL.");
33///
34/// let mut bundle = FluentBundle::default();
35///
36/// // For this example, we'll turn on BiDi support.
37/// // Please, be careful when doing it, it's a risky move.
38/// bundle.set_use_isolating(false);
39///
40/// bundle.add_resource(res)
41///     .expect("Failed to add a resource.");
42///
43/// let mut err = vec![];
44///
45/// let msg = bundle.get_message("msg-key")
46///     .expect("Failed to retrieve a message.");
47/// let value = msg.value()
48///     .expect("Failed to retrieve a value.");
49///
50/// assert_eq!(
51///     bundle.format_pattern(value, Some(&args), &mut err),
52///     "Hello, John. You have 5 messages."
53/// );
54/// ```
55#[derive(Debug, Default)]
56pub struct FluentArgs<'args>(Vec<(Cow<'args, str>, FluentValue<'args>)>);
57
58impl<'args> FluentArgs<'args> {
59    /// Creates a new empty argument map.
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    /// Pre-allocates capacity for arguments.
65    pub fn with_capacity(capacity: usize) -> Self {
66        Self(Vec::with_capacity(capacity))
67    }
68
69    /// Gets the [`FluentValue`] at the `key` if it exists.
70    pub fn get<K>(&self, key: K) -> Option<&FluentValue<'args>>
71    where
72        K: Into<Cow<'args, str>>,
73    {
74        let key = key.into();
75        if let Ok(idx) = self.0.binary_search_by_key(&&key, |(k, _)| k) {
76            Some(&self.0[idx].1)
77        } else {
78            None
79        }
80    }
81
82    /// Sets the key value pair.
83    pub fn set<K, V>(&mut self, key: K, value: V)
84    where
85        K: Into<Cow<'args, str>>,
86        V: Into<FluentValue<'args>>,
87    {
88        let key = key.into();
89        match self.0.binary_search_by_key(&&key, |(k, _)| k) {
90            Ok(idx) => self.0[idx] = (key, value.into()),
91            Err(idx) => self.0.insert(idx, (key, value.into())),
92        };
93    }
94
95    /// Iterate over a tuple of the key an [`FluentValue`].
96    pub fn iter(&self) -> impl Iterator<Item = (&str, &FluentValue)> {
97        self.0.iter().map(|(k, v)| (k.as_ref(), v))
98    }
99}
100
101impl<'args, K, V> FromIterator<(K, V)> for FluentArgs<'args>
102where
103    K: Into<Cow<'args, str>>,
104    V: Into<FluentValue<'args>>,
105{
106    fn from_iter<I>(iter: I) -> Self
107    where
108        I: IntoIterator<Item = (K, V)>,
109    {
110        let iter = iter.into_iter();
111        let mut args = if let Some(size) = iter.size_hint().1 {
112            FluentArgs::with_capacity(size)
113        } else {
114            FluentArgs::new()
115        };
116
117        for (k, v) in iter {
118            args.set(k, v);
119        }
120
121        args
122    }
123}
124
125impl<'args> IntoIterator for FluentArgs<'args> {
126    type Item = (Cow<'args, str>, FluentValue<'args>);
127    type IntoIter = std::vec::IntoIter<Self::Item>;
128
129    fn into_iter(self) -> Self::IntoIter {
130        self.0.into_iter()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn replace_existing_arguments() {
140        let mut args = FluentArgs::new();
141
142        args.set("name", "John");
143        args.set("emailCount", 5);
144        assert_eq!(args.0.len(), 2);
145        assert_eq!(
146            args.get("name"),
147            Some(&FluentValue::String(Cow::Borrowed("John")))
148        );
149        assert_eq!(args.get("emailCount"), Some(&FluentValue::try_number("5")));
150
151        args.set("name", "Jane");
152        args.set("emailCount", 7);
153        assert_eq!(args.0.len(), 2);
154        assert_eq!(
155            args.get("name"),
156            Some(&FluentValue::String(Cow::Borrowed("Jane")))
157        );
158        assert_eq!(args.get("emailCount"), Some(&FluentValue::try_number("7")));
159    }
160}