Skip to main content

native_windows_gui2/winnls/
locale.rs

1use super::*;
2use crate::NwgError;
3use crate::win32::base_helper::{from_utf16, to_utf16};
4use std::{mem, ptr};
5use winapi::um::winnls::{
6    GetLocaleInfoEx, GetSystemDefaultLocaleName, GetUserDefaultLocaleName, LCTYPE,
7};
8use winapi::um::winnt::{LOCALE_NAME_MAX_LENGTH, LPWSTR};
9
10/**
11Represent a Windows locale. Can be used to fetch a lot of information regarding the locale.
12
13Use `Locale::user` to fetch the current user local or `Local::system` to fetch the system default locale. Using the first one is recommended.
14
15```rust
16use native_windows_gui2 as nwg;
17
18let fr_locale = nwg::Locale::from_str("fr");
19let en_us_locale = nwg::Locale::from_str("en-US");
20let user_locale = nwg::Locale::user();
21let locales: Vec<String> = nwg::Locale::all();
22
23user_locale.display_name();
24```
25*/
26#[derive(Clone)]
27pub struct Locale {
28    name: String,
29    name_buffer: Vec<u16>,
30}
31
32impl Locale {
33    /// Create a new local from a locale name. If you have a str, use `from_str` instead.
34    pub fn new(name: String) -> Result<Locale, NwgError> {
35        let name_buffer = to_utf16(&name);
36        match Locale::locale_valid(&name_buffer) {
37            true => Ok(Locale { name, name_buffer }),
38            false => Err(NwgError::bad_locale("Locale name is not valid")),
39        }
40    }
41
42    /// Create a new local from a locale name. If you have a String, use `new` instead.
43    pub fn from_str<'a>(name: &'a str) -> Result<Locale, NwgError> {
44        let name_buffer = to_utf16(name);
45        match Locale::locale_valid(&name_buffer) {
46            true => Ok(Locale {
47                name: name.to_string(),
48                name_buffer,
49            }),
50            false => Err(NwgError::bad_locale("Locale name is not valid")),
51        }
52    }
53
54    /// Return the identifier (ex: en-US) of every supported locales.
55    pub fn all() -> Vec<String> {
56        use crate::win32::base_helper::from_wide_ptr;
57        use winapi::shared::minwindef::{BOOL, DWORD, LPARAM};
58        use winapi::um::winnls::EnumSystemLocalesEx;
59
60        extern "system" fn enum_locales(locale: LPWSTR, _flags: DWORD, p: LPARAM) -> BOOL {
61            let locales: *mut Vec<String> = p as *mut Vec<String>;
62            unsafe { (&mut *locales).push(from_wide_ptr(locale, None)) };
63            1
64        }
65
66        unsafe {
67            let mut locales: Vec<String> = Vec::with_capacity(10);
68            EnumSystemLocalesEx(
69                Some(enum_locales),
70                1,
71                &mut locales as *mut Vec<String> as LPARAM,
72                ptr::null_mut(),
73            );
74            locales
75        }
76    }
77
78    /// Return the current user locale
79    pub fn user() -> Locale {
80        let mut name_buffer: Vec<u16> = Vec::with_capacity(LOCALE_NAME_MAX_LENGTH);
81        unsafe {
82            name_buffer.set_len(LOCALE_NAME_MAX_LENGTH);
83            GetUserDefaultLocaleName(name_buffer.as_mut_ptr(), LOCALE_NAME_MAX_LENGTH as i32);
84        }
85
86        Locale {
87            name: from_utf16(&name_buffer),
88            name_buffer,
89        }
90    }
91
92    /// Return the current system locale
93    pub fn system() -> Locale {
94        let mut name_buffer: Vec<u16> = Vec::with_capacity(LOCALE_NAME_MAX_LENGTH);
95        unsafe {
96            name_buffer.set_len(LOCALE_NAME_MAX_LENGTH);
97            GetSystemDefaultLocaleName(name_buffer.as_mut_ptr(), LOCALE_NAME_MAX_LENGTH as i32);
98        }
99
100        Locale {
101            name: from_utf16(&name_buffer),
102            name_buffer,
103        }
104    }
105
106    /// Return the name of the locale.
107    pub fn name(&self) -> &str {
108        &self.name
109    }
110
111    /// Localized name of locale, eg "German (Germany)" in UI language
112    pub fn display_name(&self) -> String {
113        self.get_locale_info_string(0x00000002)
114    }
115
116    /// Display name (language + country/region usually) in English, eg "German (Germany)"
117    pub fn english_display_name(&self) -> String {
118        self.get_locale_info_string(0x00000072)
119    }
120
121    /// Display name in native locale language, eg "Deutsch (Deutschland)
122    pub fn native_display_name(&self) -> String {
123        self.get_locale_info_string(0x00000072)
124    }
125
126    /// Localized name of country/region, eg "Germany" in UI language
127    pub fn country_name(&self) -> String {
128        self.get_locale_info_string(0x00000006)
129    }
130
131    /// English name of country/region, eg "Germany"
132    pub fn english_country_name(&self) -> String {
133        self.get_locale_info_string(0x00001002)
134    }
135
136    /// Native name of country/region, eg "Deutschland"
137    pub fn native_country_name(&self) -> String {
138        self.get_locale_info_string(0x00000008)
139    }
140
141    /// country/region dialing code, example: en-US and en-CA return 1.
142    pub fn dialing_code(&self) -> i32 {
143        self.get_locale_info_int(0x00000005)
144    }
145
146    /// list item separator, eg "," for "1,2,3,4"
147    pub fn list_separator(&self) -> String {
148        self.get_locale_info_string(0x0000000C)
149    }
150
151    /// Returns the measurement system (metric or imperial)
152    pub fn measurement_system(&self) -> MeasurementSystem {
153        match self.get_locale_info_int(0x0000000D) {
154            0 => MeasurementSystem::Metric,
155            _ => MeasurementSystem::Imperial,
156        }
157    }
158
159    /// Returns the decimal separator, eg "." for 1,234.00
160    pub fn decimal_separator(&self) -> String {
161        self.get_locale_info_string(0x0000000E)
162    }
163
164    /// Returns the thousand separator, eg "," for 1,234.00
165    pub fn thousand_separator(&self) -> String {
166        self.get_locale_info_string(0x0000000F)
167    }
168
169    /// Returns the digit grouping, eg "3;0" for 1,000,000
170    pub fn digit_grouping(&self) -> String {
171        self.get_locale_info_string(0x00000010)
172    }
173
174    /// Returns the number of fractional digits eg 2 for 1.00
175    pub fn fractional_digit(&self) -> i32 {
176        self.get_locale_info_int(0x00000011)
177    }
178
179    /// Returns the number of leading zeros for decimal, 0 for .97, 1 for 0.97
180    pub fn leading_zeros(&self) -> i32 {
181        self.get_locale_info_int(0x00000012)
182    }
183
184    /// Returns the negative number mode. See the documentation of NegativeNumberMode
185    pub fn negative_number_mode(&self) -> NegativeNumberMode {
186        match self.get_locale_info_int(0x00001010) {
187            0 => NegativeNumberMode::Mode0,
188            1 => NegativeNumberMode::Mode1,
189            2 => NegativeNumberMode::Mode2,
190            3 => NegativeNumberMode::Mode3,
191            4 => NegativeNumberMode::Mode4,
192            _ => NegativeNumberMode::Mode1,
193        }
194    }
195
196    /// Returns native digits for 0-9, eg "0123456789"
197    pub fn native_digits(&self) -> String {
198        self.get_locale_info_string(0x00000013)
199    }
200
201    /// Returns the local monetary symbol, eg "$"
202    pub fn currency_symbol(&self) -> String {
203        self.get_locale_info_string(0x00000014)
204    }
205
206    /// Returns the intl monetary symbol, eg "USD"
207    pub fn intl_monetary_symbol(&self) -> String {
208        self.get_locale_info_string(0x00000015)
209    }
210
211    /// Returns the monetary decimal separator, eg "." for 1,234.00
212    pub fn monetary_decimal_separator(&self) -> String {
213        self.get_locale_info_string(0x00000016)
214    }
215
216    /// Returns the monetary thousand separator, eg "," for 1,234.00
217    pub fn monetary_thousand_separator(&self) -> String {
218        self.get_locale_info_string(0x00000017)
219    }
220
221    /// Returns the monetary digit grouping, eg "3;0" for 1,000,000
222    pub fn monetary_digit_grouping(&self) -> String {
223        self.get_locale_info_string(0x00000018)
224    }
225
226    /// Returns the number local monetary digits eg 2 for $1.00
227    pub fn monetary_fractional_digit(&self) -> i32 {
228        self.get_locale_info_int(0x00000019)
229    }
230
231    /// Returns the positive currency mode. See PositiveCurrency
232    pub fn currency_mode(&self) -> PositiveCurrency {
233        match self.get_locale_info_int(0x0000001B) {
234            0 => PositiveCurrency::Mode0,
235            1 => PositiveCurrency::Mode1,
236            2 => PositiveCurrency::Mode2,
237            3 => PositiveCurrency::Mode3,
238            _ => PositiveCurrency::Mode1,
239        }
240    }
241
242    /// Returns the negative positive currency mode. See NegativeCurrency
243    pub fn negative_currency_mode(&self) -> NegativeCurrency {
244        let id = self.get_locale_info_int(0x00001009) as u32;
245        match id <= 15 {
246            true => unsafe { mem::transmute(id) },
247            false => NegativeCurrency::Mode1,
248        }
249    }
250
251    /// Returns the short date format string, eg "MM/dd/yyyy"
252    pub fn short_date(&self) -> String {
253        self.get_locale_info_string(0x0000001F)
254    }
255
256    /// Returns the long date format string, eg "dddd, MMMM dd, yyyy"
257    pub fn long_date(&self) -> String {
258        self.get_locale_info_string(0x00000020)
259    }
260
261    /// Returns the time format string, eg "HH:mm:ss"
262    pub fn time(&self) -> String {
263        self.get_locale_info_string(0x00001003)
264    }
265
266    /// Returns the AM designator, eg "AM"
267    pub fn am(&self) -> String {
268        self.get_locale_info_string(0x00000028)
269    }
270
271    /// Returns the PM designator, eg "PM"
272    pub fn pm(&self) -> String {
273        self.get_locale_info_string(0x00000029)
274    }
275
276    /// Returns the type of calendar. Ex: Calendar::Gregorian
277    pub fn calendar(&self) -> Calendar {
278        let id = self.get_locale_info_int(0x00001009) as u32;
279        match id <= 23 {
280            true => unsafe { mem::transmute(id) },
281            false => Calendar::Gregorian,
282        }
283    }
284
285    /// Returns the type of calendar with a bit more precision. Ex: Calendar::GregorianUs
286    pub fn calendar2(&self) -> Calendar {
287        let id = self.get_locale_info_int(0x0000100B) as u32;
288        match id <= 23 {
289            true => unsafe { mem::transmute(id) },
290            false => Calendar::Gregorian,
291        }
292    }
293
294    /// Returns the first day of week specifier, 0-6, 0=Monday, 6=Sunday
295    pub fn first_day_of_week(&self) -> i32 {
296        self.get_locale_info_int(0x0000100C)
297    }
298
299    /// Returns the first day of year specifier. See FirstDayOfYear
300    pub fn first_day_of_year(&self) -> FirstDayOfYear {
301        match self.get_locale_info_int(0x0000100D) {
302            0 => FirstDayOfYear::Mode0,
303            1 => FirstDayOfYear::Mode1,
304            2 => FirstDayOfYear::Mode2,
305            _ => FirstDayOfYear::Mode0,
306        }
307    }
308
309    /// ISO abbreviated language name, eg "en"
310    pub fn iso_lang_name(&self) -> String {
311        self.get_locale_info_string(0x00000059)
312    }
313
314    /// ISO abbreviated country/region name, eg "US"
315    pub fn iso_country_name(&self) -> String {
316        self.get_locale_info_string(0x0000005A)
317    }
318
319    /// Returns the english name of currency, eg "Euro"
320    pub fn currency_name(&self) -> String {
321        self.get_locale_info_string(0x00001007)
322    }
323
324    /// Returns the native name of currency, eg "euro"
325    pub fn native_currency_name(&self) -> String {
326        self.get_locale_info_string(0x00001008)
327    }
328
329    /**
330        Return the localized month name. See `month_name_abv` for the abbreviated version
331
332        Parameters:
333            month_index: The month index. 1(January) to 12 (December) or 13 (if it exists).
334
335        Panics:
336            This function will panic if month index in not in the 1-13 range.
337    */
338    pub fn month_name(&self, month_index: u32) -> String {
339        match month_index {
340            1 => self.get_locale_info_string(0x00000038),
341            2 => self.get_locale_info_string(0x00000039),
342            3 => self.get_locale_info_string(0x0000003A),
343            4 => self.get_locale_info_string(0x0000003B),
344            5 => self.get_locale_info_string(0x0000003C),
345            6 => self.get_locale_info_string(0x0000003D),
346            7 => self.get_locale_info_string(0x0000003E),
347            8 => self.get_locale_info_string(0x0000003F),
348            9 => self.get_locale_info_string(0x00000040),
349            10 => self.get_locale_info_string(0x00000041),
350            11 => self.get_locale_info_string(0x00000042),
351            12 => self.get_locale_info_string(0x00000043),
352            13 => self.get_locale_info_string(0x0000100E),
353            x => panic!("{} is not a valid month index", x),
354        }
355    }
356
357    /**
358        Return the localized month name in an abbreviated version. See `month_name` for the full version
359
360        Parameters:
361            month_index: The month index. 1(January) to 12 (December) or 13 (if it exists).
362
363        Panics:
364            This function will panic if month index in not in the 1-13 range.
365    */
366    pub fn month_name_abv(&self, month_index: u32) -> String {
367        match month_index {
368            1 => self.get_locale_info_string(0x00000044),
369            2 => self.get_locale_info_string(0x00000045),
370            3 => self.get_locale_info_string(0x00000046),
371            4 => self.get_locale_info_string(0x00000047),
372            5 => self.get_locale_info_string(0x00000048),
373            6 => self.get_locale_info_string(0x00000049),
374            7 => self.get_locale_info_string(0x0000004A),
375            8 => self.get_locale_info_string(0x0000004B),
376            9 => self.get_locale_info_string(0x0000004C),
377            10 => self.get_locale_info_string(0x0000004D),
378            11 => self.get_locale_info_string(0x0000004E),
379            12 => self.get_locale_info_string(0x0000004F),
380            13 => self.get_locale_info_string(0x0000100F),
381            x => panic!("{} is not a valid month index", x),
382        }
383    }
384
385    /**
386        Return the localized day name. See `day_name_abv` for the abbreviated version
387
388        Parameters:
389            day_index: The month index. 1(Monday) to 7 (Sunday)
390
391        Panics:
392            This function will panic if month index in not in the 1-7 range.
393    */
394    pub fn day_name(&self, day_index: u32) -> String {
395        match day_index {
396            1 => self.get_locale_info_string(0x0000002A),
397            2 => self.get_locale_info_string(0x0000002B),
398            3 => self.get_locale_info_string(0x0000002C),
399            4 => self.get_locale_info_string(0x0000002D),
400            5 => self.get_locale_info_string(0x0000002E),
401            6 => self.get_locale_info_string(0x0000002F),
402            7 => self.get_locale_info_string(0x00000030),
403            x => panic!("{} is not a valid day index", x),
404        }
405    }
406
407    /**
408        Return the localized day name in an abbreviated version. See `day_name` for the full version
409
410        Parameters:
411            day_index: The month index. 1(Monday) to 7 (Sunday)
412
413        Panics:
414            This function will panic if month index in not in the 1-7 range.
415    */
416    pub fn day_name_abv(&self, day_index: u32) -> String {
417        match day_index {
418            1 => self.get_locale_info_string(0x00000031),
419            2 => self.get_locale_info_string(0x00000032),
420            3 => self.get_locale_info_string(0x00000033),
421            4 => self.get_locale_info_string(0x00000034),
422            5 => self.get_locale_info_string(0x00000035),
423            6 => self.get_locale_info_string(0x00000036),
424            7 => self.get_locale_info_string(0x00000037),
425            x => panic!("{} is not a valid day index", x),
426        }
427    }
428
429    fn get_locale_info_string(&self, info: LCTYPE) -> String {
430        unsafe {
431            let buffer_size =
432                GetLocaleInfoEx(self.name_buffer.as_ptr(), info, ptr::null_mut(), 0) as usize;
433            let mut buffer: Vec<u16> = Vec::with_capacity(buffer_size);
434            buffer.set_len(buffer_size);
435
436            GetLocaleInfoEx(
437                self.name_buffer.as_ptr(),
438                info,
439                buffer.as_mut_ptr(),
440                buffer_size as i32,
441            );
442
443            from_utf16(&buffer)
444        }
445    }
446
447    fn get_locale_info_int(&self, info: LCTYPE) -> i32 {
448        let mut out = 0i32;
449        let return_number = 0x20000000;
450
451        unsafe {
452            let out_ptr = &mut out as *mut i32 as *mut u16;
453            GetLocaleInfoEx(
454                self.name_buffer.as_ptr(),
455                info | return_number,
456                out_ptr,
457                mem::size_of::<i32>() as i32,
458            );
459        }
460
461        out
462    }
463
464    fn locale_valid(buffer: &[u16]) -> bool {
465        use winapi::um::winnls::IsValidLocaleName;
466        unsafe { IsValidLocaleName(buffer.as_ptr()) != 0 }
467    }
468}
469
470use std::fmt;
471
472impl fmt::Debug for Locale {
473    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474        write!(f, "Locale {{ name: {}, }}", self.name)
475    }
476}
477
478impl fmt::Display for Locale {
479    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480        write!(f, "{}", self.name)
481    }
482}