matrix_gui/i18n.rs
1//! Internationalization (i18n) module for matrix_gui.
2//!
3//! This module provides support for multiple languages in the GUI framework. It allows
4//! runtime language switching and provides macros for defining internationalized strings
5//! and toggle types.
6//!
7//! # Features
8//!
9//! - Runtime language selection using atomic operations
10//! - Support for multiple languages (extensible)
11//! - Macros for defining internationalized strings
12//! - Toggle types with language-aware string representations
13//!
14//! # Example
15//!
16//! ```rust
17//! use matrix_gui::prelude::*;
18//!
19//! // Define internationalized strings
20//! matrix_gui::i18n_string!(TIP_ON, "开", "ON");
21//! matrix_gui::i18n_string!(TIP_OFF, "关", "OFF");
22//!
23//! // Define a toggle type
24//! matrix_gui::i18n_toggle_type!(TipOnOff, TIP_ON, TIP_OFF);
25//!
26//! // Switch language
27//! Languages::switch_language();
28//! ```
29
30use core::{
31 ops::Deref,
32 sync::atomic::{AtomicU8, Ordering},
33};
34
35/// Current language selection stored atomically.
36///
37/// This static variable holds the index of the currently selected language.
38/// It uses atomic operations to ensure thread-safe access without locks.
39static SELECTION: AtomicU8 = AtomicU8::new(Languages::LocalLang as u8);
40
41/// Total number of supported languages.
42///
43/// This constant is computed at compile time using the `enum_iterator` crate
44/// to determine the cardinality of the `Languages` enum.
45const LANG_COUNT: usize = enum_iterator::cardinality::<Languages>();
46
47/// Supported languages in the GUI framework.
48///
49/// This enum defines the available languages for internationalization.
50/// New languages can be added by extending this enum.
51#[derive(Debug, Copy, Clone, PartialEq, enum_iterator::Sequence)]
52pub enum Languages {
53 /// Local language (default, typically Chinese)
54 LocalLang = 0,
55 /// English language
56 English = 1,
57}
58
59/// Conversion from `Languages` to a static string slice.
60///
61/// This implementation provides a human-readable name for each language.
62/// For `LocalLang`, it attempts to use the `LOCAL_LANG_STRING` environment variable
63/// at compile time, falling back to "中文" if not set.
64impl From<Languages> for &'static str {
65 fn from(lang: Languages) -> Self {
66 match lang {
67 Languages::LocalLang => option_env!("LOCAL_LANG_STRING").unwrap_or("中文"),
68 Languages::English => "English",
69 }
70 }
71}
72
73/// Conversion from a byte index to `Languages`.
74///
75/// This implementation allows converting a language index (as stored in the
76/// atomic `SELECTION` variable) back to a `Languages` enum variant.
77impl From<u8> for Languages {
78 fn from(lang: u8) -> Self {
79 match lang {
80 0 => Self::LocalLang,
81 _ => Self::English,
82 }
83 }
84}
85
86impl Languages {
87 /// Returns the next language in the sequence.
88 ///
89 /// This method cycles through the available languages, returning the next
90 /// language after the current one. It wraps around to the first language
91 /// when reaching the end.
92 ///
93 /// # Returns
94 ///
95 /// The next language in the sequence.
96 pub fn next(self) -> Self {
97 match self {
98 Self::LocalLang => Self::English,
99 Self::English => Self::LocalLang,
100 }
101 }
102
103 /// Sets the current language globally.
104 ///
105 /// This method updates the global language selection using atomic operations.
106 /// The change affects all subsequent string lookups throughout the application.
107 ///
108 /// # Arguments
109 ///
110 /// * `lang` - The language to set as current.
111 pub fn set_language(lang: Languages) {
112 SELECTION.store(lang as u8, Ordering::Relaxed);
113 }
114
115 /// Returns the currently selected language.
116 ///
117 /// This method retrieves the current language selection from the atomic
118 /// storage. The returned language determines which string variant is used
119 /// for all internationalized strings.
120 ///
121 /// # Returns
122 ///
123 /// The currently selected language.
124 pub fn get_language() -> Languages {
125 let lang_idx = SELECTION.load(Ordering::Relaxed) as usize;
126 if lang_idx == Languages::English as usize {
127 Languages::English
128 } else {
129 Languages::LocalLang
130 }
131 }
132
133 /// Switches to the next language in the sequence.
134 ///
135 /// This is a convenience method that combines `get_language()` and `next()`
136 /// to cycle through available languages. It updates the global language
137 /// selection to the next language in the sequence.
138 pub fn switch_language() {
139 Self::set_language(Self::get_language().next());
140 }
141}
142
143/// An internationalized string that supports multiple languages.
144///
145/// This struct stores string variants for all supported languages and provides
146/// automatic language selection based on the current global language setting.
147/// It dereferences to a `&str` for convenient usage.
148///
149/// # Example
150///
151/// ```rust
152/// use matrix_gui::prelude::*;
153///
154/// // Create an internationalized string
155/// let greeting = I18NString::new(["你好", "Hello"]);
156///
157/// // Use it like a string
158/// println!("{}", greeting.as_str());
159/// ```
160#[derive(Debug, Copy, Clone)]
161pub struct I18NString {
162 /// Array of string variants, one for each supported language.
163 /// The index corresponds to the `Languages` enum variant value.
164 strings: [&'static str; LANG_COUNT],
165}
166
167impl I18NString {
168 /// Creates a new internationalized string from an array of language-specific strings.
169 ///
170 /// The array must contain one string for each supported language, in the same order
171 /// as the `Languages` enum variants.
172 ///
173 /// # Arguments
174 ///
175 /// * `strings` - An array of static string slices, one for each language.
176 ///
177 /// # Returns
178 ///
179 /// A new `I18NString` instance.
180 ///
181 /// # Example
182 ///
183 /// ```rust
184 /// use matrix_gui::prelude::*;
185 ///
186 /// const GREETING: I18NString = I18NString::new(["你好", "Hello"]);
187 /// ```
188 pub const fn new(strings: [&'static str; LANG_COUNT]) -> Self {
189 Self { strings }
190 }
191
192 /// Returns the string for the currently selected language.
193 ///
194 /// This method looks up the appropriate string variant based on the global
195 /// language selection. If the current language index is out of bounds,
196 /// it falls back to the first language.
197 ///
198 /// # Returns
199 ///
200 /// A static string slice for the current language.
201 pub fn as_str(&self) -> &'static str {
202 let lang_idx = SELECTION.load(Ordering::Relaxed) as usize;
203 self.strings.get(lang_idx).unwrap_or(&self.strings[0])
204 }
205}
206
207/// Allows `I18NString` to be used like a string slice.
208///
209/// This implementation enables automatic dereferencing to `&str`, allowing
210/// `I18NString` instances to be used wherever a string slice is expected.
211impl Deref for I18NString {
212 type Target = str;
213
214 fn deref(&self) -> &Self::Target {
215 self.as_str()
216 }
217}
218
219/// Macro to define an internationalized string constant.
220///
221/// This macro creates a public constant of type `I18NString` that contains
222/// string variants for all supported languages. The strings are provided
223/// in the same order as the `Languages` enum variants.
224///
225/// # Syntax
226///
227/// ```ignore
228/// matrix_gui::i18n_string!(NAME, string_for_lang0, string_for_lang1);
229/// ```
230///
231/// # Arguments
232///
233/// * `NAME` - The identifier name for the constant
234/// * `string_for_langN` - The string for each language (must match LANG_COUNT)
235///
236/// # Examples
237///
238/// ```rust
239/// use matrix_gui::prelude::*;
240///
241/// // Define internationalized strings for UI elements
242/// matrix_gui::i18n_string!(TIP_ON, "开", "ON");
243/// matrix_gui::i18n_string!(TIP_OFF, "关", "OFF");
244/// matrix_gui::i18n_string!(MENU_SETTINGS, "设置", "Settings");
245///
246/// // Use the strings
247/// println!("{}", TIP_ON.as_str());
248/// ```
249///
250/// # Notes
251///
252/// - The number of string arguments must match the number of supported languages
253/// - The order of strings must correspond to the `Languages` enum variants
254/// - The generated constant is public and can be used across modules
255#[macro_export]
256macro_rules! i18n_string {
257 ($name:ident, $($lang:expr),+) => {
258 pub const $name: I18NString = I18NString::new([$($lang),+]);
259 };
260}
261
262/// Macro to define a toggle type with internationalized string representations.
263///
264/// This macro creates a new struct that wraps a boolean value and provides
265/// language-aware string representations through dereferencing. The struct
266/// automatically selects the appropriate string based on the boolean value.
267///
268/// # Syntax
269///
270/// ```ignore
271/// matrix_gui::i18n_toggle_type!(TypeName, true_string, false_string);
272/// ```
273///
274/// # Arguments
275///
276/// * `TypeName` - The identifier name for the new struct
277/// * `true_string` - The `I18NString` constant to use when the boolean is `true`
278/// * `false_string` - The `I18NString` constant to use when the boolean is `false`
279///
280/// # Examples
281///
282/// ```rust
283/// use matrix_gui::prelude::*;
284///
285/// // First define the internationalized strings
286/// matrix_gui::i18n_string!(TIP_ON, "开", "ON");
287/// matrix_gui::i18n_string!(TIP_OFF, "关", "OFF");
288///
289/// // Define a toggle type
290/// matrix_gui::i18n_toggle_type!(TipOnOff, TIP_ON, TIP_OFF);
291///
292/// // Use the toggle type
293/// let toggle: &str = &TipOnOff(true);
294/// println!("{}", toggle); // Prints "开" or "ON" based on current language
295/// ```
296///
297/// # Generated Code
298///
299/// The macro generates a struct with the following structure:
300///
301/// ```ignore
302/// pub struct TypeName(pub bool);
303///
304/// impl core::ops::Deref for TypeName {
305/// type Target = str;
306/// fn deref(&self) -> &Self::Target {
307/// match self.0 {
308/// true => &true_string,
309/// false => &false_string,
310/// }
311/// }
312/// }
313/// ```
314///
315/// # Notes
316///
317/// - The generated struct implements `Deref` to `str` for easy string access
318/// - The boolean value is stored in a tuple struct field
319/// - Language switching affects the string representation automatically
320#[macro_export]
321macro_rules! i18n_toggle_type {
322 ($type_name:ident, $true_str:expr, $false_str:expr) => {
323 pub struct $type_name(pub bool);
324
325 impl core::ops::Deref for $type_name {
326 type Target = str;
327 fn deref(&self) -> &Self::Target {
328 match self.0 {
329 true => &$true_str,
330 false => &$false_str,
331 }
332 }
333 }
334 };
335}