1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use crate::{
assets::localisation::SubLocaleMap,
error::GlossaError,
fallback::FallbackChain,
log::{debug, trace},
map_loader::MapLoader,
LangID,
};
use std::{borrow::Cow, hash::BuildHasher, io};
pub trait GetText<S: BuildHasher>: FallbackChain {
fn get_map(&self) -> &super::Map<S>;
/// Gets a localised string for a given map name and key.
///
/// # Example
///
/// ```no_run
/// use crate::assets::localisation::locale_hashmap;
/// use glossa::{MapLoader, GetText};
///
/// let loader = MapLoader::new(locale_hashmap());
/// let err_msg = loader.get("test", "greetings");
/// ```
fn get<'map>(
&'map self,
map_name: &'map str,
key: &'map str,
) -> crate::Result<&str> {
// Define a fallback closure that will be used if the lookup fails in the primary map.
let fb = || self.get_from_fallback_chain(map_name, key);
trace!("Try to get the sub-map for the current language from the main map.");
match self
.get_map()
.get(self.get_id())
{
// If the sub-map exists, try to get the localised string from it.
Some(sub) => {
trace!(
"Found localised string for ('map_name: {}, key: {}')",
map_name,
key
);
Self::get_from_sub_map(sub, map_name, key).map_or_else(fb, Ok)
}
_ => {
trace!("Fall back to the fallback chain.");
fb()
}
}
}
/// Similar to `.get()` but returns `io::Result<String>`, not `glossa::Result<&str>`
fn get_owned(&self, map_name: &str, key: &str) -> io::Result<String> {
self.get(map_name, key)
.map(ToOwned::to_owned)
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, e.to_string()))
}
/// Similar to `.get_owned()` but returns `io::Result<Cow<str>>`, not `io::Result<String>`
fn get_cow<'a>(
&'a self,
map_name: &'a str,
key: &'a str,
) -> io::Result<Cow<str>> {
self.get(map_name, key)
.map(Cow::from)
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, e.to_string()))
}
/// Much like `get()`, but returns the error message as a default value when it fails.
/// # Example
///
/// ```no_run
/// use glossa::{MapLoader, GetText};
///
/// let loader = MapLoader::new(locale_hashmap());
/// let msg = loader.get_or_default("error", "text-not-found");
///
/// dbg!(msg);
/// ```
fn get_or_default<'map>(
&'map self,
map_name: &'map str,
key: &'map str,
) -> Cow<str> {
self.get(map_name, key)
.map(Cow::from)
// If the lookup fails, return the error message as a default value.
.unwrap_or_else(|e| {
debug!(
"Failed to find localised string for ('map: {map_name}, key: {key}')\n Err: {e}",
);
Cow::from(e.to_string())
})
}
/// Try to get a localised string from the fallback chain for a given map-name and key.
fn get_from_fallback_chain<'map>(
&'map self,
map_name: &'map str,
key: &'map str,
) -> crate::Result<&str> {
trace!("Iterate over the languages in the fallback chain until we find one that contains the localised string");
self.set_chain_once(None)
.iter()
// If we find the localised string in a language, return it.
.find_map(|lang| {
trace!(
"Found localised string for '{}:{}' in language {:?}",
map_name,
key,
lang
);
self.get_text(lang, map_name, key)
}) // Call `get_text` on each language until a lookup succeeds.
.ok_or_else(|| GlossaError::map_text_not_found(map_name, key))
}
/// Gets a localised string for a given language, map-name and key.
fn get_text(&self, lang: &LangID, map_name: &str, key: &str) -> Option<&str> {
self.get_map()
.get(lang)
.and_then(|m| {
trace!("Try to get the localised string from the sub-map for the given map-name.");
Self::get_from_sub_map(m, map_name, key)
})
}
/// Try to get a localised string from a sub-map for a given map-name and key. To be more precise, get the text from a separate sub-map.
fn get_from_sub_map<'a>(
sub_map: &SubLocaleMap,
map_name: &str,
key: &str,
) -> Option<&'a str> {
sub_map
.get(map_name)
.and_then(|f| f().get(key).copied())
}
}
impl<S: BuildHasher> GetText<S> for MapLoader<S> {
fn get_map(&self) -> &super::Map<S> {
&self.map
}
}
#[cfg(test)]
mod tests {
// use super::*;
use crate::{
assets::localisation::locale_hashmap, map_loader::MapLoader, GetText,
};
#[test]
fn get_error_msg() -> anyhow::Result<()> {
let loader = MapLoader::new(locale_hashmap());
// loader.show_chain();
// let s = loader.get_or_default("error", "not-found");
let s = loader.get_cow("error", "not-found")?;
println!("{s}");
Ok(())
}
#[test]
#[cfg(feature = "ahash")]
fn use_ahash_and_std_maps() {
// ahash
let map1 = ahash::HashMap::from_iter(locale_hashmap().into_iter());
// std hash:
let map2 =
std::collections::HashMap::from_iter(locale_hashmap().into_iter());
let mut ld1 = MapLoader::new(map1);
let str1 = ld1.get_or_default("error", "text-not-found 1");
println!("{str1}");
*ld1.get_map_mut() = map2;
let str2 = ld1.get_or_default("error", "text-not-found 2");
println!("{str2}");
}
}