deepl/endpoint/
translate.rs1use std::future::IntoFuture;
2
3use crate::{
4 endpoint::{Formality, Pollable, Result},
5 impl_requester, Lang,
6};
7
8use serde::{Deserialize, Serialize};
9use serde_json::json;
10
11#[derive(Deserialize)]
13pub struct TranslateTextResp {
14 pub translations: Vec<Sentence>,
15}
16
17impl std::fmt::Display for TranslateTextResp {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 write!(
20 f,
21 "{}",
22 self.translations
23 .iter()
24 .map(|sent| sent.text.to_string())
25 .collect::<String>()
26 )
27 }
28}
29
30#[derive(Deserialize)]
32pub struct Sentence {
33 pub detected_source_language: Lang,
34 pub text: String,
35}
36
37#[derive(Debug, Serialize)]
45pub enum PreserveFormatting {
46 #[serde(rename = "1")]
47 Preserve,
48 #[serde(rename = "0")]
49 DontPreserve,
50}
51
52#[derive(Debug, Serialize)]
60pub enum SplitSentences {
61 #[serde(rename = "0")]
63 None,
64 #[serde(rename = "1")]
66 PunctuationAndNewlines,
67 #[serde(rename = "nonewlines")]
69 PunctuationOnly,
70}
71
72#[derive(Debug, Serialize)]
76#[serde(rename_all = "lowercase")]
77pub enum TagHandling {
78 Xml,
81 Html,
84}
85
86impl_requester! {
87 TranslateRequester {
88 @required{
89 text: Vec<String>,
90 target_lang: Lang,
91 };
92 @optional{
93 context: String,
94 source_lang: Lang,
95 split_sentences: SplitSentences,
96 preserve_formatting: PreserveFormatting,
97 formality: Formality,
98 glossary_id: String,
99 tag_handling: TagHandling,
100 non_splitting_tags: Vec<String>,
101 splitting_tags: Vec<String>,
102 ignore_tags: Vec<String>,
103 };
104 } -> Result<TranslateTextResp, Error>;
105}
106
107impl<'a> IntoFuture for TranslateRequester<'a> {
108 type Output = Result<TranslateTextResp>;
109 type IntoFuture = Pollable<'a, Self::Output>;
110
111 fn into_future(self) -> Self::IntoFuture {
112 self.send()
113 }
114}
115
116impl<'a> IntoFuture for &mut TranslateRequester<'a> {
117 type Output = Result<TranslateTextResp>;
118 type IntoFuture = Pollable<'a, Self::Output>;
119
120 fn into_future(self) -> Self::IntoFuture {
121 self.send()
122 }
123}
124
125impl<'a> TranslateRequester<'a> {
126 fn send(&self) -> Pollable<'a, Result<TranslateTextResp>> {
127 let client = self.client.clone();
128 let obj = json!(self);
129
130 let fut = async move {
131 let response = client
132 .post(client.inner.endpoint.join("translate").unwrap())
133 .json(&obj)
134 .send()
135 .await
136 .map_err(|err| Error::RequestFail(err.to_string()))?;
137
138 if !response.status().is_success() {
139 return super::extract_deepl_error(response).await;
140 }
141
142 let response: TranslateTextResp = response.json().await.map_err(|err| {
143 Error::InvalidResponse(format!("convert json bytes to Rust type: {err}"))
144 })?;
145
146 Ok(response)
147 };
148
149 Box::pin(fut)
150 }
151}
152
153impl DeepLApi {
154 pub fn translate_text(&self, text: impl ToString, target_lang: Lang) -> TranslateRequester {
196 TranslateRequester::new(self, vec![text.to_string()], target_lang)
197 }
198}
199
200#[tokio::test]
201async fn test_translate_text() {
202 let key = std::env::var("DEEPL_API_KEY").unwrap();
203 let api = DeepLApi::with(&key).new();
204 let response = api.translate_text("Hello World", Lang::ZH).await.unwrap();
205
206 assert!(!response.translations.is_empty());
207
208 let translated_results = response.translations;
209 assert_eq!(translated_results[0].text, "你好,世界");
210 assert_eq!(translated_results[0].detected_source_language, Lang::EN);
211}
212
213#[tokio::test]
214async fn test_advanced_translate() {
215 let key = std::env::var("DEEPL_API_KEY").unwrap();
216 let api = DeepLApi::with(&key).new();
217
218 let response = api.translate_text(
219 "Hello World <keep additionalarg=\"test0\">This will stay exactly the way it was</keep>",
220 Lang::DE
221 )
222 .source_lang(Lang::EN)
223 .ignore_tags(vec!["keep".to_string()])
224 .tag_handling(TagHandling::Xml)
225 .await
226 .unwrap();
227
228 assert!(!response.translations.is_empty());
229
230 let translated_results = response.translations;
231 assert_eq!(
232 translated_results[0].text,
233 "Hallo Welt <keep additionalarg=\"test0\">This will stay exactly the way it was</keep>"
234 );
235 assert_eq!(translated_results[0].detected_source_language, Lang::EN);
236}
237
238#[tokio::test]
239async fn test_advanced_translator_html() {
240 let key = std::env::var("DEEPL_API_KEY").unwrap();
241 let api = DeepLApi::with(&key).new();
242
243 let response = api
244 .translate_text(
245 "Hello World <keep translate=\"no\">This will stay exactly the way it was</keep>",
246 Lang::DE,
247 )
248 .tag_handling(TagHandling::Html)
249 .await
250 .unwrap();
251
252 assert!(!response.translations.is_empty());
253
254 let translated_results = response.translations;
255 assert_eq!(
256 translated_results[0].text,
257 "Hallo Welt <keep translate=\"no\">This will stay exactly the way it was</keep>"
258 );
259 assert_eq!(translated_results[0].detected_source_language, Lang::EN);
260}
261
262#[tokio::test]
263async fn test_formality() {
264 let api = DeepLApi::with(&std::env::var("DEEPL_API_KEY").unwrap()).new();
265
266 let text = "How are you?";
268 let src = Lang::EN;
269 let trg = Lang::ES;
270 let more = Formality::More;
271
272 let response = api
273 .translate_text(text, trg)
274 .source_lang(src)
275 .formality(more)
276 .await
277 .unwrap();
278 assert!(!response.translations.is_empty());
279
280 let text = "¿Cómo estás?";
282 let src = Lang::ES;
283 let trg = Lang::EN_US;
284 let less = Formality::PreferLess;
285
286 let response = api
287 .translate_text(text, trg)
288 .source_lang(src)
289 .formality(less)
290 .await
291 .unwrap();
292 assert!(!response.translations.is_empty());
293}