Skip to main content

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}