use log::debug;
#[derive(Debug, thiserror::Error)]
pub enum TranslateError {
#[error("Query Send Error")]
QuerySendError,
#[error("Response Error. Maybe Query is Too long, Max Query length is 5000. because of the padding for special characters, recommend is 3000 to 4000 characters")]
ResponseError,
#[error("Error: {0}")]
Other(String),
}
#[derive(Default, Debug, Clone)]
pub struct TranslateResult {
pub input_lang: String,
pub output_lang: String,
pub input_text: Vec<String>,
pub output_text: Vec<Vec<String>>,
pub input_tts: Option<Vec<String>>,
pub output_tts: Option<Vec<String>>,
}
mod lang;
pub use lang::{InputLang, OutputLang};
pub fn build_google_api_query<T, Y>(text: &String, input_lang: T, output_lang: Y) -> String
where
T: Into<InputLang>,
Y: Into<OutputLang>,
{
let input_lang: InputLang = input_lang.into();
let output_lang: OutputLang = output_lang.into();
let text = text
.replace("\\", "\\\\")
.replace("\r\n", "\\n")
.replace("\n", "\\n")
.replace("\\", "\\\\")
.replace("\"", "\\\\\\\"");
let query = format!(
r#"[[["MkEWBc","[[\"{}\",\"{}\",\"{}\",true],[null]]",null,"generic"]]]"#,
text,
input_lang.to_string(),
output_lang.to_string()
);
debug!("Built Query : {}", query);
query
}
pub async fn send_google_api_query(query: String) -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let builder =
client.post("https://translate.google.com/_/TranslateWebserverUi/data/batchexecute");
let builder = builder.header("content-length", "0");
let builder = builder.query(&[("f.req", query)]);
let response = builder.send().await?;
let text = response.text().await?;
debug!("Google Response : {}", text);
let text = text
.split_at(6)
.1
.replace("\\\\", "\\")
.replace("\\\"", "\"");
let text = text
.split_at(21)
.1
.split_once(r#"",null,null,null,"generic"],["#)
.ok_or(TranslateError::ResponseError)?
.0
.to_owned();
debug!("Stripped Response : {}", text);
Ok(text)
}
pub fn response_to_result(response: String) -> TranslateResult {
let mut result = TranslateResult::default();
let response = json::parse(&response).unwrap();
result.input_lang = response[1][4][1].to_string();
result.output_lang = response[1][4][2].to_string();
result.input_text = response[1][4][0]
.to_string()
.split('\n')
.map(|x| x.to_owned())
.collect();
for line in response[1][0][0][5].members().step_by(2) {
let mut line_result = Vec::new();
line_result.push(line[0].to_string());
for side in line[4].members().skip(1) {
line_result.push(side[0].to_string());
}
result.output_text.push(line_result);
}
result.input_tts = if !response[0][0].is_null() {
Some(
response[0][0]
.to_string()
.split('\n')
.map(|x| x.to_owned())
.collect(),
)
} else {
None
};
result.output_tts = if !response[1][0][0][1].is_null() {
Some(
response[1][0][0][1]
.to_string()
.split('\n')
.map(|x| x.to_owned())
.collect(),
)
} else {
None
};
result
}
pub async fn translate<T, Y>(
text: Vec<String>,
input_lang: T,
output_lang: Y,
) -> Result<TranslateResult, TranslateError>
where
T: Into<InputLang>,
Y: Into<OutputLang>,
{
let input_lang: InputLang = input_lang.into();
let output_lang: OutputLang = output_lang.into();
let text = text.join("\n");
let query = build_google_api_query(&text, input_lang, output_lang);
let response = match send_google_api_query(query).await {
Ok(response) => response,
Err(_) => return Err(TranslateError::QuerySendError),
};
let result = response_to_result(response);
Ok(result)
}
pub async fn translate_one_line<T, Y>(
text: String,
input_lang: T,
output_lang: Y,
) -> Result<String, TranslateError>
where
T: Into<InputLang>,
Y: Into<OutputLang>,
{
let text = vec![text];
let result = translate(text, input_lang, output_lang).await?;
Ok(result.output_text[0][0].clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_translate_one_line() {
let text = "Hello, world!".to_string();
let input_lang = "en";
let output_lang = "ko";
let result = translate_one_line(text, input_lang, output_lang)
.await
.unwrap();
dbg!(result);
assert!(true);
}
#[tokio::test]
async fn test_translate_multi_lines() {
let text = vec!["Hello, world!", "내 이름은 민수야.", "나는 20살이야."]
.iter()
.map(|x| x.to_string())
.collect();
let input_lang = "auto";
let output_lang = "fr";
let result = translate(text, input_lang, output_lang).await.unwrap();
dbg!(result);
assert!(true);
}
}