-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider fast-path for warm scenario async fluent-fallback functions #244
Comments
I think there are two actions which are currently conflated into one externally visible interface:
Combining these into one async So yes, I would support providing a synchronous message formatting call. Regarding |
The reason they are conflated is that setting an API as sync is a one way street and we didn't want to lock ourselves out of ability to execute async I/O late. For example, a scenario may happen where you request Another scenario we wanted to explore is a threshold of messages resolvable in a given locale. If a screen has 100 messages and we resolved 90 in locale A, and 10 in locale B, then that's fine. But if we resolved 3 in locale A and 97 in locale B, then maybe it makes sense to translate the whole screen in B? What if message exists, but references another message that doesn't? Should the top message fallback to locale B or stay in A? If stay in A, should it resolve the referenced message from locale B creating a mixed locale message, or return partially resolved message? We never got to rethink the error fallbacking of messages and if MF2.0 will follow Fluent here, then maybe it is ok to strengthen the model by stating that the fallback to a different locale happens only if a directly requested message is missing? Which, in return, would mean that if a directly requested message(s) are present we can guarantee that we will not perform I/O and call format synchronously? |
FWIW, my post-l10n mind looks at bundle as a mistake. I think that a mono-lingual abstraction layer in a truly multi-lingual system creates more problems than it solves. Maybe we're not talking about an entry-level thing, but more about caching already-rendered messages? That obviously raises the question what the cached thing is, as that, in general, still depend on message context, right? In the context on fallback, term references might be special, as I can see reasons for them to favor terms in the same language over terms in the overall scheme of things. Maybe they even have a different language fallback chain like just |
Interesting. What do you think should be the layer structure? Back of a napkin blurb is okay, no need to refine, just wondering what your mind holds now.
With Message being a list of parts, we could store it resolved and "replace" dynamically just pieces that come from variables maybe? Not sure how far this will take us.
How are they special? In MF2.0 we do introduce dynamic references and I imagine you'd want to maintain consistency between message and a referenced message as well (both static and dynamic), am I wrong? |
My latest thinking was more around the Localization class holding a cloud of entries, bound to the language of their resource. That would also allow for bidi separators being more context sensitive. The relationship between the Localization class and resources would be that it's directly feeding upon a generator of parsed resources per "source". Message references would start at the top of the generator, term references might be different. That term refs are different is due to the quality that a term in the same language can bring the translation of a message. That implies that all term attributes and parametrized behavior is abstracted if the message language doesn't match the term language.
Yeah, that'd be a choice. Or only cache primitive messages.
This goes back to what I detailed earlier. The term attributes and parametrized terms only make sense within a particular context. That context is constrained by linguists creating an agreement on which meta data has which meaning. For The straight-forward MVP approach to this would be to draw the lines across locales. Maybe there's a path for particular ecosystems to opt in to blurring those lines between particular locales. |
The way that I want to solve this in MF2 is to 1) more clearly associate each message with an identifiable resource that is loaded in a single operation and 2) require term/message references to identify the resource that they're pointing at in a way that's independent of any runtime variables. With those constraints, an async resource load operation can traverse its messages and find any external resource references that need to be loaded as dependencies of the current resource. This should allow for the above scenario to be identified already during the resource load.
I wonder if "screen" is the right dimension in which to be considering the implementation of this? Sure, that's almost certainly the desired user experience, but it's rather distant from the spaces in which we actually have messages. Is this perhaps related to the multi-resource "context" you included in the DOM localization draft? I could see that as an appropriate place/level at which to configure this sort of fallback. Implementation-wise, I'm thinking of the context attaching an error handler wrapper to formatting calls of its constituent messages that would track overall failures and trigger fallback when some threshold is reached.
I think this level of fallbacking should happen within/around resource loading, rather than message formatting. It should be possible to trigger the fallback based on a side effect of a message formatting call, i.e. in an error handler of some description. In general, I guess I'm thinking that fallbacking to a different locale should be considered error recovery, and that this means that it's okay to even momentarily render broken strings while we're loading the fallback locale's resources. |
When designing Fluent we actually weren't certain how much we can commit to such claim. The alternative, non "error recovery" scenario we explored is something we called "micro-locales" or "partial-locales" - for example Spanish has over 20 dialects. The idea was that we could have a "full" The challenge is that it means that 90% of strings would have to fallback and fallback becomes "normal" behavior. We wanted to explore the no-build-time, runtime-only approach as more flexible and resilient assuming we can make it performant. |
Okay, that makes sense. For micro-locales, fallbacking like this would indeed need to be treated as a normal rather than exceptional event. This does have us using one fallbacking system for two different purposes, though, and I'm not sure if that's optimal. As in, if the fallback chain would be The way I was imagining this in my head would be that a single |
The error fallbacking
fluent-fallback
'sLocalization
class performs (both in JS and Rust) is that we fall back onto the next locale if a message is missing, but not if there's an error while resolving a message that is present.That means that if the user is requesting
["key1", "key2", "key3"]
andkey3
is missing we will trigger a fallback onto the next locale for it, but ifkey1
referenceskey3
which is missing, we'll resolvekey1
in the first locale with a missing piece, like:Hello, { key3 }
.This is fairly arbitrary and when we designed it we want to retain ability to fine-tune the fallbacking model, which we now have a chance to better define when working on MF2.0.
But if we were to keep this model for MF2.0, there's an interesting optimization that may be useful for Firefox and Fluent DOM application: if we loaded strings (async), and we are asked to translate a list of keys, we can synchronously check if all keys are present in the top locale (by using
bundles[0].has_message
) and if so, we know we will not trigger any async I/O fallback!This means, we could then shortcut to resolve this API call synchronously.
So an example DOM Localization call triggered by L10n Mutations could perform something like:
This would act the same way for initial call (triggering async), and for incomplete scenarios (still using async), but for the most common scenario it would allow us to execute frame faster.
This came to play in cases like https://bugzilla.mozilla.org/show_bug.cgi?id=1737951 where engineers want to flip l10n-id back and forth and expect that while initial l10n frame may take longer, the "warm" swapping of l10n-id during UI lifecycle should be very fast because the strings are loaded.
We could even do the
messages_available
check with iteration over theCachedBundles
and then even if one of the keys is only available in a fallback but that fallback is already loaded, we still know we can operate in sync mode and no I/O will be needed.To recap, this optimization would be useful, but it requires us to ensure that:
_sync
method calling ability in async scenario - for cases where we are certain all messages are availableI'm wondering how far are we from such guarantees and how should the
Localization
class API be redesigned to model for this use case.The text was updated successfully, but these errors were encountered: