translation_lib/
lib.rs

1//! # Translation Library
2//!
3//! 一个简单高效的Rust翻译库,支持文本和HTML翻译功能。
4//!
5//! ## 主要特性
6//!
7//! - **多格式支持**: 支持纯文本、HTML和DOM翻译
8//! - **智能过滤**: 自动识别并跳过代码、URL、邮箱等不需要翻译的内容
9//! - **并发翻译**: 基于tokio的异步并发处理
10//! - **缓存机制**: 内置LRU缓存,避免重复翻译
11//! - **错误处理**: 完善的错误处理和自动重试机制
12//! - **语言检测**: 智能检测文本语言,避免不必要的翻译
13//!
14//! ## 快速开始
15//!
16//! ```no_run
17//! use translation_lib::{Translator, TranslationConfig};
18//!
19//! #[tokio::main]
20//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
21//!     let config = TranslationConfig::new("YOUR_API_URL".to_string())
22//!         .with_api_key("YOUR_API_KEY".to_string());
23//!     let translator = Translator::new(config)?;
24//!     
25//!     // 文本翻译
26//!     let result = translator.translate_text("Hello, world!", "zh").await?;
27//!     println!("翻译结果: {}", result);
28//!     
29//!     // HTML翻译
30//!     let html = r#"<p>Hello <span>World</span></p>"#;
31//!     let translated_html = translator.translate_html(html, "zh").await?;
32//!     println!("HTML翻译: {}", translated_html);
33//!     
34//!     Ok(())
35//! }
36//! ```
37//!
38//! ## 使用环境变量配置
39//!
40//! ```no_run
41//! use translation_lib::translate_text_simple;
42//!
43//! #[tokio::main]
44//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
45//!     // 设置环境变量: TRANSLATION_API_URL
46//!     let result = translate_text_simple("Hello", "zh").await?;
47//!     println!("快速翻译: {}", result);
48//!     Ok(())
49//! }
50//! ```
51
52pub mod config;
53pub mod error;
54pub mod translator;
55pub mod types;
56
57pub use config::TranslationConfig;
58pub use error::{TranslationError, TranslationResult};
59pub use translator::Translator;
60pub use types::{TextAnalysis, TextType};
61
62/// 便利函数:简单文本翻译
63pub async fn translate_text_simple(text: &str, target_lang: &str) -> TranslationResult<String> {
64    let config = TranslationConfig::from_env().unwrap_or_default();
65    let translator = Translator::new(config)?;
66    translator.translate_text(text, target_lang).await
67}
68
69/// 便利函数:简单HTML翻译
70pub async fn translate_html_simple(html: &str, target_lang: &str) -> TranslationResult<String> {
71    let config = TranslationConfig::from_env().unwrap_or_default();
72    let translator = Translator::new(config)?;
73    translator.translate_html(html, target_lang).await
74}
75
76/// 检查文本是否应该翻译
77pub fn should_translate(text: &str, target_lang: &str) -> bool {
78    let analysis = analyze_text(text);
79    analysis.is_translatable && analysis.detected_lang.as_deref() != Some(target_lang)
80}
81
82/// 分析文本特征
83pub fn analyze_text(text: &str) -> TextAnalysis {
84    use types::*;
85
86    if text.trim().is_empty() {
87        return TextAnalysis {
88            is_translatable: false,
89            detected_lang: None,
90            confidence: 0.0,
91            text_type: TextType::Empty,
92        };
93    }
94
95    // URL检测
96    if text.starts_with("http") || text.contains("://") {
97        return TextAnalysis {
98            is_translatable: false,
99            detected_lang: None,
100            confidence: 1.0,
101            text_type: TextType::Url,
102        };
103    }
104
105    // 邮箱检测
106    if text.contains('@') && text.split_whitespace().count() == 1 {
107        return TextAnalysis {
108            is_translatable: false,
109            detected_lang: None,
110            confidence: 1.0,
111            text_type: TextType::Email,
112        };
113    }
114
115    // 代码检测
116    let code_chars = text.chars().filter(|&c| "{}[]();".contains(c)).count();
117    let code_ratio = code_chars as f32 / text.len() as f32;
118    if code_ratio > 0.1 {
119        return TextAnalysis {
120            is_translatable: false,
121            detected_lang: None,
122            confidence: code_ratio,
123            text_type: TextType::Code,
124        };
125    }
126
127    // 中文检测
128    let chinese_chars = text.chars().filter(|c| is_chinese_char(*c)).count();
129    let chinese_ratio = chinese_chars as f32 / text.chars().count() as f32;
130
131    if chinese_ratio > 0.5 {
132        return TextAnalysis {
133            is_translatable: false,
134            detected_lang: Some("zh".to_string()),
135            confidence: chinese_ratio,
136            text_type: TextType::Content,
137        };
138    }
139
140    // 默认为可翻译的英文内容
141    TextAnalysis {
142        is_translatable: true,
143        detected_lang: Some("en".to_string()),
144        confidence: 1.0 - chinese_ratio,
145        text_type: TextType::Content,
146    }
147}
148
149/// 检查字符是否为中文字符
150fn is_chinese_char(c: char) -> bool {
151    matches!(c, '\u{4e00}'..='\u{9fff}' | '\u{3400}'..='\u{4dbf}' | '\u{f900}'..='\u{faff}')
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_chinese_char_detection() {
160        assert!(is_chinese_char('中'));
161        assert!(is_chinese_char('文'));
162        assert!(!is_chinese_char('a'));
163        assert!(!is_chinese_char('1'));
164    }
165
166    #[test]
167    fn test_should_translate() {
168        // 英文内容应该翻译
169        assert!(should_translate("Hello world", "zh"));
170
171        // 中文内容不应该翻译成中文
172        assert!(!should_translate("你好世界", "zh"));
173
174        // URL不应该翻译
175        assert!(!should_translate("https://example.com", "zh"));
176
177        // 邮箱不应该翻译
178        assert!(!should_translate("test@example.com", "zh"));
179
180        // 代码不应该翻译
181        assert!(!should_translate("function() { return true; }", "zh"));
182    }
183
184    #[test]
185    fn test_analyze_text() {
186        // 英文文本
187        let analysis = analyze_text("Hello world");
188        assert!(analysis.is_translatable);
189        assert_eq!(analysis.detected_lang, Some("en".to_string()));
190        assert_eq!(analysis.text_type, TextType::Content);
191
192        // 中文文本
193        let analysis = analyze_text("你好世界");
194        assert!(!analysis.is_translatable);
195        assert_eq!(analysis.detected_lang, Some("zh".to_string()));
196        assert_eq!(analysis.text_type, TextType::Content);
197
198        // URL
199        let analysis = analyze_text("https://example.com");
200        assert!(!analysis.is_translatable);
201        assert_eq!(analysis.text_type, TextType::Url);
202
203        // 邮箱
204        let analysis = analyze_text("test@example.com");
205        assert!(!analysis.is_translatable);
206        assert_eq!(analysis.text_type, TextType::Email);
207
208        // 代码
209        let analysis = analyze_text("function() { return true; }");
210        assert!(!analysis.is_translatable);
211        assert_eq!(analysis.text_type, TextType::Code);
212
213        // 空文本
214        let analysis = analyze_text("");
215        assert!(!analysis.is_translatable);
216        assert_eq!(analysis.text_type, TextType::Empty);
217    }
218
219    #[test]
220    fn test_config_validation() {
221        // 有效配置
222        let config = TranslationConfig::default();
223        assert!(config.validate().is_ok());
224
225        // 无效配置 - 空URL
226        let mut config = TranslationConfig::default();
227        config.api_url = "".to_string();
228        assert!(config.validate().is_err());
229
230        // 无效配置 - 错误的URL格式
231        let mut config = TranslationConfig::default();
232        config.api_url = "invalid-url".to_string();
233        assert!(config.validate().is_err());
234
235        // 无效配置 - 零并发数
236        let mut config = TranslationConfig::default();
237        config.max_concurrent_requests = 0;
238        assert!(config.validate().is_err());
239    }
240}