euv_ui/hook/i18n/impl.rs
1use super::*;
2
3/// Implements [`HookContextI18nExt`] for [`HookContext`].
4impl HookContextI18nExt for HookContext {
5 /// Returns a fresh [`I18n`] bound to the current component scope.
6 ///
7 /// # Returns
8 ///
9 /// - `I18n` - A `I18n` value.
10 fn i18n() -> I18n {
11 HookContext::use_hook(|| {
12 I18n::new(
13 Signal::create(String::from("en")),
14 Signal::create(String::from("en")),
15 Signal::create(HashMap::new()),
16 )
17 })
18 }
19}
20
21/// Inherent implementation of [`I18n`].
22impl I18n {
23 /// Sets the active locale to `locale`. Triggers a
24 /// reactive update so any reactive `t(key)` read
25 /// re-evaluates.
26 ///
27 /// Named `change_locale` (not `set_locale`) to avoid
28 /// colliding with the `set_locale` getter generated by
29 /// `#[derive(Data)]` on the struct field.
30 ///
31 /// # Arguments
32 ///
33 /// - `&str` - Shared reference to a `str`.
34 pub fn change_locale(&self, locale: &str) {
35 self.get_locale().set(locale.to_string());
36 }
37
38 /// Sets the fallback locale. Trigger a reactive
39 /// update for any `t(key)` whose key is missing in
40 /// the active locale — they may now resolve to a
41 /// different fallback value.
42 ///
43 /// Named `change_fallback_locale` (not
44 /// `set_fallback_locale`) for the same reason as
45 /// `change_locale`.
46 ///
47 /// # Arguments
48 ///
49 /// - `&str` - Shared reference to a `str`.
50 pub fn change_fallback_locale(&self, locale: &str) {
51 self.get_fallback_locale().set(locale.to_string());
52 }
53
54 /// Adds a batch of `(key, message)` entries to the
55 /// translation table for `locale`. Existing entries
56 /// for that locale are overwritten (last-write-wins).
57 ///
58 /// # Arguments
59 ///
60 /// - `&str` - Shared reference to a `str`.
61 /// - `&[MessageEntry]` - Shared reference to a `[MessageEntry]`.
62 pub fn add_messages(&self, locale: &str, entries: &[MessageEntry]) {
63 let mut table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
64 let entry_map: &mut HashMap<String, String> = table.entry(locale.to_string()).or_default();
65 for (key, value) in entries {
66 entry_map.insert((*key).to_string(), (*value).to_string());
67 }
68 self.get_messages().set(table);
69 }
70
71 /// Removes every entry for `locale`. After this
72 /// call, `t(key)` for any key will skip this locale
73 /// in its lookup chain.
74 ///
75 /// # Arguments
76 ///
77 /// - `&str` - Shared reference to a `str`.
78 pub fn remove_locale(&self, locale: &str) {
79 let mut table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
80 table.remove(locale);
81 self.get_messages().set(table);
82 }
83
84 /// Removes a single message from a locale. After this
85 /// call, `t(key)` for this key in this locale will
86 /// fall back to `fallback_locale`.
87 ///
88 /// # Arguments
89 ///
90 /// - `&str` - Shared reference to a `str`.
91 /// - `&str` - Shared reference to a `str`.
92 pub fn remove_message(&self, locale: &str, key: &str) {
93 let mut table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
94 if let Some(entry_map) = table.get_mut(locale) {
95 entry_map.remove(key);
96 }
97 self.get_messages().set(table);
98 }
99
100 /// Translates `key` to a string under the active
101 /// locale, falling back to `fallback_locale` if the
102 /// active locale has no entry. Returns `key` itself
103 /// if neither locale has an entry (debug-friendly).
104 ///
105 /// This is the reactive read — calling it inside a
106 /// render closure subscribes that closure to locale
107 /// changes (and to any subsequent edits to the
108 /// messages map for the active or fallback locale).
109 ///
110 /// # Arguments
111 ///
112 /// - `&str` - Shared reference to a `str`.
113 ///
114 /// # Returns
115 ///
116 /// - `String` - A `String` value.
117 pub fn t(&self, key: &str) -> String {
118 let active: String = self.get_locale().get();
119 let fallback: String = self.get_fallback_locale().get();
120 let table: HashMap<String, HashMap<String, String>> = self.get_messages().get();
121 if let Some(message) = table
122 .get(&active)
123 .and_then(|m: &HashMap<String, String>| m.get(key))
124 {
125 return message.clone();
126 }
127 if let Some(message) = table
128 .get(&fallback)
129 .and_then(|m: &HashMap<String, String>| m.get(key))
130 {
131 return message.clone();
132 }
133 key.to_string()
134 }
135
136 /// Translates `key` and substitutes `{name}`-style
137 /// placeholders from `vars`.
138 ///
139 /// Placeholders that are present in `vars` are
140 /// replaced with their corresponding value.
141 /// Placeholders that are missing from `vars` are left
142 /// as the literal `{name}` token — matching the
143 /// i18next default behavior. No escaping is
144 /// supported; add it when a real use case shows up.
145 ///
146 /// # Arguments
147 ///
148 /// - `&str` - Shared reference to a `str`.
149 /// - `&HashMap<&'static str, &'static str>` - Shared reference to a `HashMap<&'static str, &'static str>`.
150 ///
151 /// # Returns
152 ///
153 /// - `String` - A `String` value.
154 pub fn t_with(&self, key: &str, vars: &HashMap<&'static str, &'static str>) -> String {
155 let template: String = self.t(key);
156 interpolate(&template, vars)
157 }
158
159 /// Returns the number of locales currently registered
160 /// (i.e. the number of distinct keys in the messages
161 /// map's outer level).
162 ///
163 /// # Returns
164 ///
165 /// - `usize` - Count of registered locales.
166 pub fn locale_count(&self) -> usize {
167 self.get_messages().get().len()
168 }
169
170 /// Returns the number of messages registered for the
171 /// active locale.
172 ///
173 /// # Returns
174 ///
175 /// - `usize` - Count of currently-registered messages.
176 pub fn active_message_count(&self) -> usize {
177 let active: String = self.get_locale().get();
178 self.get_messages()
179 .get()
180 .get(&active)
181 .map(|m: &HashMap<String, String>| m.len())
182 .unwrap_or_default()
183 }
184}