1use crate::{Error, Lang, Result};
2use reqwest::{Client, RequestBuilder};
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4use std::{fmt, str::FromStr};
5use url::Url;
6
7const ENDPOINT_FREE: &str = "https://api-free.deepl.com";
8const ENDPOINT_PRO: &str = "https://api.deepl.com";
9
10#[derive(Debug, Serialize, Deserialize)]
12pub enum SplitSentences {
13 #[serde(rename = "0")]
15 None,
16 #[serde(rename = "1")]
20 One,
21 #[serde(rename = "nonewlines")]
25 NoNewlines,
26}
27
28#[derive(Debug, Default, Serialize, Deserialize)]
30pub enum Formality {
31 #[default]
33 Default,
34 More,
36 Less,
38 PreferMore,
41 PreferLess,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
48pub struct Language {
49 pub language: Lang,
51 pub name: String,
53 pub supports_formality: Option<bool>,
55}
56
57#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone)]
59#[serde(rename_all = "lowercase")]
60pub enum LanguageType {
61 #[default]
63 Source,
64 Target,
66}
67
68impl AsRef<str> for LanguageType {
69 fn as_ref(&self) -> &str {
70 match self {
71 Self::Source => "source",
72 Self::Target => "target",
73 }
74 }
75}
76
77impl fmt::Display for LanguageType {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.as_ref(),)
80 }
81}
82
83impl FromStr for LanguageType {
84 type Err = Error;
85
86 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
87 Ok(match s {
88 "source" => Self::Source,
89 "target" => Self::Target,
90 _ => return Err(Error::InvalidLanguageType(s.to_string())),
91 })
92 }
93}
94
95#[derive(Debug, Serialize, Deserialize)]
97pub struct Usage {
98 pub character_count: u64,
100 pub character_limit: u64,
102}
103
104#[derive(Debug, Serialize, Deserialize)]
106#[serde(rename_all = "lowercase")]
107pub enum TagHandling {
108 Xml,
110 Html,
112}
113
114#[derive(Debug, Serialize, Deserialize)]
116pub struct TextTranslation {
117 pub text: String,
119 pub detected_source_language: Lang,
121}
122
123#[derive(Debug, Serialize, Deserialize)]
125pub struct TranslateTextRequest {
126 pub text: Vec<String>,
128 pub target_lang: Lang,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub tag_handling: Option<TagHandling>,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub source_lang: Option<Lang>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub context: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub preserve_formatting: Option<bool>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub glossary_id: Option<String>,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub outline_detection: Option<bool>,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub non_splitting_tags: Option<Vec<String>>,
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub splitting_tags: Option<Vec<String>>,
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub ignore_tags: Option<Vec<String>>,
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub formality: Option<Formality>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub split_sentences: Option<SplitSentences>,
163}
164
165impl TranslateTextRequest {
166 pub fn new(text: Vec<String>, target_lang: Lang) -> Self {
168 Self {
169 text,
170 target_lang,
171 source_lang: None,
172 context: None,
173 preserve_formatting: None,
174 glossary_id: None,
175 outline_detection: None,
176 non_splitting_tags: None,
177 splitting_tags: None,
178 ignore_tags: None,
179 tag_handling: None,
180 formality: None,
181 split_sentences: None,
182 }
183 }
184}
185
186#[derive(Debug, Serialize, Deserialize)]
188pub struct TranslateTextResponse {
189 pub translations: Vec<TextTranslation>,
191}
192
193pub struct ApiOptions {
195 api_key: String,
197 endpoint: Url,
199 client: Option<Client>,
201}
202
203impl ApiOptions {
204 pub fn new(api_key: impl AsRef<str>) -> Self {
206 if api_key.as_ref().ends_with(":fx") {
207 Self::new_free(api_key)
208 } else {
209 Self::new_pro(api_key)
210 }
211 }
212
213 pub fn new_with_client(api_key: impl AsRef<str>, client: Client) -> Self {
215 let mut options = Self::new(api_key);
216 options.client = Some(client);
217 options
218 }
219
220 fn new_free(api_key: impl AsRef<str>) -> Self {
222 Self {
223 api_key: api_key.as_ref().to_owned(),
224 endpoint: Url::parse(ENDPOINT_FREE).unwrap(),
225 client: None,
226 }
227 }
228
229 fn new_pro(api_key: impl AsRef<str>) -> Self {
231 Self {
232 api_key: api_key.as_ref().to_owned(),
233 endpoint: Url::parse(ENDPOINT_PRO).unwrap(),
234 client: None,
235 }
236 }
237}
238
239pub struct DeeplApi {
241 client: Client,
242 options: ApiOptions,
243}
244
245impl DeeplApi {
246 pub fn new(mut options: ApiOptions) -> Self {
248 Self {
249 client: options.client.take().unwrap_or_else(|| Client::new()),
250 options,
251 }
252 }
253
254 pub async fn usage(&self) -> Result<Usage> {
256 let url = self.options.endpoint.join("v2/usage")?;
257 let req = self.client.get(url);
258 self.make_typed_request::<Usage>(req).await
259 }
260
261 pub async fn languages(&self, lang_type: LanguageType) -> Result<Vec<Language>> {
263 let mut url = self.options.endpoint.join("v2/languages")?;
264 url.query_pairs_mut()
265 .append_pair("type", lang_type.as_ref());
266 let req = self.client.get(url);
267 self.make_typed_request::<Vec<Language>>(req).await
268 }
269
270 pub async fn translate_text(
272 &self,
273 request: &TranslateTextRequest,
274 ) -> Result<TranslateTextResponse> {
275 let url = self.options.endpoint.join("v2/translate")?;
276 let req = self.client.post(url).json(request);
277 self.make_typed_request::<TranslateTextResponse>(req).await
278 }
279
280 async fn make_typed_request<T: DeserializeOwned>(&self, req: RequestBuilder) -> Result<T> {
281 let res = req
282 .header(
283 "Authorization",
284 format!("DeepL-Auth-Key {}", self.options.api_key),
285 )
286 .send()
287 .await?;
288 res.error_for_status_ref()?;
289 Ok(res.json::<T>().await?)
290 }
291}