Skip to content

Dynamic References #130

Closed
Closed
@zbraniecki

Description

@zbraniecki

Is your feature request related to a problem? Please describe.

There are scenarios where a translation message needs to reference another message which is not statically known.

To illustrate, let's start with a pair of messages and a message that references them:

board-name = Board
dashboard-name = Dashboard

remove-board = Remove { board-name }
remove-dashboard = Remove { dashboard-name }
let msg = api.get(semaphor ? "remove-board" : "remove-dashboard");

This is already possible and well within the scope of regular message references, but it doesn't scale well to scenarios where the referencing is more nested and/or the number of items grows.

For example a computer game may have 10, 100 or 200 monsters and a lot of messages want to reference any of them.

Having to write 3 messages to nest 3 levels deep, or having to write a remove-X message for each X is not sustainable and blows up payload size, maintenance complexity etc.

Describe the solution you'd like

Dynamic references is a concept that allows for the message ID which is to be referenced to be decided at runtime:

board-name = Board
dashboard-name = Dashboard

remove-item = Remove { $item }
let msg = api.get("remove-item", {
  item: MSG_REF(semaphor ? "board-name") : "dashboard-name")
});

or:

monster-dinosaur = Dinosaur
monster-elephant = Elephant
monster-ogre = Ogre

killed-notice = You've been killed by a { $monster }
let msg = api.get("killed-notice", {
  item: MSG_REF(validatedMonsterName)
});

Describe why your solution should shape the standard

I believe that the use cases where the dynamic references are needed are very badly served by workarounds, and if we don't provide a good API for it, users will develop data and code for handling such cases that will be inherently hard to maintain and costly to clean up.

Additional context or examples

There are implication on our decision on this feature for other facets we're considering:

  • Arguments passing (implicit, explicit etc.)
  • Fallbacks
  • CAT tool meta-data

The common workaround an engineer may do today is:

let item = api.get(semaphor ? "board-name" : "dashboard-name");

let msg = api.get("remove-item", {
  item
});

which has multiple issues with it.

  • It loses context between calls as from the second call's perspective the item looks like a hard-coded string, rather than another message from the same context.
  • Cross-language fallbacking is impossible.
  • Any CAT tools, CMS, TMS and MT operate blindly with no way to convey information about the related messages
  • Layout cannot rely on directionality of placeholder since it looks like potentially requiring a reset

Future GUI bindings impact

Lastly, this paradigm is particularly painful for GUI bindings (#118). I understand that we consider #118 to be out of scope in some ways, but I think this issue is a good testbed for how far ahead we want to think and design for.

Assuming l10n bindings for GUI will want to use declarative bindings resolved asynchronously for animation frame, cases where dynamic references are needed are particularly painful, because the user cannot declare ID and MSG_REF as an argument on the UI widget and let the l10n system resolve it.

The user has to fetch the references message, resolve it, and then declaratively define the ID and resolved message as a String argument in the binding.

If we want to allow for localization to be asynchronous, without dynamic references we'd do:

let monster = await api.get(semaphor ? "monster-elephant" : "monster-ogre"); // String

element.setL10n({id: "killed-notice", args: { monster }});

which in case of HTML for example may lead to:

<p l10n-id="killed-notice" l10n-args="{monster: 'Elephant'}"/>

Now not only did it complicate the async code, but it also hardcoded Elephant in the binding as if it was a hardcoded String.

If the system needs to retranslate this UI widget, because user changed locale, it will be able to pull up new killed-notice but will pass the pre-resolved Elephant in the old language.
To de-hardcode it, we'd need to write a helper callback to be called on each localization cycle like this:

let monster = await api.get(semaphor ? "monster-elephant" : "monster-ogre"); // String

element.setL10n({id: "killed-notice", args: { monster }});
element.onBeforeL10n(async () => {
  let monster = await api.get(semaphor ? "monster-elephant" : "monster-ogre"); 
  self.setL10nArgs({monster});
});

This will allow the system to update the element's argument before updating the main message leading to proper translation on change, but is pretty complicated to maintain and I'd say a bad developer experience.

With Dynamic References, the user can just do:

let monster = MSG_REF(semaphor ? "monster-elephant" : "monster-ogre");

element.setL10n({id: "killed-notice", args: { monster }});

which in case of HTML for example may lead to:

<p l10n-id="killed-notice" l10n-args="{monster: {type: 'msgref', id: 'monster-elephant'}}"/>

and now the DOM contains all the information to retranslate as needed without any additional information, the declarations are synchronous, the localization can be asynchronous, the state is preserved and the separation of concerns between translation/retranslation and updates is easy.

There is more background info in Fluent issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FutureDeferred for future standardizationrequirementsIssues related with MF requirements listresolve-candidateThis issue appears to have been answered or resolved, and may be closed soon.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions