html_translation_lib/
config.rs1use crate::error::{TranslationError, TranslationResult};
6use serde::{Deserialize, Serialize};
7use std::path::Path;
8use std::time::Duration;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct TranslationConfig {
13 pub source_language: String,
15
16 pub target_language: String,
18
19 pub api_url: String,
21
22 pub enable_cache: bool,
24
25 pub cache_ttl: Duration,
27
28 pub max_cache_entries: usize,
30
31 pub batch_size: usize,
33
34 pub max_retries: usize,
36
37 pub retry_delay_ms: u64,
39
40 pub timeout_seconds: u64,
42
43 pub use_indexing: bool,
45
46 pub min_text_length: usize,
48
49 pub skip_links: bool,
51
52 pub skip_code_blocks: bool,
54
55 pub custom_filters: Vec<String>,
57
58 pub api_key: Option<String>,
60
61 pub user_agent: String,
63}
64
65impl Default for TranslationConfig {
66 fn default() -> Self {
67 Self {
68 source_language: "auto".to_string(),
69 target_language: "zh".to_string(),
70 api_url: "http://localhost:1188/translate".to_string(),
71 enable_cache: true,
72 cache_ttl: Duration::from_secs(3600), max_cache_entries: 10000,
74 batch_size: 20,
75 max_retries: 3,
76 retry_delay_ms: 1000,
77 timeout_seconds: 30,
78 use_indexing: true,
79 min_text_length: 2,
80 skip_links: false,
81 skip_code_blocks: true,
82 custom_filters: Vec::new(),
83 api_key: None,
84 user_agent: format!("html-translation-lib/{}", env!("CARGO_PKG_VERSION")),
85 }
86 }
87}
88
89impl TranslationConfig {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn target_language(mut self, lang: &str) -> Self {
97 self.target_language = lang.to_string();
98 self
99 }
100
101 pub fn source_language(mut self, lang: &str) -> Self {
103 self.source_language = lang.to_string();
104 self
105 }
106
107 pub fn api_url(mut self, url: &str) -> Self {
109 self.api_url = url.to_string();
110 self
111 }
112
113 pub fn enable_cache(mut self, enabled: bool) -> Self {
115 self.enable_cache = enabled;
116 self
117 }
118
119 pub fn cache_ttl(mut self, ttl: Duration) -> Self {
121 self.cache_ttl = ttl;
122 self
123 }
124
125 pub fn batch_size(mut self, size: usize) -> Self {
127 self.batch_size = size;
128 self
129 }
130
131 pub fn max_retries(mut self, retries: usize) -> Self {
133 self.max_retries = retries;
134 self
135 }
136
137 pub fn timeout(mut self, seconds: u64) -> Self {
139 self.timeout_seconds = seconds;
140 self
141 }
142
143 pub fn api_key(mut self, key: Option<String>) -> Self {
145 self.api_key = key;
146 self
147 }
148
149 pub fn use_indexing(mut self, enabled: bool) -> Self {
151 self.use_indexing = enabled;
152 self
153 }
154
155 pub fn min_text_length(mut self, length: usize) -> Self {
157 self.min_text_length = length;
158 self
159 }
160
161 pub fn add_filter(mut self, pattern: &str) -> Self {
163 self.custom_filters.push(pattern.to_string());
164 self
165 }
166
167 pub fn from_file<P: AsRef<Path>>(path: P) -> TranslationResult<Self> {
169 let content = std::fs::read_to_string(path)?;
170 let config: TranslationConfig = toml::from_str(&content)?;
171 Ok(config)
172 }
173
174 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> TranslationResult<()> {
176 let content = toml::to_string_pretty(self)
177 .map_err(|e| TranslationError::ConfigError(format!("配置序列化失败: {e}")))?;
178 std::fs::write(path, content)?;
179 Ok(())
180 }
181
182 pub fn from_env() -> Self {
184 let mut config = Self::default();
185
186 if let Ok(target_lang) = std::env::var("TRANSLATION_TARGET_LANG") {
187 config.target_language = target_lang;
188 }
189
190 if let Ok(api_url) = std::env::var("TRANSLATION_API_URL") {
191 config.api_url = api_url;
192 }
193
194 if let Ok(api_key) = std::env::var("TRANSLATION_API_KEY") {
195 config.api_key = Some(api_key);
196 }
197
198 if let Ok(cache_enabled) = std::env::var("TRANSLATION_ENABLE_CACHE") {
199 config.enable_cache = cache_enabled.to_lowercase() == "true";
200 }
201
202 if let Ok(batch_size) = std::env::var("TRANSLATION_BATCH_SIZE") {
203 if let Ok(size) = batch_size.parse() {
204 config.batch_size = size;
205 }
206 }
207
208 config
209 }
210
211 pub fn validate(&self) -> TranslationResult<()> {
213 if self.target_language.is_empty() {
214 return Err(TranslationError::ConfigError("目标语言不能为空".to_string()));
215 }
216
217 if self.api_url.is_empty() {
218 return Err(TranslationError::ConfigError("API URL不能为空".to_string()));
219 }
220
221 if self.batch_size == 0 {
222 return Err(TranslationError::ConfigError("批处理大小必须大于0".to_string()));
223 }
224
225 if self.timeout_seconds == 0 {
226 return Err(TranslationError::ConfigError("超时时间必须大于0".to_string()));
227 }
228
229 for filter in &self.custom_filters {
231 if let Err(e) = regex::Regex::new(filter) {
232 return Err(TranslationError::ConfigError(format!(
233 "无效的正则表达式过滤规则 '{filter}': {e}"
234 )));
235 }
236 }
237
238 Ok(())
239 }
240
241 pub fn http_timeout(&self) -> Duration {
243 Duration::from_secs(self.timeout_seconds)
244 }
245
246 pub fn retry_delay(&self) -> Duration {
248 Duration::from_millis(self.retry_delay_ms)
249 }
250}
251
252pub struct TranslationConfigBuilder {
254 config: TranslationConfig,
255}
256
257impl TranslationConfigBuilder {
258 pub fn new() -> Self {
260 Self {
261 config: TranslationConfig::default(),
262 }
263 }
264
265 pub fn target_language(mut self, lang: &str) -> Self {
267 self.config.target_language = lang.to_string();
268 self
269 }
270
271 pub fn api_url(mut self, url: &str) -> Self {
273 self.config.api_url = url.to_string();
274 self
275 }
276
277 pub fn with_cache(mut self, ttl: Duration, max_entries: usize) -> Self {
279 self.config.enable_cache = true;
280 self.config.cache_ttl = ttl;
281 self.config.max_cache_entries = max_entries;
282 self
283 }
284
285 pub fn with_batching(mut self, size: usize, use_indexing: bool) -> Self {
287 self.config.batch_size = size;
288 self.config.use_indexing = use_indexing;
289 self
290 }
291
292 pub fn with_retry(mut self, max_retries: usize, delay_ms: u64) -> Self {
294 self.config.max_retries = max_retries;
295 self.config.retry_delay_ms = delay_ms;
296 self
297 }
298
299 pub fn build(self) -> TranslationResult<TranslationConfig> {
301 self.config.validate()?;
302 Ok(self.config)
303 }
304}
305
306impl Default for TranslationConfigBuilder {
307 fn default() -> Self {
308 Self::new()
309 }
310}
311
312pub fn generate_example_config<P: AsRef<Path>>(path: P) -> TranslationResult<()> {
314 let config = TranslationConfig::default();
315 config.save_to_file(path)?;
316 Ok(())
317}
318
319pub fn config_file_exists<P: AsRef<Path>>(path: P) -> bool {
321 path.as_ref().exists()
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use std::time::Duration;
328 use tempfile::NamedTempFile;
329
330 #[test]
331 fn test_default_config() {
332 let config = TranslationConfig::default();
333
334 assert_eq!(config.source_language, "auto");
335 assert_eq!(config.target_language, "zh");
336 assert_eq!(config.api_url, "http://localhost:1188/translate");
337 assert!(config.enable_cache);
338 assert_eq!(config.cache_ttl, Duration::from_secs(3600));
339 assert_eq!(config.max_cache_entries, 10000);
340 assert_eq!(config.batch_size, 20);
341 assert_eq!(config.max_retries, 3);
342 assert_eq!(config.retry_delay_ms, 1000);
343 assert_eq!(config.timeout_seconds, 30);
344 assert!(config.use_indexing);
345 assert_eq!(config.min_text_length, 2);
346 assert!(!config.skip_links);
347 assert!(config.skip_code_blocks);
348 assert!(config.custom_filters.is_empty());
349 assert!(config.api_key.is_none());
350 }
351}