Repository navigation
Replies: 3 comments 6 replies
|
Note that if accepted, I can look to contribute. I actually looked into making a PR, but as stated above I could not find how to add the new option heh. I can have a better look later tho |
|
Thanks for raising this! The frustration behind it is real, and it's close to something we've been exploring from another angle in #2517. My main concern is that the contract isn't discoverable in either direction. That's worth contrasting with #2517, where a term is an actual catalog message. The translator opens the editor, sees the variants, and knows what's available - discovery is free because the mechanism is the catalog itself. I'd also separate gender out. In most sentences the gender that matters belongs to the referent, not to a global user attribute - as soon as it's |
|
This proposal is totally makes sense to me. It also very easy to implement. The concern about discoverability of the available variables is totally correct - but it's not a blocker. If in your team, or in your AI docs you have this stated that you always have However i would steer the implementation in slightly diffrent direction. This variables is runtime-related things. Where the lingui.config is about cli configuration and compile time things. This default variables should be simply specified during creation of i18n object and it should be a way to extend/override it during app lifiteme. Say for example - we don't have a user info right after application startup, so we don't know a gender. We create an i18n object without this data, then when user data is coming you just: i18n.setVariables({
gender: user.gender
})So here are usecases i see:
So to adress (1), we just need to extend a export type I18nProps = {
locale?: Locale
locales?: Locales
messages?: AllMessages
missing?: MissingHandler
}With a new property. (name is TBD) To address (2) and (3) something as // i want to override only gender, and keep rest
i18n.setVariables((prev) => {
...prev
gender: user.gender
})
// i want to reset all to just this
i18n.setVariables({
gender: user.gender
})
// computed property
i18n.setVariables({
get gender() { return user.gender }
})To address (4) |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Motivation & Background
Let's take for example the message
Welcome!. When translated to spanish, which is a gendered language like many others, this may be translated to:Although we all know in this room that we can always use ICU-select to add gendered variants to this message, app developers may not. In a way, it is possible that EVERY message in the catalog should have ICU-select for gender "just in case". But that's obviously not optimal neither for performance nor for DevEx.
Inspiration: Android's Grammatical Inflection
This idea is inspired by https://developer.android.com/about/versions/14/features/grammatical-inflection, which allows apps to address the user using their preferred profile gender.
In Android, supporting grammatical gender relies on separate resource qualifiers (e.g.,
es-femenine/strings.xml,es-masculine/strings.xml). Replicating this multi-file approach in LinguiJS (and any other competing localization library) would require generating separate catalog files (es.femenine.po,es.masculine.po,es.po), leading to a combinatorial explosion of catalogs.Instead of bloating network requests, it would be awesome if the Translators could wrap the entire input message with an ICU-select targeting the user's gender.
For example, following through with the example above, the translators may generate the following catalog:
Since this is a valid ICU expression,
lingui compileactually compiles it readily and without a problem.The Core Problem
App developers will use
<Trans>Welcome</Trans>without passingvalues={{ __gender: userGender }}because the source string is not an ICU expression, and they were not aware that it would be "genderified".At runtime, evaluating this target
__genderis missing from the payload, so theotherbranch is chosen.Generalizing
At its core, this problem extends beyond grammatical gender. As I was writing this RFC, I realized that the the real problem is an undefined variable that might represent a "global" state, for which a default value could be provided. This allows translators, as the experts in their language, to include the variable if they deem it appropriate.
Obviously, this would not be an open-ended set of variables. As the use case has to be extremely specific, it should be possible to configure a set of variables whose default values can be configured to be guaranteed to be provided at runtime without the app developer having to explicitly declare it on the source code.
Some use cases which may be addressed by this feature:
{__gender, select, ...}based on active user profile.{__appName}or{__companyName}in white-labeled applications.{__supportEmail}or{__currentYear}in common footer/legal templates.Rather than building a specific, intrusive compiler feature for gender, LinguiJS can handle all of these gracefully by providing a missing variable resolution contract at runtime. Which I believe simplifies the problem and avoids hardcoding for one specific use case.
Goals
values={{ __gender: 'FEMENINE' }}, the caller's value always takes precedence over fallback resolvers.Design
API
Add a
defaultValuesoption (or something like that) tolingui.config.tsconfiguration file, and accept an object map that supports both static values and dynamic getter functions:Defined as:
2. Runtime Resolution Pipeline
When
i18n._(id, values)evaluates a compiled ICU message, variable lookup follows a clean order of precedence:values[varName]defined?values[varName]defaultValues[varName]defined?return typeof defaultValues[varName] === 'function' ? defaultValues[varName]() : defaultValues[varName]return undefinedImplementation (
@lingui/core)Caveats
SSR
The resolver might resolve a value available exclusively during runtime. The response renders correctly when the default value is a static string or a function with a value resolvable during SSR. However, a value accessible only at runtime (e.g.
window.MY_VALUE), the UI would "flash" the original message before switching to the new one.By Convention Only
Supported default values will be limited to a specific subset of pre-configured options. Translators (or the scripts responsible for generating translated catalog files) need to be mindful of this feature, as it may introduce an additional step into the localization pipeline. However, this functionality is entirely opt-in and maintains 100% backward compatibility.
Alternative Considered
While we're pursuing this feature to improve the experience for non-male users (given that "neutral" forms in gendered languages typically default to the masculine form) we found a way to implement it by adding a plugin that "captures" ALL
<Trans>ort()call during build-time. This plugin would "inject" our target variable into EVERY regardless of whether it's actually used. The performance impact was minimal, but the approach felt too hacky. I believe this feature may be of broader interest to others.All reactions