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}