Skip to content

feat(currency): Migrate CurrencyFormatter to adopt the new design with generic ValueRepresentation (Decimal) and add short/narrow constructors#8145

Open
younies wants to merge 9 commits into
unicode-org:mainfrom
younies:currency-short-narrow-decimal
Open

feat(currency): Migrate CurrencyFormatter to adopt the new design with generic ValueRepresentation (Decimal) and add short/narrow constructors#8145
younies wants to merge 9 commits into
unicode-org:mainfrom
younies:currency-short-narrow-decimal

Conversation

@younies

@younies younies commented Jun 26, 2026

Copy link
Copy Markdown
Member

Migrate the currency formatter based on the design doc:
https://hackmd.io/@younies/number_formatter_4x

Overview

This PR refactors the CurrencyFormatter in icu_experimental to align with the proposed design for modular, zero-copy currency formatting (Option 1 in the design doc).

Specifically, it introduces the ValueRepresentation typestate pattern to partition constructors by capability and representation, starting with the standard Decimal representation, while ensuring consistent locale and numbering system resolution across both short and narrow widths.

Changelog

  • Implement ValueRepresentation trait and Decimal marker struct to support typestate-based currency formatting.
  • Refactor CurrencyFormatter to be generic over ValueRepresentation: pub struct CurrencyFormatter<V: ValueRepresentation>.
  • Migrate the old short and narrow currency formatter to CurrencyFormatter<Decimal> with new try_new_short and try_new_narrow constructors (and their unstable/buffer variants), removing the old non-generic try_new constructors.
  • Fix Narrow Fallback: Update both compiled and unstable versions of try_new_narrow to use robust fallback loading, ensuring they correctly support numbering system overrides consistently with try_new_short.
  • Update experimental currency tests and doctests to use the new generic API, including comprehensive test cases for narrow formatting and numbering system overrides.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41

@younies younies changed the title Refactor CurrencyFormatter to be generic over ValueRepresentation Migrate CurrencyFormatter to generic ValueRepresentation (Decimal) Jun 26, 2026
@younies younies force-pushed the currency-short-narrow-decimal branch 4 times, most recently from 0e87de5 to b35fab7 Compare June 26, 2026 14:22
@younies younies changed the title Migrate CurrencyFormatter to generic ValueRepresentation (Decimal) currency: Migrate CurrencyFormatter to generic ValueRepresentation (Decimal) and add short/narrow constructors Jun 26, 2026
@younies younies changed the title currency: Migrate CurrencyFormatter to generic ValueRepresentation (Decimal) and add short/narrow constructors currency: Migrate CurrencyFormatter to adopt the new design with generic ValueRepresentation (Decimal) and add short/narrow constructors Jun 26, 2026
@younies younies requested a review from robertbastian June 26, 2026 14:26
#[test]
pub fn test_en_us() {
let locale = locale!("en-US").into();
let locale: CurrencyFormatterPreferences = locale!("en-US").into();

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.

nit:

Suggested change
let locale: CurrencyFormatterPreferences = locale!("en-US").into();
let prefs: CurrencyFormatterPreferences = locale!("en-US").into();

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.

thanks, done

pub fn format_fixed_decimal<'l>(
&'l self,
value: &'l Decimal,
value: &'l FixedDecimal,

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, pre-existing: this should probably take some integer numeric type (which is capable of representing exact decimal fractions) (like https://github.com/googleapis/googleapis/blob/fdd6a6e4ef12853400539139d014d96e34253166/google/type/money.proto#L30-L40), and use some logic on the ValueRepresentation to work with that

it does not make sense to accept a FixedDecimal, which is basically an int with a user-selected magnitude, if we use currency-specific magnitude and rounding anyway

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.

This is a very good point. The question of what the optimal input representation for currency formatting should be—whether FixedDecimal is the right fit, or if we need an exact decimal/money representation (like units/nanos)—is a crucial architectural question.

However, since this is a pre-existing design aspect (as you noted), changing it would require a much broader discussion that is out of scope for this migration PR.

To ensure we don't lose track of this, I have created a dedicated issue to host this discussion: #8146. I have also added a TODO(#8146) in format_fixed_decimal to track this and ensure we address it before stabilizing the formatter.

}

#[test]
pub fn test_en_us_aud() {

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.

this doesn't test anything that the CAD test doesn't test

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.

Good point! It is indeed redundant with the CAD test. I have removed the AUD test case to keep the test suite clean and DRY. Thanks!

try_new: skip,
try_new_with_buffer_provider,
try_new_unstable,
try_new_narrow: skip,

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.

why not use the macro-generated version?

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.

Thanks for the suggestion! We have refactored the constructors to use the macro-generated versions as much as possible (for the buffer and unstable constructors).

However, we still have to skip the compiled constructors (try_new_short/try_new_narrow) in the macro and implement them manually. This is because CurrencyFormatter is a composite formatter that internally instantiates DecimalFormatter (from icu_decimal). Since icu_experimental's local compiled data provider (Baked) only contains experimental keys and does not implement the stable decimal keys, the macro-generated compiled constructors fail to compile due to unsatisfied trait bounds on Baked.

Manually implementing them allows us to initialize DecimalFormatter using its own compiled data, while loading the currency-specific data from icu_experimental's Baked provider. This is the same established pattern used in RelativeTimeFormatter (see relativetime.rs:178).

We have added a TODO comment in the code to re-evaluate and try to use the macro-generated compiled versions once CurrencyFormatter is stabilized and migrated out of experimental.

Additionally, while refactoring, we noticed that try_new_narrow was missing the numbering system fallback loading logic that try_new_short had. We have updated both the compiled and unstable versions of narrow to use the fallback loader, so they are now fully consistent and correct.

@younies

younies commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

We have updated this PR to refactor the CurrencyFormatter design:

  • The constructors (try_new_short, try_new_narrow, and their unstable/buffer variants) now take currency_code: &CurrencyCode as input.
  • The CurrencyFormatter struct now stores this as bound_currency.
  • The format_fixed_decimal method signature has been simplified to remove the currency_code parameter, as it now uses the bound_currency stored in the struct.

This aligns the Short/Narrow formatter API with the Long formatter design (in the stacked PR #8150), ensuring a consistent API across all widths.

@younies younies force-pushed the currency-short-narrow-decimal branch from 13d73f5 to 8a68189 Compare June 29, 2026 12:15
@younies

younies commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

@robertbastian : Ping

younies added 8 commits July 2, 2026 11:11
Implement the ValueRepresentation trait and Decimal marker struct.
Migrate CurrencyFormatter to CurrencyFormatter<Decimal> with
try_new_short and try_new_narrow constructors, removing the old
non-generic try_new constructors. Update tests accordingly.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Update try_new_narrow and try_new_narrow_unstable to use robust fallback loading, ensuring consistency with short constructors and supporting numbering system overrides.
- Add explanatory comments and TODOs for the manual constructors explaining the cross-crate dependency constraint.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
…sentation

- Add TODO(unicode-org#8146) to format_fixed_decimal to track the discussion about whether FixedDecimal is the correct input type for currency formatting.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Remove test_en_us_aud to keep the test suite DRY, addressing reviewer feedback. CAD and USD tests already provide full coverage for the dollar currency prefix and narrow/short resolution logic.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Rename locale variable to prefs of type CurrencyFormatterPreferences across all test cases in format.rs for naming consistency and to address reviewer feedback.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Add test cases to test_numbering_system_override to verify that try_new_narrow correctly respects numbering system overrides (like ar-EG-u-nu-latn), ensuring consistency with try_new_short. - Rename short test variables for clarity.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
@younies younies force-pushed the currency-short-narrow-decimal branch from 8a68189 to 8e24118 Compare July 2, 2026 11:14
@younies younies requested review from Manishearth and sffc as code owners July 2, 2026 11:14
@younies younies force-pushed the currency-short-narrow-decimal branch from 8e24118 to 5ffcd74 Compare July 2, 2026 11:14
@Manishearth Manishearth removed their request for review July 2, 2026 12:41
@younies younies changed the title currency: Migrate CurrencyFormatter to adopt the new design with generic ValueRepresentation (Decimal) and add short/narrow constructors feat(currency): Migrate CurrencyFormatter to adopt the new design with generic ValueRepresentation (Decimal) and add short/narrow constructors Jul 2, 2026
@younies younies removed the request for review from sffc July 2, 2026 18:13
@younies younies force-pushed the currency-short-narrow-decimal branch from 5ffcd74 to e9b611c Compare July 3, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants