1use std::collections::HashMap;
34use std::sync::{OnceLock, RwLock};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum MessageKey {
42 ConnectionFailed,
44 ConnectionTimeout,
46 QueryError,
48 NotFound,
50 ConstraintViolation,
52 PoolExhausted,
54 PoolTimeout,
56 TxNotStarted,
58 TxCommitFailed,
60 TxRollbackFailed,
62 CacheMiss,
64 CacheWriteFailed,
66 SqlInjectionDetected,
68 MissingParameter,
70 TypeMismatch,
72 Custom,
74}
75
76impl MessageKey {
77 pub fn default_msg(self) -> &'static str {
79 match self {
80 MessageKey::ConnectionFailed => "连接失败",
81 MessageKey::ConnectionTimeout => "连接超时",
82 MessageKey::QueryError => "查询错误",
83 MessageKey::NotFound => "未找到",
84 MessageKey::ConstraintViolation => "约束违反",
85 MessageKey::PoolExhausted => "连接池耗尽",
86 MessageKey::PoolTimeout => "连接池超时",
87 MessageKey::TxNotStarted => "事务未启动",
88 MessageKey::TxCommitFailed => "事务提交失败",
89 MessageKey::TxRollbackFailed => "事务回滚失败",
90 MessageKey::CacheMiss => "缓存未命中",
91 MessageKey::CacheWriteFailed => "缓存写入失败",
92 MessageKey::SqlInjectionDetected => "检测到 SQL 注入",
93 MessageKey::MissingParameter => "参数绑定缺失",
94 MessageKey::TypeMismatch => "类型转换失败",
95 MessageKey::Custom => "",
96 }
97 }
98}
99
100pub type MessageCatalog = HashMap<MessageKey, String>;
105
106static CATALOG: OnceLock<RwLock<MessageCatalog>> = OnceLock::new();
108
109fn catalog() -> &'static RwLock<MessageCatalog> {
111 CATALOG.get_or_init(|| RwLock::new(MessageCatalog::new()))
112}
113
114pub fn set_catalog(new_catalog: MessageCatalog) {
118 let mut guard = catalog().write().expect("i18n catalog poisoned");
119 *guard = new_catalog;
120}
121
122pub fn register(key: MessageKey, msg: impl Into<String>) {
126 let mut guard = catalog().write().expect("i18n catalog poisoned");
127 guard.insert(key, msg.into());
128}
129
130pub fn clear() {
134 let mut guard = catalog().write().expect("i18n catalog poisoned");
135 guard.clear();
136}
137
138pub fn translate(key: MessageKey, args: &[&str]) -> String {
143 let guard = catalog().read().expect("i18n catalog poisoned");
144 if let Some(template) = guard.get(&key) {
145 format_args(template, args)
146 } else {
147 key.default_msg().to_string()
148 }
149}
150
151fn format_args(template: &str, args: &[&str]) -> String {
156 let mut result = String::with_capacity(template.len());
157 let mut chars = template.chars().peekable();
158 while let Some(c) = chars.next() {
159 if c == '{' {
160 let mut idx_str = String::new();
161 while let Some(&next) = chars.peek() {
162 if next == '}' {
163 chars.next();
164 break;
165 }
166 idx_str.push(next);
167 chars.next();
168 }
169 if let Ok(idx) = idx_str.parse::<usize>() {
170 if let Some(arg) = args.get(idx) {
171 result.push_str(arg);
172 } else {
173 result.push('{');
174 result.push_str(&idx_str);
175 result.push('}');
176 }
177 } else {
178 result.push('{');
179 result.push_str(&idx_str);
180 result.push('}');
181 }
182 } else {
183 result.push(c);
184 }
185 }
186 result
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_default_message() {
195 assert_eq!(MessageKey::ConnectionFailed.default_msg(), "连接失败");
196 assert_eq!(MessageKey::QueryError.default_msg(), "查询错误");
197 }
198
199 #[test]
200 fn test_translate_default() {
201 clear();
202 let msg = translate(MessageKey::ConnectionFailed, &[]);
203 assert_eq!(msg, "连接失败");
204 }
205
206 #[test]
207 fn test_translate_with_catalog() {
208 clear();
209 let mut catalog = MessageCatalog::new();
210 catalog.insert(
211 MessageKey::ConnectionFailed,
212 "Connection failed: {0}".to_string(),
213 );
214 set_catalog(catalog);
215 let msg = translate(MessageKey::ConnectionFailed, &["timeout"]);
216 assert_eq!(msg, "Connection failed: timeout");
217 clear();
218 }
219
220 #[test]
221 fn test_register_single() {
222 clear();
223 register(MessageKey::NotFound, "Not found");
224 let msg = translate(MessageKey::NotFound, &[]);
225 assert_eq!(msg, "Not found");
226 clear();
227 }
228
229 #[test]
230 fn test_format_args_out_of_bounds() {
231 let result = format_args("Hello {0} {1}", &["world"]);
232 assert_eq!(result, "Hello world {1}");
233 }
234
235 #[test]
236 fn test_format_args_no_placeholders() {
237 let result = format_args("Hello world", &[]);
238 assert_eq!(result, "Hello world");
239 }
240
241 #[test]
242 fn test_format_args_invalid_index() {
243 let result = format_args("Hello {abc}", &[]);
244 assert_eq!(result, "Hello {abc}");
245 }
246
247 #[test]
248 fn test_clear() {
249 register(MessageKey::QueryError, "Query error");
250 assert_eq!(translate(MessageKey::QueryError, &[]), "Query error");
251 clear();
252 assert_eq!(translate(MessageKey::QueryError, &[]), "查询错误");
253 }
254}