1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4#[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
17pub 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 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}