1use std::{
8 collections::{BTreeMap, BTreeSet},
9 ops::Deref,
10};
11
12use icu_plurals::PluralCategory;
13use serde::{
14 Deserialize, Deserializer, Serialize, Serializer,
15 de::{MapAccess, Visitor},
16 ser::SerializeMap,
17};
18
19use crate::sprintf::Message;
20
21fn plural_category_as_str(category: PluralCategory) -> &'static str {
22 match category {
23 PluralCategory::Zero => "zero",
24 PluralCategory::One => "one",
25 PluralCategory::Two => "two",
26 PluralCategory::Few => "few",
27 PluralCategory::Many => "many",
28 PluralCategory::Other => "other",
29 }
30}
31
32pub type TranslationTree = Tree;
33
34#[derive(Debug, Clone, Deserialize, Default)]
35pub struct Metadata {
36 #[serde(skip)]
37 pub context_locations: BTreeSet<String>,
40 pub description: Option<String>,
41}
42
43impl Serialize for Metadata {
44 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45 where
46 S: Serializer,
47 {
48 let context = self
49 .context_locations
50 .iter()
51 .map(String::as_str)
52 .collect::<Vec<&str>>()
53 .join(", ");
54
55 let mut map = serializer.serialize_map(None)?;
56
57 if !context.is_empty() {
58 map.serialize_entry("context", &context)?;
59 }
60
61 if let Some(description) = &self.description {
62 map.serialize_entry("description", description)?;
63 }
64
65 map.end()
66 }
67}
68
69impl Metadata {
70 fn add_location(&mut self, location: String) {
71 self.context_locations.insert(location);
72 }
73}
74
75#[derive(Debug, Clone, Default)]
76pub struct Tree {
77 inner: BTreeMap<String, Node>,
78}
79
80#[derive(Debug, Clone)]
81pub struct Node {
82 metadata: Option<Metadata>,
83 value: Value,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(untagged)]
88pub enum Value {
89 Tree(Tree),
90 Leaf(Message),
91}
92
93impl<'de> Deserialize<'de> for Tree {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: Deserializer<'de>,
97 {
98 struct TreeVisitor;
99
100 impl<'de> Visitor<'de> for TreeVisitor {
101 type Value = Tree;
102
103 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
104 formatter.write_str("map")
105 }
106
107 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
108 where
109 A: MapAccess<'de>,
110 {
111 let mut tree: BTreeMap<String, Node> = BTreeMap::new();
112 let mut metadata_map: BTreeMap<String, Metadata> = BTreeMap::new();
113
114 while let Some(key) = map.next_key::<String>()? {
115 if let Some(name) = key.strip_prefix('@') {
116 let metadata = map.next_value::<Metadata>()?;
117 metadata_map.insert(name.to_owned(), metadata);
118 } else {
119 let value = map.next_value::<Value>()?;
120 tree.insert(
121 key,
122 Node {
123 metadata: None,
124 value,
125 },
126 );
127 }
128 }
129
130 for (key, meta) in metadata_map {
131 if let Some(node) = tree.get_mut(&key) {
132 node.metadata = Some(meta);
133 }
134 }
135
136 Ok(Tree { inner: tree })
137 }
138 }
139
140 deserializer.deserialize_any(TreeVisitor)
141 }
142}
143
144impl Serialize for Tree {
145 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146 where
147 S: Serializer,
148 {
149 let mut map = serializer.serialize_map(None)?;
150
151 for (key, value) in &self.inner {
152 map.serialize_entry(key, &value.value)?;
153 if let Some(meta) = &value.metadata {
154 map.serialize_entry(&format!("@{key}"), meta)?;
155 }
156 }
157
158 map.end()
159 }
160}
161
162impl Tree {
163 #[must_use]
167 pub fn message(&self, key: &str) -> Option<&Message> {
168 let keys = key.split('.');
169 let node = self.walk_path(keys)?;
170 let message = node.value.as_message()?;
171 Some(message)
172 }
173
174 #[must_use]
180 pub fn pluralize(&self, key: &str, category: PluralCategory) -> Option<&Message> {
181 let keys = key.split('.');
182 let node = self.walk_path(keys)?;
183
184 let subtree = match &node.value {
185 Value::Leaf(message) => return Some(message),
186 Value::Tree(tree) => tree,
187 };
188
189 let node = if let Some(node) = subtree.inner.get(plural_category_as_str(category)) {
190 node
191 } else {
192 subtree.inner.get("other")?
194 };
195
196 let message = node.value.as_message()?;
197 Some(message)
198 }
199
200 #[doc(hidden)]
201 pub fn set_if_not_defined<K: Deref<Target = str>, I: IntoIterator<Item = K>>(
202 &mut self,
203 path: I,
204 value: Message,
205 location: Option<String>,
206 ) -> bool {
207 let mut fake_root = Node {
210 metadata: None,
211 value: Value::Tree(Tree {
212 inner: std::mem::take(&mut self.inner),
213 }),
214 };
215
216 let mut node = &mut fake_root;
217 for key in path {
218 match &mut node.value {
219 Value::Tree(tree) => {
220 node = tree.inner.entry(key.deref().to_owned()).or_insert(Node {
221 metadata: None,
222 value: Value::Tree(Tree::default()),
223 });
224 }
225 Value::Leaf(_) => {
226 panic!()
227 }
228 }
229 }
230
231 let replaced = match &node.value {
232 Value::Tree(tree) => {
233 assert!(
234 tree.inner.is_empty(),
235 "Trying to overwrite a non-empty tree"
236 );
237
238 node.value = Value::Leaf(value);
239 true
240 }
241 Value::Leaf(_) => {
242 false
244 }
245 };
246
247 if let Some(location) = location {
248 node.metadata
249 .get_or_insert(Metadata::default())
250 .add_location(location);
251 }
252
253 match fake_root {
255 Node {
256 value: Value::Tree(tree),
257 ..
258 } => self.inner = tree.inner,
259 _ => panic!("Tried to replace the root node"),
260 }
261
262 replaced
263 }
264
265 fn walk_path<K: Deref<Target = str>, I: IntoIterator<Item = K>>(
266 &self,
267 path: I,
268 ) -> Option<&Node> {
269 let mut iterator = path.into_iter();
270 let next = iterator.next()?;
271 self.walk_path_inner(next, iterator)
272 }
273
274 fn walk_path_inner<K: Deref<Target = str>, I: Iterator<Item = K>>(
275 &self,
276 next_key: K,
277 mut path: I,
278 ) -> Option<&Node> {
279 let next = self.inner.get(&*next_key)?;
280
281 match path.next() {
282 Some(next_key) => match &next.value {
283 Value::Tree(tree) => tree.walk_path_inner(next_key, path),
284 Value::Leaf(_) => None,
285 },
286 None => Some(next),
287 }
288 }
289}
290
291impl Value {
292 fn as_message(&self) -> Option<&Message> {
293 match self {
294 Value::Leaf(message) => Some(message),
295 Value::Tree(_) => None,
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::sprintf::{ArgumentList, arg_list};
304
305 #[test]
306 fn test_it_works() {
307 let tree = serde_json::json!({
308 "hello": "world",
309 "damals": {
310 "about_x_hours_ago": {
311 "one": "about one hour ago",
312 "other": "about %(count)s hours ago"
313 }
314 }
315 });
316
317 let result: Result<TranslationTree, _> = serde_json::from_value(tree);
318 assert!(result.is_ok());
319 let tree = result.unwrap();
320 let message = tree.message("hello");
321 assert!(message.is_some());
322 let message = message.unwrap();
323 assert_eq!(message.format(&ArgumentList::default()).unwrap(), "world");
324
325 let message = tree.message("damals.about_x_hours_ago.one");
326 assert!(message.is_some());
327 let message = message.unwrap();
328 assert_eq!(message.format(&arg_list!()).unwrap(), "about one hour ago");
329
330 let message = tree.pluralize("damals.about_x_hours_ago", PluralCategory::Other);
331 assert!(message.is_some());
332 let message = message.unwrap();
333 assert_eq!(
334 message.format(&arg_list!(count = 2)).unwrap(),
335 "about 2 hours ago"
336 );
337 }
338}