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
use super::*;
/// Implements [`HookContextI18nExt`] for [`HookContext`].
impl HookContextI18nExt for HookContext {
/// Returns a fresh [`I18n`] bound to the current component scope.
///
/// # Returns
///
/// - `I18n` - A `I18n` value.
fn i18n() -> I18n {
HookContext::use_hook(|| {
I18n::new(
Signal::create(String::from("en")),
Signal::create(String::from("en")),
Signal::create(HashMap::new()),
)
})
}
}
/// Inherent implementation of [`I18n`].
impl I18n {
/// Sets the active locale to `locale`. Triggers a
/// reactive update so any reactive `t(key)` read
/// re-evaluates.
///
/// Named `change_locale` (not `set_locale`) to avoid
/// colliding with the `set_locale` getter generated by
/// `#[derive(Data)]` on the struct field.
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
pub fn change_locale(&self, locale: &str) {
self.get_locale().set(locale.to_string());
}
/// Sets the fallback locale. Trigger a reactive
/// update for any `t(key)` whose key is missing in
/// the active locale — they may now resolve to a
/// different fallback value.
///
/// Named `change_fallback_locale` (not
/// `set_fallback_locale`) for the same reason as
/// `change_locale`.
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
pub fn change_fallback_locale(&self, locale: &str) {
self.get_fallback_locale().set(locale.to_string());
}
/// Adds a batch of `(key, message)` entries to the
/// translation table for `locale`. Existing entries
/// for that locale are overwritten (last-write-wins).
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
/// - `&[MessageEntry]` - Shared reference to a `[MessageEntry]`.
pub fn add_messages(&self, locale: &str, entries: &[MessageEntry]) {
let mut table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
let entry_map: &mut HashMap<String, String> = table.entry(locale.to_string()).or_default();
for (key, value) in entries {
entry_map.insert((*key).to_string(), (*value).to_string());
}
self.get_messages().set(table);
}
/// Removes every entry for `locale`. After this
/// call, `t(key)` for any key will skip this locale
/// in its lookup chain.
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
pub fn remove_locale(&self, locale: &str) {
let mut table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
table.remove(locale);
self.get_messages().set(table);
}
/// Removes a single message from a locale. After this
/// call, `t(key)` for this key in this locale will
/// fall back to `fallback_locale`.
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
/// - `&str` - Shared reference to a `str`.
pub fn remove_message(&self, locale: &str, key: &str) {
let mut table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
if let Some(entry_map) = table.get_mut(locale) {
entry_map.remove(key);
}
self.get_messages().set(table);
}
/// Translates `key` to a string under the active
/// locale, falling back to `fallback_locale` if the
/// active locale has no entry. Returns `key` itself
/// if neither locale has an entry (debug-friendly).
///
/// This is the reactive read — calling it inside a
/// render closure subscribes that closure to locale
/// changes (and to any subsequent edits to the
/// messages map for the active or fallback locale).
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
///
/// # Returns
///
/// - `String` - A `String` value.
pub fn t(&self, key: &str) -> String {
let active: String = self.get_locale().get();
let fallback: String = self.get_fallback_locale().get();
let table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
if let Some(message) = table
.get(&active)
.and_then(|m: &HashMap<String, String>| m.get(key))
{
return message.clone();
}
if let Some(message) = table
.get(&fallback)
.and_then(|m: &HashMap<String, String>| m.get(key))
{
return message.clone();
}
key.to_string()
}
/// Translates `key` and substitutes `{name}`-style
/// placeholders from `vars`.
///
/// Placeholders that are present in `vars` are
/// replaced with their corresponding value.
/// Placeholders that are missing from `vars` are left
/// as the literal `{name}` token — matching the
/// i18next default behavior. No escaping is
/// supported; add it when a real use case shows up.
///
/// # Arguments
///
/// - `&str` - Shared reference to a `str`.
/// - `&HashMap<&'static str, &'static str>` - Shared reference to a `HashMap<&'static str, &'static str>`.
///
/// # Returns
///
/// - `String` - A `String` value.
pub fn t_with(&self, key: &str, vars: &HashMap<&'static str, &'static str>) -> String {
let template: String = self.t(key);
interpolate(&template, vars)
}
/// Returns the number of locales currently registered
/// (i.e. the number of distinct keys in the messages
/// map's outer level).
///
/// # Returns
///
/// - `usize` - Count of registered locales.
pub fn locale_count(&self) -> usize {
self.get_messages().get().len()
}
/// Returns the number of messages registered for the
/// active locale.
///
/// # Returns
///
/// - `usize` - Count of currently-registered messages.
pub fn active_message_count(&self) -> usize {
let active: String = self.get_locale().get();
self.get_messages()
.get()
.get(&active)
.map(|m: &HashMap<String, String>| m.len())
.unwrap_or_default()
}
}