appstream/
translatable_string.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4pub const DEFAULT_LOCALE: &str = "C";
5
6fn element_to_xml(e: &xmltree::Element) -> String {
7    e.children
8        .iter()
9        .map(|node| match node {
10            xmltree::XMLNode::Element(ref c) => {
11                format!("<{}>{}</{}>", c.name, element_to_xml(c), c.name)
12            }
13            xmltree::XMLNode::Text(t) => t.clone(),
14            _ => "".to_string(),
15        })
16        .collect::<Vec<String>>()
17        .join("")
18}
19
20#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
21/// A wrapper around a translable string that can contains markup.
22///
23///
24/// # Example
25/// ```
26/// use appstream::MarkupTranslatableString;
27/// let description = MarkupTranslatableString::with_default("<p>Contrast checks whether the contrast between two colors meet the WCAG requirements.</p>")
28///                 .and_locale("cs", "<p>Kontroluje kontrast mezi dvěma zadanými barvami, jestli vyhovuje požadavkům pravidel pro bezbariérové weby (WCAG).</p>")
29///                 .and_locale("es", "<p>Contraste comprueba la diferencia de contraste entre dos colores que cumplen los requisitos WCAG.</p>");
30/// ```
31pub struct MarkupTranslatableString(pub BTreeMap<String, String>);
32
33impl MarkupTranslatableString {
34    /// Create a new `MarkupTranslatableString` using the default locale.
35    ///
36    /// # Arguments
37    ///
38    /// * `text` - The string that corresponds to the default locale.
39    pub fn with_default(text: &str) -> Self {
40        let mut t = Self::default();
41        t.add_for_locale(None, text);
42        t
43    }
44
45    /// Adds a new translation for a specific locale.
46    ///
47    /// Very useful when constructing a `TranslatableString` manually.
48    ///
49    /// # Arguments
50    ///
51    /// * `locale` - The locale to use, use `with_default` if you want the default locale.
52    /// * `text` - The corresponding translation.
53    #[must_use]
54    pub fn and_locale(mut self, locale: &str, text: &str) -> Self {
55        self.add_for_locale(Some(locale), text);
56        self
57    }
58
59    /// Adds a new string from a `xmltree.Element`
60    ///
61    /// XML elements containing a `lang` attribute are marked as translatable
62    /// and can be used to feed the `MarkupTranslatableString`.
63    pub fn add_for_element(&mut self, element: &xmltree::Element) {
64        let locale = element.attributes.get("lang").map(|l| l.as_str());
65        self.add_for_locale(locale, &element_to_xml(element));
66    }
67
68    /// Adds a new translation for a speicifc locale.
69    ///
70    /// # Arguments
71    ///
72    /// * `locale` - The locale to use, the default locale is used if `None` is set instead.
73    /// * `text` - The translation corresponding to the locale.
74    pub fn add_for_locale(&mut self, locale: Option<&str>, text: &str) {
75        self.0.insert(
76            locale.unwrap_or(DEFAULT_LOCALE).to_string(),
77            text.to_string(),
78        );
79    }
80
81    /// Returns the text corresponding to the default locale `C`.
82    pub fn get_default(&self) -> Option<&String> {
83        self.0.get(DEFAULT_LOCALE)
84    }
85
86    /// Retrieve the corresponding text for a specific locale if available.
87    ///
88    /// # Arguments
89    ///
90    /// * `locale` - The locale to retrieve the text for.  
91    pub fn get_for_locale(&self, locale: &str) -> Option<&String> {
92        self.0.get(locale)
93    }
94
95    /// Whether `self` contains any translatable strings.
96    pub fn is_empty(&self) -> bool {
97        self.0.is_empty()
98    }
99}
100
101#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
102/// A wrapper around a translatable string.
103///
104/// # Example
105/// ```
106/// use appstream::TranslatableString;
107/// let name = TranslatableString::with_default("Contrast")
108///             .and_locale("cs", "Kontrast")
109///             .and_locale("cs", "Kontrast");
110/// ```
111pub struct TranslatableString(pub BTreeMap<String, String>);
112
113impl TranslatableString {
114    /// Create a new `TranslatableString` using the default locale.
115    ///
116    /// # Arguments
117    ///
118    /// * `text` - The string that corresponds to the default locale.
119    pub fn with_default(text: &str) -> Self {
120        let mut t = Self::default();
121        t.add_for_locale(None, text);
122        t
123    }
124
125    /// Adds a new translation for a specific locale.
126    ///
127    /// Very useful when constructing a `TranslatableString` manually.
128    ///
129    /// # Arguments
130    ///
131    /// * `locale` - The locale to use, use `with_default` if you want the default locale.
132    /// * `text` - The corresponding translation.
133    #[must_use]
134    pub fn and_locale(mut self, locale: &str, text: &str) -> Self {
135        self.add_for_locale(Some(locale), text);
136        self
137    }
138
139    /// Adds a new string from a `xmltree.Element`
140    ///
141    /// XML elements containing a `lang` attribute are marked as translatable
142    /// and can be used to feed the `TranslatableString`.
143    pub fn add_for_element(&mut self, element: &xmltree::Element) {
144        self.add_for_locale(
145            element.attributes.get("lang").map(|l| l.as_str()),
146            &element.get_text().unwrap_or_default(), // for some reason some description tags contains empty strings.
147        );
148    }
149
150    /// Adds a new translation for a speicifc locale.
151    ///
152    /// # Arguments
153    ///
154    /// * `locale` - The locale to use, the default locale is used if `None` is set instead.
155    /// * `text` - The translation corresponding to the locale.
156    pub fn add_for_locale(&mut self, locale: Option<&str>, text: &str) {
157        self.0.insert(
158            locale.unwrap_or(DEFAULT_LOCALE).to_string(),
159            text.to_string(),
160        );
161    }
162
163    /// Returns the text corresponding to the default locale `C`.
164    pub fn get_default(&self) -> Option<&String> {
165        self.0.get(DEFAULT_LOCALE)
166    }
167
168    /// Retrieve the corresponding text for a specific locale if available.
169    ///
170    /// # Arguments
171    ///
172    /// * `locale` - The locale to retrieve the text for.    
173    pub fn get_for_locale(&self, locale: &str) -> Option<&String> {
174        self.0.get(locale)
175    }
176
177    /// Whether `self` contains any translatable strings.
178    pub fn is_empty(&self) -> bool {
179        self.0.is_empty()
180    }
181}
182
183#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
184/// A wrapper around a list of strings that are translatable.
185///
186/// It's mostly used for the list of keywords a component can have
187/// # Example
188///
189/// ```
190/// use appstream::TranslatableList;
191/// let keywords = TranslatableList::with_default(vec!["Color", "Contrast", "GNOME", "GTK"])
192///                         .and_locale("cs", vec!["barva", "kontrast"])
193///                         .and_locale("da", vec!["Farve", "Kontrast"]);
194/// ```
195pub struct TranslatableList(pub BTreeMap<String, Vec<String>>);
196
197impl TranslatableList {
198    /// Create a new `TranslatableList` using the default locale.
199    ///
200    /// # Arguments
201    ///
202    /// * `words` - List of strings to add to the default locale.
203    pub fn with_default(words: Vec<&str>) -> Self {
204        let mut t = Self::default();
205        words.iter().for_each(|w| {
206            t.add_for_locale(None, w);
207        });
208        t
209    }
210
211    /// Adds a list of strings for a specific locale.
212    ///
213    /// Very useful when constructing a `TranslatableList` manually.
214    ///
215    /// # Arguments
216    ///
217    /// * `locale` - The locale to use, use `with_default` if you want the default locale.
218    /// * `words` - The list of strings to add to this specific locale.
219    #[must_use]
220    pub fn and_locale(mut self, locale: &str, words: Vec<&str>) -> Self {
221        words.iter().for_each(|w| {
222            self.add_for_locale(Some(locale), w);
223        });
224        self
225    }
226
227    /// Adds a new string from a `xmltree.Element`
228    ///
229    /// XML elements containing a `lang` attribute are marked as translatable
230    /// and can be used to feed the `TranslatableList`.
231    pub fn add_for_element(&mut self, element: &xmltree::Element) {
232        self.add_for_locale(
233            element.attributes.get("lang").map(|l| l.as_str()),
234            &element.get_text().unwrap_or_default(),
235        );
236    }
237
238    /// Adds a new string for a specific locale.
239    ///
240    /// # Arguments
241    ///
242    /// * `locale` - The locale to use, `C` is used if `None` is provided.
243    /// * `text` - The string to add.
244    pub fn add_for_locale(&mut self, locale: Option<&str>, text: &str) {
245        self.0
246            .entry(locale.unwrap_or(DEFAULT_LOCALE).into())
247            .and_modify(|sentenses| {
248                sentenses.push(text.into());
249            })
250            .or_insert_with(|| vec![text.to_string()]);
251    }
252
253    /// Whether `self` contains any translatable strings.
254    pub fn is_empty(&self) -> bool {
255        self.0.is_empty()
256    }
257}