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 Short&Narrow Compact Currency Formatter #5450

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
@@ -0,0 +1,86 @@
// 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::{compact_options::CompactCurrencyFormatterOptions, CurrencyCode};
use crate::dimension::provider::{
currency::CurrencyEssentialsV1, currency_compact::ShortCurrencyCompactV1,
};
use fixed_decimal::FixedDecimal;
use icu_decimal::FixedDecimalFormatter;
use icu_plurals::PluralRules;

pub struct CompactFormattedCurrency<'l> {
pub(crate) value: &'l FixedDecimal,
pub(crate) currency_code: CurrencyCode,
pub(crate) options: &'l CompactCurrencyFormatterOptions,
pub(crate) essential: &'l CurrencyEssentialsV1<'l>,
pub(crate) short_currency_compact: &'l ShortCurrencyCompactV1<'l>,
pub(crate) fixed_decimal_formatter: &'l FixedDecimalFormatter,
pub(crate) plural_rules: &'l PluralRules,
}

writeable::impl_display_with_writeable!(CompactFormattedCurrency<'_>);

impl<'l> Writeable for CompactFormattedCurrency<'l> {
fn write_to<W>(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error>
where
W: core::fmt::Write + ?Sized,
{
let config = self
.essential
.pattern_config_map
.get_copied(&self.currency_code.0.to_unvalidated())
.unwrap_or(self.essential.default_pattern_config);

let placeholder_index = match self.options.width {
Width::Short => config.short_placeholder_value,
Width::Narrow => config.narrow_placeholder_value,
};

let currency_placeholder = match placeholder_index {
Some(currency::PlaceholderValue::Index(index)) => self
.essential
.placeholders
.get(index.into())
.ok_or(core::fmt::Error)?,
Some(currency::PlaceholderValue::ISO) | None => self.currency_code.0.as_str(),
};

let pattern_selection = match self.options.width {
Width::Short => config.short_pattern_selection,
Width::Narrow => config.narrow_pattern_selection,
};

let plural_category = self.plural_rules.category_for(self.value);
let pattern_selection = match pattern_selection {
currency::PatternSelection::Standard => CompactCount::Standard(plural_category),
currency::PatternSelection::StandardAlphaNextToNumber => {
CompactCount::StandardAlphaNextToNumber(plural_category)
}
};

// TODO: get the i8 for the pattern to get the appropriate pattern from the map.

let pattern = match pattern_selection {
currency::PatternSelection::Standard => self
.short_currency_compact
.compact_patterns
.get(&(0, CompactCount::Standard(PluralCategory::One))),
currency::PatternSelection::StandardAlphaNextToNumber => self
.essential
.standard_alpha_next_to_number_pattern
.as_ref(),
}
.ok_or(core::fmt::Error)?;

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

Ok(())
}
}
162 changes: 162 additions & 0 deletions components/experimental/src/dimension/currency/compact_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// 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 crate::dimension::provider::{
currency::CurrencyEssentialsV1Marker, currency_compact::ShortCurrencyCompactV1Marker,
};
use icu_decimal::FixedDecimalFormatter;
use icu_plurals::PluralRules;

use super::compact_options::CompactCurrencyFormatterOptions;

/// 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.
pub struct CompactCurrencyFormatter {
/// Short currency compact data for the compact currency formatter.
short_currency_compact: DataPayload<ShortCurrencyCompactV1Marker>,

/// Essential data for the compact currency formatter.
essential: DataPayload<CurrencyEssentialsV1Marker>,

/// A [`FixedDecimalFormatter`] to format the currency value.
fixed_decimal_formatter: FixedDecimalFormatter,

/// A [`PluralRules`] to determine the plural category of the unit.
plural_rules: PluralRules,

/// Options bag for the compact currency formatter to determine the behavior of the formatter.
/// for example: width.
options: CompactCurrencyFormatterOptions,
}

impl CompactCurrencyFormatter {
icu_provider::gen_any_buffer_data_constructors!(
(locale, options: super::options::CompactCurrencyFormatterOptions) -> error: DataError,
functions: [
try_new: skip,
try_new_with_any_provider,
try_new_with_buffer_provider,
try_new_unstable,
Self
]
);
/// Creates a new [`CompactCurrencyFormatter`] from compiled locale data and an options bag.
///
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
///
/// [📚 Help choosing a constructor](icu_provider::constructors)
#[cfg(feature = "compiled_data")]
pub fn try_new(
locale: &DataLocale,
options: super::options::CompactCurrencyFormatterOptions,
) -> Result<Self, DataError> {
let short_currency_compact = crate::provider::Baked
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(locale),
..Default::default()
})?
.payload;

let fixed_decimal_formatter =
FixedDecimalFormatter::try_new(locale, FixedDecimalFormatterOptions::default())?;
let essential = crate::provider::Baked
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(locale),
..Default::default()
})?
.payload;

let plural_rules = PluralRules::try_new_cardinal(locale)?;

Ok(Self {
short_currency_compact,
essential,
fixed_decimal_formatter,
plural_rules,
options,
})
}

#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
pub fn try_new_unstable<D>(
provider: &D,
locale: &DataLocale,
options: super::options::CompactCurrencyFormatterOptions,
) -> Result<Self, DataError>
where
D: ?Sized
+ DataProvider<super::super::provider::currency::CurrencyEssentialsV1Marker>
+ DataProvider<icu_decimal::provider::DecimalSymbolsV1Marker>,
{
let fixed_decimal_formatter = FixedDecimalFormatter::try_new_unstable(
provider,
locale,
FixedDecimalFormatterOptions::default(),
)?;

let short_currency_compact = provider
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(locale),
..Default::default()
})?
.payload;

let essential = provider
.load(DataRequest {
id: DataIdentifierBorrowed::for_locale(locale),
..Default::default()
})?
.payload;

let plural_rules = PluralRules::try_new_cardinal(locale)?;

Ok(Self {
short_currency_compact,
essential,
fixed_decimal_formatter,
plural_rules,
options,
})
}

/// Formats in the compact format a [`FixedDecimal`] value for the given currency code.
///
/// # Examples
/// ```
/// use icu::experimental::dimension::currency::compact_formatter::CompactCurrencyFormatter;
/// 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 = CompactCurrencyFormatter::try_new(&locale, &currency_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,
) -> CompactFormattedCurrency<'l> {
CompactFormattedCurrency {
value,
currency_code,
options: &self.options,
essential: self.essential.get(),
short_currency_compact: self.short_currency_compact.get(),
fixed_decimal_formatter: &self.fixed_decimal_formatter,
plural_rules: &self.plural_rules,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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 ).

//! Options for [`CompactCurrencyFormatter`](crate::dimension::currency::compact_formatter::CompactCurrencyFormatter).

/// A collection of configuration options that determine the formatting behavior of
/// [`CompactCurrencyFormatter`](crate::dimension::currency::compact_formatter::CompactCurrencyFormatter).
#[derive(Copy, Debug, Eq, PartialEq, Clone, Default)]
#[non_exhaustive]
pub struct CompactCurrencyFormatterOptions {
/// The width of the currency format.
pub width: Width,
}

impl From<Width> for CompactCurrencyFormatterOptions {
fn from(width: Width) -> Self {
Self { width }
}
}

#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
#[non_exhaustive]
pub 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. // TODO: check this
#[default]
Short,

/// Format the currency with the narrow currency symbol.
///
/// The narrow symbol may be ambiguous, so it should be evident from context which
/// currency is being represented.
///
/// For example, 1 USD formats as "$1.00" in most locales. // TODO: check this
Narrow,
}
3 changes: 3 additions & 0 deletions components/experimental/src/dimension/currency/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use tinystr::TinyAsciiStr;

pub mod compact_format;
pub mod compact_formatter;
pub mod compact_options;
pub mod format;
pub mod formatter;
pub mod long_format;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ pub use crate::provider::Baked;
))]
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(
feature = "datagen",
derive(serde::Serialize, databake::Bake),
databake(path = icu_experimental::dimension::provider::currency_displayname)
)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_experimental::dimension::provider::currency_displayname))]
#[yoke(prove_covariance_manually)]
pub struct CurrencyDisplaynameV1<'data> {
/// The display name for the currency.
Expand Down
Loading