fluent
fluent copied to clipboard
Number equality wrt. fraction digits
Should number eqality used in matching variants inside of SelectExpressions take into account the fraction digits of the number?
foo = {1.000 ->
[1] ...
[1.0] ...
[1.00] ...
[1.000] ...
*[other] ...
}
If so, should it be possible (and how?) to express numbers together with their fraction digits when passing them as variables?
# What's the API for defining $num as 1.00?
foo = {$num ->
[1] ...
[1.0] ...
[1.00] ...
[1.000] ...
*[other] ...
}
I'm having trouble trying to imagine a real-world case where this would be an issue. When would you really want a different message for 1.0 compared to 1.000, and what would those messages look like? If the differentiator is the number of fractional digits, it would almost certainly be simpler to express some preliminary logic in the wrapping language, and to have something like $fractionDigits as the selector.
In other words, I think the cleanest logic here is to:
- Compare the input to exact string matches of case keys. If the input is not a string, apply a default stringifier to it beforehand.
- If the previous does not match and the input is a number, get its plural category and match that to the case keys.
- If that still doesn't match, select the default case.
For numeric inputs that should not use a default stringifier, adding a NUMBER() wrapper for the selector allows for that to be customised.
I think there are use-cases for the program to tell the localization which precision to use. A pretty simple one would be currency values. Probably also the case where things can go wrong most badly.
The other use-case is where the program passes measured data to Fluent. The precision of those measurements could actually vary, say, it's 1.0 C in Wilmersdorf (with 2 digits precision 10), and 1.00 C in Charlottenburg (with 3 digits, 100). Or the distance between stars.
The interesting question here is why anybody would compare to literals in those scenarios, I guess.
Maybe the question here is really about this:
foo = { $distance ->
[1] {$first-star} and {$second-star} are one lightyear apart.
[one] {$first-star} and {$second-star} are {$distance} lightyear apart.
*[other] {$first-star} and {$second-star} are {$distance} lightyear apart.
}
In this case, how do we prevent [1] snatching 1.00, which should go into the plural form?
I don't think that example really works? Or maybe I'm just missing something, because I can't figure out why you'd have a [1] case if it wasn't supposed to capture a numerical value 1.00.
I think we're talking about one lightyear, 1 lightyear, but 1.0 lightyears. I added the [one] case to make room for something that actually has a multi-valued [one] or more than two plural forms.
@eemeli The reasoning isn't visible in English: the one rule fires in some locales for numbers ending in 1 (21, 31, etc.). The 1 rule fires for the value == 1 in all locales. (One probably wouldn't write something most of the time--the example I usually use is for "characters remaining" or "login attempts remaining", in which the 1 case might have a special message such as "this is your last chance" but the one case has a normal number-placement message)
I'd point out that in most locales non-zero decimal values should trigger a pluralized rule. E.g. in English we say "1 dog" but "1.5 dogs". If one wants to control the number of fractional digits displayed, that's a job for a skeleton or picture string.
Maybe we're coming at this with different presumptions about what sort of thing @stasm's original $num or @Pike's $distance are? My baseline expectation is that those are numbers, such that 1, 1.0 and 1.00 are different representations of the same input value. If it were a string instead, how could it ever reach the [one] case?
With that presumption, the simplest way to get [1.0] and [1.00] to be different would be to wrap the input as NUMBER($num, minFractionDigits: $foo) and to have the case matching happen using string rather than number equality. This fails currently for two reasons:
- Variables can't be used in function values
- For NumberLiteral cases, the matching is according to the numerical value
Going on from there, my original argument was that with the above, it would make more sense to use $foo as an explicit selector.
While playing around with this I may have identified a case that is perhaps wrong with the current spec and related to this discussion (playground link):
foo = {NUMBER($num, minimumFractionDigits: 2) ->
[2] 2
[one] one
*[other] other
}
bar = {$num ->
[2] 2
[one] one
*[other] other
}
With $num as 1, both foo and bar are one. With $num as "1", foo is one and bar is other. Similar things happen with 2 and "2". It looks like NUMBER() is casting any input first to a string, then back to a number when determining the case. This is wrong, because in English 1.00 should match other rather than one.
I have not investigated whether this is a bug in the spec, or in fluent.js.
Right now, no built-in functions are specified, including NUMBER. The specification process we're in right now is about exactly answering the questions you raised, and to decide what's a bug and what's not.
Regarding serialization, I'd prefer to not serialize numbers for matching, in particular because in the value comparison we need to deal with artifacts like non-ascii numbers (see how I avoided Latn there?), and separators.
In a scientific context, 1.0 and 1.00 are different number ranges. They're not different renderings of the same number, but conceptually different things. A localization shouldn't have the option to change the significant digits in this context, either. Using the right separators is desired, though, IMHO.
To be explicit, 1.0 is a value likely in the range of 0.95 < v < 1.05, whereas 1.00 is a value likely in the range of 0.995 < v < 1.005.
Scientifically measured values are really the only use-case I came up with, where the data determines the number of decimals.
Money is an area where the precision is fixed, and shouldn't be changed by the localization.
I do think about the selector logic purely in terms of how we render the number in the end, btw. The reason for that being that we don't need a distinction in the logic if there's no distinction in the rendering.
Last note in this rambling, when we talk about specifying the NUMBER function, we could evaluate the option of using a skeleton string instead of technical constraint arguments.
@Pike a nit: while currencies have a specific number of fraction digits, there are many applications (particularly currency conversions) in which one needs to ensure that greater precision is maintained---and in some cases displayed to the user. It's bad to make the assumption that monetary values have fixed precision. Or that the localization library should enforce rounding or display rules (it should, of course, make it easy/default to get the right number of decimal digits, because the cases I'm talking to a corner cases)