Skip to content

Commit fca5c37

Browse files
Added system locale support (#1427)
* add system locale hint, query & event * update changelog * cleanup * fix multiple hint formatting, changelog * locale formatting optimization, additional formatting tests * Update changelog.md Co-authored-by: Antoni Spaanderman <[email protected]> * remove redundant `Result` return type from `format_locale_hint` --------- Co-authored-by: Antoni Spaanderman <[email protected]>
1 parent dc6f555 commit fca5c37

File tree

5 files changed

+216
-1
lines changed

5 files changed

+216
-1
lines changed

changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ when upgrading from a version of rust-sdl2 to another.
2727

2828
[PR #1407](https://github.com/Rust-SDL2/rust-sdl2/pull/1407) Add new use_ios_framework for linking to SDL2.framework on iOS
2929

30+
[PR #1427](https://github.com/Rust-SDL2/rust-sdl2/pull/1427) **BREAKING CHANGE** Add system locale support. A new event type `Event::LocaleChanged` has been added.
31+
3032
### v0.37.0
3133

3234
[PR #1406](https://github.com/Rust-SDL2/rust-sdl2/pull/1406) Update bindings to SDL 2.0.26, add Event.is\_touch() for mouse events, upgrade wgpu to 0.20 in examples

src/sdl2/event.rs

+32
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ pub enum EventType {
320320
RenderTargetsReset = SDL_EventType::SDL_RENDER_TARGETS_RESET as u32,
321321
RenderDeviceReset = SDL_EventType::SDL_RENDER_DEVICE_RESET as u32,
322322

323+
LocaleChanged = SDL_EventType::SDL_LOCALECHANGED as u32,
324+
323325
User = SDL_EventType::SDL_USEREVENT as u32,
324326
Last = SDL_EventType::SDL_LASTEVENT as u32,
325327
}
@@ -897,6 +899,10 @@ pub enum Event {
897899
timestamp: u32,
898900
},
899901

902+
LocaleChanged {
903+
timestamp: u32,
904+
},
905+
900906
User {
901907
timestamp: u32,
902908
window_id: u32,
@@ -1975,6 +1981,10 @@ impl Event {
19751981
timestamp: raw.common.timestamp,
19761982
},
19771983

1984+
EventType::LocaleChanged => Event::LocaleChanged {
1985+
timestamp: raw.common.timestamp,
1986+
},
1987+
19781988
EventType::First => panic!("Unused event, EventType::First, was encountered"),
19791989
EventType::Last => panic!("Unusable event, EventType::Last, was encountered"),
19801990

@@ -2180,6 +2190,7 @@ impl Event {
21802190
Self::AudioDeviceRemoved { timestamp, .. } => timestamp,
21812191
Self::RenderTargetsReset { timestamp, .. } => timestamp,
21822192
Self::RenderDeviceReset { timestamp, .. } => timestamp,
2193+
Self::LocaleChanged { timestamp, .. } => timestamp,
21832194
Self::User { timestamp, .. } => timestamp,
21842195
Self::Unknown { timestamp, .. } => timestamp,
21852196
}
@@ -2573,6 +2584,27 @@ impl Event {
25732584
)
25742585
}
25752586

2587+
/// Returns `true` if this is a locale event.
2588+
///
2589+
/// # Example
2590+
///
2591+
/// ```
2592+
/// use sdl2::event::Event;
2593+
///
2594+
/// let ev = Event::LocaleChanged {
2595+
/// timestamp: 0,
2596+
/// };
2597+
/// assert!(ev.is_locale());
2598+
///
2599+
/// let another_ev = Event::Quit {
2600+
/// timestamp: 0,
2601+
/// };
2602+
/// assert!(another_ev.is_locale() == false); // Not a locale event!
2603+
/// ```
2604+
pub fn is_locale(&self) -> bool {
2605+
matches!(self, Self::LocaleChanged { .. })
2606+
}
2607+
25762608
/// Returns `true` if this is a user event.
25772609
///
25782610
/// # Example

src/sdl2/hint.rs

+89-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use crate::sys;
1+
use crate::{locale::Locale, sys};
22
use libc::c_char;
33
use std::ffi::{CStr, CString};
44

55
const VIDEO_MINIMIZE_ON_FOCUS_LOSS: &str = "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS";
6+
const PREFERRED_LOCALES: &str = "SDL_PREFERRED_LOCALES";
67

78
pub enum Hint {
89
Default,
@@ -74,6 +75,44 @@ pub fn get_video_minimize_on_focus_loss() -> bool {
7475
)
7576
}
7677

78+
/// A hint that overrides the user's locale settings.
79+
///
80+
/// [Official SDL documentation](https://wiki.libsdl.org/SDL2/SDL_HINT_PREFERRED_LOCALES)
81+
///
82+
/// # Default
83+
/// This is disabled by default.
84+
///
85+
/// # Example
86+
///
87+
/// See [`crate::locale::get_preferred_locales`].
88+
pub fn set_preferred_locales<T: std::borrow::Borrow<Locale>>(
89+
locales: impl IntoIterator<Item = T>,
90+
) -> bool {
91+
set(PREFERRED_LOCALES, &format_locale_hint(locales))
92+
}
93+
94+
fn format_locale_hint<T: std::borrow::Borrow<Locale>>(
95+
locales: impl IntoIterator<Item = T>,
96+
) -> String {
97+
use std::fmt::Write;
98+
99+
let mut iter = locales.into_iter();
100+
let (reserve, _) = iter.size_hint();
101+
// Assuming that most locales will be of the form "xx_yy",
102+
// plus 1 char for the comma.
103+
let mut formatted = String::with_capacity(reserve * 6);
104+
105+
if let Some(first) = iter.next() {
106+
write!(formatted, "{}", first.borrow()).ok();
107+
}
108+
109+
for locale in iter {
110+
write!(formatted, ",{}", locale.borrow()).ok();
111+
}
112+
113+
formatted
114+
}
115+
77116
#[doc(alias = "SDL_SetHint")]
78117
pub fn set(name: &str, value: &str) -> bool {
79118
let name = CString::new(name).unwrap();
@@ -126,3 +165,52 @@ pub fn set_with_priority(name: &str, value: &str, priority: &Hint) -> bool {
126165
) == sys::SDL_bool::SDL_TRUE
127166
}
128167
}
168+
169+
#[cfg(test)]
170+
mod test {
171+
use super::*;
172+
173+
#[test]
174+
fn locale() {
175+
// Test set_preferred_locales
176+
let locales = [Locale {
177+
lang: "en".to_string(),
178+
country: Some("US".to_string()),
179+
}];
180+
set_preferred_locales(&locales);
181+
set_preferred_locales(locales);
182+
183+
// Test hint formatting
184+
assert_eq!(format_locale_hint(&[]), "");
185+
186+
assert_eq!(
187+
format_locale_hint([Locale {
188+
lang: "en".to_string(),
189+
country: None,
190+
}]),
191+
"en"
192+
);
193+
194+
assert_eq!(
195+
format_locale_hint([Locale {
196+
lang: "en".to_string(),
197+
country: Some("US".to_string()),
198+
}]),
199+
"en_US"
200+
);
201+
202+
assert_eq!(
203+
format_locale_hint([
204+
Locale {
205+
lang: "en".to_string(),
206+
country: Some("US".to_string()),
207+
},
208+
Locale {
209+
lang: "fr".to_string(),
210+
country: Some("FR".to_string()),
211+
},
212+
]),
213+
"en_US,fr_FR"
214+
);
215+
}
216+
}

src/sdl2/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ pub mod haptic;
7474
pub mod hint;
7575
pub mod joystick;
7676
pub mod keyboard;
77+
pub mod locale;
7778
pub mod log;
7879
pub mod messagebox;
7980
pub mod mouse;

src/sdl2/locale.rs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//! System locale information.
2+
3+
/// A locale defines a user's language and (optionally) region.
4+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5+
pub struct Locale {
6+
pub lang: String,
7+
pub country: Option<String>,
8+
}
9+
10+
impl std::fmt::Display for Locale {
11+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12+
write!(f, "{}", self.lang)?;
13+
14+
if let Some(region) = &self.country {
15+
write!(f, "_{}", region)?;
16+
}
17+
18+
Ok(())
19+
}
20+
}
21+
22+
/// Get the user's preferred locales.
23+
///
24+
/// [Official SDL documentation](https://wiki.libsdl.org/SDL_GetPreferredLocales)
25+
///
26+
/// # Example
27+
/// ```
28+
/// let locales = [sdl2::locale::Locale {
29+
/// lang: "en".to_string(),
30+
/// country: Some("US".to_string()),
31+
/// }];
32+
///
33+
/// sdl2::hint::set_preferred_locales(&locales);
34+
///
35+
/// let preferred_locales = sdl2::locale::get_preferred_locales().collect::<Vec<_>>();
36+
/// assert_eq!(preferred_locales, locales);
37+
/// ```
38+
pub fn get_preferred_locales() -> LocaleIterator {
39+
unsafe {
40+
LocaleIterator {
41+
raw: sys::SDL_GetPreferredLocales(),
42+
index: 0,
43+
}
44+
}
45+
}
46+
47+
pub struct LocaleIterator {
48+
raw: *mut sys::SDL_Locale,
49+
index: isize,
50+
}
51+
52+
impl Drop for LocaleIterator {
53+
fn drop(&mut self) {
54+
unsafe { sys::SDL_free(self.raw as *mut _) }
55+
}
56+
}
57+
58+
impl Iterator for LocaleIterator {
59+
type Item = Locale;
60+
61+
fn next(&mut self) -> Option<Self::Item> {
62+
let locale = unsafe { get_locale(self.raw.offset(self.index))? };
63+
self.index += 1;
64+
Some(locale)
65+
}
66+
}
67+
68+
unsafe fn get_locale(ptr: *const sys::SDL_Locale) -> Option<Locale> {
69+
let sdl_locale = ptr.as_ref()?;
70+
71+
if sdl_locale.language.is_null() {
72+
return None;
73+
}
74+
let lang = std::ffi::CStr::from_ptr(sdl_locale.language)
75+
.to_string_lossy()
76+
.into_owned();
77+
78+
let region = try_get_string(sdl_locale.country);
79+
80+
Some(Locale {
81+
lang,
82+
country: region,
83+
})
84+
}
85+
86+
unsafe fn try_get_string(ptr: *const i8) -> Option<String> {
87+
if ptr.is_null() {
88+
None
89+
} else {
90+
Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
91+
}
92+
}

0 commit comments

Comments
 (0)