Skip to content

Commit 9777c6c

Browse files
Parse system locales in env_preferences (#6158)
As per the discussion in #6028, I have created a POSIX locale parser/converter, currently hidden in the private `env_preferences::parse` module. This is meant to change at some point while the PR is being drafted, especially once I add support for other platforms. Once more platforms are supported, I would also like to implement universal and platform-specific APIs, as per [this comment](#6028 (comment)) by @zbraniecki. My current thinking on code structure is to have some distinction between platform `fetch` and `parse` code (either using modules or files), but please let me know if all platform logic should just be kept in the same file. Of course, feedback on the code itself would be very much appreciated! --------- Co-authored-by: Robert Bastian <[email protected]>
1 parent 211db9b commit 9777c6c

File tree

20 files changed

+2593
-73
lines changed

20 files changed

+2593
-73
lines changed

Cargo.lock

Lines changed: 64 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

utils/env_preferences/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ include.workspace = true
1818

1919
[dependencies]
2020
core-foundation-sys = "0.8.6"
21+
displaydoc = { workspace = true }
22+
icu_locale_core = { workspace = true, features = ["alloc"] }
2123
libc = "0.2.155"
2224

2325
[dependencies.windows]
24-
version = "0.56.0"
26+
version = "0.60.0"
2527
features = [
2628
"System",
2729
"Foundation",
@@ -30,8 +32,8 @@ features = [
3032
"Globalization",
3133
"Globalization_DateTimeFormatting",
3234
"Win32",
33-
"Win32_Globalization"
35+
"Win32_Globalization",
3436
]
3537

3638
[dev-dependencies]
37-
icu_locale = { path = "../../components/locale" }
39+
windows-core = "0.60.1"

utils/env_preferences/README.md

Lines changed: 3 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

utils/env_preferences/src/apple.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ fn get_string(ptr: CFStringRef) -> Result<String, RetrievalError> {
6161

6262
/// Retrieves system locales for Apple operating systems, in the order preferred by the
6363
/// user, it consumes [`CFLocaleCopyPreferredLanguages`](https://developer.apple.com/documentation/corefoundation/1542887-cflocalecopypreferredlanguages)
64-
/// to copy the languages prefered by the user.
65-
pub fn get_locales() -> Result<Vec<String>, RetrievalError> {
64+
/// to copy the languages preferred by the user.
65+
pub fn get_raw_locales() -> Result<Vec<String>, RetrievalError> {
6666
let mut languages: Vec<String> = Vec::new();
6767

6868
// SAFETY: The call to `CFLocaleCopyPreferredLanguages` returns an immutable reference to `CFArray` which is owned by us
6969
// https://developer.apple.com/documentation/corefoundation/cfarrayref. It is ensured that `locale_carr_ref` is not mutated
70-
// Immutablility ensures that nothing is overriden during it's scope
70+
// Immutablility ensures that nothing is overridden during it's scope
7171
let locale_carr_ref = unsafe { CFLocaleCopyPreferredLanguages() };
7272

7373
if !locale_carr_ref.is_null() {

utils/env_preferences/src/error.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
// called LICENSE at the top level of the ICU4X source tree
33
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
44

5+
use displaydoc::Display;
56
use std::{ffi::FromVecWithNulError, str::Utf8Error};
67

7-
#[derive(Debug)]
8+
use crate::parse::posix::PosixParseError;
9+
10+
/// An error encountered while retrieving the system locale
11+
#[derive(Debug, PartialEq)]
812
pub enum RetrievalError {
913
/// Error converting into `&CStr` to `&str`
1014
ConversionError(Utf8Error),
@@ -22,6 +26,7 @@ pub enum RetrievalError {
2226
NullTimeZone,
2327

2428
/// UnknownCategory when retrieving locale for linux
29+
#[cfg(any(doc, target_os = "linux"))]
2530
UnknownCategory,
2631

2732
/// Error handling for windows system
@@ -49,3 +54,45 @@ impl From<FromVecWithNulError> for RetrievalError {
4954
Self::FromVecWithNulError(input)
5055
}
5156
}
57+
58+
/// An error encountered while either retrieving or parsing a system locale
59+
#[derive(Display, Debug, PartialEq)]
60+
pub enum ParseError {
61+
#[displaydoc("Locale failed native parsing logic: {0}")]
62+
Posix(PosixParseError),
63+
#[displaydoc("Unable to parse ICU4X locale: {0}")]
64+
Icu(icu_locale_core::ParseError),
65+
}
66+
67+
impl From<PosixParseError> for ParseError {
68+
fn from(value: PosixParseError) -> Self {
69+
Self::Posix(value)
70+
}
71+
}
72+
73+
impl From<icu_locale_core::ParseError> for ParseError {
74+
fn from(value: icu_locale_core::ParseError) -> Self {
75+
Self::Icu(value)
76+
}
77+
}
78+
79+
/// An error encountered while either retrieving or parsing a system locale
80+
#[derive(Display, Debug)]
81+
pub enum LocaleError {
82+
#[displaydoc("Unable to retrieve locales: {0:?}")]
83+
Retrieval(RetrievalError),
84+
#[displaydoc("Unable to parse locale: {0}")]
85+
Parse(ParseError),
86+
}
87+
88+
impl From<RetrievalError> for LocaleError {
89+
fn from(value: RetrievalError) -> Self {
90+
Self::Retrieval(value)
91+
}
92+
}
93+
94+
impl From<ParseError> for LocaleError {
95+
fn from(value: ParseError) -> Self {
96+
Self::Parse(value)
97+
}
98+
}

utils/env_preferences/src/lib.rs

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,81 @@
55
//! # env_preferences
66
//!
77
//! `env_preferences` is a crate to retrieve system locale and preferences for
8-
//! Apple, Linux & Windows systems
8+
//! Apple, Linux & Windows systems.
99
//!
10-
//! It currently fetches locales for the operating system
11-
//! currently in `String` format.
12-
//!
13-
//! In the current setup, it is not ensured that the locale retrieved will be
14-
//! converted to [`ICU4X Locale`](https://crates.io/crates/icu_locale)
10+
//! It provides functionality to fetch preferred locales from the user's operating
11+
//! system and parse them lossily to an ICU4X [`Locale`](icu_locale_core::Locale).
1512
//!
1613
//! It also retrieves preferences for [`Calendar`](https://crates.io/crates/icu_calendar)
1714
//! & [`TimeZone`](https://crates.io/crates/icu_time)
1815
1916
mod error;
20-
pub use error::RetrievalError;
17+
pub mod parse;
2118

22-
#[cfg(target_os = "linux")]
23-
mod linux;
24-
#[cfg(target_os = "linux")]
25-
pub use linux::*;
26-
#[cfg(target_os = "macos")]
27-
mod apple;
28-
#[cfg(target_os = "macos")]
29-
pub use apple::*;
30-
#[cfg(target_os = "windows")]
31-
mod windows;
32-
#[cfg(target_os = "windows")]
33-
pub use windows::*;
19+
pub use error::{LocaleError, ParseError, RetrievalError};
20+
21+
#[cfg(any(doc, target_os = "macos"))]
22+
pub mod apple;
23+
#[cfg(any(doc, target_os = "linux"))]
24+
pub mod posix;
25+
#[cfg(any(doc, target_os = "windows"))]
26+
pub mod windows;
3427
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
3528
compile_error!(
3629
"Unsupported target OS. Supported operating systems are Apple, Linux & Windows as of now"
3730
);
31+
32+
#[cfg(target_os = "macos")]
33+
use apple as system;
34+
#[cfg(target_os = "linux")]
35+
use posix as system;
36+
#[cfg(target_os = "windows")]
37+
use windows as system;
38+
39+
#[cfg(target_os = "macos")]
40+
use parse::apple::AppleLocale as SystemLocale;
41+
#[cfg(target_os = "linux")]
42+
use parse::posix::PosixLocale as SystemLocale;
43+
#[cfg(target_os = "windows")]
44+
use parse::windows::WindowsLocale as SystemLocale;
45+
46+
/// List the user's available locales as the platform-provided [`String`]s, ordered by preference.
47+
///
48+
/// <div class="warning">
49+
///
50+
/// The output of this function is platform-dependent and **is not guaranteed** to be a valid
51+
/// BCP-47 identifier. To get a list of parsed locales, see [`get_locales_lossy()`].
52+
///
53+
/// </div>
54+
///
55+
/// Specific information can be found at the platform's implementation:
56+
/// - [`apple::get_raw_locales()`]
57+
/// - [`posix::get_raw_locales()`]
58+
/// - [`windows::get_raw_locales()`]
59+
pub fn get_raw_locales() -> Result<Vec<String>, RetrievalError> {
60+
system::get_raw_locales()
61+
}
62+
63+
/// List the user's available locales as ICU4X [`Locale`](icu_locale_core::Locale)s, ordered by preference.
64+
///
65+
/// This performs a best-effort conversion that may lose some (or all!) data in certain cases.
66+
/// For getting a list of raw system locales, see [`get_raw_locales()`].
67+
///
68+
/// Specific information can be found at the platform's implementation:
69+
/// - [`parse::apple::AppleLocale`]
70+
/// - [`parse::posix::PosixLocale`]
71+
/// - [`parse::windows::WindowsLocale`]
72+
pub fn get_locales_lossy() -> Result<Vec<icu_locale_core::Locale>, LocaleError> {
73+
let raw_locales = get_raw_locales()?;
74+
let system_locales = raw_locales
75+
.iter()
76+
.map(String::as_str)
77+
.map(SystemLocale::try_from_str)
78+
.collect::<Result<Vec<SystemLocale>, ParseError>>()?;
79+
80+
system_locales
81+
.iter()
82+
.map(SystemLocale::try_convert_lossy)
83+
.map(|result| result.map_err(LocaleError::from))
84+
.collect::<Result<Vec<icu_locale_core::Locale>, LocaleError>>()
85+
}

0 commit comments

Comments
 (0)