Skip to content
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 18 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions components/experimental/src/dimension/currency/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ use fixed_decimal::FixedDecimal;
use icu_decimal::FixedDecimalFormatter;
use writeable::Writeable;

use crate::dimension::currency::formatter::CurrencyCode;
use crate::dimension::currency::options::CurrencyFormatterOptions;
use crate::dimension::currency::options::Width;
use crate::dimension::provider::currency;
use crate::dimension::provider::currency::CurrencyEssentialsV1;

use crate::dimension::currency::CurrencyCode;

pub struct FormattedCurrency<'l> {
pub(crate) value: &'l FixedDecimal,
pub(crate) currency_code: CurrencyCode,
pub(crate) currency_code: &'l CurrencyCode,
pub(crate) options: &'l CurrencyFormatterOptions,
pub(crate) essential: &'l CurrencyEssentialsV1<'l>,
pub(crate) fixed_decimal_formatter: &'l FixedDecimalFormatter,
Expand Down Expand Up @@ -78,7 +79,7 @@ mod tests {
use tinystr::*;
use writeable::assert_writeable_eq;

use crate::dimension::currency::formatter::{CurrencyCode, CurrencyFormatter};
use crate::dimension::currency::{formatter::CurrencyFormatter, CurrencyCode};

#[test]
pub fn test_en_us() {
Expand All @@ -88,12 +89,12 @@ mod tests {

// Positive case
let positive_value = "12345.67".parse().unwrap();
let formatted_currency = fmt.format_fixed_decimal(&positive_value, currency_code);
let formatted_currency = fmt.format_fixed_decimal(&positive_value, &currency_code);
assert_writeable_eq!(formatted_currency, "$12,345.67");

// Negative case
let negative_value = "-12345.67".parse().unwrap();
let formatted_currency = fmt.format_fixed_decimal(&negative_value, currency_code);
let formatted_currency = fmt.format_fixed_decimal(&negative_value, &currency_code);
assert_writeable_eq!(formatted_currency, "$-12,345.67");
}

Expand All @@ -105,12 +106,12 @@ mod tests {

// Positive case
let positive_value = "12345.67".parse().unwrap();
let formatted_currency = fmt.format_fixed_decimal(&positive_value, currency_code);
let formatted_currency = fmt.format_fixed_decimal(&positive_value, &currency_code);
assert_writeable_eq!(formatted_currency, "12\u{202f}345,67\u{a0}€");

// Negative case
let negative_value = "-12345.67".parse().unwrap();
let formatted_currency = fmt.format_fixed_decimal(&negative_value, currency_code);
let formatted_currency = fmt.format_fixed_decimal(&negative_value, &currency_code);
assert_writeable_eq!(formatted_currency, "-12\u{202f}345,67\u{a0}€");
}

Expand All @@ -122,12 +123,12 @@ mod tests {

// Positive case
let positive_value = "12345.67".parse().unwrap();
let formatted_currency = fmt.format_fixed_decimal(&positive_value, currency_code);
let formatted_currency = fmt.format_fixed_decimal(&positive_value, &currency_code);
assert_writeable_eq!(formatted_currency, "\u{200f}١٢٬٣٤٥٫٦٧\u{a0}ج.م.\u{200f}");

// Negative case
let negative_value = "-12345.67".parse().unwrap();
let formatted_currency = fmt.format_fixed_decimal(&negative_value, currency_code);
let formatted_currency = fmt.format_fixed_decimal(&negative_value, &currency_code);
assert_writeable_eq!(
formatted_currency,
"\u{200f}\u{61c}-١٢٬٣٤٥٫٦٧\u{a0}ج.م.\u{200f}"
Expand Down
15 changes: 5 additions & 10 deletions components/experimental/src/dimension/currency/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
use fixed_decimal::FixedDecimal;
use icu_decimal::{options::FixedDecimalFormatterOptions, FixedDecimalFormatter};
use icu_provider::prelude::*;
use tinystr::TinyAsciiStr;

use super::super::provider::currency::CurrencyEssentialsV1Marker;
use super::format::FormattedCurrency;
use super::options::CurrencyFormatterOptions;
use super::CurrencyCode;

extern crate alloc;

Expand All @@ -34,10 +34,6 @@ pub struct CurrencyFormatter {
fixed_decimal_formatter: FixedDecimalFormatter,
}

/// A currency code, such as "USD" or "EUR".
#[derive(Clone, Copy)]
pub struct CurrencyCode(pub TinyAsciiStr<3>);

impl CurrencyFormatter {
icu_provider::gen_any_buffer_data_constructors!(
(locale, options: super::options::CurrencyFormatterOptions) -> error: DataError,
Expand Down Expand Up @@ -110,9 +106,8 @@ impl CurrencyFormatter {
///
/// # Examples
/// ```
/// use icu::experimental::dimension::currency::formatter::{
/// CurrencyCode, CurrencyFormatter,
/// };
/// use icu::experimental::dimension::currency::formatter::CurrencyFormatter;
/// use icu::experimental::dimension::currency::CurrencyCode;
/// use icu::locale::locale;
/// use tinystr::*;
/// use writeable::Writeable;
Expand All @@ -121,15 +116,15 @@ impl CurrencyFormatter {
/// let fmt = CurrencyFormatter::try_new(&locale, Default::default()).unwrap();
/// let value = "12345.67".parse().unwrap();
/// let currency_code = CurrencyCode(tinystr!(3, "USD"));
/// let formatted_currency = fmt.format_fixed_decimal(&value, currency_code);
/// 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");
/// ```
pub fn format_fixed_decimal<'l>(
&'l self,
value: &'l FixedDecimal,
currency_code: CurrencyCode,
currency_code: &'l CurrencyCode,
) -> FormattedCurrency<'l> {
FormattedCurrency {
value,
Expand Down
127 changes: 127 additions & 0 deletions components/experimental/src/dimension/currency/long_format.rs
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 core::str::FromStr;

use fixed_decimal::FixedDecimal;

use icu_decimal::FixedDecimalFormatter;
use icu_pattern::DoublePlaceholderPattern;
use icu_plurals::PluralRules;
use writeable::Writeable;

use crate::dimension::provider::currency_patterns::{CurrencyPatternsDataV1, PatternCount};
use crate::dimension::provider::extended_currency::{Count, CurrencyExtendedDataV1};

use super::CurrencyCode;

pub struct LongFormattedCurrency<'l> {
pub(crate) value: &'l FixedDecimal,
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 count = Count::from(plural_category);
let pattern_count = PatternCount::from(plural_category);

let display_names = &self.extended.display_names;

let display_name = display_names
.get(&count)
.or_else(|| display_names.get(&Count::Other))
// TOOD: check the cases of using `Count::DisplayName`
.unwrap_or_else(|| self.currency_code.0.as_str());

let patterns = &self.patterns.unit_patterns;
let pattern = patterns
.get(&pattern_count)
.or_else(|| patterns.get(&PatternCount::Other))
.ok_or(core::fmt::Error)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmmmm

in this case we have two options,

  1. to not return error at all and use default pattern
  2. move this logic to format_fixed_decimal method

I prefer option 2.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So PatternCount::Other should always exist, so it would make sense to not have it in the map, but put it as a required field on the struct. You'd have

other: Pattern,
addtional_cases: ZeroMap<PatternCount, Pattern>,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, other must be present. I added this condition to the datagen`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if this was represented in the type system, instead of with a trust-me-unwrap.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point: #5374 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :)


// Parse the pattern string into a DoublePlaceholderPattern
// The pattern is expected to be in the form of "{0} {1}"
let pattern = DoublePlaceholderPattern::from_str(pattern).map_err(|_| core::fmt::Error)?;

pattern
.interpolate((
self.fixed_decimal_formatter.format(self.value),
display_name,
))
.write_to(sink)?;

Ok(())
}
}

// 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, &currency_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, &currency_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, &currency_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}-١٢٬٣٤٥٫٦٧ جنيه مصري");
}
}
Loading