1 | use std::borrow::Cow; |
2 | use std::iter::FromIterator; |
3 | |
4 | use 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)] |
56 | pub struct FluentArgs<'args>(Vec<(Cow<'args, str>, FluentValue<'args>)>); |
57 | |
58 | impl<'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 | |
101 | impl<'args, K, V> FromIterator<(K, V)> for FluentArgs<'args> |
102 | where |
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: ::IntoIter = iter.into_iter(); |
111 | let mut args: FluentArgs<'_> = if let Some(size: usize) = iter.size_hint().1 { |
112 | FluentArgs::with_capacity(size) |
113 | } else { |
114 | FluentArgs::new() |
115 | }; |
116 | |
117 | for (k: K, v: V) in iter { |
118 | args.set(key:k, value:v); |
119 | } |
120 | |
121 | args |
122 | } |
123 | } |
124 | |
125 | impl<'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)] |
135 | mod 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 | } |
161 | |