onepub-dev / money.dart

Dart implementation of Money and Currency classes with Money formatter.
MIT License
60 stars 31 forks source link

Money formatting does not work as expected #74

Open rekire opened 1 year ago

rekire commented 1 year ago

In my app you can choose the locale therefore I want that the money types are correctly formatted for the locale. I found out that the formatting is just based on the currency not on the user's locale. I'm referering here the field invertSeparators in the class Currency.

Here is a test case I wrote with the dart's default implementation:

void main() {
  test('currency formatting', () async {
    final price = Decimal.parse('1234.33').toDouble();
    assertFormat(locale: 'de_DE', currency: 'EUR', expected: '1.234,33 €', price: price);
    assertFormat(locale: 'en_US', currency: 'EUR', expected: '€1,234.33', price: price);
    assertFormat(locale: 'de_CH', currency: 'EUR', expected: '€ 1’234.33', price: price);
    assertFormat(locale: 'fr_FR', currency: 'EUR', expected: '1 234,33 €', price: price);
    assertFormat(locale: 'fr_CH', currency: 'EUR', expected: '1 234,33 €', price: price);
  });
}

void assertFormat({
  required String locale,
  required String currency,
  required String expected,
  required double price,
}) {
  final format = NumberFormat.simpleCurrency(locale: locale, name: currency);
  final formatted = format.format(price);
  if (formatted != expected) {
    fail('In the locale $locale the output was "$formatted" instead of "$expected"');
  }
  print('$locale: $formatted');
}

There you can see that in different locales you get different formatting:

de_DE: 1.234,33 € en_US: €1,234.33 de_CH: € 1’234.33 fr_FR: 1 234,33 € fr_CH: 1 234,33 €

To be honest I would prefer when I could pass the Money type with the NumberFormat. Out of my view there is not much missing to make it working. By the way I would also like to see that I could pass a custom locale und not just using the system locale.

rekire commented 1 year ago

I spend some hours to find out if I can create a wrapper to implement the missing methods to make it work smoothly. I was unable to make it working. The simplest way is to cast the amount to double like this:

money.amount.toDecimal().toDouble()
bsutton commented 1 year ago

Locales are on my to-do list however a number format won't work, it has a limitation that stopped my using it in the first place. Locales are discussed in #43 .

I think the summary is that we need to build a replacement for common_currencies.dart that includes all the locale variants.

To facilitate tree shaking we require the user to register the selection of locales they want to use or all of them.

We create a new method

Currency.fromLocale(Locale.enUSD);

We use a code generator to generate all the locale formats from one of the source mentioned in #43.

I think we should add a scale override in the fromLocale.

Currency.fromLocale(Locale.enUSD, scale:0);

This would affect both the scale and format of the returned currency.

I think it makes sense to affect the format?

We could allow the user to pass in a format but then what is the use of the locale except to control the separator?

There is also the issue of locales like India which use different 1000s grouping which we don't support as yet.

If you choose an Indian locale I think the expected result would be the correct groupings, separator and symbol location.

So I think passing in the format to the find method make no sense hence just the scale as an option.