Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use core::fmt::Display;

use crate::dimension::provider::{
currency::compact::ShortCurrencyCompactV1, currency::essentials::CurrencyEssentialsV1,
currency::compact::ShortCurrencyCompactV1,
currency::essentials::{CurrencyEssentials, CurrencyEssentialsV1, PatternSelection},
currency::fractions::{CurrencyFractionsV1, FractionInfo, Rounding},
};
use fixed_decimal::Decimal;
use icu_decimal::preferences::CompactDecimalFormatterPreferences;
Expand All @@ -15,6 +17,7 @@ use icu_plurals::{PluralRules, PluralRulesPreferences};
use icu_provider::prelude::*;
use writeable::Writeable;

use super::formatter::apply_precision;
use super::{CurrencyCode, options::CurrencyFormatterOptions};
use icu_pattern::DoublePlaceholderPattern;

Expand Down Expand Up @@ -64,6 +67,8 @@ pub struct CompactCurrencyFormatter {

plural_rules: PluralRules,

fractions: DataPayload<CurrencyFractionsV1>,

/// Options bag for the compact currency formatter to determine the behavior of the formatter.
/// for example: width.
options: CurrencyFormatterOptions,
Expand Down Expand Up @@ -130,12 +135,15 @@ impl CompactCurrencyFormatter {

let plural_rules = PluralRules::try_new_cardinal((&prefs).into())?;

let fractions = crate::provider::Baked.load(Default::default())?.payload;

Ok(Self {
_short_currency_compact: short_currency_compact,
essential,
decimal_formatter,
compact_data,
plural_rules,
fractions,
options,
})
}
Expand All @@ -150,6 +158,7 @@ impl CompactCurrencyFormatter {
D: ?Sized
+ DataProvider<CurrencyEssentialsV1>
+ DataProvider<ShortCurrencyCompactV1>
+ DataProvider<CurrencyFractionsV1>
+ DataProvider<icu_decimal::provider::DecimalCompactShortV1>
+ DataProvider<icu_decimal::provider::DecimalSymbolsV1>
+ DataProvider<icu_decimal::provider::DecimalDigitsV1>
Expand Down Expand Up @@ -194,12 +203,15 @@ impl CompactCurrencyFormatter {
return Err(DataError::custom("missing standard pattern"));
}

let fractions = provider.load(Default::default())?.payload;

Ok(Self {
_short_currency_compact: short_currency_compact,
essential,
decimal_formatter,
compact_data,
plural_rules,
fractions,
options,
})
}
Expand All @@ -225,13 +237,21 @@ impl CompactCurrencyFormatter {
value: &'l Decimal,
currency_code: &'l CurrencyCode,
) -> impl Writeable + Display + 'l {
let (currency_placeholder, pattern, _pattern_selection) = self
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());

let fraction_info = self.resolve_fraction_info(
Some(self.essential.get()),
Some(pattern_selection),
*currency_code,
);

let value = apply_precision(value.clone(), fraction_info);

// 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.

Expand All @@ -252,4 +272,48 @@ impl CompactCurrencyFormatter {
)),
)
}

/// Resolves the fraction/precision information for a given currency.
///
/// The resolution follows a 3-step fallback:
/// 1. Try currency-specific override in the global map (e.g., JPY = 0 decimals).
/// 2. Try locale-specific pattern precision from `CurrencyEssentials` (if available).
/// 3. Fallback to the global default.
fn resolve_fraction_info(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

add a comment to this function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done. Added doc comments to both `compact_formatter.rs` and `formatter.rs`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

issue: move this method to be on CurrencyFractions or CurrencyEssentials. it is not CompactCurrencyFormatter specific

&self,
essentials: Option<&CurrencyEssentials>,
pattern_selection: Option<PatternSelection>,
currency_code: CurrencyCode,
) -> FractionInfo {
let iso_code = currency_code.0.to_unvalidated();

// 1. Try currency-specific override in global map
if let Some(ule) = self.fractions.get().fractions.get(&iso_code) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use get_copied to get the aligned type

return zerovec::ule::AsULE::from_unaligned(*ule);
}

// 2. Try locale-specific pattern precision
if let Some(essentials) = essentials {
let selection = pattern_selection.unwrap_or(PatternSelection::Standard);
let index = match selection {
PatternSelection::Standard => essentials.standard_fractions_index,
PatternSelection::StandardAlphaNextToNumber => {
essentials.standard_next_to_alpha_fractions_index
}
};
if index != u8::MAX
&& let Some(digits) = essentials.fractions_vec.get(index as usize)
{
return FractionInfo {
digits,
rounding: Rounding::R1,
cash_digits: None,
cash_rounding: None,
};
}
}

// 3. Fallback to global default
self.fractions.get().default
}
}
105 changes: 96 additions & 9 deletions components/experimental/src/dimension/currency/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,13 @@ mod tests {
"-$12,345.67"
);

// TODO(#8151): This should format to 2 decimal places ($123.46 or $123.00) once we use currency patterns.
// Currently it uses decimal patterns which do not pad '123' to 2 decimal places, and do not round '123.4567'.
let value_no_decimals = "123".parse().unwrap();
assert_writeable_eq!(fmt_short.format_fixed_decimal(&value_no_decimals), "$123");
let value_4_decimals = "123.4567".parse().unwrap();
assert_writeable_eq!(
fmt_short.format_fixed_decimal(&value_4_decimals),
"$123.4567"
fmt_short.format_fixed_decimal(&value_no_decimals),
"$123.00"
);
let value_4_decimals = "123.4567".parse().unwrap();
assert_writeable_eq!(fmt_short.format_fixed_decimal(&value_4_decimals), "$123.46");

// Narrow
let fmt_narrow =
Expand All @@ -54,11 +52,33 @@ mod tests {
"-$12,345.67"
);

// TODO(#8151): This should format to 2 decimal places ($123.46 or $123.00) once we use currency patterns.
assert_writeable_eq!(fmt_narrow.format_fixed_decimal(&value_no_decimals), "$123");
assert_writeable_eq!(
fmt_narrow.format_fixed_decimal(&value_no_decimals),
"$123.00"
);
assert_writeable_eq!(
fmt_narrow.format_fixed_decimal(&value_4_decimals),
"$123.4567"
"$123.46"
);

// Long
let fmt_long = CurrencyFormatter::try_new_long(prefs, &currency_code).unwrap();
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&positive_value),
"12,345.67 US dollars"
);
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&negative_value),
"-12,345.67 US dollars"
);

assert_writeable_eq!(
fmt_long.format_fixed_decimal(&value_no_decimals),
"123.00 US dollars"
);
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&value_4_decimals),
"123.46 US dollars"
);
}

Expand Down Expand Up @@ -91,6 +111,17 @@ mod tests {
fmt_narrow.format_fixed_decimal(&negative_value),
"-12\u{202f}345,67\u{a0}€"
);

// Long
let fmt_long = CurrencyFormatter::try_new_long(prefs, &currency_code).unwrap();
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&positive_value),
"12\u{202f}345,67 euros"
);
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&negative_value),
"-12\u{202f}345,67 euros"
);
}

#[test]
Expand Down Expand Up @@ -126,6 +157,17 @@ mod tests {
fmt_narrow.format_fixed_decimal(&negative_value),
"\u{61c}-\u{200f}١٢٬٣٤٥٫٦٧\u{a0}E£"
);

// Long
let fmt_long = CurrencyFormatter::try_new_long(prefs, &currency_code).unwrap();
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&positive_value),
"١٢٬٣٤٥٫٦٧ جنيه مصري"
);
assert_writeable_eq!(
fmt_long.format_fixed_decimal(&negative_value),
"\u{61c}-١٢٬٣٤٥٫٦٧ جنيه مصري"
);
}

#[test]
Expand Down Expand Up @@ -188,6 +230,20 @@ mod tests {
fmt_latn_narrow.format_fixed_decimal(&value),
"\u{200f}12,345.67\u{a0}E£"
);

// 5. Default numbering system (arab) - Long
let fmt_arab_long = CurrencyFormatter::try_new_long(prefs_arab, &currency_code).unwrap();
assert_writeable_eq!(
fmt_arab_long.format_fixed_decimal(&value),
"١٢٬٣٤٥٫٦٧ جنيه مصري"
);

// 6. Locale extension override (latn) - Long
let fmt_latn_long = CurrencyFormatter::try_new_long(prefs_latn, &currency_code).unwrap();
assert_writeable_eq!(
fmt_latn_long.format_fixed_decimal(&value),
"12,345.67 جنيه مصري"
);
}

#[test]
Expand All @@ -205,4 +261,35 @@ mod tests {
CurrencyFormatter::<Decimal>::try_new_narrow(prefs, &currency_code).unwrap();
assert_writeable_eq!(fmt_narrow.format_fixed_decimal(&value), "$12,345.67");
}

#[test]
pub fn test_long_fallback_to_iso() {
let prefs: CurrencyFormatterPreferences = locale!("en-US").into();
let currency_code = CurrencyCode(tinystr!(3, "XYZ"));
let value = "12345.67".parse().unwrap();

let fmt_long = CurrencyFormatter::try_new_long(prefs, &currency_code).unwrap();
assert_writeable_eq!(fmt_long.format_fixed_decimal(&value), "12,345.67 XYZ");
}

#[test]
pub fn test_jpy() {
let prefs: CurrencyFormatterPreferences = locale!("en-US").into();
let currency_code = CurrencyCode(tinystr!(3, "JPY"));
let value = "12345.67".parse().unwrap();

// JPY has 0 decimals (defined in global CurrencyFractionsV1)
// Short
let fmt_short = CurrencyFormatter::<Decimal>::try_new_short(prefs, &currency_code).unwrap();
assert_writeable_eq!(fmt_short.format_fixed_decimal(&value), "¥12,346");

// Narrow
let fmt_narrow =
CurrencyFormatter::<Decimal>::try_new_narrow(prefs, &currency_code).unwrap();
assert_writeable_eq!(fmt_narrow.format_fixed_decimal(&value), "¥12,346");

// Long
let fmt_long = CurrencyFormatter::try_new_long(prefs, &currency_code).unwrap();
assert_writeable_eq!(fmt_long.format_fixed_decimal(&value), "12,346 Japanese yen");
}
}
Loading
Loading