use std::error::Error as StdError;
use std::future::IntoFuture;
use crate::{
endpoint::{Formality, Pollable, Result},
impl_requester, Lang,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Deserialize)]
pub struct TranslateTextResp {
pub translations: Vec<Sentence>,
}
impl std::fmt::Display for TranslateTextResp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.translations
.iter()
.map(|sent| sent.text.to_string())
.collect::<String>()
)
}
}
#[derive(Deserialize)]
pub struct Sentence {
pub detected_source_language: Lang,
pub text: String,
}
#[derive(Debug, Serialize)]
pub enum PreserveFormatting {
#[serde(rename = "1")]
Preserve,
#[serde(rename = "0")]
DontPreserve,
}
#[derive(Debug, Serialize)]
pub enum SplitSentences {
#[serde(rename = "0")]
None,
#[serde(rename = "1")]
PunctuationAndNewlines,
#[serde(rename = "nonewlines")]
PunctuationOnly,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TagHandling {
Xml,
Html,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ModelType {
LatencyOptimized,
QualityOptimized,
PreferQualityOptimized,
}
impl_requester! {
TranslateRequester {
@required{
text: Vec<String>,
target_lang: Lang,
};
@optional{
context: String,
source_lang: Lang,
split_sentences: SplitSentences,
preserve_formatting: PreserveFormatting,
formality: Formality,
glossary_id: String,
tag_handling: TagHandling,
model_type: ModelType,
non_splitting_tags: Vec<String>,
splitting_tags: Vec<String>,
ignore_tags: Vec<String>,
};
} -> Result<TranslateTextResp, Error>;
}
impl<'a> IntoFuture for TranslateRequester<'a> {
type Output = Result<TranslateTextResp>;
type IntoFuture = Pollable<'a, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
self.send()
}
}
impl<'a> IntoFuture for &mut TranslateRequester<'a> {
type Output = Result<TranslateTextResp>;
type IntoFuture = Pollable<'a, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
self.send()
}
}
impl<'a> TranslateRequester<'a> {
fn send(&self) -> Pollable<'a, Result<TranslateTextResp>> {
let client = self.client.clone();
let obj = json!(self);
let fut = async move {
let response = client
.post(client.inner.endpoint.join("translate").unwrap())
.json(&obj)
.send()
.await
.map_err(|err| Error::RequestFail(err.to_string()))?;
if !response.status().is_success() {
return super::extract_deepl_error(response).await;
}
let response: TranslateTextResp = response.json().await.map_err(|err| {
if err.is_decode() {
if let Some(source) = err.source() {
return Error::InvalidResponse(format!(
"convert json bytes to Rust type: {source}"
));
}
}
Error::InvalidResponse(format!("convert json bytes to Rust type: {err}"))
})?;
Ok(response)
};
Box::pin(fut)
}
}
pub trait ToTranslatable {
fn to_translatable(&self) -> Vec<String>;
}
impl ToTranslatable for String {
fn to_translatable(&self) -> Vec<String> {
vec![self.to_owned()]
}
}
impl ToTranslatable for &str {
fn to_translatable(&self) -> Vec<String> {
vec![self.to_string()]
}
}
impl ToTranslatable for Vec<String> {
fn to_translatable(&self) -> Vec<String> {
self.clone()
}
}
impl ToTranslatable for &[String] {
fn to_translatable(&self) -> Vec<String> {
self.to_vec()
}
}
impl ToTranslatable for &[&str] {
fn to_translatable(&self) -> Vec<String> {
self.iter().map(|s| s.to_string()).collect()
}
}
impl<const SIZE: usize> ToTranslatable for [&str; SIZE] {
fn to_translatable(&self) -> Vec<String> {
self.iter().map(|s| s.to_string()).collect()
}
}
impl DeepLApi {
pub fn translate_text(
&self,
input: impl ToTranslatable,
target_lang: Lang,
) -> TranslateRequester<'_> {
TranslateRequester::new(self, input.to_translatable(), target_lang)
}
}
#[tokio::test]
async fn test_translate_text() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let api = DeepLApi::with(&key).new();
let response = api.translate_text("Hello World", Lang::ZH).await.unwrap();
assert!(!response.translations.is_empty());
let translated_results = response.translations;
assert_eq!(translated_results[0].text, "你好,世界");
assert_eq!(translated_results[0].detected_source_language, Lang::EN);
let response = api
.translate_text(["内存安全", "疾如闪电"], Lang::EN)
.await
.unwrap();
assert!(!response.translations.is_empty());
let translated_results = response.translations;
assert_eq!(translated_results[0].text, "Memory Safety");
assert_eq!(translated_results[0].detected_source_language, Lang::ZH);
assert_eq!(translated_results[1].text.to_lowercase(), "as fast as lightning");
assert_eq!(translated_results[1].detected_source_language, Lang::ZH);
}
#[tokio::test]
async fn test_advanced_translate() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let api = DeepLApi::with(&key).new();
let response = api.translate_text(
"Hello World <keep additionalarg=\"test0\">This will stay exactly the way it was</keep>",
Lang::DE
)
.source_lang(Lang::EN)
.ignore_tags(vec!["keep".to_string()])
.tag_handling(TagHandling::Xml)
.await
.unwrap();
assert!(!response.translations.is_empty());
let translated_results = response.translations;
assert_eq!(
translated_results[0].text,
"Hallo Welt <keep additionalarg=\"test0\">This will stay exactly the way it was</keep>"
);
assert_eq!(translated_results[0].detected_source_language, Lang::EN);
}
#[tokio::test]
async fn test_models() {
let api = DeepLApi::with(&std::env::var("DEEPL_API_KEY").unwrap()).new();
for model_type in [
ModelType::LatencyOptimized,
ModelType::QualityOptimized,
ModelType::QualityOptimized,
] {
let response = api
.translate_text("No te muevas, pringao", Lang::EN)
.source_lang(Lang::ES)
.model_type(model_type)
.await
.unwrap();
assert!(response
.translations
.first()
.expect("should be a translation")
.text
.contains("Don't move")); }
}
#[tokio::test]
async fn test_advanced_translator_html() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let api = DeepLApi::with(&key).new();
let response = api
.translate_text(
"Hello World <keep translate=\"no\">This will stay exactly the way it was</keep>",
Lang::DE,
)
.tag_handling(TagHandling::Html)
.await
.unwrap();
assert!(!response.translations.is_empty());
let translated_results = response.translations;
assert_eq!(
translated_results[0].text,
"Hallo Welt <keep translate=\"no\">This will stay exactly the way it was</keep>"
);
assert_eq!(translated_results[0].detected_source_language, Lang::EN);
}
#[tokio::test]
async fn test_formality() {
let api = DeepLApi::with(&std::env::var("DEEPL_API_KEY").unwrap()).new();
let text = "How are you?";
let src = Lang::EN;
let trg = Lang::ES;
let more = Formality::More;
let response = api
.translate_text(text, trg)
.source_lang(src)
.formality(more)
.await
.unwrap();
assert!(!response.translations.is_empty());
let text = "¿Cómo estás?";
let src = Lang::ES;
let trg = Lang::EN_US;
let less = Formality::PreferLess;
let response = api
.translate_text(text, trg)
.source_lang(src)
.formality(less)
.await
.unwrap();
assert!(!response.translations.is_empty());
}