async_translate/microsoft/
mod.rs1use crate::translator::Translator;
7use anyhow::Result;
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use tokio::sync::{Mutex, Semaphore};
12
13#[derive(Debug, Clone)]
18pub struct MicrosoftConfig {
19 pub endpoint: String,
21 pub concurrent_limit: usize,
23}
24
25impl Default for MicrosoftConfig {
26 fn default() -> Self {
27 Self {
28 endpoint: "https://api-edge.cognitive.microsofttranslator.com".to_string(),
29 concurrent_limit: 10,
30 }
31 }
32}
33
34pub struct MicrosoftTranslator {
39 client: Client,
40 config: MicrosoftConfig,
41 semaphore: Arc<Semaphore>,
43 auth: Arc<Mutex<Option<String>>>,
45}
46
47impl MicrosoftTranslator {
48 pub fn new(config: MicrosoftConfig) -> Self {
60 Self {
61 client: Client::new(),
62 config: config.clone(),
63 semaphore: Arc::new(Semaphore::new(config.concurrent_limit)),
64 auth: Arc::new(Mutex::new(None)),
65 }
66 }
67
68 async fn authenticate(&self) -> Result<String> {
77 let mut auth_attempts = 3;
78 while auth_attempts > 0 {
79 auth_attempts -= 1;
80 match self.client
81 .get("https://edge.microsoft.com/translate/auth")
82 .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
83 .send()
84 .await
85 {
86 Ok(response) => {
87 if response.status().is_success() {
88 let token = response.text().await?;
89 return Ok(token);
90 } else {
91 if auth_attempts <= 0 {
92 return Err(anyhow::anyhow!("Failed to authenticate with Microsoft Translator: HTTP {}", response.status()));
93 }
94 }
95 }
96 Err(e) => {
97 if auth_attempts <= 0 {
98 return Err(anyhow::anyhow!("Failed to connect to Microsoft Translator: {}", e));
99 }
100 }
101 }
102 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
103 }
104 Err(anyhow::anyhow!("Failed to get Microsoft Translator authorization after retries"))
105 }
106}
107
108#[async_trait::async_trait]
109impl Translator for MicrosoftTranslator {
110 async fn translate(&self, text: &str, target_lang: &str) -> Result<String> {
111 let _permit = self.semaphore.acquire().await?;
113
114 let token = {
116 let mut auth_guard = self.auth.lock().await;
117 if auth_guard.is_none() {
118 *auth_guard = Some(self.authenticate().await?);
119 }
120 auth_guard.clone().unwrap()
121 };
122
123 #[derive(Serialize)]
125 struct Request {
126 text: String,
127 }
128
129 let request = vec![Request {
130 text: text.to_string(),
131 }];
132
133 let params = [
135 ("api-version", "3.0"),
136 ("to", target_lang),
137 ("includeSentenceLength", "true"),
138 ];
139
140 let response = self.client
142 .post(&format!("{}/translate", self.config.endpoint))
143 .header("Authorization", format!("Bearer {}", token))
144 .header("Content-Type", "application/json")
145 .query(¶ms)
146 .json(&request)
147 .send()
148 .await?;
149
150 if !response.status().is_success() {
152 let status = response.status();
153 let error_text = response.text().await?;
154 return Err(anyhow::anyhow!("HTTP error {}: {}", status, error_text));
155 }
156
157 #[derive(Deserialize)]
159 struct Translation {
160 text: String,
161 }
162
163 #[derive(Deserialize)]
164 struct ResponseItem {
165 translations: Vec<Translation>,
166 }
167
168 let response_body: Vec<ResponseItem> = response.json().await?;
169
170 if response_body.is_empty() || response_body[0].translations.is_empty() {
171 return Err(anyhow::anyhow!("No translation results returned"));
172 }
173
174 Ok(response_body[0].translations[0].text.clone())
175 }
176}
177
178#[cfg(test)]
179mod tests;