use alloc::{string::String, vec::Vec};
use core::cmp::Ordering;
use core::ops::Deref;
use azul_css::AzString;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2_foundation::{
NSArray, NSCalendar, NSCalendarIdentifierGregorian, NSDate,
NSDateComponents, NSDateFormatter, NSDateFormatterStyle, NSListFormatter, NSLocale, NSNumber,
NSNumberFormatter, NSNumberFormatterStyle, NSRange, NSString, NSStringCompareOptions,
};
use super::{FormatLength, IcuDate, IcuDateTime, IcuResult, IcuTime, ListType, PluralCategory, decimal_string, plural_for};
#[derive(Debug)]
pub struct IcuLocalizer {
locale_string: AzString,
}
impl IcuLocalizer {
pub fn new(locale_str: &str) -> Self {
Self { locale_string: AzString::from(locale_str) }
}
pub fn from_system_language(system_language: &AzString) -> Self {
Self::new(system_language.as_str())
}
pub fn get_locale(&self) -> AzString {
self.locale_string.clone()
}
pub fn get_language(&self) -> AzString {
let lang = self.locale_string.as_str()
.split(['-', '_'])
.next()
.unwrap_or(self.locale_string.as_str());
AzString::from(lang)
}
pub fn get_region(&self) -> Option<AzString> {
self.locale_string.as_str().split(['-', '_']).nth(1).map(AzString::from)
}
pub fn set_locale(&mut self, locale_str: &str) -> bool {
self.locale_string = AzString::from(locale_str);
true
}
pub fn load_data_blob(&mut self, _data: Vec<u8>) {
}
fn make_ns_locale(&self) -> Retained<NSLocale> {
unsafe {
let ident = NSString::from_str(self.locale_string.as_str());
NSLocale::localeWithLocaleIdentifier(&ident)
}
}
pub fn format_integer(&mut self, value: i64) -> AzString {
unsafe {
let fmt = NSNumberFormatter::new();
fmt.setNumberStyle(NSNumberFormatterStyle::DecimalStyle);
fmt.setLocale(Some(&self.make_ns_locale()));
let n = NSNumber::new_i64(value);
fmt.stringFromNumber(&n)
.map(|s| AzString::from(s.to_string()))
.unwrap_or_else(|| AzString::from(value.to_string()))
}
}
pub fn format_decimal(&mut self, integer_part: i64, decimal_places: i16) -> AzString {
let value_str = decimal_string(integer_part, decimal_places);
let dp = decimal_places.max(0) as usize;
let value: f64 = value_str.parse().unwrap_or(0.0);
unsafe {
let fmt = NSNumberFormatter::new();
fmt.setNumberStyle(NSNumberFormatterStyle::DecimalStyle);
fmt.setLocale(Some(&self.make_ns_locale()));
fmt.setMinimumFractionDigits(dp);
fmt.setMaximumFractionDigits(dp);
let n = NSNumber::new_f64(value);
fmt.stringFromNumber(&n)
.map(|s| AzString::from(s.to_string()))
.unwrap_or_else(|| AzString::from(value_str))
}
}
pub fn get_plural_category(&mut self, value: i64) -> PluralCategory {
let lang = self.locale_string.as_str()
.split(['-', '_'])
.next()
.unwrap_or("en");
plural_for(value, lang)
}
pub fn pluralize(
&mut self,
value: i64,
zero: &str,
one: &str,
two: &str,
few: &str,
many: &str,
other: &str,
) -> AzString {
let template = match self.get_plural_category(value) {
PluralCategory::Zero => zero,
PluralCategory::One => one,
PluralCategory::Two => two,
PluralCategory::Few => few,
PluralCategory::Many => many,
PluralCategory::Other => other,
};
AzString::from(template.replace("{}", &value.to_string()))
}
pub fn format_list(&mut self, items: &[AzString], list_type: ListType) -> AzString {
if let ListType::Unit = list_type {
let strs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
return AzString::from(strs.join(", "));
}
unsafe {
let ns_strings: Vec<Retained<NSString>> =
items.iter().map(|s| NSString::from_str(s.as_str())).collect();
let any_refs: Vec<&AnyObject> =
ns_strings.iter().map(|s| s.deref().deref().deref()).collect();
let array = NSArray::<AnyObject>::from_slice(&any_refs);
let formatter = NSListFormatter::new();
formatter.setLocale(Some(&self.make_ns_locale()));
match formatter.stringFromItems(&array) {
Some(result) => AzString::from(result.to_string()),
None => {
let str_refs: Vec<&NSString> = ns_strings.iter().map(|s| s.as_ref()).collect();
let str_array = NSArray::from_slice(&str_refs);
let result = NSListFormatter::localizedStringByJoiningStrings(&str_array);
AzString::from(result.to_string())
}
}
}
}
pub fn format_date(&mut self, date: IcuDate, length: FormatLength) -> IcuResult {
unsafe {
match make_ns_date(date.year, date.month as isize, date.day as isize) {
None => IcuResult::err("Invalid date"),
Some(ns_date) => {
let fmt = NSDateFormatter::new();
fmt.setDateStyle(ns_date_style(length));
fmt.setTimeStyle(NSDateFormatterStyle::NoStyle);
fmt.setLocale(Some(&self.make_ns_locale()));
IcuResult::ok(fmt.stringFromDate(&ns_date).to_string())
}
}
}
}
pub fn format_time(&mut self, time: IcuTime, include_seconds: bool) -> IcuResult {
let style = if include_seconds {
NSDateFormatterStyle::MediumStyle } else {
NSDateFormatterStyle::ShortStyle };
unsafe {
match make_ns_time(time.hour as isize, time.minute as isize, time.second as isize) {
None => IcuResult::err("Invalid time"),
Some(ns_date) => {
let fmt = NSDateFormatter::new();
fmt.setDateStyle(NSDateFormatterStyle::NoStyle);
fmt.setTimeStyle(style);
fmt.setLocale(Some(&self.make_ns_locale()));
IcuResult::ok(fmt.stringFromDate(&ns_date).to_string())
}
}
}
}
pub fn format_datetime(&mut self, datetime: IcuDateTime, length: FormatLength) -> IcuResult {
unsafe {
match make_ns_datetime(
datetime.date.year,
datetime.date.month as isize,
datetime.date.day as isize,
datetime.time.hour as isize,
datetime.time.minute as isize,
datetime.time.second as isize,
) {
None => IcuResult::err("Invalid datetime"),
Some(ns_date) => {
let fmt = NSDateFormatter::new();
fmt.setDateStyle(ns_date_style(length));
fmt.setTimeStyle(NSDateFormatterStyle::ShortStyle);
fmt.setLocale(Some(&self.make_ns_locale()));
IcuResult::ok(fmt.stringFromDate(&ns_date).to_string())
}
}
}
}
pub fn compare(&mut self, a: &str, b: &str) -> Ordering {
unsafe {
let a_ns = NSString::from_str(a);
let b_ns = NSString::from_str(b);
let range = NSRange::new(0, a_ns.len());
let locale = self.make_ns_locale();
let result = a_ns.compare_options_range_locale(
&b_ns,
NSStringCompareOptions(0),
range,
Some(locale.deref().deref()),
);
Ordering::from(result)
}
}
pub fn sort_strings(&mut self, strings: &mut [AzString]) {
strings.sort_by(|a, b| self.compare(a.as_str(), b.as_str()));
}
pub fn sorted_strings(&mut self, strings: &[AzString]) -> Vec<AzString> {
let mut v = strings.to_vec();
self.sort_strings(&mut v);
v
}
pub fn strings_equal(&mut self, a: &str, b: &str) -> bool {
self.compare(a, b) == Ordering::Equal
}
pub fn get_sort_key(&mut self, s: &str) -> Vec<u8> {
s.as_bytes().to_vec()
}
}
impl Default for IcuLocalizer {
fn default() -> Self {
Self::new("en-US")
}
}
impl Clone for IcuLocalizer {
fn clone(&self) -> Self {
Self { locale_string: self.locale_string.clone() }
}
}
fn ns_date_style(length: FormatLength) -> NSDateFormatterStyle {
match length {
FormatLength::Short => NSDateFormatterStyle::ShortStyle,
FormatLength::Medium => NSDateFormatterStyle::MediumStyle,
FormatLength::Long => NSDateFormatterStyle::LongStyle,
}
}
unsafe fn gregorian() -> Option<Retained<NSCalendar>> {
NSCalendar::calendarWithIdentifier(NSCalendarIdentifierGregorian)
}
unsafe fn make_ns_date(year: i32, month: isize, day: isize) -> Option<Retained<NSDate>> {
let cal = gregorian()?;
let c = NSDateComponents::new();
c.setYear(year as isize);
c.setMonth(month);
c.setDay(day);
cal.dateFromComponents(&c)
}
unsafe fn make_ns_time(hour: isize, minute: isize, second: isize) -> Option<Retained<NSDate>> {
let cal = gregorian()?;
let c = NSDateComponents::new();
c.setYear(2000);
c.setMonth(1);
c.setDay(1);
c.setHour(hour);
c.setMinute(minute);
c.setSecond(second);
cal.dateFromComponents(&c)
}
unsafe fn make_ns_datetime(
year: i32,
month: isize,
day: isize,
hour: isize,
minute: isize,
second: isize,
) -> Option<Retained<NSDate>> {
let cal = gregorian()?;
let c = NSDateComponents::new();
c.setYear(year as isize);
c.setMonth(month);
c.setDay(day);
c.setHour(hour);
c.setMinute(minute);
c.setSecond(second);
cal.dateFromComponents(&c)
}