Skip to main content

day_localization/
collate.rs

1// Copyright © The Daybrite Project
2// SPDX-License-Identifier: MPL-2.0
3
4//! Locale-aware string comparison and sorting (docs/localization.md "Sorting"), backed by
5//! icu4x's collator. `zh` sorts by pinyin (the CLDR default); `-u-co-` locale extensions select
6//! tailorings explicitly (`"zh-u-co-stroke"`). Collators are cached per full locale string in a
7//! thread-local (single-threaded, like the bundle state); on any failure the comparison degrades
8//! to code-point order rather than erroring.
9
10use std::cell::RefCell;
11use std::cmp::Ordering;
12use std::collections::HashMap;
13
14use icu_collator::options::CollatorOptions;
15use icu_collator::{Collator, CollatorBorrowed};
16
17day_reactive::tls_slots! {
18    collate;
19    /// `None` = construction failed for that locale (negative-cached to avoid re-trying per call).
20    static COLLATORS: RefCell<HashMap<String, Option<CollatorBorrowed<'static>>>> =
21        RefCell::new(HashMap::new());
22
23}
24
25fn with_collator<R>(locale: &str, f: impl FnOnce(Option<&CollatorBorrowed<'static>>) -> R) -> R {
26    COLLATORS.with(|c| {
27        let mut map = c.borrow_mut();
28        let entry = map.entry(locale.to_string()).or_insert_with(|| {
29            let parsed: icu_locale_core::Locale = locale.parse().ok()?;
30            Collator::try_new((&parsed).into(), CollatorOptions::default()).ok()
31        });
32        f(entry.as_ref())
33    })
34}
35
36/// Compare two strings in `locale`'s collation order (untracked; accepts `-u-co-` extensions,
37/// e.g. `"zh-u-co-stroke"`). Falls back to code-point order if the locale has no collation data.
38pub fn compare_in(locale: &str, a: &str, b: &str) -> Ordering {
39    with_collator(locale, |c| match c {
40        Some(c) => c.compare(a, b),
41        None => a.cmp(b),
42    })
43}
44
45/// Compare two strings in the current locale's collation order. Reads the locale signal
46/// (tracked), so a sort inside a reactive closure re-runs when the locale switches.
47pub fn compare(a: &str, b: &str) -> Ordering {
48    let locale = crate::locale().get();
49    compare_in(&locale, a, b)
50}
51
52/// Sort a slice in place in the current locale's collation order (tracked, like [`compare`]).
53pub fn sort_localized<T: AsRef<str>>(items: &mut [T]) {
54    let locale = crate::locale().get();
55    with_collator(&locale, |c| match c {
56        Some(c) => items.sort_by(|a, b| c.compare(a.as_ref(), b.as_ref())),
57        None => items.sort_by(|a, b| a.as_ref().cmp(b.as_ref())),
58    });
59}