dinero.js
dinero.js copied to clipboard
Feature Request: Enhance toFormat to support generic return types
Use Case
We ran into a scenario where we want to format a Dinero
object in a user-specified locale but not to a single string
, but to its component parts - e.g. localized currency symbol, localized numeric amount in the localized order (e.g. currency symbol may prefix or suffix the numeric amount).
The reason for doing that is so we can use a library such as react-native-currency-input where we can specify separate prefix
and suffix
components to render the localized currency symbol separate from the monetary numeric value.
Currently the toFormat() function only supports a Transformer
function which returns a string
, the Transformer
can't return a generic return type, even though there does not appear to be any reason not to.
Existed Related Support
A common way of localizing monetary values is to use Intl.NumberFormat#format which returns a single string
and so can already be used with toFormat
:
const d = dinero({ amount: 500, currency: USD });
const formattedResult = toFormat(d, ({ amount, currency }) => {
const formatter = new Intl.NumberFormat('fr-CA', {
style: 'currency',
currency: currency.code,
});
return formatter.format(amount);
});
// formattedResult = "5,00 $ US"
However Intl.NumberFormat
also provides a formatToParts() function which localizes and formats a monetary value and returns an array of component parts that can be rendered individually as desired.
Desired Solution
What is desired is for toFormat()
to support a generic return type so that the following is also supported:
const d = dinero({ amount: 500, currency: USD });
const formattedResult = toFormat(d, ({ amount, currency }) => {
const formatter = new Intl.NumberFormat('fr-CA', {
style: 'currency',
currency: currency.code,
});
return formatter.formatToParts(amount);
});
// formattedResult = [{"type":"integer","value":"5"}, {"type":"decimal","value":","}, {"type":"fraction","value":"00"}, {"type":"literal","value":" "}, {"type":"currency","value":"$ US"}]
For backwards compatibility that generic return type can default to a string, but the passed in formatter could return a different result object type if desired, like the above does.
Contribution
The changes required to support this use case are minimal and are fully backwards compatible (as far as I can tell) and so I've implemented it myself in #646 and submitted for review.
It's worth pointing out that this PR purely contribues type changes, and doesn't introduce any runtime changes.
I would love to see this merged in as this was motivated by a near-term upcoming user story to implement a localized TextInput
field where the user just edits the monetary amount and the currency symbol is localized appropriately and rendered separately. There are also later stories that we believe will also make use of this.
The PR (#646) is built on top of the main
branch, but in the meantime I've also built patch files on top of the latest released version as of this comment (2.0.0-alpha.8
) in order for us to proceed with dependent stories. I've attached them below in case they are useful for anyone else.
They can be applied by:
- Adding them to a
patches
sub-directory - Renaming them to remove the
.txt
suffices - Applying them automatically on build through patch-package
Patches
Safety
As they contain no runtime changes the patch is safe to consume by intermediate libraries in order to provide additional formatting options. We've done just that in our intermediate Money
library.
Example Usage
Here's a simplified example of what this PR/patches enable:
// Internal Shared Formatter
const sharedCreateFormatter = <T>(
locale: Intl.Locale,
numberFormatter: NumberFormatter<T>,
formattingOptionsFn?: FormattingOptionsProvider
): MoneyFormatter<T> => {
// This Transformer function returns a generic return type which requires the following PR to be merged into dinerojs:
// https://github.com/dinerojs/dinero.js/pull/646
// And in the meantime is being patched via patch-package
const transformer = (transformerOptions: TransformerOptions): T => {
const options = formattingOptionsFn ? formattingOptionsFn(transformerOptions) : undefined;
const format = new Intl.NumberFormat(locale.baseName, options);
return numberFormatter(transformerOptions, format);
};
return (money: Money) => toFormat(money, transformer);
};
// --------------------------
// External String Formatter
// --------------------------
const stringFormatter: NumberFormatter<string> = ({ amount }: TransformerOptions, numberFormat: Intl.NumberFormat) => numberFormat.format(amount);
export const createFormatter = (locale: Intl.Locale, style: FormatStyle | keyof typeof FormatStyle = FormatStyle.STANDARD): MoneyFormatter<string> =>
sharedCreateFormatter(locale, stringFormatter, createFormattingOptions(style));
// Locale is passed as an Intl.Locale Object for additional typesafety to avoid style parameter accidentally being set as locale if missed
export const formatMoney = (money: Money, locale: Intl.Locale, style?: FormatStyle | keyof typeof FormatStyle): string =>
createFormatter(locale, style)(money);
// -------------------------
// External Parts Formatter
// -------------------------
const partsFormatter: NumberFormatter<FormattedMoneyParts> = ({ amount, currency }: TransformerOptions, numberFormat: Intl.NumberFormat) => ({
amount,
currency: currency.code,
formattedParts: numberFormat.formatToParts(amount),
});
export const createPartsFormatter = (
locale: Intl.Locale,
style: FormatStyle | keyof typeof FormatStyle = FormatStyle.STANDARD
): MoneyFormatter<FormattedMoneyParts> => sharedCreateFormatter(locale, partsFormatter, createFormattingOptions(style));
// Locale is passed as an Intl.Locale Object for additional typesafety to avoid style parameter accidentally being set as locale if missed
export const formatMoneyIntoParts = (money: Money, locale: Intl.Locale, style?: FormatStyle | keyof typeof FormatStyle): FormattedMoneyParts =>
createPartsFormatter(locale, style)(money);
toFormat
was removed in v2.0.0-alpha.11
in favor of toDecimal
which supports a generic output.