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
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt};

use reqwest::header;

use super::{Error, Result};
use crate::{DeepL, Language};

/// A glossary language pair
#[derive(Debug, Deserialize, Serialize)]
pub struct GlossaryLanguagePair {
    /// Source language
    pub source_lang: String,
    /// Target language
    pub target_lang: String,
}

/// Defines the set of supported language pairs for a glossary
#[derive(Debug, Deserialize, Serialize)]
pub struct GlossaryLanguagePairsResult {
    /// List of supported glossary language pairs
    pub supported_languages: Vec<GlossaryLanguagePair>,
}

/// Format in which glossary entries are provided
#[derive(Clone, Copy, Debug)]
pub enum GlossaryEntriesFormat {
    /// Tab-separated values
    Tsv,
    /// Comma-separated values
    Csv,
}

/// Information that uniquely identifies a glossary
#[derive(Debug, Deserialize, Serialize)]
pub struct Glossary {
    /// A unique ID assigned to a glossary
    pub glossary_id: String,
    /// Indicates if the newly created glossary can already be used in translate requests.
    /// If the created glossary is not yet ready, you have to wait and check the ready status
    /// of the glossary before using it in a translate request.
    pub ready: bool,
    /// Name associated with the glossary
    pub name: String,
    /// The language in which the source texts in the glossary are specified
    pub source_lang: String,
    /// The language in which the target texts in the glossary are specified
    pub target_lang: String,
    /// The creation time of the glossary in ISO 8601-1:2019 format (e.g. 2021-08-03T14:16:18.329Z)
    pub creation_time: String,
    /// The number of entries in the glossary
    pub entry_count: u64,
}

/// The result of getting available glossaries
#[derive(Debug, Deserialize, Serialize)]
pub struct GlossariesResult {
    /// List of glossaries
    pub glossaries: Vec<Glossary>,
}

impl AsRef<str> for GlossaryEntriesFormat {
    fn as_ref(&self) -> &str {
        match self {
            Self::Tsv => "tsv",
            Self::Csv => "csv",
        }
    }
}

impl fmt::Display for GlossaryEntriesFormat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

impl DeepL {
    /// GET /glossary-language-pairs
    ///
    /// Get supported glossary language pairs
    pub fn glossary_languages(&self) -> Result<GlossaryLanguagePairsResult> {
        let url = format!("{}/glossary-language-pairs", self.url);

        let resp = self.get(url).send().map_err(Error::Reqwest)?;

        if !resp.status().is_success() {
            return super::convert(resp);
        }

        resp.json().map_err(|_| Error::Deserialize)
    }

    /// POST /glossaries
    ///
    /// Create a new glossary.
    ///
    /// [`DeepL`] supports creating custom glossaries, i.e. a collection of entries which when
    /// used in a translation ensures that a given word from the source language always maps to
    /// the same target word in the glossary, giving a user more control in cases where
    /// translation might otherwise be unreliable or ambiguous. A given glossary is defined by one
    /// source language and one target language where the source word in each entry is unique.
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use deeprl::*;
    /// # let dl = DeepL::new(&std::env::var("DEEPL_API_KEY").unwrap());
    /// let name = "my_glossary".to_string();
    /// let source_lang = Language::EN;
    /// let target_lang = Language::IT;
    /// let entries = "hello,ciao".to_string();
    /// let fmt = GlossaryEntriesFormat::Csv;
    ///
    /// let glossary = dl.glossary_new(
    ///     name,
    ///     source_lang,
    ///     target_lang,
    ///     entries,
    ///     fmt
    /// )
    /// .unwrap();
    /// assert!(!glossary.glossary_id.is_empty());
    /// ```
    pub fn glossary_new(
        &self,
        name: String,
        source_lang: Language,
        target_lang: Language,
        entries: String,
        fmt: GlossaryEntriesFormat,
    ) -> Result<Glossary> {
        let url = format!("{}/glossaries", self.url);

        let params = HashMap::from([
            ("name", name),
            ("source_lang", source_lang.to_string()),
            ("target_lang", target_lang.to_string()),
            ("entries", entries),
            ("entries_format", fmt.to_string()),
        ]);

        let resp = self
            .post(url)
            .form(&params)
            .send()
            .map_err(Error::Reqwest)?;

        if !resp.status().is_success() {
            return super::convert(resp);
        }

        resp.json().map_err(|_| Error::Deserialize)
    }

    /// GET /glossaries
    ///
    /// List current active glossaries
    pub fn glossaries(&self) -> Result<GlossariesResult> {
        let url = format!("{}/glossaries", self.url);

        let resp = self.get(url).send().map_err(Error::Reqwest)?;

        if !resp.status().is_success() {
            return super::convert(resp);
        }

        resp.json().map_err(|_| Error::Deserialize)
    }

    /// GET /glossaries/`{glossary_id}`
    ///
    /// Get meta information for a specified glossary (excluding entries)
    pub fn glossary_info(&self, glossary_id: &str) -> Result<Glossary> {
        let url = format!("{}/glossaries/{}", self.url, glossary_id);

        let resp = self.get(url).send().map_err(Error::Reqwest)?;

        if !resp.status().is_success() {
            return super::convert(resp);
        }

        resp.json().map_err(|_| Error::Deserialize)
    }

    /// GET /glossaries/`{glossary_id}`/entries
    ///
    /// Retrieve entries for a specified glossary.
    // Currently supports receiving entries in TSV format.
    pub fn glossary_entries(&self, glossary_id: &str) -> Result<HashMap<String, String>> {
        let url = format!("{}/glossaries/{}/entries", self.url, glossary_id);
        let accept = header::HeaderValue::from_static("text/tab-separated-values");

        let resp = self
            .get(url)
            .header(header::ACCEPT, accept)
            .send()
            .map_err(Error::Reqwest)?;

        if !resp.status().is_success() {
            return super::convert(resp);
        }

        let t = resp.text().map_err(|_| Error::InvalidResponse).unwrap();
        // The response text contains newline-separated entries
        // where each entry contains two strings separated by a tab.
        // First we split entries on '\n', then for each entry, split words
        // on '\t' and build a map of source to target words
        let raw_entries: Vec<&str> = t.split('\n').collect();

        let mut map = HashMap::new();
        for entry in raw_entries {
            let words: Vec<&str> = entry.split('\t').collect();
            if words.len() != 2 {
                continue;
            }
            map.insert(words[0].to_string(), words[1].to_string());
        }

        Ok(map)
    }

    /// DELETE /glossaries/`{glossary_id}`
    ///
    /// Destroy a glossary
    pub fn glossary_delete(&self, glossary_id: &str) -> Result<()> {
        let url = format!("{}/glossaries/{}", self.url, glossary_id);

        let _ = self.delete(url).send().map_err(Error::Reqwest);

        Ok(())
    }
}