It is sometimes desired to parametrize message references in placeables. In this issue I'd like to propose a new argument type, extending FluentType
which could be used to programmatically pass message references as arguments to messages.
Problem Statement
Redundancy is considered good for localization. It allows localizers to tailor the wording and the grammar of the translation of each particular case. Also see Fluent Good Practices.
In general, the pattern of having one message per item is preferred over factoring the action out to its own message (Delete This { $item }
) and passing the translated item
in some way.
# Having two separate messages allows localizers
# to customize translations in each, if needed.
delete-picture = Delete This Picture
delete-video = Delete This Video
In some cases, however, this pattern doesn't scale well.
Consider this example from Firefox (source):
# %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)
Or the use-case @cruelbob gives in #79 (comment):
Collect meat from cows, pigs and sheep.
One of my favorite games, Heroes of Might and Magic III, pits armies consisting of over 140 different unit types in battles against each other. After every move, the battle log reads:
The Bone Dragon does 46 damage. 2 Griffins perish.
Or:
The Cyclops Kings do 233 damage. One Giant perishes.
If we wanted to avoid concatenation of sentences (two sentences per creature: one for do X damage
and one for X creatures perish
), we'd end up with 141² = 19,881 different permutations of creature pairs.
This doesn't scale well.
Proposed Solution
Introducing some redundancy should still be preferred for small sets of items. For large sets leading to lots and lots of permutations, it should be possible to parametrize the translation of placeables.
I'll use the example of HoMM3 because the other two also require the List Formatting feature to make sense.
I'd like to make it possible to pass external arguments which resolve to message references. Given the following FTL:
-creature-bone-dragon =
{
*[singular] Bone Dragon
[plural] Bone Dragons
}
-creature-griffin =
{
*[singular] Griffin
[plural] Griffins
}
# … Hundreds more …
battle-log-attack-perish =
{ $attacker_count ->
[one] The { $attacker_name[singular] } does
*[other] The { $attacker_name[plural] } do
} { $damage_points } damage. { $perish_count ->
[one] One { $defender_name[singular] } perishes.
*[other] { $defender_count } { $defender_name[plural] } perish.
}
…both $attacker_name
and $defender_name
would be arguments of type FluentReference
(extending FluentType
; same as FluentNumber
and FluentDateTime
). The developer would pass them like so:
let msg = ctx.getMessage("battle-log-attack-perish");
log(ctx.format(msg, {
attacker_name: new FluentReference("-creature-bone-dragon"),
attacker_count: 1,
defender_name: new FluentReference("-creature-griffin"),
perish_count: 2,
damage_points: 46
}));
This change mostly requires additions to the MessageContext
resolution logic. Syntax-wise, the VariantExpression
and the AttributeExpression
should be changed to accept both message identifiers as well as external arguments as parent objects (like in the $attacker_name[singular]
example above).
Open Questions
- Should we also allow public messages to be dynamically referenced like this?
Sign-offs
(toggle)
Also CC @flodolo.