rust_i18n_support/
backend.rs1use std::collections::HashMap;
2
3pub trait Backend: Send + Sync + 'static {
5 fn available_locales(&self) -> Vec<&str>;
7 fn translate(&self, locale: &str, key: &str) -> Option<&str>;
9}
10
11pub trait BackendExt: Backend {
12 fn extend<T: Backend>(self, other: T) -> CombinedBackend<Self, T>
14 where
15 Self: Sized,
16 {
17 CombinedBackend(self, other)
18 }
19}
20
21pub struct CombinedBackend<A, B>(A, B);
22
23impl<A, B> Backend for CombinedBackend<A, B>
24where
25 A: Backend,
26 B: Backend,
27{
28 fn available_locales(&self) -> Vec<&str> {
29 let mut available_locales = self.0.available_locales();
30 for locale in self.1.available_locales() {
31 if !available_locales.contains(&locale) {
32 available_locales.push(locale);
33 }
34 }
35 available_locales
36 }
37
38 #[inline]
39 fn translate(&self, locale: &str, key: &str) -> Option<&str> {
40 self.1
41 .translate(locale, key)
42 .or_else(|| self.0.translate(locale, key))
43 }
44}
45
46pub struct SimpleBackend {
48 translations: HashMap<String, HashMap<String, String>>,
50}
51
52impl SimpleBackend {
53 pub fn new() -> Self {
55 SimpleBackend {
56 translations: HashMap::new(),
57 }
58 }
59
60 pub fn add_translations(&mut self, locale: &str, data: &HashMap<&str, &str>) {
72 let data = data
73 .iter()
74 .map(|(k, v)| ((*k).into(), (*v).into()))
75 .collect::<HashMap<_, _>>();
76
77 let trs = self.translations.entry(locale.into()).or_default();
78 trs.extend(data);
79 }
80}
81
82impl Backend for SimpleBackend {
83 fn available_locales(&self) -> Vec<&str> {
84 let mut locales = self
85 .translations
86 .keys()
87 .map(|k| k.as_str())
88 .collect::<Vec<_>>();
89 locales.sort();
90 locales
91 }
92
93 fn translate(&self, locale: &str, key: &str) -> Option<&str> {
94 if let Some(trs) = self.translations.get(locale) {
95 return trs.get(key).map(|s| s.as_str());
96 }
97
98 None
99 }
100}
101
102impl BackendExt for SimpleBackend {}
103
104impl Default for SimpleBackend {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use std::collections::HashMap;
113
114 use super::SimpleBackend;
115 use super::{Backend, BackendExt};
116
117 #[test]
118 fn test_simple_backend() {
119 let mut backend = SimpleBackend::new();
120 let mut data = HashMap::<&str, &str>::new();
121 data.insert("hello", "Hello");
122 data.insert("foo", "Foo bar");
123 backend.add_translations("en", &data);
124
125 let mut data_cn = HashMap::<&str, &str>::new();
126 data_cn.insert("hello", "你好");
127 data_cn.insert("foo", "Foo 测试");
128 backend.add_translations("zh-CN", &data_cn);
129
130 assert_eq!(backend.translate("en", "hello"), Some("Hello"));
131 assert_eq!(backend.translate("en", "foo"), Some("Foo bar"));
132 assert_eq!(backend.translate("zh-CN", "hello"), Some("你好"));
133 assert_eq!(backend.translate("zh-CN", "foo"), Some("Foo 测试"));
134
135 assert_eq!(backend.available_locales(), vec!["en", "zh-CN"]);
136 }
137
138 #[test]
139 fn test_combined_backend() {
140 let mut backend = SimpleBackend::new();
141 let mut data = HashMap::<&str, &str>::new();
142 data.insert("hello", "Hello");
143 data.insert("foo", "Foo bar");
144 backend.add_translations("en", &data);
145
146 let mut data_cn = HashMap::<&str, &str>::new();
147 data_cn.insert("hello", "你好");
148 data_cn.insert("foo", "Foo 测试");
149 backend.add_translations("zh-CN", &data_cn);
150
151 let mut backend2 = SimpleBackend::new();
152 let mut data2 = HashMap::<&str, &str>::new();
153 data2.insert("hello", "Hello2");
154 backend2.add_translations("en", &data2);
155
156 let mut data_cn2 = HashMap::<&str, &str>::new();
157 data_cn2.insert("hello", "你好2");
158 backend2.add_translations("zh-CN", &data_cn2);
159
160 let combined = backend.extend(backend2);
161 assert_eq!(combined.translate("en", "hello"), Some("Hello2"));
162 assert_eq!(combined.translate("zh-CN", "hello"), Some("你好2"));
163
164 assert_eq!(combined.available_locales(), vec!["en", "zh-CN"]);
165 }
166}