feat: Implement currency precision fallback#8169
Conversation
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
e845d8a to
82a26af
Compare
| pattern.interpolate(( | ||
| self.decimal_formatter | ||
| .format_unsigned(icu_decimal::Cow::Borrowed(&value.absolute)), | ||
| .format_unsigned(icu_decimal::Cow::Owned(value.absolute)), |
There was a problem hiding this comment.
We need Cow::Owned here because value is a local variable.
In format_fixed_decimal, we apply precision which clones and modifies the input, creating a new local FixedDecimal (value). If we used Cow::Borrowed(&value.absolute), the returned FormattedDecimal would borrow from this local variable, which would result in a lifetime error since the local variable is dropped when the function returns.
By using Cow::Owned(value.absolute), we move the owned data into the Cow so it can be safely returned. Since UnsignedDecimal uses inline storage for typical digit counts, this Cow::Owned usually does not trigger heap allocations.
An alternative to avoid Cow::Owned would be to define a custom FormattedCurrency struct that owns the modified FixedDecimal and implements Writeable, but that would add more complexity for now.
| }) | ||
| .allow_identifier_not_found()? | ||
| .map(|res| res.payload); |
There was a problem hiding this comment.
We allow IdentifierNotFound here to support fallback to the ISO code when the localized display name for a currency is missing.
According to UTS #35 (LDML Part 3: Numbers) Section 3.11.1:
If no displayName element is found at any level in the fallback chain, the system uses the currency code itself (e.g., "ZWD") as the display name.
If we didn't use allow_identifier_not_found()?, the formatter construction would fail completely for unsupported currencies. By allowing it, we can still format the number and gracefully fall back to the ISO code (e.g., 12,345.67 XYZ).
Revert manual precision additions to CurrencyEssentials and CurrencyExtendedData, and instead load and use the existing CurrencyFractionsV1 singleton to resolve currency precision. Also apply a fix to check_pattern in essentials provider to allow mismatches in grouping separators (Indian vs Western). TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Introduce CurrencyPatternFractionsV1 marker to store precision extracted from CLDR currency patterns at datagen time. Update CurrencyFormatter and CompactCurrencyFormatter to load this data and implement a 3-step fallback for resolving currency precision: 1. Global currency-specific overrides (from CurrencyFractionsV1). 2. Locale-specific pattern precision (from CurrencyPatternFractionsV1). 3. Global default (2 decimals). TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Update CurrencyFormatter to make CurrencyExtendedDataV1 (display names) optional. If the extended data for a currency is missing (e.g., for a fake or unsupported currency), we now fall back to using its ISO code as the display name, instead of failing construction. Add test_long_fallback_to_iso to verify this behavior. TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Following the decision that Long formatting does not need locale-specific pattern precision fallback (as it has no pattern-defined precision and should fall back to the global default), we merge the pattern precision data (fractions_vec, standard_index, standard_next_to_alpha) directly into CurrencyEssentials. This optimizes Short and Narrow formatting by reducing the number of loaded data markers from 2 to 1, while Long formatting remains optimized by only loading CurrencyFractionsV1 (global overrides). Updates: - Removed CurrencyPatternFractionsV1 marker. - Added fractions_vec, standard_index, standard_next_to_alpha to CurrencyEssentials. - Updated datagen to populate these fields in CurrencyEssentialsV1. - Updated CurrencyFormatter and CompactCurrencyFormatter to use CurrencyEssentials for locale-specific precision. - Regenerated baked data. TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
- Pass CurrencyCode by value in CompactCurrencyFormatter::resolve_fraction_info. - Collapse nested if statements using Option::filter in resolve_fraction_info (both formatters). - Remove unused load_with_fallback from compact_formatter.rs. - Remove unused ZeroVec import from fractions.rs. - Remove unnecessary qualifications in essentials.rs (provider). - Remove needless borrow of locale in essentials.rs (provider). TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
This aligns the function name with its return type (FractionInfo) and makes it more descriptive of what it actually does. TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Added comments in formatter.rs referencing UTS unicode-org#35 Section 3.11.1 explaining that we fall back to the currency code itself if the displayName is not found. TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
TAG=agy CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
375131d to
99b5d32
Compare
| ) | ||
| } | ||
|
|
||
| fn resolve_fraction_info( |
There was a problem hiding this comment.
add a comment to this function.
There was a problem hiding this comment.
Done. Added doc comments to both `compact_formatter.rs` and `formatter.rs`.
This PR implements currency precision fallback, integrates pattern precision into
CurrencyEssentials, and adds support for per-currency fallback (i.e., resolving precision based on the specific currency, such as 0 decimal places for JPY or 2 decimal places for USD, before falling back to a global default).It is stacked on top of #8150.
Changelog
Add Pattern Precision into
CurrencyEssentials:fractions_vec,standard_index, andstandard_next_to_alphafields toCurrencyEssentialsto store locale-specific pattern precision.CurrencyEssentialsV1to extract this precision from CLDR patterns at datagen time.ShortandNarrowformatters by reducing the number of loaded data markers from 2 to 1.Implement Currency Precision Fallback:
CurrencyFormatterandCompactCurrencyFormatterto useCurrencyEssentialsfor locale-specific precision.CurrencyFractionsV1map (e.g., JPY = 0).CurrencyEssentials(only forShort/Narrowformats).CurrencyFractionsV1(2 decimals).Longformat, since it doesn't useCurrencyEssentials, it bypasses step 2 and falls back directly to the global default (or currency override).Support Per-Currency Fallback (ISO Code Fallback):
CurrencyFormatterto makeCurrencyExtendedDataV1(display names) optional.XYZ), it now gracefully falls back to using the currency's ISO code as the display name instead of failing construction.Tests and Data:
Longformat fallback to ISO code (test_long_fallback_to_iso).TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29