diff options
Diffstat (limited to 'intl/l10n/rust/fluent-ffi/src/builtins.rs')
-rw-r--r-- | intl/l10n/rust/fluent-ffi/src/builtins.rs | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/intl/l10n/rust/fluent-ffi/src/builtins.rs b/intl/l10n/rust/fluent-ffi/src/builtins.rs new file mode 100644 index 0000000000..c7ffe8c3ee --- /dev/null +++ b/intl/l10n/rust/fluent-ffi/src/builtins.rs @@ -0,0 +1,389 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::ffi; +use fluent::types::{FluentNumberOptions, FluentType, FluentValue}; +use fluent::FluentArgs; +use intl_memoizer::IntlLangMemoizer; +use intl_memoizer::Memoizable; +use nsstring::nsCString; +use std::borrow::Cow; +use std::ptr::NonNull; +use unic_langid::LanguageIdentifier; + +pub struct NumberFormat { + raw: Option<NonNull<ffi::RawNumberFormatter>>, +} + +/** + * According to http://userguide.icu-project.org/design, as long as we constrain + * ourselves to const APIs ICU is const-correct. + */ +unsafe impl Send for NumberFormat {} +unsafe impl Sync for NumberFormat {} + +impl NumberFormat { + pub fn new(locale: LanguageIdentifier, options: &FluentNumberOptions) -> Self { + let loc: String = locale.to_string(); + Self { + raw: unsafe { + NonNull::new(ffi::FluentBuiltInNumberFormatterCreate( + &loc.into(), + &options.into(), + )) + }, + } + } + + pub fn format(&self, input: f64) -> String { + if let Some(raw) = self.raw { + unsafe { + let mut byte_count = 0; + let mut capacity = 0; + let buffer = ffi::FluentBuiltInNumberFormatterFormat( + raw.as_ptr(), + input, + &mut byte_count, + &mut capacity, + ); + if buffer.is_null() { + return String::new(); + } + String::from_raw_parts(buffer, byte_count, capacity) + } + } else { + String::new() + } + } +} + +impl Drop for NumberFormat { + fn drop(&mut self) { + if let Some(raw) = self.raw { + unsafe { ffi::FluentBuiltInNumberFormatterDestroy(raw.as_ptr()) }; + } + } +} + +impl Memoizable for NumberFormat { + type Args = (FluentNumberOptions,); + type Error = &'static str; + fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> { + Ok(NumberFormat::new(lang, &args.0)) + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum FluentDateTimeStyle { + Full, + Long, + Medium, + Short, + None, +} + +impl Default for FluentDateTimeStyle { + fn default() -> Self { + Self::None + } +} + +impl From<&str> for FluentDateTimeStyle { + fn from(input: &str) -> Self { + match input { + "full" => Self::Full, + "long" => Self::Long, + "medium" => Self::Medium, + "short" => Self::Short, + _ => Self::None, + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum FluentDateTimeHourCycle { + H24, + H23, + H12, + H11, + None, +} + +impl Default for FluentDateTimeHourCycle { + fn default() -> Self { + Self::None + } +} + +impl From<&str> for FluentDateTimeHourCycle { + fn from(input: &str) -> Self { + match input { + "h24" => Self::H24, + "h23" => Self::H23, + "h12" => Self::H12, + "h11" => Self::H11, + _ => Self::None, + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum FluentDateTimeTextComponent { + Long, + Short, + Narrow, + None, +} + +impl Default for FluentDateTimeTextComponent { + fn default() -> Self { + Self::None + } +} + +impl From<&str> for FluentDateTimeTextComponent { + fn from(input: &str) -> Self { + match input { + "long" => Self::Long, + "short" => Self::Short, + "narrow" => Self::Narrow, + _ => Self::None, + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum FluentDateTimeNumericComponent { + Numeric, + TwoDigit, + None, +} + +impl Default for FluentDateTimeNumericComponent { + fn default() -> Self { + Self::None + } +} + +impl From<&str> for FluentDateTimeNumericComponent { + fn from(input: &str) -> Self { + match input { + "numeric" => Self::Numeric, + "2-digit" => Self::TwoDigit, + _ => Self::None, + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum FluentDateTimeMonthComponent { + Numeric, + TwoDigit, + Long, + Short, + Narrow, + None, +} + +impl Default for FluentDateTimeMonthComponent { + fn default() -> Self { + Self::None + } +} + +impl From<&str> for FluentDateTimeMonthComponent { + fn from(input: &str) -> Self { + match input { + "numeric" => Self::Numeric, + "2-digit" => Self::TwoDigit, + "long" => Self::Long, + "short" => Self::Short, + "narrow" => Self::Narrow, + _ => Self::None, + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum FluentDateTimeTimeZoneNameComponent { + Long, + Short, + None, +} + +impl Default for FluentDateTimeTimeZoneNameComponent { + fn default() -> Self { + Self::None + } +} + +impl From<&str> for FluentDateTimeTimeZoneNameComponent { + fn from(input: &str) -> Self { + match input { + "long" => Self::Long, + "short" => Self::Short, + _ => Self::None, + } + } +} + +#[repr(C)] +#[derive(Default, Debug, Clone, Hash, PartialEq, Eq)] +pub struct FluentDateTimeOptions { + pub date_style: FluentDateTimeStyle, + pub time_style: FluentDateTimeStyle, + pub hour_cycle: FluentDateTimeHourCycle, + pub weekday: FluentDateTimeTextComponent, + pub era: FluentDateTimeTextComponent, + pub year: FluentDateTimeNumericComponent, + pub month: FluentDateTimeMonthComponent, + pub day: FluentDateTimeNumericComponent, + pub hour: FluentDateTimeNumericComponent, + pub minute: FluentDateTimeNumericComponent, + pub second: FluentDateTimeNumericComponent, + pub time_zone_name: FluentDateTimeTimeZoneNameComponent, +} + +impl FluentDateTimeOptions { + pub fn merge(&mut self, opts: &FluentArgs) { + for (key, value) in opts.iter() { + match (key, value) { + ("dateStyle", FluentValue::String(n)) => { + self.date_style = n.as_ref().into(); + } + ("timeStyle", FluentValue::String(n)) => { + self.time_style = n.as_ref().into(); + } + ("hourCycle", FluentValue::String(n)) => { + self.hour_cycle = n.as_ref().into(); + } + ("weekday", FluentValue::String(n)) => { + self.weekday = n.as_ref().into(); + } + ("era", FluentValue::String(n)) => { + self.era = n.as_ref().into(); + } + ("year", FluentValue::String(n)) => { + self.year = n.as_ref().into(); + } + ("month", FluentValue::String(n)) => { + self.month = n.as_ref().into(); + } + ("day", FluentValue::String(n)) => { + self.day = n.as_ref().into(); + } + ("hour", FluentValue::String(n)) => { + self.hour = n.as_ref().into(); + } + ("minute", FluentValue::String(n)) => { + self.minute = n.as_ref().into(); + } + ("second", FluentValue::String(n)) => { + self.second = n.as_ref().into(); + } + ("timeZoneName", FluentValue::String(n)) => { + self.time_zone_name = n.as_ref().into(); + } + _ => {} + } + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct FluentDateTime { + epoch: f64, + options: FluentDateTimeOptions, +} + +impl FluentType for FluentDateTime { + fn duplicate(&self) -> Box<dyn FluentType + Send> { + Box::new(self.clone()) + } + fn as_string(&self, intls: &IntlLangMemoizer) -> Cow<'static, str> { + let result = intls + .with_try_get::<DateTimeFormat, _, _>((self.options.clone(),), |dtf| { + dtf.format(self.epoch) + }) + .expect("Failed to retrieve a DateTimeFormat instance."); + result.into() + } + fn as_string_threadsafe( + &self, + _: &intl_memoizer::concurrent::IntlLangMemoizer, + ) -> Cow<'static, str> { + unimplemented!() + } +} + +impl std::fmt::Display for FluentDateTime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DATETIME: {}", self.epoch) + } +} + +impl FluentDateTime { + pub fn new(epoch: f64, options: FluentDateTimeOptions) -> Self { + Self { epoch, options } + } +} + +pub struct DateTimeFormat { + raw: Option<NonNull<ffi::RawDateTimeFormatter>>, +} + +/** + * According to http://userguide.icu-project.org/design, as long as we constrain + * ourselves to const APIs ICU is const-correct. + */ +unsafe impl Send for DateTimeFormat {} +unsafe impl Sync for DateTimeFormat {} + +impl DateTimeFormat { + pub fn new(locale: LanguageIdentifier, options: FluentDateTimeOptions) -> Self { + // ICU needs null-termination here, otherwise we could use nsCStr. + let loc: nsCString = locale.to_string().into(); + Self { + raw: unsafe { NonNull::new(ffi::FluentBuiltInDateTimeFormatterCreate(&loc, options)) }, + } + } + + pub fn format(&self, input: f64) -> String { + if let Some(raw) = self.raw { + unsafe { + let mut byte_count = 0; + let buffer = + ffi::FluentBuiltInDateTimeFormatterFormat(raw.as_ptr(), input, &mut byte_count); + if buffer.is_null() { + return String::new(); + } + String::from_raw_parts(buffer, byte_count as usize, byte_count as usize) + } + } else { + String::new() + } + } +} + +impl Drop for DateTimeFormat { + fn drop(&mut self) { + if let Some(raw) = self.raw { + unsafe { ffi::FluentBuiltInDateTimeFormatterDestroy(raw.as_ptr()) }; + } + } +} + +impl Memoizable for DateTimeFormat { + type Args = (FluentDateTimeOptions,); + type Error = &'static str; + fn construct(lang: LanguageIdentifier, args: Self::Args) -> Result<Self, Self::Error> { + Ok(DateTimeFormat::new(lang, args.0)) + } +} |