fluent icon indicating copy to clipboard operation
fluent copied to clipboard

Negative numbers and positive/negative selectors?

Open rugk opened this issue 6 years ago • 12 comments

The docs at https://projectfluent.org/fluent/guide/selectors.html really miss how you can handle negative numbers…

rugk avatar Jan 19 '19 19:01 rugk

Also, how would I split *[other] into negative (<0) and positive (>0) integer ranges?

Also can I "ceil" numbers or so…?

rugk avatar Jan 19 '19 19:01 rugk

Did you even ever consider negative numbers or do you live in a world of positive numbers only? :stuck_out_tongue_winking_eye: (just kidding, I am really having a hard time in fluent to do it)

rugk avatar Jan 19 '19 19:01 rugk

Same use-case as in https://github.com/projectfluent/fluent/issues/227#issuecomment-455810107

rugk avatar Jan 19 '19 20:01 rugk

Thanks for filing this, @rugk. You're right—the guide doesn't mention negative numbers at all. I'll fix this.

To type negative numbers in Fluent, prefix them with a - (minus). (Just as you did in the gist.)

stasm avatar Jan 21 '19 12:01 stasm

Well... this only answer one of the questions though. My second coment lists two more.

rugk avatar Jan 21 '19 13:01 rugk

I wrote a lengthier comment in https://github.com/projectfluent/fluent/issues/227#issuecomment-456083729 which also touches on these two additional questions. In it, I suggested using custom functions for both named ranges as well as any arithmetic operations.

I'll leave this issue open to fix the documentation issue related to the lack of examples of how to use negative numbers in the Fluent syntax.

stasm avatar Jan 21 '19 14:01 stasm

Okay, so while this issue is about problematic docs ("negative numbers"), it's also about a feature that i totally miss: "negative selectors". As said:

Also, how would I split *[other] into negative (<0) and positive (>0) integer ranges?

So e.g. I need to do this:

floor_ENGLI_in_EN = { $level ->
    [negative] on basement floor B{ $level - 2*$level }
    [-1] on basement floor B
    [0] on ground floor
    [one] on floor 2
   *[other] on floor { $level + 1 }
}

Or:

floor_ENGLI_in_EN = { $level ->
    [negative] on basement floor B{ $level - 2*$level }
    [-1] on basement floor B
    [0] on ground floor
    [one] on floor 2
   *[other] on floor { $level + 1 }
}

Or, syntax-wise:

floor_ENGLI_in_EN = { $level ->
    [-other] on basement floor B{ $level - 2*$level }
    [-1] on basement floor B
    [0] on ground floor
    [one] on floor 2
   *[other] on floor { $level + 1 }
}

Or:

floor_ENGLI_in_EN = { $level ->
    [-] on basement floor B{ $level - 2*$level }
    [-1] on basement floor B
    [0] on ground floor
    [one] on floor 2
   *[+] on floor { $level + 1 }
}

Whatever you do, without such a selector, one cannot completly fullfill that use-case.

rugk avatar Jan 21 '19 14:01 rugk

In your particular use-case, is the distinction between underground and overground common enough to warrant two separate strings, each getting a positive number of floor levels?

There's a level of over-use of Fluent in some numbering cases. Like using "Please select photos to upload" for 0 in a "Upload { $photocount} Photos" string. I personally think the developer should use a distinct string for "Please select". That's also better for things like translation memory and possibly MT tooling.

Pike avatar Jan 21 '19 14:01 Pike

Ah, so you mean something like:

floor_ENGLI_in_EN_UNDER = { $level ->
    *[other] on basement floor B{ $level - 2*$level }
    [-1] on basement floor B
}
floor_ENGLI_in_EN_OVER = { $level ->
    [0] on ground floor
    [one] on floor 2
   *[other] on floor { $level + 1 }
}

So I get the problem why it would not make sense in your photo example (especially as the strings are totally different), but IMHO, in this example such a distinction would be a rather ugly workaround, because:

  1. It requires me to decide to which group 0 (ground floor) belongs to, i.e to "over" or "under"? While I would have an opinion on that, translators may not like it/be confused…
  2. As explained my data source is exactly this one integer. So if I split it in two groups, I would have to evaluate it in my code/logic, which is not really needed, if you just want to display a string. (Especially if both strings are supposed to get a positive number.)
  3. In contrast to the photos example, the strings here follow the same logic, only the "position (i.e. floor)" varies, e.g. it could be expanded to "XY is located in floor Z" -> "XY is located in basement floor 1" or "XY is located in floor 2". IMHO that perfectly fits into the use case of fluent's selectors.

So IMHO it would be unintuitive to split it into two strings and I see no advantage of doing so, I only see the listed disadvantages…

Other things I can think of:

  • While here it may not be the case, maybe in some use cases the localized values would not fit into the groups the programmer (arbitrarily) defined. E.g. if there would be a language, where floors <= 0 would be called "basement floor" and the ground floor starts with 1… Or something similar which shifts the groups…

rugk avatar Jan 21 '19 20:01 rugk

Regarding the negative and positive ranges, you can use a custom function to select the desired range.

floor_ENGLI_in_EN = { LOCATION($level) ->
    [under] on basement floor B{ FLOOR($level, ground: 1, sign: "positive" ) }
    [ground] on ground floor
   *[over] on floor { FLOOR($level, ground: 1) }
}

Or, using arithmetic functions:

# The made-up SIGN function returns -1, 0, or 1 here. It could be made
# to return a string-typed description (negative, zero, positive) instead.
floor_ENGLI_in_EN = { SIGN($level) ->
    [-1] on basement floor B{ ABS($level) }
    [0] on ground floor
   *[1] on floor { INCR($level) }
}

If you'd wish to define floors -1 and 1 as special cases, you'd need to use nested selectors. It's not possible right now to define exact matches when using custom functions. Exact matches only work for the built-in plural category matching.

# This currently doesn't work in Fluent.
# Custom functions can't uses exact matching.
floor_ENGLI_in_EN = { LOCATION($level) ->
    [under] on basement floor B{ FLOOR($level, ground: 1, sign: "positive") }
    [-1] on basement floor B
    [ground] on ground floor
    [1] on first floor
   *[over] on floor { FLOOR($level, ground: 1) }
}

The work-around is to use nested select expressions which single out the special cases:

# This will work.
floor_ENGLI_in_EN = { LOCATION($level) ->
    [under]
        { $level ->
            [-1] on basement floor B
           *[other] on basement floor B{ FLOOR($level, ground: 1, sign: "positive") }
        }
    [ground] on ground floor
   *[over]
        { $level ->
            [1] on first floor
           *[other] on floor { FLOOR($level, ground: 1) }
        }
}

Or, again using custom arithmetic functions:

# This will work as well.
floor_ENGLI_in_EN = { SIGN($level) ->
    [-1]
        { $level ->
            [-1] on basement floor B
           *[other] on basement floor B{ ABS($level) }
        }
    [0] on ground floor
   *[1]
        { $level ->
            [1] on first floor
           *[other] on floor { INCR($level) }
        }
}

stasm avatar Jan 22 '19 11:01 stasm

Ah, great solutions, thanks. :smiley: I also did not know that you can actually nest selectors.

That said, I think if you only had this one selector for negative numbers, the whole syntax/defintion here would be a lot simpler.

rugk avatar Jan 22 '19 12:01 rugk

I also did not know that you can actually nest selectors.

Variant values (which follow variant keys, e.g. [other]) are just like values of messages and attributes. They can contain text as well as interpolate variables and other expressions inside of { }, including select expressions.

stasm avatar Jan 22 '19 13:01 stasm