pub mod config;
pub mod error;
pub mod translator;
pub mod types;
pub use config::TranslationConfig;
pub use error::{TranslationError, TranslationResult};
pub use translator::Translator;
pub use types::{TextAnalysis, TextType};
pub async fn translate_text_simple(text: &str, target_lang: &str) -> TranslationResult<String> {
let config = TranslationConfig::from_env().unwrap_or_default();
let translator = Translator::new(config)?;
translator.translate_text(text, target_lang).await
}
pub async fn translate_html_simple(html: &str, target_lang: &str) -> TranslationResult<String> {
let config = TranslationConfig::from_env().unwrap_or_default();
let translator = Translator::new(config)?;
translator.translate_html(html, target_lang).await
}
pub fn should_translate(text: &str, target_lang: &str) -> bool {
let analysis = analyze_text(text);
analysis.is_translatable && analysis.detected_lang.as_deref() != Some(target_lang)
}
pub fn analyze_text(text: &str) -> TextAnalysis {
use types::*;
if text.trim().is_empty() {
return TextAnalysis {
is_translatable: false,
detected_lang: None,
confidence: 0.0,
text_type: TextType::Empty,
};
}
if text.starts_with("http") || text.contains("://") {
return TextAnalysis {
is_translatable: false,
detected_lang: None,
confidence: 1.0,
text_type: TextType::Url,
};
}
if text.contains('@') && text.split_whitespace().count() == 1 {
return TextAnalysis {
is_translatable: false,
detected_lang: None,
confidence: 1.0,
text_type: TextType::Email,
};
}
let code_chars = text.chars().filter(|&c| "{}[]();".contains(c)).count();
let code_ratio = code_chars as f32 / text.len() as f32;
if code_ratio > 0.1 {
return TextAnalysis {
is_translatable: false,
detected_lang: None,
confidence: code_ratio,
text_type: TextType::Code,
};
}
let chinese_chars = text.chars().filter(|c| is_chinese_char(*c)).count();
let chinese_ratio = chinese_chars as f32 / text.chars().count() as f32;
if chinese_ratio > 0.5 {
return TextAnalysis {
is_translatable: false,
detected_lang: Some("zh".to_string()),
confidence: chinese_ratio,
text_type: TextType::Content,
};
}
TextAnalysis {
is_translatable: true,
detected_lang: Some("en".to_string()),
confidence: 1.0 - chinese_ratio,
text_type: TextType::Content,
}
}
fn is_chinese_char(c: char) -> bool {
matches!(c, '\u{4e00}'..='\u{9fff}' | '\u{3400}'..='\u{4dbf}' | '\u{f900}'..='\u{faff}')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chinese_char_detection() {
assert!(is_chinese_char('中'));
assert!(is_chinese_char('文'));
assert!(!is_chinese_char('a'));
assert!(!is_chinese_char('1'));
}
#[test]
fn test_should_translate() {
assert!(should_translate("Hello world", "zh"));
assert!(!should_translate("你好世界", "zh"));
assert!(!should_translate("https://example.com", "zh"));
assert!(!should_translate("test@example.com", "zh"));
assert!(!should_translate("function() { return true; }", "zh"));
}
#[test]
fn test_analyze_text() {
let analysis = analyze_text("Hello world");
assert!(analysis.is_translatable);
assert_eq!(analysis.detected_lang, Some("en".to_string()));
assert_eq!(analysis.text_type, TextType::Content);
let analysis = analyze_text("你好世界");
assert!(!analysis.is_translatable);
assert_eq!(analysis.detected_lang, Some("zh".to_string()));
assert_eq!(analysis.text_type, TextType::Content);
let analysis = analyze_text("https://example.com");
assert!(!analysis.is_translatable);
assert_eq!(analysis.text_type, TextType::Url);
let analysis = analyze_text("test@example.com");
assert!(!analysis.is_translatable);
assert_eq!(analysis.text_type, TextType::Email);
let analysis = analyze_text("function() { return true; }");
assert!(!analysis.is_translatable);
assert_eq!(analysis.text_type, TextType::Code);
let analysis = analyze_text("");
assert!(!analysis.is_translatable);
assert_eq!(analysis.text_type, TextType::Empty);
}
#[test]
fn test_config_validation() {
let config = TranslationConfig::default();
assert!(config.validate().is_ok());
let mut config = TranslationConfig::default();
config.api_url = "".to_string();
assert!(config.validate().is_err());
let mut config = TranslationConfig::default();
config.api_url = "invalid-url".to_string();
assert!(config.validate().is_err());
let mut config = TranslationConfig::default();
config.max_concurrent_requests = 0;
assert!(config.validate().is_err());
}
}