1use serde_json::Value;
129
130#[derive(Debug, Clone, PartialEq, Hash)]
132pub struct Translation {
133 pub url: String,
134 pub source: Language,
135 pub target: Language,
136 pub input: String,
137 pub output: String,
138}
139
140pub async fn translate<T: AsRef<str>>(
142 source: Language,
143 target: Language,
144 input: T,
145 key: Option<T>,
146) -> Result<Translation, TranslateError> {
147 let url = "https://libretranslate.com/";
148
149 let key: Option<String> = key.map(|data| data.as_ref().to_string());
150
151 let data = translate_url(source, target, input.as_ref(), url, key).await?;
152
153 Ok(data)
154}
155
156pub async fn translate_url<T: AsRef<str>>(
158 source: Language,
159 target: Language,
160 input: T,
161 url: T,
162 key: Option<String>,
163) -> Result<Translation, TranslateError> {
164 let complete_url: String;
165
166 if url.as_ref().ends_with('/') {
167 complete_url = format!("{}translate", url.as_ref());
168 } else {
169 complete_url = format!("{}/translate", url.as_ref());
170 };
171
172 if input.as_ref().chars().count() >= 5000 {
173 return Err(TranslateError::LengthError);
174 };
175
176 let data: Value = match key {
177 Some(key) => {
178 serde_json::json!({
179 "q": input.as_ref(),
180 "source": source.as_code(),
181 "target": target.as_code(),
182 "api_key": key,
183 })
184 }
185 None => {
186 serde_json::json!({
187 "q": input.as_ref(),
188 "source": source.as_code(),
189 "target": target.as_code(),
190 })
191 }
192 };
193
194 let body = match surf::http::Body::from_json(&data) {
195 Ok(data) => data,
196 Err(error) => return Err(TranslateError::HttpError(error.to_string())),
197 };
198
199 let url = complete_url.clone();
200
201 let res = match surf::post(complete_url).body(body).recv_string().await {
202 Ok(data) => data,
203 Err(error) => return Err(TranslateError::HttpError(error.to_string())),
204 };
205
206 let parsed_json: Value = match serde_json::from_str(&res) {
207 Ok(parsed_json) => parsed_json,
208 Err(error) => {
209 return Err(TranslateError::ParseError(error.to_string()));
210 }
211 };
212
213 if let Value::String(error) = &parsed_json["error"] {
214 return Err(TranslateError::ParseError(error.to_string()));
215 }
216
217 let output = match &parsed_json["translatedText"] {
218 Value::String(output) => output,
219 _ => {
220 return Err(TranslateError::ParseError(String::from(
221 "Unable to find translatedText in parsed JSON",
222 )))
223 }
224 };
225
226 let input = input.as_ref().to_string();
227 let output = output.to_string();
228
229 Ok(Translation {
230 url,
231 source,
232 target,
233 input,
234 output,
235 })
236}
237
238use std::str::FromStr;
239
240#[derive(Debug, Clone, PartialEq, Copy, Hash)]
242pub enum Language {
243 Detect,
244 English,
245 Arabic,
246 Chinese,
247 French,
248 German,
249 Italian,
250 Japanese,
251 Portuguese,
252 Russian,
253 Spanish,
254 Polish,
255}
256
257impl Language {
258 pub fn as_code(&self) -> &'static str {
260 match self {
261 Language::Detect => "auto",
262 Language::English => "en",
263 Language::Arabic => "ar",
264 Language::Chinese => "zh",
265 Language::French => "fr",
266 Language::German => "de",
267 Language::Italian => "it",
268 Language::Japanese => "ja",
269 Language::Portuguese => "pt",
270 Language::Russian => "ru",
271 Language::Spanish => "es",
272 Language::Polish => "pl",
273 }
274 }
275
276 pub fn as_pretty(&self) -> &'static str {
278 match self {
279 Language::Detect => "Detected",
280 Language::English => "English",
281 Language::Arabic => "Arabic",
282 Language::Chinese => "Chinese",
283 Language::French => "French",
284 Language::German => "German",
285 Language::Italian => "Italian",
286 Language::Japanese => "Japanese",
287 Language::Portuguese => "Portuguese",
288 Language::Russian => "Russian",
289 Language::Spanish => "Spanish",
290 Language::Polish => "pl",
291 }
292 }
293
294 pub fn from<T: AsRef<str>>(s: T) -> Result<Self, LanguageError> {
296 return Self::from_str(s.as_ref());
297 }
298
299 #[cfg(feature = "unicode_langid")]
301 pub fn from_unic_langid(s: unic_langid::LanguageIdentifier) -> Result<Self, LanguageError> {
302 match s.language.as_str() {
303 "en" => Ok(Language::English),
304 "ar" => Ok(Language::Arabic),
305 "zh" => Ok(Language::Chinese),
306 "fr" => Ok(Language::French),
307 "de" => Ok(Language::German),
308 "it" => Ok(Language::Italian),
309 "pt" => Ok(Language::Portuguese),
310 "ru" => Ok(Language::Russian),
311 "es" => Ok(Language::Spanish),
312 "ja" => Ok(Language::Japanese),
313 "pl" => Ok(Language::Polish),
314 &_ => Err(LanguageError::FormatError("Unknown Language".to_string())),
315 }
316 }
317}
318
319impl Default for Language {
321 fn default() -> Self {
322 Language::English
323 }
324}
325
326impl FromStr for Language {
327 type Err = LanguageError;
328
329 fn from_str(s: &str) -> Result<Self, Self::Err> {
330 match s.to_string().to_lowercase().as_str() {
331 "en" => Ok(Language::English),
332 "ar" => Ok(Language::Arabic),
333 "zh" => Ok(Language::Chinese),
334 "fr" => Ok(Language::French),
335 "de" => Ok(Language::German),
336 "it" => Ok(Language::Italian),
337 "pt" => Ok(Language::Portuguese),
338 "ru" => Ok(Language::Russian),
339 "es" => Ok(Language::Spanish),
340 "ja" => Ok(Language::Japanese),
341 "pl" => Ok(Language::Polish),
342 "english" => Ok(Language::English),
343 "arabic" => Ok(Language::Arabic),
344 "chinese" => Ok(Language::Chinese),
345 "french" => Ok(Language::French),
346 "german" => Ok(Language::German),
347 "italian" => Ok(Language::Italian),
348 "portuguese" => Ok(Language::Portuguese),
349 "russian" => Ok(Language::Russian),
350 "spanish" => Ok(Language::Spanish),
351 "japanese" => Ok(Language::Japanese),
352 "polish" => Ok(Language::Polish),
353 "auto" => Ok(Language::Detect),
354 &_ => Err(LanguageError::FormatError(s.to_string())),
355 }
356 }
357}
358
359impl std::fmt::Display for Language {
360 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
361 match self {
362 Language::Detect => write!(f, "auto"),
363 Language::English => write!(f, "en"),
364 Language::Arabic => write!(f, "ar"),
365 Language::Chinese => write!(f, "zh"),
366 Language::French => write!(f, "fr"),
367 Language::German => write!(f, "de"),
368 Language::Italian => write!(f, "it"),
369 Language::Portuguese => write!(f, "pt"),
370 Language::Russian => write!(f, "ru"),
371 Language::Spanish => write!(f, "es"),
372 Language::Japanese => write!(f, "ja"),
373 Language::Polish => write!(f, "pl"),
374 }
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Hash)]
380pub enum LanguageError {
381 FormatError(String),
382}
383
384impl std::error::Error for LanguageError {}
385
386impl std::fmt::Display for LanguageError {
387 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
388 match self {
389 LanguageError::FormatError(error) => {
390 write!(f, "Unknown Language: {}", error)
391 }
392 }
393 }
394}
395
396#[derive(Debug, Clone, PartialEq, Hash)]
398pub enum TranslateError {
399 HttpError(String),
400 ParseError(String),
401 DetectError,
402 LengthError,
403}
404
405impl std::error::Error for TranslateError {}
406
407impl std::fmt::Display for TranslateError {
408 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
409 match self {
410 TranslateError::HttpError(error) => {
411 write!(f, "HTTP request error: {}", error.to_string())
412 }
413 TranslateError::ParseError(error) => {
414 write!(f, "JSON parsing error: {}", error.to_string())
415 }
416 TranslateError::DetectError => {
417 write!(f, "Language detection error")
418 }
419 TranslateError::LengthError => {
420 write!(f, "Requested text is too long")
421 }
422 }
423 }
424}
425
426pub struct Query<'a> {
428 pub url: &'a str,
429 pub text: &'a str,
430 pub source: Language,
431 pub target: Language,
432}
433
434impl<'a> Query<'a> {
435 pub fn to_lang(mut self, language: Language) -> Query<'a> {
436 self.target = language;
437 self
438 }
439
440 pub fn from_lang(mut self, language: Language) -> Query<'a> {
441 self.source = language;
442 self
443 }
444
445 pub fn url(mut self, url: &'a str) -> Query {
446 self.url = url;
447 self
448 }
449
450 pub async fn translate(self) -> Result<String, TranslateError> {
451 let res = crate::translate_url(self.source, self.target, self.text, self.url, None).await?;
452 Ok(res.output)
453 }
454}
455
456pub trait Translate {
458 fn to_lang(&self, language: Language) -> Query;
459 fn from_lang(&self, language: Language) -> Query;
460}
461
462impl<T> Translate for T
463where
464 T: AsRef<str>,
465{
466 fn to_lang(&self, language: Language) -> Query {
467 Query {
468 url: "https://libretranslate.com/",
469 text: self.as_ref(),
470 source: Language::Detect,
471 target: language,
472 }
473 }
474
475 fn from_lang(&self, language: Language) -> Query {
476 Query {
477 url: "https://libretranslate.com/",
478 text: self.as_ref(),
479 source: language,
480 target: Language::default(),
481 }
482 }
483}
484
485#[derive(Debug, Clone, PartialEq, Hash)]
487pub struct TranslationBuilder {
488 pub url: String,
489 pub source: Language,
490 pub target: Language,
491 pub input: String,
492 key: Option<String>,
493}
494
495impl TranslationBuilder {
496 pub fn new() -> Self {
497 Self {
498 url: String::from("https://libretranslate.com/"),
499 source: Language::Detect,
500 target: Language::default(),
501 input: String::new(),
502 key: None,
503 }
504 }
505
506 pub fn url<T: AsRef<str>>(mut self, url: T) -> Self {
507 self.url = url.as_ref().to_string();
508 self
509 }
510
511 pub fn from_lang(mut self, lang: Language) -> Self {
512 self.source = lang;
513 self
514 }
515
516 pub fn to_lang(mut self, lang: Language) -> Self {
517 self.target = lang;
518 self
519 }
520
521 pub fn text<T: AsRef<str>>(mut self, text: T) -> Self {
522 self.input = text.as_ref().to_string();
523 self
524 }
525
526 pub fn key<T: AsRef<str>>(mut self, key: T) -> Self {
527 self.key = Some(key.as_ref().to_string());
528 self
529 }
530
531 pub async fn translate(mut self) -> Result<Translation, TranslateError> {
532 if self.input.is_empty() {
533 return Ok(Translation {
534 url: self.url,
535 source: self.source,
536 target: self.target,
537 input: self.input,
538 output: String::new(),
539 });
540 };
541
542 let data = translate_url(
543 self.source,
544 self.target,
545 self.input.clone(),
546 self.url.clone(),
547 self.key,
548 )
549 .await?;
550
551 self.source = data.source;
552 self.target = data.target;
553
554 Ok(Translation {
555 url: self.url,
556 source: self.source,
557 target: self.target,
558 input: self.input,
559 output: data.output,
560 })
561 }
562}
563impl Default for TranslationBuilder {
564 fn default() -> Self {
565 Self::new()
566 }
567}