Skip to main content

bern_protocol/
text.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4/// A user-visible text declared together with all of its translations.
5///
6/// `key` is assigned by the compiler and traces back to the declaration.
7/// `params` lists interpolation names such as `count` in `{count}`.
8#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct LocalizedText {
10    #[serde(default, skip_serializing_if = "String::is_empty")]
11    pub key: String,
12    pub translations: BTreeMap<String, String>,
13    #[serde(default, skip_serializing_if = "Vec::is_empty")]
14    pub params: Vec<String>,
15}
16
17/// Resource key of an enum variant label.
18pub fn label_key(enum_name: &str, variant: &str) -> String {
19    format!("{enum_name}.{variant}")
20}
21
22impl LocalizedText {
23    pub fn new<'a>(pairs: impl IntoIterator<Item = (&'a str, &'a str)>) -> Self {
24        Self {
25            key: String::new(),
26            translations: pairs
27                .into_iter()
28                .map(|(language, text)| (language.to_string(), text.to_string()))
29                .collect(),
30            params: Vec::new(),
31        }
32    }
33
34    pub fn get(&self, language: &str) -> Option<&str> {
35        self.translations.get(language).map(String::as_str)
36    }
37
38    /// Interpolation names referenced by one translation, in order of first
39    /// use. The text is checked as a whole: see [`crate::format`].
40    pub fn placeholders(text: &str) -> Result<Vec<String>, String> {
41        let pieces = crate::format::parse(text)?;
42        let mut names: Vec<String> = Vec::new();
43        for (name, _) in crate::format::uses(&pieces) {
44            if !names.contains(&name) {
45                names.push(name);
46            }
47        }
48        Ok(names)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn placeholders_are_parsed_and_braces_escape() {
58        assert_eq!(
59            LocalizedText::placeholders("已播放 {count} 集 {{x}}").unwrap(),
60            vec!["count"]
61        );
62        assert!(LocalizedText::placeholders("{Count}").is_err());
63        assert!(LocalizedText::placeholders("{count").is_err());
64        assert!(LocalizedText::placeholders("a } b").is_err());
65    }
66}