projectfluent / fluent

Fluent — planning, spec and documentation
https://projectfluent.org
Apache License 2.0
1.42k stars 45 forks source link

List formatting #79

Open vladkolotvin opened 6 years ago

vladkolotvin commented 6 years ago

Support for list formatting would be extremely useful. I have noticed that you had dropped support for it with reason that it is too complex.

https://github.com/projectfluent/fluent/commit/529600ff0e98a84f7e6f905bd6f17df8731b3455

But complexity can't be a reason to drop it entirely. Now we haven't any alternative. There is some proposal for list formatting API in ecma402.

https://github.com/tc39/proposal-intl-list-format

It can be an argument to think about reimplementing this functionality.

stasm commented 6 years ago

Thanks for filing this issue!

I removed the list data type because we didn't have clear use-cases for it, it was under-spec'ed and made the JS implementation more complex. The increased complexity stemmed from the fact that the elements of lists should also be valid types in Fluent which made lists monadic in nature.

It's helpful to see interest in bringing it back :) Can you describe your use-case in a little bit more detail?

Some of the recent changes in the implementation of MessageContext (like https://github.com/projectfluent/fluent.js/commit/8c419b175345fd1764494e1396b1ab508b1c049f) should make it easier to implement list types at some point in the future. It hasn't been a priority for us for now.

Some open questions remain:

The author of the TC39 proposal you linked is @zbraniecki, Fluent's core contributor :) I'm positive he will be interested in this issue now or when the proposal advances to Stage 3 or 4.

vladkolotvin commented 6 years ago

Besides basic example for social networks

"Ann, Peter and other 5 other people liked your photo"

as a game developer I'm stuck with localising complex quests descriptions. Example:

"Collect meat from cows, pigs and sheep."

or

"New dungeons are unlocked on 10, 20 and 30 levels."

It is really hard to maintain such descriptions manually. Parameters are defined in config files or calculated at runtime and can be changed at any moment.

Concerning open questions: Can lists be used as selectors? - At first glance this isn't a blocker for list formatting feature and can be or not implemented in the future. If a list is passed in to a Function, should we toString its elements first? - Why not? Can lists be used as values of named arguments to Functions? - I didn't find named arguments in documentation. Could you give some details?

stasm commented 6 years ago

Thanks for the real-life examples!

In your use-cases, does the number of elements in a list vary? In other words, is it "cows, pigs and sheep" for some users and "cows and pigs" or just "cows" for others? If so, the names of animals would likely come in form of a single external argument, like the following:

# The placeable could also be written as LIST($sources).
quest-desc = Collect meat from { $sources }.

This would require two additions to Fluent:

  1. External arguments (like $sources) would need to accept list-typed data. As I mentioned before, we'd need to specify what happens if someone tries to use an argument like this as a selector. We could start by not supporting list-typed selectors and always choosing the default variant if they're used as selectors none the less.

  2. The values in $sources should be localized. Ideally, we'd have a way to resolve them to other messages in the same MessageContext. I think this is doable without any changes to the Fluent Syntax. I'll open an issue in the fluent.js repo with a proposed solution.

stasm commented 6 years ago

Here's another example from Firefox (source). Thanks to @flodolo for helping me find it.

# %S is the website origin (e.g. www.mozilla.org)
getUserMedia.sharingMenuCamera = %S (camera)
getUserMedia.sharingMenuMicrophone = %S (microphone)
getUserMedia.sharingMenuAudioCapture = %S (tab audio)
getUserMedia.sharingMenuApplication = %S (application)
getUserMedia.sharingMenuScreen = %S (screen)
getUserMedia.sharingMenuWindow = %S (window)
getUserMedia.sharingMenuBrowser = %S (tab)
getUserMedia.sharingMenuCameraMicrophone = %S (camera and microphone)
getUserMedia.sharingMenuCameraMicrophoneApplication = %S (camera, microphone and application)
getUserMedia.sharingMenuCameraMicrophoneScreen = %S (camera, microphone and screen)
getUserMedia.sharingMenuCameraMicrophoneWindow = %S (camera, microphone and window)
getUserMedia.sharingMenuCameraMicrophoneBrowser = %S (camera, microphone and tab)
getUserMedia.sharingMenuCameraAudioCapture = %S (camera and tab audio)
getUserMedia.sharingMenuCameraAudioCaptureApplication = %S (camera, tab audio and application)
getUserMedia.sharingMenuCameraAudioCaptureScreen = %S (camera, tab audio and screen)
getUserMedia.sharingMenuCameraAudioCaptureWindow = %S (camera, tab audio and window)
getUserMedia.sharingMenuCameraAudioCaptureBrowser = %S (camera, tab audio and tab)
getUserMedia.sharingMenuCameraApplication = %S (camera and application)
getUserMedia.sharingMenuCameraScreen = %S (camera and screen)
getUserMedia.sharingMenuCameraWindow = %S (camera and window)
getUserMedia.sharingMenuCameraBrowser = %S (camera and tab)
getUserMedia.sharingMenuMicrophoneApplication = %S (microphone and application)
getUserMedia.sharingMenuMicrophoneScreen = %S (microphone and screen)
getUserMedia.sharingMenuMicrophoneWindow = %S (microphone and window)
getUserMedia.sharingMenuMicrophoneBrowser = %S (microphone and tab)
getUserMedia.sharingMenuAudioCaptureApplication = %S (tab audio and application)
getUserMedia.sharingMenuAudioCaptureScreen = %S (tab audio and screen)
getUserMedia.sharingMenuAudioCaptureWindow = %S (tab audio and window)
getUserMedia.sharingMenuAudioCaptureBrowser = %S (tab audio and tab)
vladkolotvin commented 6 years ago

Does the number of elements in a list vary? In other words, is it "cows, pigs and sheep" for some users and "cows and pigs" or just "cows" for others?

By phrase "calculated at runtime" I meant exactly that. Elements themselves and their number can be changed.

I'll open an issue in the fluent.js repo with a proposed solution.

👍

stasm commented 6 years ago

I ended up opening an issue in this repo, since it's more of a Fluent Design question. See #80.

Manishearth commented 1 year ago

Rust is also finding a need for this. At the moment we pre-format lists using ICU4X, but it gets tricky if you want to sometimes format lists as a, b, c, d, and 5 more, since ideally we'd be able to do something like:

val = list(
{count($mylist) -> {
  [1] $mylist,
  [2] $mylist,
  [3] $mylist
  *[other] concat($mylist, {{count($mylist)} more})
}

})

(pardon my half-remembered fluent syntax)