-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement LongCurrencyFormatter
for Long Currency Formatting
#5351
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
0dbd034
Create LongCurrencyFormatter
younies 852edb6
chore: Add `CurrencyPatternsDataV1` to `LongFormattedCurrency`
younies a40cf58
refactor: Update LongCurrencyFormatter constructor to accept currency…
younies 071721b
refactor: Update LongCurrencyFormatter to include plural rules in con…
younies e9c607a
refactor: Add PluralCategory conversion for Count enum in extended_cu…
younies fcf7844
Fix and implement
younies a9e6ca9
refactor: Update currency formatting to include currency code in long…
younies 58a180a
Fix doctest
younies 9ac101e
refactor: Update currency formatting to include currency code as refe…
younies a6fc125
Revert "refactor: Update currency formatting to include currency code…
younies b08407e
refactor: Update LongFormattedCurrency to use interpolated pattern in…
younies 695fd05
refactor: fix
younies 993a4e4
refactor: add a todo and panic
younies e85e959
Add comment and fix a return error
younies f57137b
Merge branch 'main' into long-currency-formatter
younies 9642f32
Merge remote-tracking branch 'upstream' into long-currency-formatter
younies 562be18
Fix comments
younies 05e805e
Merge branch 'main' into long-currency-formatter
younies File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
101 changes: 101 additions & 0 deletions
101
components/experimental/src/dimension/currency/long_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,101 @@ | ||
// 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::FixedDecimal; | ||
|
||
use icu_decimal::FixedDecimalFormatter; | ||
use icu_plurals::PluralRules; | ||
use writeable::Writeable; | ||
|
||
use crate::dimension::provider::currency_patterns::CurrencyPatternsDataV1; | ||
use crate::dimension::provider::extended_currency::CurrencyExtendedDataV1; | ||
|
||
use super::CurrencyCode; | ||
|
||
pub struct LongFormattedCurrency<'l> { | ||
pub(crate) value: &'l FixedDecimal, | ||
// 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) fixed_decimal_formatter: &'l FixedDecimalFormatter, | ||
pub(crate) plural_rules: &'l PluralRules, | ||
} | ||
|
||
writeable::impl_display_with_writeable!(LongFormattedCurrency<'_>); | ||
|
||
impl<'l> Writeable for LongFormattedCurrency<'l> { | ||
fn write_to<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error> | ||
where | ||
W: core::fmt::Write + ?Sized, | ||
{ | ||
let plural_category = self.plural_rules.category_for(self.value); | ||
let display_name = self.extended.display_names.get_str(plural_category); | ||
let pattern = self.patterns.patterns.get_pattern(plural_category); | ||
let formatted_value = self.fixed_decimal_formatter.format(self.value); | ||
let interpolated = pattern.interpolate((formatted_value, display_name)); | ||
interpolated.write_to(sink) | ||
} | ||
} | ||
|
||
// TODO: add more tests for this module to cover more locales & currencies. | ||
#[cfg(test)] | ||
mod tests { | ||
use icu_locale_core::locale; | ||
use tinystr::*; | ||
use writeable::assert_writeable_eq; | ||
|
||
use crate::dimension::currency::{long_formatter::LongCurrencyFormatter, CurrencyCode}; | ||
|
||
#[test] | ||
pub fn test_en_us() { | ||
let locale = locale!("en-US").into(); | ||
let currency_code = CurrencyCode(tinystr!(3, "USD")); | ||
let fmt = LongCurrencyFormatter::try_new(&locale, ¤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,345.67 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,345.67 US dollars"); | ||
} | ||
|
||
#[test] | ||
pub fn test_fr_fr() { | ||
let locale = locale!("fr-FR").into(); | ||
let currency_code = CurrencyCode(tinystr!(3, "EUR")); | ||
let fmt = LongCurrencyFormatter::try_new(&locale, ¤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\u{202f}345,67 euros"); | ||
|
||
// 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\u{202f}345,67 euros"); | ||
} | ||
|
||
#[test] | ||
pub fn test_ar_eg() { | ||
let locale = locale!("ar-EG").into(); | ||
let currency_code = CurrencyCode(tinystr!(3, "EGP")); | ||
let fmt = LongCurrencyFormatter::try_new(&locale, ¤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, "١٢٬٣٤٥٫٦٧ جنيه مصري"); | ||
|
||
// 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, "\u{61c}-١٢٬٣٤٥٫٦٧ جنيه مصري"); | ||
} | ||
} |
173 changes: 173 additions & 0 deletions
173
components/experimental/src/dimension/currency/long_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,173 @@ | ||
// 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::FixedDecimal; | ||
use icu_decimal::{options::FixedDecimalFormatterOptions, FixedDecimalFormatter}; | ||
use icu_plurals::PluralRules; | ||
use icu_provider::prelude::*; | ||
|
||
use crate::dimension::provider::{ | ||
currency_patterns::CurrencyPatternsDataV1Marker, | ||
extended_currency::CurrencyExtendedDataV1Marker, | ||
}; | ||
|
||
use super::{long_format::LongFormattedCurrency, CurrencyCode}; | ||
|
||
extern crate alloc; | ||
|
||
/// A formatter for monetary values. | ||
/// | ||
/// [`LongCurrencyFormatter`] 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. | ||
pub struct LongCurrencyFormatter { | ||
/// Extended data for the currency formatter. | ||
extended: DataPayload<CurrencyExtendedDataV1Marker>, | ||
|
||
/// Formatting patterns for each currency plural category. | ||
patterns: DataPayload<CurrencyPatternsDataV1Marker>, | ||
|
||
/// A [`FixedDecimalFormatter`] to format the currency value. | ||
fixed_decimal_formatter: FixedDecimalFormatter, | ||
|
||
/// A [`PluralRules`] to determine the plural category of the unit. | ||
plural_rules: PluralRules, | ||
} | ||
|
||
impl LongCurrencyFormatter { | ||
icu_provider::gen_any_buffer_data_constructors!( | ||
(locale: &DataLocale, 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 [`LongCurrencyFormatter`] 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(locale: &DataLocale, currency_code: &CurrencyCode) -> Result<Self, DataError> { | ||
let fixed_decimal_formatter = | ||
FixedDecimalFormatter::try_new(locale, FixedDecimalFormatterOptions::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(locale)?; | ||
|
||
Ok(Self { | ||
extended, | ||
patterns, | ||
fixed_decimal_formatter, | ||
plural_rules, | ||
}) | ||
} | ||
|
||
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] | ||
pub fn try_new_unstable<D>( | ||
provider: &D, | ||
locale: &DataLocale, | ||
currency_code: &CurrencyCode, | ||
) -> Result<Self, DataError> | ||
where | ||
D: ?Sized | ||
+ DataProvider<super::super::provider::extended_currency::CurrencyExtendedDataV1Marker> | ||
+ DataProvider<super::super::provider::currency_patterns::CurrencyPatternsDataV1Marker> | ||
+ DataProvider<icu_decimal::provider::DecimalSymbolsV1Marker> | ||
+ DataProvider<icu_plurals::provider::CardinalV1Marker>, | ||
{ | ||
let fixed_decimal_formatter = FixedDecimalFormatter::try_new_unstable( | ||
provider, | ||
locale, | ||
FixedDecimalFormatterOptions::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, locale)?; | ||
|
||
Ok(Self { | ||
extended, | ||
patterns, | ||
fixed_decimal_formatter, | ||
plural_rules, | ||
}) | ||
} | ||
|
||
/// Formats in the long format a [`FixedDecimal`] value for the given currency code. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// use icu::experimental::dimension::currency::long_formatter::LongCurrencyFormatter; | ||
/// use icu::experimental::dimension::currency::CurrencyCode; | ||
/// use icu::locale::locale; | ||
/// use tinystr::*; | ||
/// use writeable::Writeable; | ||
/// | ||
/// let locale = locale!("en-US").into(); | ||
/// let currency_code = CurrencyCode(tinystr!(3, "USD")); | ||
/// let fmt = LongCurrencyFormatter::try_new(&locale, ¤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,345.67 US dollars"); | ||
/// ``` | ||
pub fn format_fixed_decimal<'l>( | ||
&'l self, | ||
value: &'l FixedDecimal, | ||
currency_code: CurrencyCode, | ||
) -> LongFormattedCurrency<'l> { | ||
LongFormattedCurrency { | ||
value, | ||
_currency_code: currency_code, | ||
extended: self.extended.get(), | ||
patterns: self.patterns.get(), | ||
fixed_decimal_formatter: &self.fixed_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
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Praise: good code here, just loading things and interpolating them. No allocations.