extern crate self as i18n; use std::{collections::HashMap, sync::OnceLock}; use message_ids::MessageId; use strum::IntoEnumIterator; pub mod message_ids; const I18N_FILE: &str = "i18n.csv"; static I18N: OnceLock>> = OnceLock::new(); fn init() -> HashMap<&'static str, HashMap<&'static str, &'static str>> { let mut i18n_map = HashMap::new(); let mut i18n_id_map = HashMap::new(); let mut rdr = csv::Reader::from_path(I18N_FILE).expect("读取多语言文件失败"); let headers = rdr.headers().expect("解析多语言文件失败"); let header_length = headers.len(); for i in 1..header_length { let lang_id = headers.get(i).unwrap(); i18n_id_map.insert(i, lang_id.to_string()); } for result in rdr.deserialize() { let record: Vec = result.expect("解析多语言文件失败"); let column_length = record.len(); let message_id: &'static str = Box::leak(Box::new(record[0].clone())); for i in 1..column_length { let lang_id: &'static str = Box::leak(Box::new(i18n_id_map.get(&i).unwrap().clone())); let message: &'static str = Box::leak(Box::new(record[i].clone())); i18n_map .entry(lang_id) .or_insert_with(HashMap::new) .insert(message_id, message); } } // 检查多语言文件是否完整 for (lang_id, lang_map) in i18n_map.iter() { MessageId::iter().for_each(|message_id| { if !lang_map.contains_key(message_id.as_ref()) { tracing::error!("多语言文件缺失消息:{},语言:{}", message_id, lang_id); } }); } i18n_map } fn init_i18n() -> &'static HashMap<&'static str, HashMap<&'static str, &'static str>> { I18N.get_or_init(init) } /// 获取语言模板 pub fn lang(lang_id: &str, message_id: MessageId) -> &'static str { init_i18n() .get(lang_id) .and_then(|map| map.get(message_id.as_ref())) .unwrap_or_else(|| &"UNKNOWN") } /// 将模板消息转换为最终消息,支持模板占位符 pub fn convert_message_with_params(message_template: &'static str, args: Vec<&str>) -> String { if message_template.is_empty() { return String::new(); } if message_template.contains("{}") { let message_split_array: Vec<&str> = message_template.split("{}").collect(); let capacity = message_template.len() + args.iter().map(|s| s.len()).sum::(); let mut message = String::with_capacity(capacity); message_split_array.iter().zip(args.iter().chain(std::iter::once(&""))).for_each(|(part, arg)| { message.push_str(part); message.push_str(arg); }); message } else { String::from(message_template) } } #[macro_export] macro_rules! message { // 通过lang id和message id获取消息 ($lang_id:expr, $message_id:expr) => { i18n::lang($lang_id, $message_id) }; // 通过lang id和message id获取消息,并将参数格式化到消息中 ($lang_id:expr, $message_id:expr, $($arg:expr),*) => { { let message_template: &'static str = i18n::lang($lang_id, $message_id); let args = vec![$($arg),*]; i18n::convert_message_with_params(message_template, args) } }; }