From c545415e209ef8397388725d8ecd7f75c3050e37 Mon Sep 17 00:00:00 2001 From: Robert Bastian <4706271+robertbastian@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:50:57 +0200 Subject: [PATCH 1/3] wip --- components/decimal/src/compact_formatter.rs | 50 +- components/decimal/src/lib.rs | 2 +- .../src/dimension/currency/compact_format.rs | 124 ++++- .../dimension/currency/compact_formatter.rs | 348 +++++-------- .../src/dimension/currency/format.rs | 50 +- .../src/dimension/currency/formatter.rs | 475 +++++++++++------- .../dimension/currency/long_compact_format.rs | 89 ---- .../currency/long_compact_formatter.rs | 227 --------- .../src/dimension/currency/mod.rs | 9 +- .../src/dimension/currency/options.rs | 22 +- 10 files changed, 592 insertions(+), 804 deletions(-) delete mode 100644 components/experimental/src/dimension/currency/long_compact_format.rs delete mode 100644 components/experimental/src/dimension/currency/long_compact_formatter.rs diff --git a/components/decimal/src/compact_formatter.rs b/components/decimal/src/compact_formatter.rs index cd6e3bbde4d..1024d8be54c 100644 --- a/components/decimal/src/compact_formatter.rs +++ b/components/decimal/src/compact_formatter.rs @@ -76,7 +76,8 @@ use writeable::Writeable; #[derive(Debug)] pub struct CompactDecimalFormatter { plural_rules: PluralRules, - decimal_formatter: DecimalFormatter, + #[doc(hidden)] + pub decimal_formatter: DecimalFormatter, compact_data: DataPayload::DataStruct>>, } @@ -347,20 +348,26 @@ impl CompactDecimalFormatter { /// "999K" /// ); /// ``` - pub fn format(&self, value: &Decimal) -> impl Writeable + Display + '_ + use<'_> { + pub fn format<'a>(&'a self, value: &Decimal) -> impl Writeable + Display + 'a { + self.decimal_formatter + .format_sign(value.sign, self.format_unsigned(&value.absolute)) + } + + #[doc(hidden)] + pub fn format_unsigned<'a>( + &'a self, + value: &UnsignedDecimal, + ) -> FormattedUnsignedCompactDecimal<'a> { let (compact_pattern, significand) = self .compact_data .get() - .get_pattern_and_significand(&value.absolute, &self.plural_rules); - - self.decimal_formatter.format_sign( - value.sign, - compact_pattern - .unwrap_or(Pattern::::PASS_THROUGH) - .interpolate([self - .decimal_formatter - .format_unsigned(Cow::Owned(significand))]), - ) + .get_pattern_and_significand(value, &self.plural_rules); + + FormattedUnsignedCompactDecimal { + pattern: compact_pattern, + significand, + decimal_formatter: &self.decimal_formatter, + } } /// Formats a [`Decimal`], returning a [`String`]. @@ -542,6 +549,25 @@ impl CompactDecimalFormatter { } } +#[doc(hidden)] +#[derive(Debug)] +pub struct FormattedUnsignedCompactDecimal<'l> { + pattern: Option<&'l Pattern>, + significand: UnsignedDecimal, + decimal_formatter: &'l DecimalFormatter, +} + +impl Writeable for FormattedUnsignedCompactDecimal<'_> { + fn write_to(&self, sink: &mut W) -> core::fmt::Result { + self.pattern + .unwrap_or(Pattern::::PASS_THROUGH) + .interpolate([self + .decimal_formatter + .format_unsigned(Cow::Borrowed(&self.significand))]) + .write_to(sink) + } +} + impl<'a, P: PatternBackend> CompactPatterns<'a, P> { /// Gets the compact pattern and significand for the given decimal pub fn get_pattern_and_significand( diff --git a/components/decimal/src/lib.rs b/components/decimal/src/lib.rs index 4188c3ff74f..09f81ba3910 100644 --- a/components/decimal/src/lib.rs +++ b/components/decimal/src/lib.rs @@ -130,7 +130,7 @@ pub use decimal_formatter::{ }; #[cfg(feature = "unstable")] -pub use compact_formatter::CompactDecimalFormatter; +pub use compact_formatter::{CompactDecimalFormatter, FormattedUnsignedCompactDecimal}; pub use preferences::DecimalFormatterPreferences; diff --git a/components/experimental/src/dimension/currency/compact_format.rs b/components/experimental/src/dimension/currency/compact_format.rs index 2149596bef1..5a5a70bfa21 100644 --- a/components/experimental/src/dimension/currency/compact_format.rs +++ b/components/experimental/src/dimension/currency/compact_format.rs @@ -8,78 +8,168 @@ mod tests { use tinystr::*; use writeable::assert_writeable_eq; - use crate::dimension::currency::{CurrencyCode, compact_formatter::CompactCurrencyFormatter}; + use crate::dimension::currency::{CurrencyCode, formatter::CurrencyFormatter}; #[test] pub fn test_en_us() { - let locale = locale!("en-US").into(); + let prefs = locale!("en-US").into(); let currency_code = CurrencyCode(tinystr!(3, "USD")); - let fmt = CompactCurrencyFormatter::try_new(locale, Default::default()).unwrap(); + let fmt = CurrencyFormatter::try_new_compact_short(prefs, ¤cy_code).unwrap(); // Positive case let positive_value = "12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); assert_writeable_eq!(formatted_currency, "$12K"); // Negative case let negative_value = "-12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); assert_writeable_eq!(formatted_currency, "-$12K"); } #[test] pub fn test_fr_fr() { - let locale = locale!("fr-FR").into(); + let prefs = locale!("fr-FR").into(); let currency_code = CurrencyCode(tinystr!(3, "EUR")); - let fmt = CompactCurrencyFormatter::try_new(locale, Default::default()).unwrap(); + let fmt = CurrencyFormatter::try_new_compact_short(prefs, ¤cy_code).unwrap(); // Positive case let positive_value = "12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); assert_writeable_eq!(formatted_currency, "12\u{a0}k\u{a0}€"); // Negative case let negative_value = "-12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); assert_writeable_eq!(formatted_currency, "-12\u{a0}k\u{a0}€"); } #[test] pub fn test_zh_cn() { - let locale = locale!("zh-CN").into(); + let prefs = locale!("zh-CN").into(); let currency_code = CurrencyCode(tinystr!(3, "CNY")); - let fmt = CompactCurrencyFormatter::try_new(locale, Default::default()).unwrap(); + let fmt = CurrencyFormatter::try_new_compact_short(prefs, ¤cy_code).unwrap(); // Positive case let positive_value = "12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); assert_writeable_eq!(formatted_currency, "¥1.2万"); // Negative case let negative_value = "-12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); assert_writeable_eq!(formatted_currency, "-¥1.2万"); } #[test] pub fn test_ar_eg() { - let locale = locale!("ar-EG").into(); + let prefs = locale!("ar-EG").into(); let currency_code = CurrencyCode(tinystr!(3, "EGP")); - let fmt = CompactCurrencyFormatter::try_new(locale, Default::default()).unwrap(); + let fmt = CurrencyFormatter::try_new_compact_short(prefs, ¤cy_code).unwrap(); // Positive case let positive_value = "12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); // TODO(#6064) assert_writeable_eq!(formatted_currency, "\u{200f}١٢\u{a0}ألف\u{a0}ج.م.\u{200f}"); // "ج.م.١٢ألف" // Negative case let negative_value = "-12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value, ¤cy_code); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); // TODO(#6064) assert_writeable_eq!( formatted_currency, "\u{61c}-\u{200f}١٢\u{a0}ألف\u{a0}ج.م.\u{200f}" ); } + #[test] + pub fn test_en_us_long() { + let prefs = locale!("en-US").into(); + + let currency_code = CurrencyCode(tinystr!(3, "USD")); + let fmt = CurrencyFormatter::try_new_compact_long(prefs, ¤cy_code).unwrap(); + + // Positive case + let positive_value = "12345.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); + assert_writeable_eq!(formatted_currency, "12 thousand US dollars"); + + // Negative case + let negative_value = "-12345.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); + assert_writeable_eq!(formatted_currency, "-12 thousand US dollars"); + } + + #[test] + pub fn test_en_us_millions_long() { + let prefs = locale!("en-US").into(); + + let currency_code = CurrencyCode(tinystr!(3, "USD")); + let fmt = CurrencyFormatter::try_new_compact_long(prefs, ¤cy_code).unwrap(); + + // Positive case + let positive_value = "12345000.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); + assert_writeable_eq!(formatted_currency, "12 million US dollars"); + + // Negative case + let negative_value = "-12345000.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); + assert_writeable_eq!(formatted_currency, "-12 million US dollars"); + } + + #[test] + pub fn test_fr_fr_long() { + let prefs = locale!("fr-FR").into(); + + let currency_code = CurrencyCode(tinystr!(3, "USD")); + let fmt = CurrencyFormatter::try_new_compact_long(prefs, ¤cy_code).unwrap(); + + // Positive case + let positive_value = "12345.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); + assert_writeable_eq!(formatted_currency, "12 mille dollars des États-Unis"); + + // Negative case + let negative_value = "-12345.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); + assert_writeable_eq!(formatted_currency, "-12 mille dollars des États-Unis"); + } + + #[test] + pub fn test_fr_fr_millions_long() { + let prefs = locale!("fr-FR").into(); + + let currency_code = CurrencyCode(tinystr!(3, "USD")); + let fmt = CurrencyFormatter::try_new_compact_long(prefs, ¤cy_code).unwrap(); + + // Positive case + let positive_value = "12345000.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&positive_value); + assert_writeable_eq!(formatted_currency, "12 millions dollars des États-Unis"); + + // Negative case + let negative_value = "-12345000.67".parse().unwrap(); + let formatted_currency = fmt.format_fixed_decimal(&negative_value); + assert_writeable_eq!(formatted_currency, "-12 millions dollars des États-Unis"); + } + + #[test] + pub fn test_alpha_next_to_number_and_small_numbers() { + let prefs = locale!("en-US").into(); + let usd = CurrencyCode(tinystr!(3, "USD")); + let sek = CurrencyCode(tinystr!(3, "SEK")); + + let fmt_usd = CurrencyFormatter::try_new_compact_short(prefs, &usd).unwrap(); + let fmt_sek = CurrencyFormatter::try_new_compact_short(prefs, &sek).unwrap(); + + // Small number (magnitude < 3, no compact suffix): should fall back cleanly + let small_value = "123".parse().unwrap(); + assert_writeable_eq!(fmt_usd.format_fixed_decimal(&small_value), "$123"); + assert_writeable_eq!(fmt_sek.format_fixed_decimal(&small_value), "SEK\u{a0}123"); + + // Compact number with alphabetical currency code: should use alpha_next_to_number pattern with non-breaking space + let compact_value = "12345.67".parse().unwrap(); + assert_writeable_eq!(fmt_sek.format_fixed_decimal(&compact_value), "SEK\u{a0}12K"); + } } diff --git a/components/experimental/src/dimension/currency/compact_formatter.rs b/components/experimental/src/dimension/currency/compact_formatter.rs index 36f55cc2f8d..314e2e31a7a 100644 --- a/components/experimental/src/dimension/currency/compact_formatter.rs +++ b/components/experimental/src/dimension/currency/compact_formatter.rs @@ -2,254 +2,178 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use core::fmt::Display; - -use crate::dimension::provider::{ - currency::compact::ShortCurrencyCompactV1, currency::essentials::CurrencyEssentialsV1, -}; -use fixed_decimal::Decimal; -use icu_decimal::preferences::CompactDecimalFormatterPreferences; -use icu_decimal::{DecimalFormatter, DecimalFormatterPreferences}; -use icu_locale_core::preferences::{define_preferences, prefs_convert}; -use icu_plurals::{PluralRules, PluralRulesPreferences}; +use crate::dimension::provider::currency::{essentials::*, extended::*, patterns::*}; +use icu_decimal::CompactDecimalFormatter; use icu_provider::prelude::*; -use writeable::Writeable; - -use super::{CurrencyCode, options::CurrencyFormatterOptions}; -use icu_pattern::DoublePlaceholderPattern; - -extern crate alloc; - -define_preferences!( - /// The preferences for currency formatting. - [Copy] - CompactCurrencyFormatterPreferences, - { - /// The user's preferred numbering system. - /// - /// Corresponds to the `-u-nu` in Unicode Locale Identifier. - numbering_system: crate::dimension::preferences::NumberingSystem - } -); - -prefs_convert!( - CompactCurrencyFormatterPreferences, - DecimalFormatterPreferences, - { numbering_system } -); -prefs_convert!( - CompactCurrencyFormatterPreferences, - CompactDecimalFormatterPreferences -); -prefs_convert!(CompactCurrencyFormatterPreferences, PluralRulesPreferences); - -/// A formatter for monetary values. -/// -/// [`CompactCurrencyFormatter`] supports: -/// 1. Rendering in the locale's currency system. -/// 2. Locale-sensitive grouping separator positions. -/// -/// Read more about the options in the [`super::options`] module. -#[derive(Debug)] -pub struct CompactCurrencyFormatter { - /// Short currency compact data for the compact currency formatter. - _short_currency_compact: DataPayload, - - /// Essential data for the compact currency formatter. - essential: DataPayload, - - decimal_formatter: DecimalFormatter, - compact_data: DataPayload, +use super::formatter::{Compact, CurrencyFormatter, CurrencyFormatterPreferences}; +use super::{CurrencyCode, options::Width}; - plural_rules: PluralRules, - - /// Options bag for the compact currency formatter to determine the behavior of the formatter. - /// for example: width. - options: CurrencyFormatterOptions, -} +impl CurrencyFormatter { + icu_provider::gen_buffer_data_constructors!( + (prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode) -> error: DataError, + functions: [ + try_new_compact_short: skip, + try_new_compact_short_with_buffer_provider, + try_new_compact_short_unstable, + Self + ] + ); -impl CompactCurrencyFormatter { icu_provider::gen_buffer_data_constructors!( - (prefs: CompactCurrencyFormatterPreferences, options: CurrencyFormatterOptions) -> error: DataError, + (prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode) -> error: DataError, functions: [ - try_new: skip, - try_new_with_buffer_provider, - try_new_unstable, + try_new_compact_narrow: skip, + try_new_compact_narrow_with_buffer_provider, + try_new_compact_narrow_unstable, Self ] ); - /// Creates a new [`CompactCurrencyFormatter`] from compiled locale data and an options bag. + /// Creates a new [`CurrencyFormatter`] for short compact formatting from compiled locale data. /// /// ✨ *Enabled with the `compiled_data` Cargo feature.* /// /// [📚 Help choosing a constructor](icu_provider::constructors) #[cfg(feature = "compiled_data")] - pub fn try_new( - prefs: CompactCurrencyFormatterPreferences, - options: CurrencyFormatterOptions, + pub fn try_new_compact_short( + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode, ) -> Result { - let short_locale = ShortCurrencyCompactV1::make_locale(prefs.locale_preferences); - - let short_currency_compact = crate::provider::Baked - .load(DataRequest { - id: DataIdentifierBorrowed::for_locale(&short_locale), - ..Default::default() - })? - .payload; - - let essential_locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); - - let essential: DataPayload = crate::provider::Baked - .load(DataRequest { - id: DataIdentifierBorrowed::for_locale(&essential_locale), - ..Default::default() - })? - .payload; - - if essential.get().standard_pattern().is_none() { - return Err(DataError::custom("missing standard pattern")); - } - - let decimal_formatter = DecimalFormatter::try_new((&prefs).into(), Default::default())?; - - let compact_data = DataProvider::::load( - &icu_decimal::provider::Baked, - DataRequest { - id: DataIdentifierBorrowed::for_locale( - &icu_decimal::provider::DecimalCompactShortV1::make_locale( - prefs.locale_preferences, - ), - ), - ..Default::default() - }, - )? - .payload - .cast(); - - let plural_rules = PluralRules::try_new_cardinal((&prefs).into())?; + Self::try_new_essential( + CompactDecimalFormatter::try_new_short((&prefs).into(), Default::default())?, + prefs, + *currency_code, + Width::Short, + ) + } - Ok(Self { - _short_currency_compact: short_currency_compact, - essential, - decimal_formatter, - compact_data, - plural_rules, - options, - }) + /// Creates a new [`CurrencyFormatter`] for narrow compact formatting from compiled locale data. + /// + /// ✨ *Enabled with the `compiled_data` Cargo feature.* + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + #[cfg(feature = "compiled_data")] + pub fn try_new_compact_narrow( + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode, + ) -> Result { + Self::try_new_essential( + CompactDecimalFormatter::try_new_short((&prefs).into(), Default::default())?, + prefs, + *currency_code, + Width::Narrow, + ) } - #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)] - pub fn try_new_unstable( + #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_compact_short)] + pub fn try_new_compact_short_unstable( provider: &D, - prefs: CompactCurrencyFormatterPreferences, - options: CurrencyFormatterOptions, + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode, ) -> Result where D: ?Sized + DataProvider - + DataProvider + DataProvider + DataProvider + DataProvider + DataProvider, { - let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); - - let decimal_formatter = - DecimalFormatter::try_new_unstable(provider, (&prefs).into(), Default::default())?; - - let compact_data = DataProvider::::load( + Self::try_new_essential_unstable( provider, - DataRequest { - id: DataIdentifierBorrowed::for_locale( - &icu_decimal::provider::DecimalCompactShortV1::make_locale( - prefs.locale_preferences, - ), - ), - ..Default::default() - }, - )? - .payload - .cast(); - - let plural_rules = PluralRules::try_new_cardinal_unstable(provider, (&prefs).into())?; - - let short_currency_compact = provider - .load(DataRequest { - id: DataIdentifierBorrowed::for_locale(&locale), - ..Default::default() - })? - .payload; - - let essential: DataPayload = provider - .load(DataRequest { - id: DataIdentifierBorrowed::for_locale(&locale), - ..Default::default() - })? - .payload; - - if essential.get().standard_pattern().is_none() { - return Err(DataError::custom("missing standard pattern")); - } + CompactDecimalFormatter::try_new_short_unstable( + provider, + (&prefs).into(), + Default::default(), + )?, + prefs, + *currency_code, + Width::Short, + ) + } - Ok(Self { - _short_currency_compact: short_currency_compact, - essential, - decimal_formatter, - compact_data, - plural_rules, - options, - }) + #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_compact_narrow)] + pub fn try_new_compact_narrow_unstable( + provider: &D, + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode, + ) -> Result + where + D: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_essential_unstable( + provider, + CompactDecimalFormatter::try_new_short_unstable( + provider, + (&prefs).into(), + Default::default(), + )?, + prefs, + *currency_code, + Width::Narrow, + ) } - /// Formats in the compact format a [`Decimal`] value for the given currency code. + icu_provider::gen_buffer_data_constructors!( + ( + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode + ) -> error: DataError, + functions: [ + try_new_compact_long: skip, + try_new_compact_long_with_buffer_provider, + try_new_compact_long_unstable, + Self + ] + ); + + /// Creates a new [`CurrencyFormatter`] from compiled locale data. /// - /// # Examples - /// ``` - /// use icu::experimental::dimension::currency::compact_formatter::CompactCurrencyFormatter; - /// use icu::experimental::dimension::currency::CurrencyCode; - /// use icu::locale::locale; - /// use tinystr::*; - /// use writeable::assert_writeable_eq; + /// ✨ *Enabled with the `compiled_data` Cargo feature.* /// - /// let locale = locale!("en-US").into(); - /// let currency_code = CurrencyCode(tinystr!(3, "USD")); - /// let fmt = CompactCurrencyFormatter::try_new(locale, Default::default()).unwrap(); - /// let value = "12345.67".parse().unwrap(); - /// assert_writeable_eq!(fmt.format_fixed_decimal(&value, ¤cy_code), "$12K"); - /// ``` - pub fn format_fixed_decimal<'l>( - &'l self, - value: &'l Decimal, - currency_code: &'l CurrencyCode, - ) -> impl Writeable + Display + 'l { - let (currency_placeholder, pattern, _pattern_selection) = self - .essential - .get() - .name_and_pattern(self.options.width, currency_code); - - let pattern = pattern.unwrap_or_else(|| <&DoublePlaceholderPattern>::default()); - - // TODO: The current behavior is the behavior when there is no compact currency pattern found. - // Therefore, in the next PR, we will add the code to handle using the compact currency patterns. - - let (compact_pattern, significand) = self - .compact_data - .get() - .get_pattern_and_significand(&value.absolute, &self.plural_rules); + /// [📚 Help choosing a constructor](icu_provider::constructors) + #[cfg(feature = "compiled_data")] + pub fn try_new_compact_long( + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode, + ) -> Result { + Self::try_new_long_internal( + CompactDecimalFormatter::try_new_long((&prefs).into(), Default::default())?, + prefs, + *currency_code, + ) + } - self.decimal_formatter.format_sign( - value.sign, - pattern.interpolate(( - compact_pattern - .unwrap_or(icu_pattern::SinglePlaceholderPattern::PASS_THROUGH) - .interpolate([self - .decimal_formatter - .format_unsigned(icu_decimal::Cow::Owned(significand))]), - currency_placeholder, - )), + #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_compact_long)] + pub fn try_new_compact_long_unstable( + provider: &D, + prefs: CurrencyFormatterPreferences, + currency_code: &CurrencyCode, + ) -> Result + where + D: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_long_internal_unstable( + provider, + CompactDecimalFormatter::try_new_long_unstable( + provider, + (&prefs).into(), + Default::default(), + )?, + prefs, + *currency_code, ) } } diff --git a/components/experimental/src/dimension/currency/format.rs b/components/experimental/src/dimension/currency/format.rs index 4a57f4cb006..ded335e8889 100644 --- a/components/experimental/src/dimension/currency/format.rs +++ b/components/experimental/src/dimension/currency/format.rs @@ -9,18 +9,15 @@ mod tests { use tinystr::*; use writeable::assert_writeable_eq; - use crate::dimension::currency::{ - CurrencyCode, - formatter::{CurrencyFormatter, CurrencyFormatterPreferences, Decimal}, - }; + use crate::dimension::currency::{CurrencyCode, formatter::CurrencyFormatter}; #[test] pub fn test_en_us() { - let prefs: CurrencyFormatterPreferences = locale!("en-US").into(); + let prefs = locale!("en-US").into(); let currency_code = CurrencyCode(tinystr!(3, "USD")); // Short - let fmt_short = CurrencyFormatter::::try_new_short(prefs, ¤cy_code).unwrap(); + let fmt_short = CurrencyFormatter::try_new_short(prefs, ¤cy_code).unwrap(); let positive_value = "12345.67".parse().unwrap(); assert_writeable_eq!( fmt_short.format_fixed_decimal(&positive_value), @@ -43,8 +40,7 @@ mod tests { ); // Narrow - let fmt_narrow = - CurrencyFormatter::::try_new_narrow(prefs, ¤cy_code).unwrap(); + let fmt_narrow = CurrencyFormatter::try_new_narrow(prefs, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_narrow.format_fixed_decimal(&positive_value), "$12,345.67" @@ -85,11 +81,11 @@ mod tests { #[test] pub fn test_fr_fr() { - let prefs: CurrencyFormatterPreferences = locale!("fr-FR").into(); + let prefs = locale!("fr-FR").into(); let currency_code = CurrencyCode(tinystr!(3, "EUR")); // Short - let fmt_short = CurrencyFormatter::::try_new_short(prefs, ¤cy_code).unwrap(); + let fmt_short = CurrencyFormatter::try_new_short(prefs, ¤cy_code).unwrap(); let positive_value = "12345.67".parse().unwrap(); assert_writeable_eq!( fmt_short.format_fixed_decimal(&positive_value), @@ -102,8 +98,7 @@ mod tests { ); // Narrow - let fmt_narrow = - CurrencyFormatter::::try_new_narrow(prefs, ¤cy_code).unwrap(); + let fmt_narrow = CurrencyFormatter::try_new_narrow(prefs, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_narrow.format_fixed_decimal(&positive_value), "12\u{202f}345,67\u{a0}€" @@ -127,11 +122,11 @@ mod tests { #[test] pub fn test_ar_eg() { - let prefs: CurrencyFormatterPreferences = locale!("ar-EG").into(); + let prefs = locale!("ar-EG").into(); let currency_code = CurrencyCode(tinystr!(3, "EGP")); // Short - let fmt_short = CurrencyFormatter::::try_new_short(prefs, ¤cy_code).unwrap(); + let fmt_short = CurrencyFormatter::try_new_short(prefs, ¤cy_code).unwrap(); let positive_value = "12345.67".parse().unwrap(); // TODO(#6064) assert_writeable_eq!( @@ -146,8 +141,7 @@ mod tests { ); // Narrow - let fmt_narrow = - CurrencyFormatter::::try_new_narrow(prefs, ¤cy_code).unwrap(); + let fmt_narrow = CurrencyFormatter::try_new_narrow(prefs, ¤cy_code).unwrap(); // TODO(#6064) assert_writeable_eq!( fmt_narrow.format_fixed_decimal(&positive_value), @@ -173,20 +167,19 @@ mod tests { #[test] pub fn test_usd_in_fr_fr() { - let prefs: CurrencyFormatterPreferences = locale!("fr-FR").into(); + let prefs = locale!("fr-FR").into(); let currency_code = CurrencyCode(tinystr!(3, "USD")); let value = "12345.67".parse().unwrap(); // Short USD in fr-FR should be US$ or $US - let fmt_short = CurrencyFormatter::::try_new_short(prefs, ¤cy_code).unwrap(); + let fmt_short = CurrencyFormatter::try_new_short(prefs, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_short.format_fixed_decimal(&value), "12\u{202f}345,67\u{a0}$US" ); // Narrow USD in fr-FR should be $ - let fmt_narrow = - CurrencyFormatter::::try_new_narrow(prefs, ¤cy_code).unwrap(); + let fmt_narrow = CurrencyFormatter::try_new_narrow(prefs, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_narrow.format_fixed_decimal(&value), "12\u{202f}345,67\u{a0}$" @@ -201,16 +194,14 @@ mod tests { let value = "12345.67".parse().unwrap(); // 1. Default numbering system (arab) - Short - let fmt_arab_short = - CurrencyFormatter::::try_new_short(prefs_arab, ¤cy_code).unwrap(); + let fmt_arab_short = CurrencyFormatter::try_new_short(prefs_arab, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_arab_short.format_fixed_decimal(&value), "\u{200f}١٢٬٣٤٥٫٦٧\u{a0}ج.م.\u{200f}" ); // 2. Locale extension override (latn) - Short - let fmt_latn_short = - CurrencyFormatter::::try_new_short(prefs_latn, ¤cy_code).unwrap(); + let fmt_latn_short = CurrencyFormatter::try_new_short(prefs_latn, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_latn_short.format_fixed_decimal(&value), "\u{200f}12,345.67\u{a0}ج.م.\u{200f}" @@ -218,7 +209,7 @@ mod tests { // 3. Default numbering system (arab) - Narrow let fmt_arab_narrow = - CurrencyFormatter::::try_new_narrow(prefs_arab, ¤cy_code).unwrap(); + CurrencyFormatter::try_new_narrow(prefs_arab, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_arab_narrow.format_fixed_decimal(&value), "\u{200f}١٢٬٣٤٥٫٦٧\u{a0}E£" @@ -226,7 +217,7 @@ mod tests { // 4. Locale extension override (latn) - Narrow let fmt_latn_narrow = - CurrencyFormatter::::try_new_narrow(prefs_latn, ¤cy_code).unwrap(); + CurrencyFormatter::try_new_narrow(prefs_latn, ¤cy_code).unwrap(); assert_writeable_eq!( fmt_latn_narrow.format_fixed_decimal(&value), "\u{200f}12,345.67\u{a0}E£" @@ -249,17 +240,16 @@ mod tests { #[test] pub fn test_en_us_cad() { - let prefs: CurrencyFormatterPreferences = locale!("en-US").into(); + let prefs = locale!("en-US").into(); let currency_code = CurrencyCode(tinystr!(3, "CAD")); let value = "12345.67".parse().unwrap(); // Short - let fmt_short = CurrencyFormatter::::try_new_short(prefs, ¤cy_code).unwrap(); + let fmt_short = CurrencyFormatter::try_new_short(prefs, ¤cy_code).unwrap(); assert_writeable_eq!(fmt_short.format_fixed_decimal(&value), "CA$12,345.67"); // Narrow - let fmt_narrow = - CurrencyFormatter::::try_new_narrow(prefs, ¤cy_code).unwrap(); + let fmt_narrow = CurrencyFormatter::try_new_narrow(prefs, ¤cy_code).unwrap(); assert_writeable_eq!(fmt_narrow.format_fixed_decimal(&value), "$12,345.67"); } } diff --git a/components/experimental/src/dimension/currency/formatter.rs b/components/experimental/src/dimension/currency/formatter.rs index 3ed94a9a3f9..e35fcdb2057 100644 --- a/components/experimental/src/dimension/currency/formatter.rs +++ b/components/experimental/src/dimension/currency/formatter.rs @@ -3,12 +3,13 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use core::fmt::Display; -use core::marker::PhantomData; -use fixed_decimal::Decimal as FixedDecimal; +use fixed_decimal::{Decimal as FixedDecimal, UnsignedDecimal}; +use icu_decimal::preferences::CompactDecimalFormatterPreferences; use icu_decimal::{ - DecimalFormatter, DecimalFormatterPreferences, options::DecimalFormatterOptions, + CompactDecimalFormatter, FormattedUnsignedCompactDecimal, FormattedUnsignedDecimal, }; +use icu_decimal::{DecimalFormatter, DecimalFormatterPreferences}; use icu_locale_core::preferences::{define_preferences, prefs_convert}; use icu_plurals::{PluralRules, PluralRulesPreferences}; use icu_provider::prelude::*; @@ -40,19 +41,72 @@ prefs_convert!(CurrencyFormatterPreferences, DecimalFormatterPreferences, { numbering_system }); prefs_convert!(CurrencyFormatterPreferences, PluralRulesPreferences); +prefs_convert!( + CurrencyFormatterPreferences, + CompactDecimalFormatterPreferences, + { numbering_system } +); /// A trait for value representation in currency formatting. -pub trait ValueRepresentation {} +pub trait ValueRepresentation { + /// Data required for this specific value representation. + type Formatter: core::fmt::Debug; + type Formatted<'a>: Writeable; + + fn format<'a>(data: &'a Self::Formatter, value: &'a UnsignedDecimal) -> Self::Formatted<'a>; + fn format_sign( + data: &Self::Formatter, + value: impl Writeable, + sign: fixed_decimal::Sign, + ) -> impl Writeable + Display; +} /// Representation for decimal currency formatting. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct Decimal; -impl ValueRepresentation for Decimal {} +impl ValueRepresentation for Decimal { + type Formatter = DecimalFormatter; + type Formatted<'a> = FormattedUnsignedDecimal<'a>; + + fn format<'a>(data: &'a Self::Formatter, value: &'a UnsignedDecimal) -> Self::Formatted<'a> { + data.format_unsigned(icu_decimal::Cow::Borrowed(value)) + } + + fn format_sign( + data: &Self::Formatter, + value: impl Writeable, + sign: fixed_decimal::Sign, + ) -> impl Writeable + Display { + data.format_sign(sign, value) + } +} + +/// Representation for compact currency formatting. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct Compact; + +impl ValueRepresentation for Compact { + type Formatter = CompactDecimalFormatter; + type Formatted<'a> = FormattedUnsignedCompactDecimal<'a>; + + fn format<'a>(data: &'a Self::Formatter, value: &'a UnsignedDecimal) -> Self::Formatted<'a> { + data.format_unsigned(value) + } + + fn format_sign( + data: &Self::Formatter, + value: impl Writeable, + sign: fixed_decimal::Sign, + ) -> impl Writeable + Display { + data.decimal_formatter.format_sign(sign, value) + } +} #[derive(Debug)] -enum CurrencyFormatterData { +pub(crate) enum CurrencyFormatterData { Essential { essential: DataPayload, width: Width, @@ -74,12 +128,152 @@ enum CurrencyFormatterData { /// Read more about the options in the [`super::options`] module. #[derive(Debug)] pub struct CurrencyFormatter { - /// A [`DecimalFormatter`] to format the currency value. - decimal_formatter: DecimalFormatter, + value_formatter: V::Formatter, + currency_data: CurrencyFormatterData, +} + +impl CurrencyFormatter { + #[cfg(feature = "compiled_data")] + pub(crate) fn try_new_essential( + value_formatter: V::Formatter, + prefs: CurrencyFormatterPreferences, + currency: CurrencyCode, + width: Width, + ) -> Result { + let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); + let decimal_prefs = DecimalFormatterPreferences::from(&prefs); + + let req_id = decimal_prefs.nu_id(&locale); + let default_id = DataIdentifierBorrowed::for_locale(&locale); + let ids = req_id.into_iter().chain(core::iter::once(default_id)); + let essential = + load_with_fallback::(&crate::provider::Baked, ids)?.payload; + + if essential.get().standard_pattern().is_none() { + return Err(DataError::custom("missing standard pattern")); + } - data: CurrencyFormatterData, + Ok(Self { + value_formatter, + currency_data: CurrencyFormatterData::Essential { + essential, + width, + currency, + }, + }) + } + + pub(crate) fn try_new_essential_unstable( + provider: &D, + value_formatter: V::Formatter, + prefs: CurrencyFormatterPreferences, + currency: CurrencyCode, + width: Width, + ) -> Result + where + D: ?Sized + DataProvider, + { + let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); + let decimal_prefs = DecimalFormatterPreferences::from(&prefs); + + let req_id = decimal_prefs.nu_id(&locale); + let default_id = DataIdentifierBorrowed::for_locale(&locale); + let ids = req_id.into_iter().chain(core::iter::once(default_id)); + let essential = load_with_fallback::(provider, ids)?.payload; + + if essential.get().standard_pattern().is_none() { + return Err(DataError::custom("missing standard pattern")); + } + + Ok(Self { + value_formatter, + currency_data: CurrencyFormatterData::Essential { + essential, + width, + currency, + }, + }) + } - _marker: PhantomData, + #[cfg(feature = "compiled_data")] + pub(crate) fn try_new_long_internal( + value_formatter: V::Formatter, + prefs: CurrencyFormatterPreferences, + currency: CurrencyCode, + ) -> Result { + let locale = CurrencyPatternsDataV1::make_locale(prefs.locale_preferences); + let marker_attributes = + DataMarkerAttributes::try_from_str(currency.0.as_str()).map_err(|_| { + DataErrorKind::IdentifierNotFound + .into_error() + .with_debug_context("failed to get data marker attribute from a `CurrencyCode`") + })?; + let extended = crate::provider::Baked + .load(DataRequest { + id: DataIdentifierBorrowed::for_marker_attributes_and_locale( + marker_attributes, + &locale, + ), + ..Default::default() + })? + .payload; + + let patterns = crate::provider::Baked.load(Default::default())?.payload; + + let plural_rules = PluralRules::try_new_cardinal((&prefs).into())?; + + Ok(Self { + value_formatter, + currency_data: CurrencyFormatterData::Long { + extended, + patterns, + plural_rules, + }, + }) + } + + pub(crate) fn try_new_long_internal_unstable( + provider: &D, + value_formatter: V::Formatter, + prefs: CurrencyFormatterPreferences, + currency: CurrencyCode, + ) -> Result + where + D: ?Sized + + DataProvider + + DataProvider + + DataProvider, + { + let locale = CurrencyPatternsDataV1::make_locale(prefs.locale_preferences); + let marker_attributes = + DataMarkerAttributes::try_from_str(currency.0.as_str()).map_err(|_| { + DataErrorKind::IdentifierNotFound + .into_error() + .with_debug_context("failed to get data marker attribute from a `CurrencyCode`") + })?; + let extended = provider + .load(DataRequest { + id: DataIdentifierBorrowed::for_marker_attributes_and_locale( + marker_attributes, + &locale, + ), + ..Default::default() + })? + .payload; + + let patterns = provider.load(Default::default())?.payload; + + let plural_rules = PluralRules::try_new_cardinal_unstable(provider, (&prefs).into())?; + + Ok(Self { + value_formatter, + currency_data: CurrencyFormatterData::Long { + extended, + patterns, + plural_rules, + }, + }) + } } impl CurrencyFormatter { @@ -118,34 +312,12 @@ impl CurrencyFormatter { prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode, ) -> Result { - let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); - // TODO: We should depend on the currency format patterns directly and not depend on - // the decimal formatter. If we do use the decimal formatter, we need to take care of - // synchronization of different numbering systems (e.g. if DecimalFormatter falls back - // to a different numbering system than the one resolved for CurrencyFormatter). - let decimal_prefs = DecimalFormatterPreferences::from(&prefs); - let decimal_formatter = - DecimalFormatter::try_new(decimal_prefs, DecimalFormatterOptions::default())?; - - let req_id = decimal_prefs.nu_id(&locale); - let default_id = DataIdentifierBorrowed::for_locale(&locale); - let ids = req_id.into_iter().chain(core::iter::once(default_id)); - let essential = - load_with_fallback::(&crate::provider::Baked, ids)?.payload; - - if essential.get().standard_pattern().is_none() { - return Err(DataError::custom("missing standard pattern")); - } - - Ok(Self { - decimal_formatter, - data: CurrencyFormatterData::Essential { - essential, - width: Width::Short, - currency: *currency_code, - }, - _marker: PhantomData, - }) + Self::try_new_essential( + DecimalFormatter::try_new((&prefs).into(), Default::default())?, + prefs, + *currency_code, + Width::Short, + ) } /// Creates a new [`CurrencyFormatter`] for narrow formatting from compiled locale data. @@ -158,30 +330,12 @@ impl CurrencyFormatter { prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode, ) -> Result { - let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); - let decimal_prefs = DecimalFormatterPreferences::from(&prefs); - let decimal_formatter = - DecimalFormatter::try_new(decimal_prefs, DecimalFormatterOptions::default())?; - - let req_id = decimal_prefs.nu_id(&locale); - let default_id = DataIdentifierBorrowed::for_locale(&locale); - let ids = req_id.into_iter().chain(core::iter::once(default_id)); - let essential = - load_with_fallback::(&crate::provider::Baked, ids)?.payload; - - if essential.get().standard_pattern().is_none() { - return Err(DataError::custom("missing standard pattern")); - } - - Ok(Self { - decimal_formatter, - data: CurrencyFormatterData::Essential { - essential, - width: Width::Narrow, - currency: *currency_code, - }, - _marker: PhantomData, - }) + Self::try_new_essential( + DecimalFormatter::try_new((&prefs).into(), Default::default())?, + prefs, + *currency_code, + Width::Narrow, + ) } #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_short)] @@ -196,35 +350,13 @@ impl CurrencyFormatter { + DataProvider + DataProvider, { - let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); - // TODO: We should depend on the currency format patterns directly and not depend on - // the decimal formatter. If we do use the decimal formatter, we need to take care of - // synchronization of different numbering systems (e.g. if DecimalFormatter falls back - // to a different numbering system than the one resolved for CurrencyFormatter). - let decimal_prefs = DecimalFormatterPreferences::from(&prefs); - let decimal_formatter = DecimalFormatter::try_new_unstable( + Self::try_new_essential_unstable( provider, - decimal_prefs, - DecimalFormatterOptions::default(), - )?; - let req_id = decimal_prefs.nu_id(&locale); - let default_id = DataIdentifierBorrowed::for_locale(&locale); - let ids = req_id.into_iter().chain(core::iter::once(default_id)); - let essential = load_with_fallback::(provider, ids)?.payload; - - if essential.get().standard_pattern().is_none() { - return Err(DataError::custom("missing standard pattern")); - } - - Ok(Self { - decimal_formatter, - data: CurrencyFormatterData::Essential { - essential, - width: Width::Short, - currency: *currency_code, - }, - _marker: PhantomData, - }) + DecimalFormatter::try_new_unstable(provider, (&prefs).into(), Default::default())?, + prefs, + *currency_code, + Width::Short, + ) } #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_narrow)] @@ -239,31 +371,13 @@ impl CurrencyFormatter { + DataProvider + DataProvider, { - let locale = CurrencyEssentialsV1::make_locale(prefs.locale_preferences); - let decimal_prefs = DecimalFormatterPreferences::from(&prefs); - let decimal_formatter = DecimalFormatter::try_new_unstable( + Self::try_new_essential_unstable( provider, - decimal_prefs, - DecimalFormatterOptions::default(), - )?; - let req_id = decimal_prefs.nu_id(&locale); - let default_id = DataIdentifierBorrowed::for_locale(&locale); - let ids = req_id.into_iter().chain(core::iter::once(default_id)); - let essential = load_with_fallback::(provider, ids)?.payload; - - if essential.get().standard_pattern().is_none() { - return Err(DataError::custom("missing standard pattern")); - } - - Ok(Self { - decimal_formatter, - data: CurrencyFormatterData::Essential { - essential, - width: Width::Narrow, - currency: *currency_code, - }, - _marker: PhantomData, - }) + DecimalFormatter::try_new_unstable(provider, (&prefs).into(), Default::default())?, + prefs, + *currency_code, + Width::Narrow, + ) } icu_provider::gen_buffer_data_constructors!( @@ -301,40 +415,11 @@ impl CurrencyFormatter { prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode, ) -> Result { - let locale = CurrencyPatternsDataV1::make_locale(prefs.locale_preferences); - let decimal_formatter = - DecimalFormatter::try_new((&prefs).into(), DecimalFormatterOptions::default())?; - - let marker_attributes = DataMarkerAttributes::try_from_str(currency_code.0.as_str()) - .map_err(|_| { - DataErrorKind::IdentifierNotFound - .into_error() - .with_debug_context("failed to get data marker attribute from a `CurrencyCode`") - })?; - - let extended = crate::provider::Baked - .load(DataRequest { - id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - marker_attributes, - &locale, - ), - ..Default::default() - })? - .payload; - - let patterns = crate::provider::Baked.load(Default::default())?.payload; - - let plural_rules = PluralRules::try_new_cardinal((&prefs).into())?; - - Ok(Self { - decimal_formatter, - data: CurrencyFormatterData::Long { - extended, - patterns, - plural_rules, - }, - _marker: PhantomData, - }) + Self::try_new_long_internal( + DecimalFormatter::try_new((&prefs).into(), Default::default())?, + prefs, + *currency_code, + ) } #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new_long)] @@ -351,49 +436,21 @@ impl CurrencyFormatter { + DataProvider + DataProvider, { - let locale = CurrencyPatternsDataV1::make_locale(prefs.locale_preferences); - let decimal_formatter = DecimalFormatter::try_new_unstable( + Self::try_new_long_internal_unstable( provider, - (&prefs).into(), - DecimalFormatterOptions::default(), - )?; - - let marker_attributes = DataMarkerAttributes::try_from_str(currency_code.0.as_str()) - .map_err(|_| { - DataErrorKind::IdentifierNotFound - .into_error() - .with_debug_context("failed to get data marker attribute from a `CurrencyCode`") - })?; - let extended = provider - .load(DataRequest { - id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - marker_attributes, - &locale, - ), - ..Default::default() - })? - .payload; - - let patterns = provider.load(Default::default())?.payload; - - let plural_rules = PluralRules::try_new_cardinal_unstable(provider, (&prefs).into())?; - - Ok(Self { - decimal_formatter, - data: CurrencyFormatterData::Long { - extended, - patterns, - plural_rules, - }, - _marker: PhantomData, - }) + DecimalFormatter::try_new_unstable(provider, (&prefs).into(), Default::default())?, + prefs, + *currency_code, + ) } +} +impl CurrencyFormatter { /// Formats a [`FixedDecimal`] value. /// /// # Examples /// ``` - /// use icu::experimental::dimension::currency::formatter::{CurrencyFormatter, Decimal}; + /// use icu::experimental::dimension::currency::formatter::CurrencyFormatter; /// use icu::experimental::dimension::currency::CurrencyCode; /// use icu::locale::locale; /// use tinystr::*; @@ -401,20 +458,50 @@ impl CurrencyFormatter { /// /// let locale = locale!("en-US").into(); /// let currency_code = CurrencyCode(tinystr!(3, "USD")); - /// let fmt = CurrencyFormatter::::try_new_short(locale, ¤cy_code).unwrap(); + /// let fmt = CurrencyFormatter::try_new_short(locale, ¤cy_code).unwrap(); /// let value = "12345.67".parse().unwrap(); /// assert_writeable_eq!( /// fmt.format_fixed_decimal(&value), /// "$12,345.67" /// ); /// ``` + /// + /// ``` + /// use icu::experimental::dimension::currency::formatter::CurrencyFormatter; + /// use icu::experimental::dimension::currency::CurrencyCode; + /// use icu::locale::locale; + /// use tinystr::*; + /// use writeable::assert_writeable_eq; + /// + /// let locale = locale!("en-US").into(); + /// let currency_code = CurrencyCode(tinystr!(3, "USD")); + /// let fmt = CurrencyFormatter::try_new_compact_short(locale, ¤cy_code).unwrap(); + /// let value = "12345.67".parse().unwrap(); + /// assert_writeable_eq!(fmt.format_fixed_decimal(&value), "$12K"); + /// ``` + /// + /// ``` + /// use icu::experimental::dimension::currency::formatter::CurrencyFormatter; + /// use icu::experimental::dimension::currency::CurrencyCode; + /// use icu::locale::locale; + /// use tinystr::*; + /// use writeable::assert_writeable_eq; + /// + /// let currency_prefs = locale!("en-US").into(); + /// let currency_code = CurrencyCode(tinystr!(3, "USD")); + /// let fmt = CurrencyFormatter::try_new_compact_long(currency_prefs, ¤cy_code).unwrap(); + /// let value = "12345.67".parse().unwrap(); + /// assert_writeable_eq!(fmt.format_fixed_decimal(&value), "12 thousand US dollars"); + /// ``` pub fn format_fixed_decimal<'l>( &'l self, value: &'l FixedDecimal, ) -> impl Writeable + Display + 'l { + let formatted_value = V::format(&self.value_formatter, &value.absolute); + // TODO(#8146): Evaluate if FixedDecimal is the correct input type or if we should use // an exact decimal/money representation. - let (pattern, currency_str) = match &self.data { + let (pattern, currency_str) = match &self.currency_data { CurrencyFormatterData::Essential { essential, width, @@ -439,20 +526,22 @@ impl CurrencyFormatter { } }; - self.decimal_formatter.format_sign( + // Per UTS #35 (LDML / TR35 Part 3: Numbers, Section 3.2.1), when a pattern does not specify an + // explicit negative subpattern, the default negative format is formed by prepending the localized + // minus sign to the entire positive pattern (e.g., `-¤#,##0` producing `-$12K`). + // Therefore, `format_sign` is applied as the outermost wrapper around the glued currency string so + // that the minus sign modifies the full monetary expression rather than just the numeric significand. + V::format_sign( + &self.value_formatter, + pattern.interpolate((formatted_value, currency_str)), value.sign, - pattern.interpolate(( - self.decimal_formatter - .format_unsigned(icu_decimal::Cow::Borrowed(&value.absolute)), - currency_str, - )), ) } } // TODO: Discuss reusing the `load_with_fallback` helper from `icu_decimal` // (or moving it to a shared location) instead of duplicating it here. -fn load_with_fallback<'a, M: DataMarker>( +pub(crate) fn load_with_fallback<'a, M: DataMarker>( provider: &(impl DataProvider + ?Sized), ids: impl Iterator>, ) -> Result, DataError> { diff --git a/components/experimental/src/dimension/currency/long_compact_format.rs b/components/experimental/src/dimension/currency/long_compact_format.rs deleted file mode 100644 index 575ca5bab8f..00000000000 --- a/components/experimental/src/dimension/currency/long_compact_format.rs +++ /dev/null @@ -1,89 +0,0 @@ -// This file is part of ICU4X. For terms of use, please see the file -// called LICENSE at the top level of the ICU4X source tree -// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). - -#[cfg(test)] -mod tests { - use icu_locale_core::locale; - use tinystr::*; - use writeable::assert_writeable_eq; - - use crate::dimension::currency::CurrencyCode; - use crate::dimension::currency::long_compact_formatter::LongCompactCurrencyFormatter; - - #[test] - pub fn test_en_us() { - let currency_formatter_prefs = locale!("en-US").into(); - - let currency_code = CurrencyCode(tinystr!(3, "USD")); - let fmt = LongCompactCurrencyFormatter::try_new(currency_formatter_prefs, ¤cy_code) - .unwrap(); - - // Positive case - let positive_value = "12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value); - assert_writeable_eq!(formatted_currency, "12 thousand US dollars"); - - // Negative case - let negative_value = "-12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value); - assert_writeable_eq!(formatted_currency, "-12 thousand US dollars"); - } - - #[test] - pub fn test_en_us_millions() { - let currency_formatter_prefs = locale!("en-US").into(); - - let currency_code = CurrencyCode(tinystr!(3, "USD")); - let fmt = LongCompactCurrencyFormatter::try_new(currency_formatter_prefs, ¤cy_code) - .unwrap(); - - // Positive case - let positive_value = "12345000.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value); - assert_writeable_eq!(formatted_currency, "12 million US dollars"); - - // Negative case - let negative_value = "-12345000.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value); - assert_writeable_eq!(formatted_currency, "-12 million US dollars"); - } - - #[test] - pub fn test_fr_fr() { - let currency_formatter_prefs = locale!("fr-FR").into(); - - let currency_code = CurrencyCode(tinystr!(3, "USD")); - let fmt = LongCompactCurrencyFormatter::try_new(currency_formatter_prefs, ¤cy_code) - .unwrap(); - - // Positive case - let positive_value = "12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value); - assert_writeable_eq!(formatted_currency, "12 mille dollars des États-Unis"); - - // Negative case - let negative_value = "-12345.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value); - assert_writeable_eq!(formatted_currency, "-12 mille dollars des États-Unis"); - } - - #[test] - pub fn test_fr_fr_millions() { - let currency_formatter_prefs = locale!("fr-FR").into(); - - let currency_code = CurrencyCode(tinystr!(3, "USD")); - let fmt = LongCompactCurrencyFormatter::try_new(currency_formatter_prefs, ¤cy_code) - .unwrap(); - - // Positive case - let positive_value = "12345000.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&positive_value); - assert_writeable_eq!(formatted_currency, "12 millions dollars des États-Unis"); - - // Negative case - let negative_value = "-12345000.67".parse().unwrap(); - let formatted_currency = fmt.format_fixed_decimal(&negative_value); - assert_writeable_eq!(formatted_currency, "-12 millions dollars des États-Unis"); - } -} diff --git a/components/experimental/src/dimension/currency/long_compact_formatter.rs b/components/experimental/src/dimension/currency/long_compact_formatter.rs deleted file mode 100644 index b4437333834..00000000000 --- a/components/experimental/src/dimension/currency/long_compact_formatter.rs +++ /dev/null @@ -1,227 +0,0 @@ -// This file is part of ICU4X. For terms of use, please see the file -// called LICENSE at the top level of the ICU4X source tree -// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). - -use core::fmt::Display; - -use fixed_decimal::Decimal; -use icu_decimal::DecimalFormatter; -use icu_plurals::PluralRules; -use icu_provider::prelude::*; -use writeable::Writeable; - -use crate::dimension::currency::compact_formatter::CompactCurrencyFormatterPreferences; -use crate::dimension::provider::currency::{ - extended::CurrencyExtendedDataV1, patterns::CurrencyPatternsDataV1, -}; - -use super::CurrencyCode; - -extern crate alloc; - -/// A formatter for monetary values. -/// -/// [`LongCompactCurrencyFormatter`] supports: -/// 1. Rendering in the locale's currency system. -/// 2. Locale-sensitive grouping separator positions. -#[derive(Debug)] -pub struct LongCompactCurrencyFormatter { - /// Extended data for the currency formatter. - extended: DataPayload, - - /// Formatting patterns for each currency plural category. - patterns: DataPayload, - - decimal_formatter: DecimalFormatter, - - compact_data: DataPayload, - - /// A [`PluralRules`] to determine the plural category of the unit. - plural_rules: PluralRules, -} - -impl LongCompactCurrencyFormatter { - icu_provider::gen_buffer_data_constructors!( - ( - prefs: CompactCurrencyFormatterPreferences, - currency_code: &CurrencyCode - ) -> error: DataError, - functions: [ - try_new: skip, - try_new_with_buffer_provider, - try_new_unstable, - Self - ] - ); - - /// Creates a new [`LongCompactCurrencyFormatter`] from compiled locale data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) - #[cfg(feature = "compiled_data")] - pub fn try_new( - prefs: CompactCurrencyFormatterPreferences, - currency_code: &CurrencyCode, - ) -> Result { - let decimal_formatter = DecimalFormatter::try_new((&prefs).into(), Default::default())?; - - let compact_data = DataProvider::::load( - &icu_decimal::provider::Baked, - DataRequest { - id: DataIdentifierBorrowed::for_locale( - &icu_decimal::provider::DecimalCompactLongV1::make_locale( - prefs.locale_preferences, - ), - ), - ..Default::default() - }, - )? - .payload - .cast(); - - let marker_attributes = DataMarkerAttributes::try_from_str(currency_code.0.as_str()) - .map_err(|_| { - DataErrorKind::IdentifierNotFound - .into_error() - .with_debug_context("failed to get data marker attribute from a `CurrencyCode`") - })?; - - let locale = &CurrencyPatternsDataV1::make_locale(prefs.locale_preferences); - - let extended = crate::provider::Baked - .load(DataRequest { - id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - marker_attributes, - locale, - ), - ..Default::default() - })? - .payload; - - let patterns = crate::provider::Baked.load(Default::default())?.payload; - - let plural_rules = PluralRules::try_new_cardinal((&prefs).into())?; - - Ok(Self { - extended, - patterns, - decimal_formatter, - compact_data, - plural_rules, - }) - } - - #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)] - pub fn try_new_unstable( - provider: &D, - prefs: CompactCurrencyFormatterPreferences, - currency_code: &CurrencyCode, - ) -> Result - where - D: ?Sized - + DataProvider - + DataProvider - + DataProvider - + DataProvider - + DataProvider - + DataProvider, - { - let locale = CurrencyPatternsDataV1::make_locale(prefs.locale_preferences); - - let marker_attributes = DataMarkerAttributes::try_from_str(currency_code.0.as_str()) - .map_err(|_| { - DataErrorKind::IdentifierNotFound - .into_error() - .with_debug_context("failed to get data marker attribute from a `CurrencyCode`") - })?; - - let extended = provider - .load(DataRequest { - id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - marker_attributes, - &locale, - ), - ..Default::default() - })? - .payload; - - let patterns = provider.load(Default::default())?.payload; - - let plural_rules = PluralRules::try_new_cardinal_unstable(provider, (&prefs).into())?; - - let decimal_formatter = - DecimalFormatter::try_new_unstable(provider, (&prefs).into(), Default::default())?; - - let compact_data = DataProvider::::load( - provider, - DataRequest { - id: DataIdentifierBorrowed::for_locale( - &icu_decimal::provider::DecimalCompactLongV1::make_locale( - prefs.locale_preferences, - ), - ), - ..Default::default() - }, - )? - .payload - .cast(); - - Ok(Self { - extended, - patterns, - decimal_formatter, - compact_data, - plural_rules, - }) - } - - /// Formats in the long format a [`Decimal`] value for the given currency code. - /// - /// # Examples - /// ``` - /// use icu::experimental::dimension::currency::long_compact_formatter::LongCompactCurrencyFormatter; - /// use icu::experimental::dimension::currency::CurrencyCode; - /// use icu::locale::locale; - /// use tinystr::*; - /// use writeable::assert_writeable_eq; - /// - /// let currency_prefs = locale!("en-US").into(); - /// let currency_code = CurrencyCode(tinystr!(3, "USD")); - /// let fmt = LongCompactCurrencyFormatter::try_new(currency_prefs, ¤cy_code).unwrap(); - /// let value = "12345.67".parse().unwrap(); - /// assert_writeable_eq!(fmt.format_fixed_decimal(&value), "12 thousand US dollars"); - /// ``` - pub fn format_fixed_decimal<'l>(&'l self, value: &'l Decimal) -> impl Writeable + Display + 'l { - let operands = value.into(); - - let display_name = self - .extended - .get() - .display_names - .get(operands, &self.plural_rules); - - let pattern = self - .patterns - .get() - .patterns - .get(operands, &self.plural_rules); - - let (compact_pattern, significand) = self - .compact_data - .get() - .get_pattern_and_significand(&value.absolute, &self.plural_rules); - - self.decimal_formatter.format_sign( - value.sign, - pattern.interpolate(( - compact_pattern - .unwrap_or(icu_pattern::SinglePlaceholderPattern::PASS_THROUGH) - .interpolate([self - .decimal_formatter - .format_unsigned(icu_decimal::Cow::Owned(significand))]), - display_name, - )), - ) - } -} diff --git a/components/experimental/src/dimension/currency/mod.rs b/components/experimental/src/dimension/currency/mod.rs index 8752ac9dc8c..0a8ee9e62cd 100644 --- a/components/experimental/src/dimension/currency/mod.rs +++ b/components/experimental/src/dimension/currency/mod.rs @@ -4,15 +4,16 @@ use tinystr::TinyAsciiStr; -pub mod compact_format; pub mod compact_formatter; -pub mod format; pub mod formatter; -pub mod long_compact_format; -pub mod long_compact_formatter; pub mod options; /// A currency code, such as "USD" or "EUR". #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[allow(clippy::exhaustive_structs)] // newtype pub struct CurrencyCode(pub TinyAsciiStr<3>); + +#[cfg(test)] +pub mod compact_format; +#[cfg(test)] +pub mod format; diff --git a/components/experimental/src/dimension/currency/options.rs b/components/experimental/src/dimension/currency/options.rs index a63e2421dc1..3d09dc17ac1 100644 --- a/components/experimental/src/dimension/currency/options.rs +++ b/components/experimental/src/dimension/currency/options.rs @@ -12,28 +12,13 @@ use serde::{Deserialize, Serialize}; #[derive(Copy, Debug, Eq, PartialEq, Clone, Default, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] -pub struct CurrencyFormatterOptions { - // TODO: Remove this option after migrating all formatters (including CompactCurrencyFormatter) - // to use specific constructors, as the width is determined at construction time. - /// The width of the currency format. - pub width: Width, -} - -impl From for CurrencyFormatterOptions { - fn from(width: Width) -> Self { - Self { width } - } -} +pub struct CurrencyFormatterOptions {} -#[derive(Default, Debug, Eq, PartialEq, Clone, Copy, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[non_exhaustive] -pub enum Width { +#[derive(Debug, Clone, Copy)] +pub(crate) enum Width { /// Format the currency with the standard (short) currency symbol. /// /// For example, 1 USD formats as "$1.00" in en-US and "US$1" in most other locales. - #[cfg_attr(feature = "serde", serde(rename = "short"))] - #[default] Short, /// Format the currency with the narrow currency symbol. @@ -42,6 +27,5 @@ pub enum Width { /// currency is being represented. /// /// For example, 1 USD formats as "$1.00" in most locales. - #[cfg_attr(feature = "serde", serde(rename = "narrow"))] Narrow, } From 3a5e189c87dc5387d64de70787dfb5f4faadfddc Mon Sep 17 00:00:00 2001 From: Robert Bastian <4706271+robertbastian@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:32:49 +0200 Subject: [PATCH 2/3] abstractformatter --- components/decimal/src/abstract_formatter.rs | 67 +++++++++ components/decimal/src/compact_formatter.rs | 132 ++++++++---------- components/decimal/src/decimal_formatter.rs | 6 +- components/decimal/src/lib.rs | 5 + .../dimension/currency/compact_formatter.rs | 4 +- .../src/dimension/currency/formatter.rs | 85 ++--------- 6 files changed, 150 insertions(+), 149 deletions(-) create mode 100644 components/decimal/src/abstract_formatter.rs diff --git a/components/decimal/src/abstract_formatter.rs b/components/decimal/src/abstract_formatter.rs new file mode 100644 index 00000000000..52d2697e99d --- /dev/null +++ b/components/decimal/src/abstract_formatter.rs @@ -0,0 +1,67 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use fixed_decimal::UnsignedDecimal; +use writeable::Writeable; + +use crate::{ + CompactDecimalFormatter, DecimalFormatter, FormattedSign, FormattedUnsignedCompactDecimal, + FormattedUnsignedDecimal, +}; + +pub trait Sealed {} + +/// A trait representing an abstract number formatter. +/// +/// This is a building block for more complicated formatters, like currency or units. +pub trait AbstractFormatter: core::fmt::Debug + Sealed { + #[doc(hidden)] + type FormattedUnsigned<'a>: Writeable + where + Self: 'a; + + #[doc(hidden)] + fn format_unsigned<'a>(&'a self, value: &'a UnsignedDecimal) -> Self::FormattedUnsigned<'a>; + + #[doc(hidden)] + fn format_sign<'a, W: Writeable>( + &'a self, + value: W, + sign: fixed_decimal::Sign, + ) -> FormattedSign<'a, W>; +} + +impl Sealed for DecimalFormatter {} +impl AbstractFormatter for DecimalFormatter { + type FormattedUnsigned<'a> = FormattedUnsignedDecimal<'a>; + + fn format_unsigned<'a>(&'a self, value: &'a UnsignedDecimal) -> Self::FormattedUnsigned<'a> { + self.format_unsigned(crate::Cow::Borrowed(value)) + } + + fn format_sign<'a, W: Writeable>( + &'a self, + value: W, + sign: fixed_decimal::Sign, + ) -> FormattedSign<'a, W> { + self.format_sign(sign, value) + } +} + +impl Sealed for CompactDecimalFormatter {} +impl AbstractFormatter for CompactDecimalFormatter { + type FormattedUnsigned<'a> = FormattedUnsignedCompactDecimal<'a>; + + fn format_unsigned<'a>(&'a self, value: &'a UnsignedDecimal) -> Self::FormattedUnsigned<'a> { + self.format_unsigned(value) + } + + fn format_sign<'a, W: Writeable>( + &'a self, + value: W, + sign: fixed_decimal::Sign, + ) -> FormattedSign<'a, W> { + self.decimal_formatter.format_sign(sign, value) + } +} diff --git a/components/decimal/src/compact_formatter.rs b/components/decimal/src/compact_formatter.rs index 1024d8be54c..60c81b06eff 100644 --- a/components/decimal/src/compact_formatter.rs +++ b/components/decimal/src/compact_formatter.rs @@ -76,8 +76,7 @@ use writeable::Writeable; #[derive(Debug)] pub struct CompactDecimalFormatter { plural_rules: PluralRules, - #[doc(hidden)] - pub decimal_formatter: DecimalFormatter, + pub(crate) decimal_formatter: DecimalFormatter, compact_data: DataPayload::DataStruct>>, } @@ -353,19 +352,73 @@ impl CompactDecimalFormatter { .format_sign(value.sign, self.format_unsigned(&value.absolute)) } - #[doc(hidden)] - pub fn format_unsigned<'a>( + pub(crate) fn format_unsigned<'a>( &'a self, value: &UnsignedDecimal, ) -> FormattedUnsignedCompactDecimal<'a> { - let (compact_pattern, significand) = self + let log10_type = value.nonzero_magnitude_start(); + + let entry = self + .compact_data + .get() + .0 + .iter() + .enumerate() + .filter(|&(_, t)| i16::from(t.sized) <= log10_type) + .last(); + + let exponent = entry + .map(|(_, t)| t.sized - t.variable.get_default().0.get()) + .unwrap_or_default(); + let rounding_magnitude = if log10_type > i16::from(exponent) { + // If we have at least 2 digits before the decimal point, + // round to eliminate the fractional part. + i16::from(exponent) + } else { + // …otherwise, round to two significant digits + log10_type - 1 + }; + if let Some(t) = self .compact_data .get() - .get_pattern_and_significand(value, &self.plural_rules); + .0 + .get(entry.map(|(idx, _)| idx + 1).unwrap_or_default()) + { + let next_exponent = t.sized - t.variable.get_default().0.get(); + + let rounds_to_next_exponent = log10_type + 1 == i16::from(next_exponent) + && value.digit_at(rounding_magnitude - 1) >= 5 + && (rounding_magnitude..=log10_type).all(|m| value.digit_at(m) == 9); + + // We got bumped up a magnitude by rounding. + if rounds_to_next_exponent { + return FormattedUnsignedCompactDecimal { + pattern: Some(t.variable.get(1.into(), &self.plural_rules).1), + significand: UnsignedDecimal::ONE, + decimal_formatter: &self.decimal_formatter, + }; + } + } FormattedUnsignedCompactDecimal { - pattern: compact_pattern, - significand, + pattern: entry.map(|(_, t)| { + t.variable + .get( + (&value + .clone() + .rounded(rounding_magnitude) + .multiplied_pow10(-i16::from(exponent)) + .trimmed_end()) + .into(), + &self.plural_rules, + ) + .1 + }), + significand: value + .clone() + .rounded(rounding_magnitude) + .multiplied_pow10(-i16::from(exponent)) + .trimmed_end(), decimal_formatter: &self.decimal_formatter, } } @@ -549,7 +602,7 @@ impl CompactDecimalFormatter { } } -#[doc(hidden)] +#[doc(hidden)] // TODO(#3647): should be private #[derive(Debug)] pub struct FormattedUnsignedCompactDecimal<'l> { pattern: Option<&'l Pattern>, @@ -568,67 +621,6 @@ impl Writeable for FormattedUnsignedCompactDecimal<'_> { } } -impl<'a, P: PatternBackend> CompactPatterns<'a, P> { - /// Gets the compact pattern and significand for the given decimal - pub fn get_pattern_and_significand( - &'a self, - value: &UnsignedDecimal, - rules: &PluralRules, - ) -> (Option<&'a Pattern

>, UnsignedDecimal) { - let log10_type = value.nonzero_magnitude_start(); - - let entry = self - .0 - .iter() - .enumerate() - .filter(|&(_, t)| i16::from(t.sized) <= log10_type) - .last(); - - let exponent = entry - .map(|(_, t)| t.sized - t.variable.get_default().0.get()) - .unwrap_or_default(); - - let rounding_magnitude = if log10_type > i16::from(exponent) { - // If we have at least 2 digits before the decimal point, - // round to eliminate the fractional part. - i16::from(exponent) - } else { - // …otherwise, round to two significant digits - log10_type - 1 - }; - - if let Some(t) = self - .0 - .get(entry.map(|(idx, _)| idx + 1).unwrap_or_default()) - { - let next_exponent = t.sized - t.variable.get_default().0.get(); - - let rounds_to_next_exponent = log10_type + 1 == i16::from(next_exponent) - && value.digit_at(rounding_magnitude - 1) >= 5 - && (rounding_magnitude..=log10_type).all(|m| value.digit_at(m) == 9); - - // We got bumped up a magnitude by rounding. - if rounds_to_next_exponent { - return ( - Some(t.variable.get(1.into(), rules).1), - UnsignedDecimal::ONE, - ); - } - } - - let significand = value - .clone() - .rounded(rounding_magnitude) - .multiplied_pow10(-i16::from(exponent)) - .trimmed_end(); - - ( - entry.map(|(_, t)| t.variable.get((&significand).into(), rules).1), - significand, - ) - } -} - #[cfg(feature = "serde")] #[cfg(test)] mod tests { diff --git a/components/decimal/src/decimal_formatter.rs b/components/decimal/src/decimal_formatter.rs index fd2350a8337..c39775103b8 100644 --- a/components/decimal/src/decimal_formatter.rs +++ b/components/decimal/src/decimal_formatter.rs @@ -118,8 +118,7 @@ impl DecimalFormatter { )) } - #[doc(hidden)] // TODO(#3647): should be private - pub fn format_unsigned<'l>( + pub(crate) fn format_unsigned<'l>( &'l self, value: Cow<'l, UnsignedDecimal>, ) -> FormattedUnsignedDecimal<'l> { @@ -131,8 +130,7 @@ impl DecimalFormatter { } } - #[doc(hidden)] // TODO(#3647): should be private - pub fn format_sign<'l, T>(&'l self, sign: Sign, value: T) -> FormattedSign<'l, T> { + pub(crate) fn format_sign<'l, T>(&'l self, sign: Sign, value: T) -> FormattedSign<'l, T> { FormattedSign { sign: match sign { Sign::None => None, diff --git a/components/decimal/src/lib.rs b/components/decimal/src/lib.rs index 09f81ba3910..76cb56cbedb 100644 --- a/components/decimal/src/lib.rs +++ b/components/decimal/src/lib.rs @@ -113,6 +113,8 @@ impl<'a, T> core::ops::Deref for Cow<'a, T> { } } +#[cfg(feature = "unstable")] +mod abstract_formatter; #[cfg(feature = "unstable")] mod compact_formatter; mod decimal_formatter; @@ -132,6 +134,9 @@ pub use decimal_formatter::{ #[cfg(feature = "unstable")] pub use compact_formatter::{CompactDecimalFormatter, FormattedUnsignedCompactDecimal}; +#[cfg(feature = "unstable")] +pub use abstract_formatter::AbstractFormatter; + pub use preferences::DecimalFormatterPreferences; /// Types that can be fed to [`DecimalFormatter`] and their utilities diff --git a/components/experimental/src/dimension/currency/compact_formatter.rs b/components/experimental/src/dimension/currency/compact_formatter.rs index 314e2e31a7a..0a70a92f2b4 100644 --- a/components/experimental/src/dimension/currency/compact_formatter.rs +++ b/components/experimental/src/dimension/currency/compact_formatter.rs @@ -6,10 +6,10 @@ use crate::dimension::provider::currency::{essentials::*, extended::*, patterns: use icu_decimal::CompactDecimalFormatter; use icu_provider::prelude::*; -use super::formatter::{Compact, CurrencyFormatter, CurrencyFormatterPreferences}; +use super::formatter::{CurrencyFormatter, CurrencyFormatterPreferences}; use super::{CurrencyCode, options::Width}; -impl CurrencyFormatter { +impl CurrencyFormatter { icu_provider::gen_buffer_data_constructors!( (prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode) -> error: DataError, functions: [ diff --git a/components/experimental/src/dimension/currency/formatter.rs b/components/experimental/src/dimension/currency/formatter.rs index e35fcdb2057..1ac955f5455 100644 --- a/components/experimental/src/dimension/currency/formatter.rs +++ b/components/experimental/src/dimension/currency/formatter.rs @@ -4,12 +4,9 @@ use core::fmt::Display; -use fixed_decimal::{Decimal as FixedDecimal, UnsignedDecimal}; +use fixed_decimal::Decimal as FixedDecimal; use icu_decimal::preferences::CompactDecimalFormatterPreferences; -use icu_decimal::{ - CompactDecimalFormatter, FormattedUnsignedCompactDecimal, FormattedUnsignedDecimal, -}; -use icu_decimal::{DecimalFormatter, DecimalFormatterPreferences}; +use icu_decimal::{AbstractFormatter, DecimalFormatter, DecimalFormatterPreferences}; use icu_locale_core::preferences::{define_preferences, prefs_convert}; use icu_plurals::{PluralRules, PluralRulesPreferences}; use icu_provider::prelude::*; @@ -47,64 +44,6 @@ prefs_convert!( { numbering_system } ); -/// A trait for value representation in currency formatting. -pub trait ValueRepresentation { - /// Data required for this specific value representation. - type Formatter: core::fmt::Debug; - type Formatted<'a>: Writeable; - - fn format<'a>(data: &'a Self::Formatter, value: &'a UnsignedDecimal) -> Self::Formatted<'a>; - fn format_sign( - data: &Self::Formatter, - value: impl Writeable, - sign: fixed_decimal::Sign, - ) -> impl Writeable + Display; -} - -/// Representation for decimal currency formatting. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct Decimal; - -impl ValueRepresentation for Decimal { - type Formatter = DecimalFormatter; - type Formatted<'a> = FormattedUnsignedDecimal<'a>; - - fn format<'a>(data: &'a Self::Formatter, value: &'a UnsignedDecimal) -> Self::Formatted<'a> { - data.format_unsigned(icu_decimal::Cow::Borrowed(value)) - } - - fn format_sign( - data: &Self::Formatter, - value: impl Writeable, - sign: fixed_decimal::Sign, - ) -> impl Writeable + Display { - data.format_sign(sign, value) - } -} - -/// Representation for compact currency formatting. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct Compact; - -impl ValueRepresentation for Compact { - type Formatter = CompactDecimalFormatter; - type Formatted<'a> = FormattedUnsignedCompactDecimal<'a>; - - fn format<'a>(data: &'a Self::Formatter, value: &'a UnsignedDecimal) -> Self::Formatted<'a> { - data.format_unsigned(value) - } - - fn format_sign( - data: &Self::Formatter, - value: impl Writeable, - sign: fixed_decimal::Sign, - ) -> impl Writeable + Display { - data.decimal_formatter.format_sign(sign, value) - } -} - #[derive(Debug)] pub(crate) enum CurrencyFormatterData { Essential { @@ -127,15 +66,15 @@ pub(crate) enum CurrencyFormatterData { /// /// Read more about the options in the [`super::options`] module. #[derive(Debug)] -pub struct CurrencyFormatter { - value_formatter: V::Formatter, +pub struct CurrencyFormatter { + value_formatter: V, currency_data: CurrencyFormatterData, } -impl CurrencyFormatter { +impl CurrencyFormatter { #[cfg(feature = "compiled_data")] pub(crate) fn try_new_essential( - value_formatter: V::Formatter, + value_formatter: V, prefs: CurrencyFormatterPreferences, currency: CurrencyCode, width: Width, @@ -165,7 +104,7 @@ impl CurrencyFormatter { pub(crate) fn try_new_essential_unstable( provider: &D, - value_formatter: V::Formatter, + value_formatter: V, prefs: CurrencyFormatterPreferences, currency: CurrencyCode, width: Width, @@ -197,7 +136,7 @@ impl CurrencyFormatter { #[cfg(feature = "compiled_data")] pub(crate) fn try_new_long_internal( - value_formatter: V::Formatter, + value_formatter: V, prefs: CurrencyFormatterPreferences, currency: CurrencyCode, ) -> Result { @@ -234,7 +173,7 @@ impl CurrencyFormatter { pub(crate) fn try_new_long_internal_unstable( provider: &D, - value_formatter: V::Formatter, + value_formatter: V, prefs: CurrencyFormatterPreferences, currency: CurrencyCode, ) -> Result @@ -276,7 +215,7 @@ impl CurrencyFormatter { } } -impl CurrencyFormatter { +impl CurrencyFormatter { icu_provider::gen_buffer_data_constructors!( (prefs: CurrencyFormatterPreferences, currency_code: &CurrencyCode) -> error: DataError, functions: [ @@ -445,7 +384,7 @@ impl CurrencyFormatter { } } -impl CurrencyFormatter { +impl CurrencyFormatter { /// Formats a [`FixedDecimal`] value. /// /// # Examples @@ -497,7 +436,7 @@ impl CurrencyFormatter { &'l self, value: &'l FixedDecimal, ) -> impl Writeable + Display + 'l { - let formatted_value = V::format(&self.value_formatter, &value.absolute); + let formatted_value = V::format_unsigned(&self.value_formatter, &value.absolute); // TODO(#8146): Evaluate if FixedDecimal is the correct input type or if we should use // an exact decimal/money representation. From 46749eb1a50138832eb91b1a0600f60aa0ff795f Mon Sep 17 00:00:00 2001 From: Younies Mahmoud Date: Wed, 8 Jul 2026 13:40:12 +0000 Subject: [PATCH 3/3] fix(decimal): Remove unused PatternBackend import TAG=agy CONV=85455991-6f0d-44c3-87be-4411f043a296 --- components/decimal/src/compact_formatter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/decimal/src/compact_formatter.rs b/components/decimal/src/compact_formatter.rs index 60c81b06eff..9e31cf30c2c 100644 --- a/components/decimal/src/compact_formatter.rs +++ b/components/decimal/src/compact_formatter.rs @@ -16,7 +16,7 @@ use crate::{ #[cfg(feature = "alloc")] use alloc::string::String; use fixed_decimal::UnsignedDecimal; -use icu_pattern::{Pattern, PatternBackend, SinglePlaceholder}; +use icu_pattern::{Pattern, SinglePlaceholder}; use icu_plurals::PluralRules; use icu_provider::DataError; use icu_provider::{marker::ErasedMarker, prelude::*};