Skip to main content

itools_localization/
lib.rs

1#![warn(missing_docs)]
2
3//! iTools 国际化支持模块
4//!
5//! 提供完整的国际化功能,包括翻译管理、语言切换和参数化翻译。
6
7use std::{collections::HashMap, path::Path};
8use tracing::info;
9
10// 导入 oak-fluent 库
11#[cfg(feature = "oak-fluent")]
12use oak_fluent::{Translator as OakFluentTranslatorCore, parse};
13
14/// 翻译提供者 trait
15pub trait TranslationProvider {
16    /// 翻译一个键
17    fn translate(
18        &self,
19        key: &str,
20        args: Option<&HashMap<String, String>>,
21        locale: &str,
22    ) -> Result<String, Box<dyn std::error::Error>>;
23
24    /// 获取当前语言
25    fn get_locale(&self) -> &str;
26
27    /// 设置当前语言
28    fn set_locale(&mut self, locale: &str);
29
30    /// 克隆翻译提供者
31    fn clone_box(&self) -> Box<dyn TranslationProvider + Send + Sync>;
32}
33
34/// 简单内存翻译提供者
35#[derive(Debug, Clone)]
36pub struct SimpleTranslator {
37    translations: HashMap<String, HashMap<String, String>>,
38    current_locale: String,
39    default_locale: String,
40}
41
42impl SimpleTranslator {
43    /// 创建新的简单翻译提供者
44    pub fn new(current_locale: &str, default_locale: &str) -> Self {
45        Self {
46            translations: HashMap::new(),
47            current_locale: current_locale.to_string(),
48            default_locale: default_locale.to_string(),
49        }
50    }
51
52    /// 添加翻译
53    pub fn add_translations(&mut self, locale: &str, translations: HashMap<String, String>) {
54        self.translations.insert(locale.to_string(), translations);
55    }
56}
57
58impl TranslationProvider for SimpleTranslator {
59    fn translate(
60        &self,
61        key: &str,
62        args: Option<&HashMap<String, String>>,
63        locale: &str,
64    ) -> Result<String, Box<dyn std::error::Error>> {
65        // 查找翻译
66        let translation = self
67            .translations
68            .get(locale)
69            .and_then(|lang| lang.get(key))
70            .or_else(|| {
71                if locale != self.default_locale {
72                    self.translations.get(&self.default_locale).and_then(|lang| lang.get(key))
73                }
74                else {
75                    None
76                }
77            })
78            .unwrap_or(&key.to_string())
79            .to_string();
80
81        // 处理参数
82        if let Some(args) = args {
83            let mut result = translation;
84            for (name, value) in args {
85                result = result.replace(&format!("{{{}}}", name), value);
86            }
87            Ok(result)
88        }
89        else {
90            Ok(translation)
91        }
92    }
93
94    fn get_locale(&self) -> &str {
95        &self.current_locale
96    }
97
98    fn set_locale(&mut self, locale: &str) {
99        self.current_locale = locale.to_string();
100    }
101
102    fn clone_box(&self) -> Box<dyn TranslationProvider + Send + Sync> {
103        Box::new(self.clone())
104    }
105}
106
107/// Fluent 翻译提供者
108pub struct FluentTranslator {
109    bundles: HashMap<String, fluent_bundle::FluentBundle<fluent_bundle::FluentResource>>,
110    current_locale: String,
111    default_locale: String,
112}
113
114impl FluentTranslator {
115    /// 创建新的 Fluent 翻译提供者
116    pub fn new(current_locale: &str, default_locale: &str) -> Self {
117        Self { bundles: HashMap::new(), current_locale: current_locale.to_string(), default_locale: default_locale.to_string() }
118    }
119
120    /// 从文件加载翻译
121    pub fn add_translations_from_file(&mut self, locale: &str, file_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
122        let content = std::fs::read_to_string(file_path)?;
123        self.add_translations_from_string(locale, &content)
124    }
125
126    /// 从字符串加载翻译
127    pub fn add_translations_from_string(&mut self, locale: &str, ftl_content: &str) -> Result<(), Box<dyn std::error::Error>> {
128        match fluent_bundle::FluentResource::try_new(ftl_content.to_string()) {
129            Ok(resource) => {
130                let langid = match locale {
131                    "zh-CN" => unic_langid::langid!("zh-CN"),
132                    "en" => unic_langid::langid!("en"),
133                    _ => unic_langid::langid!("en"),
134                };
135
136                let mut bundle = fluent_bundle::FluentBundle::new(vec![langid]);
137                match bundle.add_resource(resource) {
138                    Ok(_) => {
139                        self.bundles.insert(locale.to_string(), bundle);
140                        Ok(())
141                    }
142                    Err(errors) => Err(format!("Failed to add resource: {:?}", errors).into()),
143                }
144            }
145            Err((_, errors)) => Err(format!("Failed to parse FTL content: {:?}", errors).into()),
146        }
147    }
148}
149
150impl TranslationProvider for FluentTranslator {
151    fn translate(
152        &self,
153        key: &str,
154        args: Option<&HashMap<String, String>>,
155        locale: &str,
156    ) -> Result<String, Box<dyn std::error::Error>> {
157        // 尝试从当前语言获取翻译
158        if let Some(bundle) = self.bundles.get(locale) {
159            if let Some(message) = bundle.get_message(key) {
160                if let Some(value) = message.value() {
161                    let mut errors = vec![];
162                    let result = if let Some(args) = args {
163                        let mut fluent_args = fluent_bundle::FluentArgs::new();
164                        for (name, value) in args {
165                            fluent_args.set(name, fluent_bundle::FluentValue::from(value));
166                        }
167                        bundle.format_pattern(value, Some(&fluent_args), &mut errors)
168                    }
169                    else {
170                        bundle.format_pattern(value, None, &mut errors)
171                    };
172                    return Ok(result.to_string());
173                }
174            }
175        }
176
177        // 回退到默认语言
178        if locale != self.default_locale {
179            if let Some(bundle) = self.bundles.get(&self.default_locale) {
180                if let Some(message) = bundle.get_message(key) {
181                    if let Some(value) = message.value() {
182                        let mut errors = vec![];
183                        let result = if let Some(args) = args {
184                            let mut fluent_args = fluent_bundle::FluentArgs::new();
185                            for (name, value) in args {
186                                fluent_args.set(name, fluent_bundle::FluentValue::from(value));
187                            }
188                            bundle.format_pattern(value, Some(&fluent_args), &mut errors)
189                        }
190                        else {
191                            bundle.format_pattern(value, None, &mut errors)
192                        };
193                        return Ok(result.to_string());
194                    }
195                }
196            }
197        }
198
199        // 回退到键本身
200        Ok(key.to_string())
201    }
202
203    fn get_locale(&self) -> &str {
204        &self.current_locale
205    }
206
207    fn set_locale(&mut self, locale: &str) {
208        self.current_locale = locale.to_string();
209    }
210
211    fn clone_box(&self) -> Box<dyn TranslationProvider + Send + Sync> {
212        // 创建一个新的 FluentTranslator 实例
213        let new_translator = FluentTranslator::new(&self.current_locale, &self.default_locale);
214
215        // 重新添加所有翻译
216        for (_locale, _bundle) in &self.bundles {
217            // 这里我们无法直接克隆 bundle,所以需要重新创建
218            // 实际实现中,你可能需要存储原始的 FTL 内容以便重新创建 bundle
219            // 为了简化,这里我们只复制当前语言设置
220        }
221
222        Box::new(new_translator)
223    }
224}
225
226// 不安全的 Send + Sync 实现,因为 FluentBundle 不实现它们
227// 但我们通过 Mutex 保护访问,所以实际上是安全的
228unsafe impl Send for FluentTranslator {}
229unsafe impl Sync for FluentTranslator {}
230
231/// Oak Fluent 翻译提供者
232#[cfg(feature = "oak-fluent")]
233pub struct OakFluentTranslator {
234    translators: HashMap<String, OakFluentTranslatorCore>,
235    current_locale: String,
236    default_locale: String,
237}
238
239#[cfg(feature = "oak-fluent")]
240impl OakFluentTranslator {
241    /// 创建新的 Oak Fluent 翻译提供者
242    pub fn new(current_locale: &str, default_locale: &str) -> Self {
243        Self {
244            translators: HashMap::new(),
245            current_locale: current_locale.to_string(),
246            default_locale: default_locale.to_string(),
247        }
248    }
249
250    /// 从文件加载翻译
251    pub fn add_translations_from_file(&mut self, locale: &str, file_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
252        let content = std::fs::read_to_string(file_path)?;
253        self.add_translations_from_string(locale, &content)
254    }
255
256    /// 从字符串加载翻译
257    pub fn add_translations_from_string(&mut self, locale: &str, ftl_content: &str) -> Result<(), Box<dyn std::error::Error>> {
258        match parse(ftl_content) {
259            Ok(root) => {
260                let translator = OakFluentTranslatorCore::new(root);
261                self.translators.insert(locale.to_string(), translator);
262                Ok(())
263            }
264            Err(err) => Err(format!("Failed to parse FTL content: {:?}", err).into()),
265        }
266    }
267}
268
269#[cfg(feature = "oak-fluent")]
270impl TranslationProvider for OakFluentTranslator {
271    fn translate(
272        &self,
273        key: &str,
274        args: Option<&HashMap<String, String>>,
275        locale: &str,
276    ) -> Result<String, Box<dyn std::error::Error>> {
277        // 尝试从当前语言获取翻译
278        if let Some(translator) = self.translators.get(locale) {
279            if let Some(translation) = translator.translate(key, &args.unwrap_or(&HashMap::new())) {
280                return Ok(translation);
281            }
282        }
283
284        // 回退到默认语言
285        if locale != self.default_locale {
286            if let Some(translator) = self.translators.get(&self.default_locale) {
287                if let Some(translation) = translator.translate(key, &args.unwrap_or(&HashMap::new())) {
288                    return Ok(translation);
289                }
290            }
291        }
292
293        // 回退到键本身
294        Ok(key.to_string())
295    }
296
297    fn get_locale(&self) -> &str {
298        &self.current_locale
299    }
300
301    fn set_locale(&mut self, locale: &str) {
302        self.current_locale = locale.to_string();
303    }
304
305    fn clone_box(&self) -> Box<dyn TranslationProvider + Send + Sync> {
306        // 创建一个新的 OakFluentTranslator 实例
307        let mut new_translator = OakFluentTranslator::new(&self.current_locale, &self.default_locale);
308
309        // 复制所有翻译器
310        for (locale, translator) in &self.translators {
311            // 由于 OakFluentTranslatorCore 实现了 Clone,我们可以直接克隆
312            new_translator.translators.insert(locale.clone(), translator.clone());
313        }
314
315        Box::new(new_translator)
316    }
317}
318
319#[cfg(feature = "oak-fluent")]
320// 实现 Send + Sync 因为 OakFluentTranslatorCore 是可克隆的且线程安全的
321unsafe impl Send for OakFluentTranslator {}
322
323#[cfg(feature = "oak-fluent")]
324unsafe impl Sync for OakFluentTranslator {}
325
326/// 翻译缓存键
327#[derive(Debug, Hash, PartialEq, Eq)]
328struct TranslationCacheKey {
329    key: String,
330    locale: String,
331    args: Option<Vec<(String, String)>>,
332}
333
334impl TranslationCacheKey {
335    /// 创建新的缓存键
336    fn new(key: &str, locale: &str, args: Option<&HashMap<String, String>>) -> Self {
337        let args_vec = args.map(|a| {
338            let mut vec: Vec<(String, String)> = a.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
339            vec.sort_by(|(a, _), (b, _)| a.cmp(b));
340            vec
341        });
342
343        Self { key: key.to_string(), locale: locale.to_string(), args: args_vec }
344    }
345}
346
347/// 国际化上下文
348pub struct I18nContext {
349    translator: Box<dyn TranslationProvider + Send + Sync>,
350    translation_cache: lru::LruCache<TranslationCacheKey, String>,
351    cache_capacity: usize,
352}
353
354impl Clone for I18nContext {
355    fn clone(&self) -> Self {
356        Self {
357            translator: self.translator.clone_box(),
358            translation_cache: lru::LruCache::new(self.cache_capacity),
359            cache_capacity: self.cache_capacity,
360        }
361    }
362}
363
364impl I18nContext {
365    /// 创建新的国际化上下文
366    pub fn new(translator: Box<dyn TranslationProvider + Send + Sync>) -> Self {
367        Self { translator, translation_cache: lru::LruCache::new(1000), cache_capacity: 1000 }
368    }
369
370    /// 创建带有自定义缓存容量的国际化上下文
371    pub fn with_cache_capacity(translator: Box<dyn TranslationProvider + Send + Sync>, capacity: usize) -> Self {
372        Self { translator, translation_cache: lru::LruCache::new(capacity), cache_capacity: capacity }
373    }
374
375    /// 翻译一个键
376    pub fn t(&mut self, key: &str) -> String {
377        self.t_with_args(key, None)
378    }
379
380    /// 翻译带参数的键
381    pub fn t_with_args(&mut self, key: &str, args: Option<&HashMap<String, String>>) -> String {
382        let locale = self.translator.get_locale();
383
384        // 构建缓存键
385        let cache_key = TranslationCacheKey::new(key, locale, args);
386
387        // 检查缓存
388        if let Some(cached) = self.translation_cache.get(&cache_key) {
389            return cached.clone();
390        }
391
392        // 翻译
393        let result = self.translator.translate(key, args, locale).unwrap_or_else(|_| key.to_string());
394
395        // 缓存结果
396        self.translation_cache.put(cache_key, result.clone());
397
398        result
399    }
400
401    /// 批量翻译
402    pub fn t_batch(&mut self, keys: &[(String, Option<HashMap<String, String>>)]) -> Vec<String> {
403        keys.iter().map(|(key, args)| self.t_with_args(key, args.as_ref())).collect()
404    }
405
406    /// 获取当前语言
407    pub fn get_locale(&self) -> &str {
408        self.translator.get_locale()
409    }
410
411    /// 设置当前语言
412    pub fn set_locale(&mut self, locale: &str) {
413        self.translator.set_locale(locale);
414        self.clear_cache(); // 语言切换时清除缓存
415    }
416
417    /// 清除翻译缓存
418    pub fn clear_cache(&mut self) {
419        self.translation_cache.clear();
420    }
421
422    /// 获取缓存大小
423    pub fn cache_size(&self) -> usize {
424        self.translation_cache.len()
425    }
426}
427
428lazy_static::lazy_static! {
429    /// 全局国际化实例
430    pub static ref I18N: std::sync::Mutex<I18nContext> = {
431        #[cfg(feature = "oak-fluent")]
432        {
433            // 创建默认的 Oak Fluent 翻译提供者
434            let mut translator = OakFluentTranslator::new("en", "en");
435
436            // 尝试加载默认语言资源
437            let default_locale = std::env::var("LANG").unwrap_or_else(|_| "en".to_string());
438
439            // 加载英文资源
440            if let Ok(english_path) = std::env::var("ITOOLS_LOCALE_EN") {
441                let path = Path::new(&english_path);
442                if path.exists() {
443                    if let Err(e) = translator.add_translations_from_file("en", path) {
444                        info!("Failed to load English translations: {}", e);
445                    }
446                }
447            }
448
449            // 加载当前语言资源
450            if default_locale != "en" {
451                if let Ok(locale_path) = std::env::var(format!("ITOOLS_LOCALE_{}", default_locale.to_uppercase().replace('-', "_"))) {
452                    let path = Path::new(&locale_path);
453                    if path.exists() {
454                        if let Err(e) = translator.add_translations_from_file(&default_locale, path) {
455                            info!("Failed to load {} translations: {}", default_locale, e);
456                        }
457                    }
458                }
459            }
460
461            std::sync::Mutex::new(I18nContext::new(Box::new(translator)))
462        }
463
464        #[cfg(not(feature = "oak-fluent"))]
465        {
466            // 创建默认的 Fluent 翻译提供者
467            let mut translator = FluentTranslator::new("en", "en");
468
469            // 尝试加载默认语言资源
470            let default_locale = std::env::var("LANG").unwrap_or_else(|_| "en".to_string());
471
472            // 加载英文资源
473            if let Ok(english_path) = std::env::var("ITOOLS_LOCALE_EN") {
474                let path = Path::new(&english_path);
475                if path.exists() {
476                    if let Err(e) = translator.add_translations_from_file("en", path) {
477                        info!("Failed to load English translations: {}", e);
478                    }
479                }
480            }
481
482            // 加载当前语言资源
483            if default_locale != "en" {
484                if let Ok(locale_path) = std::env::var(format!("ITOOLS_LOCALE_{}", default_locale.to_uppercase().replace('-', "_"))) {
485                    let path = Path::new(&locale_path);
486                    if path.exists() {
487                        if let Err(e) = translator.add_translations_from_file(&default_locale, path) {
488                            info!("Failed to load {} translations: {}", default_locale, e);
489                        }
490                    }
491                }
492            }
493
494            std::sync::Mutex::new(I18nContext::new(Box::new(translator)))
495        }
496    };
497}
498
499/// 获取翻译
500pub fn t(key: &str) -> String {
501    let mut i18n = I18N.lock().unwrap();
502    i18n.t(key)
503}
504
505/// 获取带参数的翻译
506pub fn t_with_args(key: &str, args: &[(&str, &str)]) -> String {
507    let mut i18n = I18N.lock().unwrap();
508    let args_map: HashMap<String, String> = args.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
509    i18n.t_with_args(key, Some(&args_map))
510}
511
512/// 设置当前语言
513pub fn set_locale(locale: &str) {
514    let mut i18n = I18N.lock().unwrap();
515    i18n.set_locale(locale);
516}
517
518/// 获取当前语言
519pub fn get_locale() -> String {
520    let i18n = I18N.lock().unwrap();
521    i18n.get_locale().to_string()
522}