fluent.js icon indicating copy to clipboard operation
fluent.js copied to clipboard

Number parsing overrides minimumFractionDigits

Open zbraniecki opened this issue 5 years ago • 8 comments

The way we store number's precision has been rewritten in https://github.com/projectfluent/fluent.js/commit/2bd94bc33a555c12ce176ed8b519159d3cd28e48 to store a value as a number and precision as minimumFractionDigits option to intl number formatter.

This has an unfortunate consequence in that the number of fraction digits specified in the input hardcodes the mfd option, which is different from how Intl.NumberFormat works.

Here's an example (ignore for now that we wanted to limit currency setting from L10n, same argument applies for partial arguments or other forms of default precision):

(5).toLocaleString("en"); // "5"
(5.00).toLocaleString("en"); // "5"
(5).toLocaleString("en", {style: "currency", currency: "USD"}); // "$5.00"
(5.00).toLocaleString("en", {style: "currency", currency: "USD"}); // "$5.00"

But in Fluent, this will happen:

key1 = Hello { 5 } World
key2 = Hello { 5.00 } World
key3 = Hello { NUMBER(5, style: "currency", currency: "USD") } World
key4 = Hello { NUMBER(5.00, style: "currency", currency: "USD") } World

output:

Hello 5 World
Hello 5.00 World
Hello $5 World
Hello $5.00 World

I aligned fluent-rs with fluent.js here https://github.com/projectfluent/fluent-rs/commit/df9c489d6a451e181d939096883eea708efd3b87 but am a bit concerned about this behavior difference.

@stasm, @pike - do you think this is an issue or a feature?

zbraniecki avatar Jan 24 '20 10:01 zbraniecki

I recall a whiteboard in Berlin .... that made it sound like a feature. But I don't remember.

Pike avatar Jan 24 '20 12:01 Pike

This has an unfortunate consequence in that the number of fraction digits specified in the input hardcodes the mfd option, which is different from how Intl.NumberFormat works.

Why do you think this is unfortunate? The behavior of Intl.NumberFormat is due to how number literals work in JS. Fluent doesn't have to be bound by the same rules.

In Fluent, number literals carry the information about their precision. The following expressions produce the same result:

key1 = {3.00}
key2 = {NUMBER(3, minimumFractionDigits:2)}

Do you think it shouldn't work this way?

Related spec issue: https://github.com/projectfluent/fluent/issues/268

stasm avatar Jan 24 '20 14:01 stasm

I think the issue is with this:

key3 = Hello { NUMBER(5, style: "currency", currency: "USD") } World

vs.

(5).toLocaleString("en", {style: "currency", currency: "USD"}); // "$5.00"

In JS case I said take 5 and format it as currency using default fraction digits for the number because I have not specificied MFD manually. In Fluent I said take 5 and format it as currency enforcing zero MFD.

There's no way to say the former in Fluent scenario because not specifying fractionals is equivalent of setting minimumFractionDigits: 0 explicitly. There's no way to say auto or None.

zbraniecki avatar Jan 24 '20 15:01 zbraniecki

Ah, I see what you mean, thanks.

My current thinking is to approach this with a pragmatic mindset. I'd expect localizers to just write key = $5,00 if they know the value a priori. Otherwise this isn't a use-case for number literals. Number literals are mostly useful as variant keys and values of named arguments.

We still need to specify their different behaviors, but I'd like to also keep the larger picture in mind when doing it.

stasm avatar Jan 24 '20 16:01 stasm

Yeah, as I said, currency may not be the best example, but with the growth of Intl.NumberFormat I can see this becoming a limitation at some point. For example it may be there default formatting of some units will be different or percent in different locales. And that formatting may by default have some MFD, while the way Fluent works now will force the MFD: 0 in all of them.

zbraniecki avatar Jan 24 '20 17:01 zbraniecki

Here's an example of how we could limit the impact:

The minimumFractionDigits is set based on the number of fraction digits in the input number string only if:

  • there's a fraction separator (.)
  • the last digit of the fraction is 0

This means that 5.20 has MFD: 2, 5.0 has MFD: 1, but 5.2, 5 and 5.3231 has auto.

zbraniecki avatar Jan 24 '20 17:01 zbraniecki

Do you expect number literals to be used with intl formatters? I'd argue that if a) locale is known and b) the value is known, the right solution is to just format the value by typing it as a string.

This means that 5.20 has MFD: 2, 5.0 has MFD: 1, but 5.2, 5 and 5.3231 has auto.

How would you express 5.2 which is supposed to have MFD equal to exactly 1?

stasm avatar Jan 24 '20 18:01 stasm

How would you express 5.2 which is supposed to have MFD equal to exactly 1?

If you want to enforce MFD: 1, you set it via minimumFractionDigits: 1. If you don't care, you just pass and we set is as "auto".

zbraniecki avatar Jan 24 '20 18:01 zbraniecki