deepl_rustls/endpoint/
translate.rs1use std::{collections::HashMap, future::IntoFuture};
2
3use crate::{
4 endpoint::{Formality, Pollable, Result},
5 impl_requester, Lang,
6};
7
8use serde::Deserialize;
9
10#[derive(Deserialize)]
12pub struct TranslateTextResp {
13 pub translations: Vec<Sentence>,
14}
15
16impl std::fmt::Display for TranslateTextResp {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(
19 f,
20 "{}",
21 self.translations
22 .iter()
23 .map(|sent| sent.text.to_string())
24 .collect::<String>()
25 )
26 }
27}
28
29#[derive(Deserialize)]
31pub struct Sentence {
32 pub detected_source_language: Lang,
33 pub text: String,
34}
35
36pub enum PreserveFormatting {
44 Preserve,
45 DontPreserve,
46}
47
48impl AsRef<str> for PreserveFormatting {
49 fn as_ref(&self) -> &str {
50 match self {
51 PreserveFormatting::Preserve => "1",
52 PreserveFormatting::DontPreserve => "0",
53 }
54 }
55}
56
57pub enum SplitSentences {
65 None,
67 PunctuationAndNewlines,
69 PunctuationOnly,
71}
72
73impl AsRef<str> for SplitSentences {
74 fn as_ref(&self) -> &str {
75 match self {
76 SplitSentences::None => "0",
77 SplitSentences::PunctuationAndNewlines => "1",
78 SplitSentences::PunctuationOnly => "nonewlines",
79 }
80 }
81}
82
83pub enum TagHandling {
87 Xml,
90 Html,
93}
94
95impl AsRef<str> for TagHandling {
96 fn as_ref(&self) -> &str {
97 match self {
98 TagHandling::Xml => "xml",
99 TagHandling::Html => "html",
100 }
101 }
102}
103
104impl_requester! {
105 TranslateRequester {
106 @required{
107 text: String,
108 target_lang: Lang,
109 };
110 @optional{
111 context: String,
112 source_lang: Lang,
113 split_sentences: SplitSentences,
114 preserve_formatting: PreserveFormatting,
115 formality: Formality,
116 glossary_id: String,
117 tag_handling: TagHandling,
118 non_splitting_tags: Vec<String>,
119 splitting_tags: Vec<String>,
120 ignore_tags: Vec<String>,
121 };
122 } -> Result<TranslateTextResp, Error>;
123}
124
125impl<'a> IntoFuture for TranslateRequester<'a> {
126 type Output = Result<TranslateTextResp>;
127 type IntoFuture = Pollable<'a, Self::Output>;
128
129 fn into_future(mut self) -> Self::IntoFuture {
130 self.send()
131 }
132}
133
134impl<'a> IntoFuture for &mut TranslateRequester<'a> {
135 type Output = Result<TranslateTextResp>;
136 type IntoFuture = Pollable<'a, Self::Output>;
137
138 fn into_future(self) -> Self::IntoFuture {
139 self.send()
140 }
141}
142
143impl<'a> TranslateRequester<'a> {
144 fn to_form(&self) -> HashMap<&'static str, String> {
145 let mut param = HashMap::new();
146 param.insert("text", self.text.to_string());
147
148 if let Some(la) = &self.source_lang {
149 param.insert("source_lang", la.as_ref().to_string());
150 }
151
152 param.insert("target_lang", self.target_lang.as_ref().to_string());
153
154 if let Some(ss) = &self.split_sentences {
155 param.insert("split_sentences", ss.as_ref().to_string());
156 }
157
158 if let Some(pf) = &self.preserve_formatting {
159 param.insert("preserve_formatting", pf.as_ref().to_string());
160 }
161
162 if let Some(fm) = &self.formality {
163 param.insert("formality", fm.as_ref().to_string());
164 }
165
166 if let Some(id) = &self.glossary_id {
167 param.insert("glossary_id", id.to_string());
168 }
169
170 if let Some(th) = &self.tag_handling {
171 param.insert("tag_handling", th.as_ref().to_string());
172 }
173
174 if let Some(tags) = &self.non_splitting_tags {
175 if !tags.is_empty() {
176 param.insert("non_splitting_tags", tags.join(","));
177 }
178 }
179
180 if let Some(tags) = &self.splitting_tags {
181 if !tags.is_empty() {
182 param.insert("splitting_tags", tags.join(","));
183 }
184 }
185
186 if let Some(tags) = &self.ignore_tags {
187 if !tags.is_empty() {
188 param.insert("ignore_tags", tags.join(","));
189 }
190 }
191
192 param
193 }
194
195 fn send(&mut self) -> Pollable<'a, Result<TranslateTextResp>> {
196 let client = self.client.clone();
197 let form = self.to_form();
198
199 let fut = async move {
200 let response = client
201 .post(client.inner.endpoint.join("translate").unwrap())
202 .form(&form)
203 .send()
204 .await
205 .map_err(|err| Error::RequestFail(err.to_string()))?;
206
207 if !response.status().is_success() {
208 return super::extract_deepl_error(response).await;
209 }
210
211 let response: TranslateTextResp = response.json().await.map_err(|err| {
212 Error::InvalidResponse(format!("convert json bytes to Rust type: {err}"))
213 })?;
214
215 Ok(response)
216 };
217
218 Box::pin(fut)
219 }
220}
221
222impl DeepLApi {
223 pub fn translate_text(&self, text: impl ToString, target_lang: Lang) -> TranslateRequester {
265 TranslateRequester::new(self, text.to_string(), target_lang)
266 }
267}
268
269#[tokio::test]
270async fn test_translate_text() {
271 let key = std::env::var("DEEPL_API_KEY").unwrap();
272 let api = DeepLApi::with(&key).new();
273 let response = api.translate_text("Hello World", Lang::ZH).await.unwrap();
274
275 assert!(!response.translations.is_empty());
276
277 let translated_results = response.translations;
278 assert_eq!(translated_results[0].text, "你好,世界");
279 assert_eq!(translated_results[0].detected_source_language, Lang::EN);
280}
281
282#[tokio::test]
283async fn test_advanced_translate() {
284 let key = std::env::var("DEEPL_API_KEY").unwrap();
285 let api = DeepLApi::with(&key).new();
286
287 let response = api.translate_text(
288 "Hello World <keep additionalarg=\"test0\">This will stay exactly the way it was</keep>",
289 Lang::DE
290 )
291 .source_lang(Lang::EN)
292 .ignore_tags(vec!["keep".to_string()])
293 .tag_handling(TagHandling::Xml)
294 .await
295 .unwrap();
296
297 assert!(!response.translations.is_empty());
298
299 let translated_results = response.translations;
300 assert_eq!(
301 translated_results[0].text,
302 "Hallo Welt <keep additionalarg=\"test0\">This will stay exactly the way it was</keep>"
303 );
304 assert_eq!(translated_results[0].detected_source_language, Lang::EN);
305}
306
307#[tokio::test]
308async fn test_advanced_translator_html() {
309 let key = std::env::var("DEEPL_API_KEY").unwrap();
310 let api = DeepLApi::with(&key).new();
311
312 let response = api
313 .translate_text(
314 "Hello World <keep translate=\"no\">This will stay exactly the way it was</keep>",
315 Lang::DE,
316 )
317 .tag_handling(TagHandling::Html)
318 .await
319 .unwrap();
320
321 assert!(!response.translations.is_empty());
322
323 let translated_results = response.translations;
324 assert_eq!(
325 translated_results[0].text,
326 "Hallo Welt <keep translate=\"no\">This will stay exactly the way it was</keep>"
327 );
328 assert_eq!(translated_results[0].detected_source_language, Lang::EN);
329}
330
331#[tokio::test]
332async fn test_formality() {
333 let api = DeepLApi::with(&std::env::var("DEEPL_API_KEY").unwrap()).new();
334
335 let text = "How are you?";
337 let src = Lang::EN;
338 let trg = Lang::ES;
339 let more = Formality::More;
340
341 let response = api
342 .translate_text(text, trg)
343 .source_lang(src)
344 .formality(more)
345 .await
346 .unwrap();
347
348 assert!(!response.translations.is_empty());
349 assert_eq!(response.translations[0].text, "¿Cómo está?");
350
351 let text = "¿Cómo estás?";
353 let src = Lang::ES;
354 let trg = Lang::EN_US;
355 let less = Formality::PreferLess;
356
357 let response = api
358 .translate_text(text, trg)
359 .source_lang(src)
360 .formality(less)
361 .await
362 .unwrap();
363
364 assert!(!response.translations.is_empty());
365 assert_eq!(response.translations[0].text, "How are you doing?");
366}