use icu_casemap::options::TitlecaseOptions;
use icu_casemap::{CaseMapper as IcuCaseMapper, TitlecaseMapper};
use icu_locale_core::LanguageIdentifier;
use std::str::FromStr;
pub struct CaseMapper {
inner: icu_casemap::CaseMapperBorrowed<'static>,
title: icu_casemap::TitlecaseMapperBorrowed<'static>,
}
fn root_langid() -> LanguageIdentifier {
LanguageIdentifier::from_str("und").expect("'und' is a valid BCP-47 language identifier")
}
fn parse_langid(locale_id: &str) -> LanguageIdentifier {
LanguageIdentifier::from_str(locale_id).unwrap_or_else(|_| root_langid())
}
impl CaseMapper {
pub fn new() -> Self {
Self {
inner: IcuCaseMapper::new(),
title: TitlecaseMapper::new(),
}
}
pub fn to_uppercase(&self, text: &str, locale_id: &str) -> String {
let lang = parse_langid(locale_id);
self.inner.uppercase_to_string(text, &lang).into_owned()
}
pub fn to_lowercase(&self, text: &str, locale_id: &str) -> String {
let lang = parse_langid(locale_id);
self.inner.lowercase_to_string(text, &lang).into_owned()
}
pub fn to_titlecase(&self, text: &str, locale_id: &str) -> String {
let lang = parse_langid(locale_id);
self.title
.titlecase_segment_to_string(text, &lang, TitlecaseOptions::default())
.into_owned()
}
}
impl Default for CaseMapper {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uppercase_basic_ascii() {
let m = CaseMapper::new();
assert_eq!(m.to_uppercase("hello", "en"), "HELLO");
}
#[test]
fn lowercase_basic_ascii() {
let m = CaseMapper::new();
assert_eq!(m.to_lowercase("WORLD", "en"), "world");
}
#[test]
fn german_sharp_s_uppercase() {
let m = CaseMapper::new();
let result = m.to_uppercase("straße", "de");
assert_eq!(result, "STRASSE", "ß should uppercase to SS");
}
#[test]
fn turkish_uppercase_i() {
let m = CaseMapper::new();
let result = m.to_uppercase("istanbul", "tr");
assert_eq!(result, "İSTANBUL");
}
#[test]
fn turkish_lowercase_capital_i() {
let m = CaseMapper::new();
let result = m.to_lowercase("I", "tr");
assert_eq!(result, "ı");
}
#[test]
fn turkish_dotted_i_lowercase() {
let m = CaseMapper::new();
let result = m.to_lowercase("İSTANBUL", "tr");
assert!(result.contains('i'), "Turkish İ should lowercase to i");
}
#[test]
fn invalid_locale_falls_back_gracefully() {
let m = CaseMapper::new();
let result = m.to_uppercase("test", "not-a-locale!!!");
assert_eq!(result, "TEST");
}
#[test]
fn unicode_greek_sigma() {
let m = CaseMapper::new();
let result = m.to_lowercase("ΣΙΓΜΑ", "el");
assert!(!result.is_empty());
let first_char = result.chars().next().expect("non-empty result");
assert!(
first_char == 'σ' || first_char == 'ς',
"Expected lowercase sigma, got {first_char}"
);
}
#[test]
fn titlecase_basic() {
let m = CaseMapper::new();
let result = m.to_titlecase("hello world", "en");
assert!(
result.starts_with('H'),
"Expected titlecase to start with H, got: {result}"
);
}
#[test]
fn titlecase_turkish_i() {
let m = CaseMapper::new();
let result = m.to_titlecase("istanbul", "tr");
assert_eq!(result, "İstanbul");
}
#[test]
fn round_trip_lower_upper() {
let m = CaseMapper::new();
let original = "Hello, World!";
let lower = m.to_lowercase(original, "en");
let upper = m.to_uppercase(&lower, "en");
let expected = m.to_uppercase(original, "en");
assert_eq!(upper, expected);
}
#[test]
fn cyrillic_case() {
let m = CaseMapper::new();
assert_eq!(m.to_uppercase("привет мир", "ru"), "ПРИВЕТ МИР");
assert_eq!(m.to_lowercase("ПРИВЕТ МИР", "ru"), "привет мир");
}
}