google_translator/
lib.rs

1use log::debug;
2
3#[derive(Debug, thiserror::Error)]
4pub enum TranslateError {
5    #[error("Query Send Error")]
6    QuerySendError,
7    #[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")]
8    ResponseError,
9    #[error("Error: {0}")]
10    Other(String),
11}
12
13/// 번역 결과물
14#[derive(Default, Debug, Clone)]
15pub struct TranslateResult {
16    pub input_lang: String,
17    pub output_lang: String,
18    /// 번역 전 문장, 라인별로 구분되어있음
19    pub input_text: Vec<String>,
20    /// 번역 후 문장, output_text[0]에는 첫번째 라인의 번역 결과물들이 들어있음
21    /// output_text[0][0]은 가장 가능성이 높은 결과물
22    /// output_text[0][1]은 다음으로 가능성이 높은 결과물
23    pub output_text: Vec<Vec<String>>,
24    /// 번역 전 문장에 대한 발음
25    pub input_tts: Option<Vec<String>>,
26    /// 번역 후 문장의 최선의 결과물에 대한 발음
27    pub output_tts: Option<Vec<String>>,
28}
29
30mod lang;
31pub use lang::{InputLang, OutputLang};
32
33/// translate.google.com에서 사용하는 발송 쿼리문을 생성한다.
34/// Hello\\\\nHello\\nHow Are You
35/// Google의 번역기에서 \ 을 입력하면 \\\\로 변환되며, 줄바꿈은 \\n으로, "은 \\\"으로 변환된다.
36pub fn build_google_api_query<T, Y>(text: &String, input_lang: T, output_lang: Y) -> String
37where
38    T: Into<InputLang>,
39    Y: Into<OutputLang>,
40{
41    let input_lang: InputLang = input_lang.into();
42    let output_lang: OutputLang = output_lang.into();
43
44    // 번역 쿼리문에는 줄바꿈이 \\n으로 들어가있다. 이에 맞추어 보내야한다.
45    let text = text
46        .replace("\\", "\\\\")
47        .replace("\r\n", "\\n")
48        .replace("\n", "\\n")
49        .replace("\\", "\\\\")
50        .replace("\"", "\\\\\\\"");
51    // 쿼리문 설정
52    let query = format!(
53        // 구글 내부 쿼리문 형태에 따른다
54        r#"[[["MkEWBc","[[\"{}\",\"{}\",\"{}\",true],[null]]",null,"generic"]]]"#,
55        text,
56        input_lang.to_string(),
57        output_lang.to_string()
58    );
59    debug!("Built Query : {}", query);
60    query
61}
62
63/// translate.google.com의 api 서버에 요청을 보낸 후 쓸모없는 값을 제거한다.
64/// 출력값은 Json 형태이다.
65/// object[1][0][0][5] 형태로 번역 결과에 접근할 수 있으며, 해당 부분의 배열에 2단계씩 건너뛰어 번역 결과가 들어있다.
66pub async fn send_google_api_query(query: String) -> Result<String, Box<dyn std::error::Error>> {
67    // 클라이언트 생성
68    let client = reqwest::Client::new();
69
70    // 번역요청 주소 전달
71    let builder =
72        client.post("https://translate.google.com/_/TranslateWebserverUi/data/batchexecute");
73    // content-length 설정
74    let builder = builder.header("content-length", "0");
75    // 내부 내용 쿼리로 설정 및 전송
76    let builder = builder.query(&[("f.req", query)]);
77    let response = builder.send().await?;
78    let text = response.text().await?;
79    debug!("Google Response : {}", text);
80
81    // 받아온 반환값 중 불필요한 내용 제거
82    let text = text
83        .split_at(6)
84        .1
85        .replace("\\\\", "\\")
86        .replace("\\\"", "\"");
87    let text = text
88        .split_at(21)
89        .1
90        .split_once(r#"",null,null,null,"generic"],["#)
91        .ok_or(TranslateError::ResponseError)?
92        .0
93        .to_owned();
94    debug!("Stripped Response : {}", text);
95    Ok(text)
96}
97
98/// Google 서버에서 받아온 결과 쿼리문을 구조체로 변환한다.
99pub fn response_to_result(response: String) -> TranslateResult {
100    // 기본 변수 선언
101    let mut result = TranslateResult::default();
102    let response = json::parse(&response).unwrap();
103
104    // 입출력 언어 저장
105    result.input_lang = response[1][4][1].to_string();
106    result.output_lang = response[1][4][2].to_string();
107
108    // 입력값 저장
109    result.input_text = response[1][4][0]
110        .to_string()
111        .split('\n')
112        .map(|x| x.to_owned())
113        .collect();
114
115    // 출력값 저장
116    for line in response[1][0][0][5].members().step_by(2) {
117        let mut line_result = Vec::new();
118        // 최선의 번역결과 저장
119        line_result.push(line[0].to_string());
120        for side in line[4].members().skip(1) {
121            // 그 외의 추측 결과 저장
122            line_result.push(side[0].to_string());
123        }
124        result.output_text.push(line_result);
125    }
126
127    // tts 저장
128    result.input_tts = if !response[0][0].is_null() {
129        Some(
130            response[0][0]
131                .to_string()
132                .split('\n')
133                .map(|x| x.to_owned())
134                .collect(),
135        )
136    } else {
137        None
138    };
139    result.output_tts = if !response[1][0][0][1].is_null() {
140        Some(
141            response[1][0][0][1]
142                .to_string()
143                .split('\n')
144                .map(|x| x.to_owned())
145                .collect(),
146        )
147    } else {
148        None
149    };
150
151    result
152}
153
154/*/////////////////////////////////////////////////////////////////////////////
155///////////////////////////////////////////////////////////////////////////////
156//////////////////////////////////////////////////////////////////////////// */
157
158pub async fn translate<T, Y>(
159    text: Vec<String>,
160    input_lang: T,
161    output_lang: Y,
162) -> Result<TranslateResult, TranslateError>
163where
164    T: Into<InputLang>,
165    Y: Into<OutputLang>,
166{
167    let input_lang: InputLang = input_lang.into();
168    let output_lang: OutputLang = output_lang.into();
169
170    // 입력값 생성
171    let text = text.join("\n");
172
173    // translate.google.com 발송 쿼리문 생성
174    let query = build_google_api_query(&text, input_lang, output_lang);
175
176    // 번역 후 결과물 (Json형태)
177    let response = match send_google_api_query(query).await {
178        Ok(response) => response,
179        Err(_) => return Err(TranslateError::QuerySendError),
180    };
181
182    let result = response_to_result(response);
183
184    Ok(result)
185}
186
187pub async fn translate_one_line<T, Y>(
188    text: String,
189    input_lang: T,
190    output_lang: Y,
191) -> Result<String, TranslateError>
192where
193    T: Into<InputLang>,
194    Y: Into<OutputLang>,
195{
196    let text = vec![text];
197
198    let result = translate(text, input_lang, output_lang).await?;
199
200    Ok(result.output_text[0][0].clone())
201}
202
203/*/////////////////////////////////////////////////////////////////////////////
204///////////////////////////////////////////////////////////////////////////////
205//////////////////////////////////////////////////////////////////////////// */
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[tokio::test]
212    async fn test_translate_one_line() {
213        let text = "Hello, world!".to_string();
214        let input_lang = "en";
215        let output_lang = "ko";
216        let result = translate_one_line(text, input_lang, output_lang)
217            .await
218            .unwrap();
219        dbg!(result);
220        assert!(true);
221    }
222
223    #[tokio::test]
224    async fn test_translate_multi_lines() {
225        let text = vec!["Hello, world!", "내 이름은 민수야.", "나는 20살이야."]
226            .iter()
227            .map(|x| x.to_string())
228            .collect();
229        let input_lang = "auto";
230        let output_lang = "fr";
231        let result = translate(text, input_lang, output_lang).await.unwrap();
232        dbg!(result);
233        assert!(true);
234    }
235}