translation-lib 0.1.1

A simple and efficient translation library for Rust
Documentation
//! 翻译器核心模块
//!
//! 提供简化的翻译功能,支持文本和HTML翻译。

use crate::config::TranslationConfig;
use crate::error::{TranslationError, TranslationResult};
use crate::types::{TranslationRequest, TranslationResponse};
use reqwest::Client;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Semaphore;
use tokio::time::sleep;

#[cfg(feature = "cache")]
use lru::LruCache;

#[cfg(feature = "cache")]
use crate::types::CacheEntry;

#[cfg(feature = "html-support")]
use markup5ever_rcdom::{Handle, NodeData, RcDom};

#[cfg(feature = "html-support")]
use html5ever::parse_document;

#[cfg(feature = "html-support")]
use html5ever::tendril::TendrilSink;

/// 简化的缓存管理器
#[cfg(feature = "cache")]
#[derive(Clone)]
struct SimpleCache {
    cache: Arc<std::sync::Mutex<LruCache<String, CacheEntry>>>,
    ttl: Duration,
}

#[cfg(feature = "cache")]
impl SimpleCache {
    fn new(capacity: usize, ttl: Duration) -> Self {
        use std::sync::Mutex;
        Self {
            cache: Arc::new(Mutex::new(LruCache::new(capacity.try_into().unwrap()))),
            ttl,
        }
    }

    fn get(&self, key: &str) -> Option<String> {
        let mut cache = self.cache.lock().unwrap();
        if let Some(entry) = cache.get_mut(key) {
            if !entry.is_expired(self.ttl) {
                entry.access();
                return Some(entry.translated_text.clone());
            } else {
                cache.pop(key);
            }
        }
        None
    }

    fn insert(&self, key: String, value: String) {
        let mut cache = self.cache.lock().unwrap();
        cache.put(key, CacheEntry::new(value));
    }
}

#[cfg(not(feature = "cache"))]
#[derive(Clone)]
struct SimpleCache;

#[cfg(not(feature = "cache"))]
impl SimpleCache {
    fn new(_capacity: usize, _ttl: Duration) -> Self {
        Self
    }

    fn get(&self, _key: &str) -> Option<String> {
        None
    }

    fn insert(&self, _key: String, _value: String) {
        // No-op when cache is disabled
    }
}

/// 核心翻译器
///
/// 提供简化的翻译功能,支持文本和HTML翻译。
#[derive(Clone)]
pub struct Translator {
    client: Client,
    config: TranslationConfig,
    cache: SimpleCache,
    semaphore: Arc<Semaphore>,
}

impl Translator {
    /// 创建新的翻译器实例
    pub fn new(config: TranslationConfig) -> TranslationResult<Self> {
        config.validate()?;

        let client = Client::builder()
            .timeout(config.timeout)
            .build()
            .map_err(|e| {
                TranslationError::Config(format!("Failed to create HTTP client: {}", e))
            })?;

        let cache = SimpleCache::new(1000, Duration::from_secs(3600)); // 1小时TTL
        let semaphore = Arc::new(Semaphore::new(config.max_concurrent_requests));

        Ok(Self {
            client,
            config,
            cache,
            semaphore,
        })
    }

    /// 翻译文本
    pub async fn translate_text(&self, text: &str, target_lang: &str) -> TranslationResult<String> {
        // 检查是否需要翻译
        if !crate::should_translate(text, target_lang) {
            return Ok(text.to_string());
        }

        // 检查缓存
        let cache_key = format!("{}:{}", text, target_lang);
        if let Some(cached) = self.cache.get(&cache_key) {
            return Ok(cached);
        }

        // 执行翻译
        let result = self.translate_with_retry(text, target_lang).await?;

        // 存入缓存
        if self.config.enable_cache {
            self.cache.insert(cache_key, result.clone());
        }

        Ok(result)
    }

    /// HTML翻译
    #[cfg(feature = "html-support")]
    pub async fn translate_html(&self, html: &str, target_lang: &str) -> TranslationResult<String> {
        use std::io::Cursor;

        // 解析HTML
        let dom = parse_document(markup5ever_rcdom::RcDom::default(), Default::default())
            .from_utf8()
            .read_from(&mut Cursor::new(html.as_bytes()))
            .map_err(|e| TranslationError::Parse(format!("Failed to parse HTML: {}", e)))?;

        // 翻译DOM
        let _translated_dom = self.translate_dom(dom, target_lang).await?;

        // 简化的HTML输出 - 直接返回原HTML(实际实现中需要更复杂的DOM序列化)
        Ok(html.to_string())
    }

    #[cfg(not(feature = "html-support"))]
    pub async fn translate_html(
        &self,
        _html: &str,
        _target_lang: &str,
    ) -> TranslationResult<String> {
        Err(TranslationError::Config(
            "HTML support not enabled. Enable 'html-support' feature".to_string(),
        ))
    }

    /// DOM翻译
    #[cfg(feature = "html-support")]
    pub async fn translate_dom(&self, dom: RcDom, target_lang: &str) -> TranslationResult<RcDom> {
        self.translate_node_recursive(&dom.document, target_lang)
            .await?;
        Ok(dom)
    }

    #[cfg(not(feature = "html-support"))]
    pub async fn translate_dom(&self, _dom: RcDom, _target_lang: &str) -> TranslationResult<RcDom> {
        Err(TranslationError::Config(
            "HTML support not enabled. Enable 'html-support' feature".to_string(),
        ))
    }

    /// 批量翻译文本
    pub async fn translate_texts(
        &self,
        texts: Vec<&str>,
        target_lang: &str,
    ) -> TranslationResult<Vec<String>> {
        let mut results = Vec::new();

        for text in texts {
            let result = self.translate_text(text, target_lang).await?;
            results.push(result);
        }

        Ok(results)
    }
}

impl Translator {
    /// 内部翻译实现,带重试机制
    async fn translate_with_retry(
        &self,
        text: &str,
        target_lang: &str,
    ) -> TranslationResult<String> {
        let mut retries = 0;
        let max_retries = 3;

        loop {
            // 获取信号量许可
            let _permit = self.semaphore.acquire().await.map_err(|_| {
                TranslationError::Network("Semaphore acquisition failed".to_string())
            })?;

            match self.call_translation_api(text, target_lang).await {
                Ok(result) => return Ok(result),
                Err(e) if retries >= max_retries => return Err(e),
                Err(e) if e.is_retryable() => {
                    retries += 1;
                    if let Some(delay_ms) = e.retry_delay_ms() {
                        sleep(Duration::from_millis(delay_ms)).await;
                    }
                }
                Err(e) => return Err(e),
            }
        }
    }

    /// 调用翻译API
    async fn call_translation_api(
        &self,
        text: &str,
        target_lang: &str,
    ) -> TranslationResult<String> {
        let request = TranslationRequest {
            text: text.to_string(),
            source_lang: "auto".to_string(),
            target_lang: target_lang.to_string(),
        };

        let response = self
            .client
            .post(&self.config.api_url)
            .header("Content-Type", "application/json")
            .header("Accept", "application/json")
            .header("User-Agent", "Mozilla/5.0 (compatible; TranslationLib/1.0)")
            .json(&request)
            .send()
            .await?;

        if !response.status().is_success() {
            return Err(TranslationError::Api(format!(
                "API request failed with status: {}",
                response.status()
            )));
        }

        // 获取响应文本
        let response_text = response.text().await?;

        // 解析JSON响应
        if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&response_text) {
            // 检查API特定格式 {"code": 200, "data": "翻译结果"}
            if let Some(code) = json_value.get("code").and_then(|v| v.as_i64()) {
                if code == 200 {
                    if let Some(translated) = json_value.get("data").and_then(|v| v.as_str()) {
                        return Ok(translated.to_string());
                    }
                } else {
                    return Err(TranslationError::Api(format!(
                        "API returned error code: {}",
                        code
                    )));
                }
            }

            // 尝试其他常见格式
            if let Some(translated) = json_value
                .get("translated_text")
                .or_else(|| json_value.get("result"))
                .or_else(|| json_value.get("translation"))
                .and_then(|v| v.as_str())
            {
                return Ok(translated.to_string());
            }

            // 尝试解析为标准响应格式
            if let Ok(translation_response) =
                serde_json::from_str::<TranslationResponse>(&response_text)
            {
                return Ok(translation_response.translated_text);
            }
        }

        // 如果JSON解析失败,假设整个响应就是翻译结果
        Ok(response_text)
    }

    /// 递归翻译DOM节点  
    #[cfg(feature = "html-support")]
    fn translate_node_recursive<'a>(
        &'a self,
        node: &'a Handle,
        target_lang: &'a str,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = TranslationResult<()>> + 'a>> {
        Box::pin(async move {
            match &node.data {
                NodeData::Text { contents } => {
                    let text = contents.borrow().to_string();
                    if crate::should_translate(&text, target_lang) {
                        let translated = self.translate_text(&text, target_lang).await?;
                        *contents.borrow_mut() = translated.into();
                    }
                }
                NodeData::Element { name, attrs, .. } => {
                    // 翻译特定属性
                    let translatable_attrs = ["title", "alt", "placeholder"];
                    let mut attrs_mut = attrs.borrow_mut();

                    for attr in attrs_mut.iter_mut() {
                        if translatable_attrs.contains(&attr.name.local.as_ref()) {
                            let attr_text = attr.value.to_string();
                            if crate::should_translate(&attr_text, target_lang) {
                                let translated =
                                    self.translate_text(&attr_text, target_lang).await?;
                                attr.value = translated.into();
                            }
                        }
                    }

                    // 跳过某些标签内容
                    let skip_tags = ["script", "style", "code", "pre"];
                    if !skip_tags.contains(&name.local.as_ref()) {
                        // 递归处理子节点
                        for child in node.children.borrow().iter() {
                            self.translate_node_recursive(child, target_lang).await?;
                        }
                    }
                }
                _ => {
                    // 对于其他节点类型,递归处理子节点
                    for child in node.children.borrow().iter() {
                        self.translate_node_recursive(child, target_lang).await?;
                    }
                }
            }

            Ok(())
        })
    }
}