-
Notifications
You must be signed in to change notification settings - Fork 182
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Long Compact Currency Formatter (#5456)
- Loading branch information
Showing
3 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
127 changes: 127 additions & 0 deletions
127
components/experimental/src/dimension/currency/long_compact_format.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// 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 super::CurrencyCode; | ||
use crate::compactdecimal::CompactDecimalFormatter; | ||
use crate::dimension::provider::currency_patterns::CurrencyPatternsDataV1; | ||
use crate::dimension::provider::extended_currency::CurrencyExtendedDataV1; | ||
use fixed_decimal::SignedFixedDecimal; | ||
use icu_plurals::PluralRules; | ||
use writeable::Writeable; | ||
|
||
pub struct FormattedLongCompactCurrency<'l> { | ||
pub(crate) signed_fixed_decimal: &'l SignedFixedDecimal, | ||
// TODO: use this if the display name is not exist and make the extended data optional. | ||
pub(crate) _currency_code: CurrencyCode, | ||
pub(crate) extended: &'l CurrencyExtendedDataV1<'l>, | ||
pub(crate) patterns: &'l CurrencyPatternsDataV1<'l>, | ||
pub(crate) compact_decimal_formatter: &'l CompactDecimalFormatter, | ||
pub(crate) plural_rules: &'l PluralRules, | ||
} | ||
|
||
writeable::impl_display_with_writeable!(FormattedLongCompactCurrency<'_>); | ||
|
||
impl Writeable for FormattedLongCompactCurrency<'_> { | ||
fn write_to<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error> | ||
where | ||
W: core::fmt::Write + ?Sized, | ||
{ | ||
let operands = self.signed_fixed_decimal.into(); | ||
|
||
let display_name = self.extended.display_names.get(operands, self.plural_rules); | ||
let pattern = self.patterns.patterns.get(operands, self.plural_rules); | ||
|
||
let formatted_value = self | ||
.compact_decimal_formatter | ||
.format_fixed_decimal(self.signed_fixed_decimal); | ||
let interpolated = pattern.interpolate((formatted_value, display_name)); | ||
interpolated.write_to(sink) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use icu_locale_core::locale; | ||
use tinystr::*; | ||
use writeable::assert_writeable_eq; | ||
|
||
use crate::dimension::currency::long_compact_formatter::LongCompactCurrencyFormatter; | ||
use crate::dimension::currency::CurrencyCode; | ||
|
||
#[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, currency_code); | ||
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, currency_code); | ||
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, currency_code); | ||
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, currency_code); | ||
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, currency_code); | ||
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, currency_code); | ||
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, currency_code); | ||
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, currency_code); | ||
assert_writeable_eq!(formatted_currency, "-12 millions dollars des États-Unis"); | ||
} | ||
} |
217 changes: 217 additions & 0 deletions
217
components/experimental/src/dimension/currency/long_compact_formatter.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
// 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 ). | ||
|
||
//! Experimental. | ||
use fixed_decimal::SignedFixedDecimal; | ||
use icu_plurals::{PluralRules, PluralRulesPreferences}; | ||
use icu_provider::prelude::*; | ||
|
||
use crate::{ | ||
compactdecimal::CompactDecimalFormatter, | ||
compactdecimal::CompactDecimalFormatterOptions, | ||
compactdecimal::CompactDecimalFormatterPreferences, | ||
dimension::provider::{ | ||
currency_patterns::CurrencyPatternsDataV1Marker, | ||
extended_currency::CurrencyExtendedDataV1Marker, | ||
}, | ||
}; | ||
use icu_locale_core::preferences::{ | ||
define_preferences, extensions::unicode::keywords::NumberingSystem, prefs_convert, | ||
}; | ||
|
||
use super::{long_compact_format::FormattedLongCompactCurrency, CurrencyCode}; | ||
|
||
extern crate alloc; | ||
|
||
define_preferences!( | ||
/// The preferences for currency formatting. | ||
[Copy] | ||
LongCompactCurrencyFormatterPreferences, | ||
{ | ||
numbering_system: NumberingSystem | ||
} | ||
); | ||
|
||
prefs_convert!( | ||
LongCompactCurrencyFormatterPreferences, | ||
CompactDecimalFormatterPreferences, | ||
{ numbering_system } | ||
); | ||
prefs_convert!( | ||
LongCompactCurrencyFormatterPreferences, | ||
PluralRulesPreferences | ||
); | ||
|
||
/// A formatter for monetary values. | ||
/// | ||
/// [`LongCompactCurrencyFormatter`] supports: | ||
/// 1. Rendering in the locale's currency system. | ||
/// 2. Locale-sensitive grouping separator positions. | ||
pub struct LongCompactCurrencyFormatter { | ||
/// Extended data for the currency formatter. | ||
extended: DataPayload<CurrencyExtendedDataV1Marker>, | ||
|
||
/// Formatting patterns for each currency plural category. | ||
patterns: DataPayload<CurrencyPatternsDataV1Marker>, | ||
|
||
/// A [`CompactDecimalFormatter`] to format the currency value in compact form. | ||
compact_decimal_formatter: CompactDecimalFormatter, | ||
|
||
/// A [`PluralRules`] to determine the plural category of the unit. | ||
plural_rules: PluralRules, | ||
} | ||
|
||
impl LongCompactCurrencyFormatter { | ||
icu_provider::gen_any_buffer_data_constructors!( | ||
( | ||
prefs: LongCompactCurrencyFormatterPreferences, | ||
currency_code: &CurrencyCode | ||
) -> error: DataError, | ||
functions: [ | ||
try_new: skip, | ||
try_new_with_any_provider, | ||
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: LongCompactCurrencyFormatterPreferences, | ||
currency_code: &CurrencyCode, | ||
) -> Result<Self, DataError> { | ||
let compact_decimal_formatter = CompactDecimalFormatter::try_new_long( | ||
(&prefs).into(), | ||
CompactDecimalFormatterOptions::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 locale = &DataLocale::from_preferences_locale::<CurrencyPatternsDataV1Marker>( | ||
prefs.locale_prefs, | ||
); | ||
|
||
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, | ||
compact_decimal_formatter, | ||
plural_rules, | ||
}) | ||
} | ||
|
||
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] | ||
pub fn try_new_unstable<D>( | ||
provider: &D, | ||
prefs: LongCompactCurrencyFormatterPreferences, | ||
currency_code: &CurrencyCode, | ||
) -> Result<Self, DataError> | ||
where | ||
D: ?Sized | ||
+ DataProvider< | ||
crate::dimension::provider::extended_currency::CurrencyExtendedDataV1Marker, | ||
> + DataProvider< | ||
crate::dimension::provider::currency_patterns::CurrencyPatternsDataV1Marker, | ||
> + DataProvider<icu_decimal::provider::DecimalSymbolsV2Marker> | ||
+ DataProvider<icu_decimal::provider::DecimalDigitsV1Marker> | ||
+ DataProvider<icu_plurals::provider::CardinalV1Marker> | ||
+ DataProvider<crate::compactdecimal::provider::LongCompactDecimalFormatDataV1Marker>, | ||
{ | ||
let locale = | ||
DataLocale::from_preferences_locale::<CurrencyPatternsDataV1Marker>(prefs.locale_prefs); | ||
|
||
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 compact_decimal_formatter = CompactDecimalFormatter::try_new_long_unstable( | ||
provider, | ||
(&prefs).into(), | ||
CompactDecimalFormatterOptions::default(), | ||
)?; | ||
|
||
Ok(Self { | ||
extended, | ||
patterns, | ||
compact_decimal_formatter, | ||
plural_rules, | ||
}) | ||
} | ||
|
||
/// Formats in the long format a [`SignedFixedDecimal`] 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::Writeable; | ||
/// | ||
/// 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(); | ||
/// let formatted_currency = fmt.format_fixed_decimal(&value, currency_code); | ||
/// let mut sink = String::new(); | ||
/// formatted_currency.write_to(&mut sink).unwrap(); | ||
/// assert_eq!(sink.as_str(), "12 thousand US dollars"); | ||
/// ``` | ||
pub fn format_fixed_decimal<'l>( | ||
&'l self, | ||
value: &'l SignedFixedDecimal, | ||
currency_code: CurrencyCode, | ||
) -> FormattedLongCompactCurrency<'l> { | ||
FormattedLongCompactCurrency { | ||
signed_fixed_decimal: value, | ||
_currency_code: currency_code, | ||
extended: self.extended.get(), | ||
patterns: self.patterns.get(), | ||
compact_decimal_formatter: &self.compact_decimal_formatter, | ||
plural_rules: &self.plural_rules, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters