1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

pub const DEFAULT_LOCALE: &str = "C";

fn element_to_xml(e: &xmltree::Element) -> String {
    e.children
        .iter()
        .map(|node| match node {
            xmltree::XMLNode::Element(c) => {
                format!("<{}>{}</{}>", c.name, element_to_xml(c), c.name)
            }
            xmltree::XMLNode::Text(t) => t.clone(),
            _ => "".to_string(),
        })
        .collect::<Vec<String>>()
        .join("")
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
/// A wrapper around a translable string that can contains markup.
///
///
/// # Example
/// ```
/// use appstream::MarkupTranslatableString;
/// let description = MarkupTranslatableString::with_default("<p>Contrast checks whether the contrast between two colors meet the WCAG requirements.</p>")
///                 .and_locale("cs", "<p>Kontroluje kontrast mezi dvěma zadanými barvami, jestli vyhovuje požadavkům pravidel pro bezbariérové weby (WCAG).</p>")
///                 .and_locale("es", "<p>Contraste comprueba la diferencia de contraste entre dos colores que cumplen los requisitos WCAG.</p>");
/// ```
pub struct MarkupTranslatableString(pub BTreeMap<String, String>);

impl MarkupTranslatableString {
    /// Create a new `MarkupTranslatableString` using the default locale.
    ///
    /// # Arguments
    ///
    /// * `text` - The string that corresponds to the default locale.
    pub fn with_default(text: &str) -> Self {
        let mut t = Self::default();
        t.add_for_locale(None, text);
        t
    }

    /// Adds a new translation for a specific locale.
    ///
    /// Very useful when constructing a `TranslatableString` manually.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to use, use `with_default` if you want the default locale.
    /// * `text` - The corresponding translation.
    pub fn and_locale(mut self, locale: &str, text: &str) -> Self {
        self.add_for_locale(Some(locale), text);
        self
    }

    /// Adds a new string from a `xmltree.Element`
    ///
    /// XML elements containing a `lang` attribute are marked as translatable
    /// and can be used to feed the `MarkupTranslatableString`.
    pub fn add_for_element(&mut self, element: &xmltree::Element) {
        let locale = element.attributes.get("lang").map(|l| l.as_str());
        self.add_for_locale(locale, &element_to_xml(element));
    }

    /// Adds a new translation for a speicifc locale.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to use, the default locale is used if `None` is set instead.
    /// * `text` - The translation corresponding to the locale.
    pub fn add_for_locale(&mut self, locale: Option<&str>, text: &str) {
        self.0.insert(
            locale.unwrap_or_else(|| DEFAULT_LOCALE).to_string(),
            text.to_string(),
        );
    }

    /// Returns the text corresponding to the default locale `C`.
    pub fn get_default(&self) -> Option<&String> {
        self.0.get(DEFAULT_LOCALE)
    }

    /// Retrieve the corresponding text for a specific locale if available.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to retrieve the text for.  
    pub fn get_for_locale(&self, locale: &str) -> Option<&String> {
        self.0.get(locale)
    }

    /// Whether `self` contains any translatable strings.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
/// A wrapper around a translatable string.
///
/// # Example
/// ```
/// use appstream::TranslatableString;
/// let name = TranslatableString::with_default("Contrast")
///             .and_locale("cs", "Kontrast")
///             .and_locale("cs", "Kontrast");
/// ```
pub struct TranslatableString(pub BTreeMap<String, String>);

impl TranslatableString {
    /// Create a new `TranslatableString` using the default locale.
    ///
    /// # Arguments
    ///
    /// * `text` - The string that corresponds to the default locale.
    pub fn with_default(text: &str) -> Self {
        let mut t = Self::default();
        t.add_for_locale(None, text);
        t
    }

    /// Adds a new translation for a specific locale.
    ///
    /// Very useful when constructing a `TranslatableString` manually.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to use, use `with_default` if you want the default locale.
    /// * `text` - The corresponding translation.
    pub fn and_locale(mut self, locale: &str, text: &str) -> Self {
        self.add_for_locale(Some(locale), text);
        self
    }

    /// Adds a new string from a `xmltree.Element`
    ///
    /// XML elements containing a `lang` attribute are marked as translatable
    /// and can be used to feed the `TranslatableString`.
    pub fn add_for_element(&mut self, element: &xmltree::Element) {
        let locale = element.attributes.get("lang").map(|l| l.as_str());
        if let Some(text) = element.get_text() {
            self.add_for_locale(locale, &text.into_owned());
        }
    }

    /// Adds a new translation for a speicifc locale.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to use, the default locale is used if `None` is set instead.
    /// * `text` - The translation corresponding to the locale.
    pub fn add_for_locale(&mut self, locale: Option<&str>, text: &str) {
        self.0.insert(
            locale.unwrap_or_else(|| DEFAULT_LOCALE).to_string(),
            text.to_string(),
        );
    }

    /// Returns the text corresponding to the default locale `C`.
    pub fn get_default(&self) -> Option<&String> {
        self.0.get(DEFAULT_LOCALE)
    }

    /// Retrieve the corresponding text for a specific locale if available.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to retrieve the text for.    
    pub fn get_for_locale(&self, locale: &str) -> Option<&String> {
        self.0.get(locale)
    }

    /// Whether `self` contains any translatable strings.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
/// A wrapper around a list of strings that are translatable.
///
/// It's mostly used for the list of keywords a component can have
/// # Example
///
/// ```
/// use appstream::TranslatableList;
/// let keywords = TranslatableList::with_default(vec!["Color", "Contrast", "GNOME", "GTK"])
///                         .and_locale("cs", vec!["barva", "kontrast"])
///                         .and_locale("da", vec!["Farve", "Kontrast"]);
/// ```
pub struct TranslatableList(pub BTreeMap<String, Vec<String>>);

impl TranslatableList {
    /// Create a new `TranslatableList` using the default locale.
    ///
    /// # Arguments
    ///
    /// * `words` - List of strings to add to the default locale.
    pub fn with_default(words: Vec<&str>) -> Self {
        let mut t = Self::default();
        words.iter().for_each(|w| {
            t.add_for_locale(None, w);
        });
        t
    }

    /// Adds a list of strings for a specific locale.
    ///
    /// Very useful when constructing a `TranslatableList` manually.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to use, use `with_default` if you want the default locale.
    /// * `words` - The list of strings to add to this specific locale.
    pub fn and_locale(mut self, locale: &str, words: Vec<&str>) -> Self {
        words.iter().for_each(|w| {
            self.add_for_locale(Some(locale), w);
        });
        self
    }

    /// Adds a new string from a `xmltree.Element`
    ///
    /// XML elements containing a `lang` attribute are marked as translatable
    /// and can be used to feed the `TranslatableList`.
    pub fn add_for_element(&mut self, element: &xmltree::Element) {
        self.add_for_locale(
            element.attributes.get("lang").map(|l| l.as_str()),
            &element.get_text().unwrap().into_owned(),
        );
    }

    /// Adds a new string for a specific locale.
    ///
    /// # Arguments
    ///
    /// * `locale` - The locale to use, `C` is used if `None` is provided.
    /// * `text` - The string to add.
    pub fn add_for_locale(&mut self, locale: Option<&str>, text: &str) {
        self.0
            .entry(locale.unwrap_or_else(|| DEFAULT_LOCALE).into())
            .and_modify(|sentenses| {
                sentenses.push(text.into());
            })
            .or_insert_with(|| vec![text.to_string()]);
    }

    /// Whether `self` contains any translatable strings.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}