Describe the bug
One of my translated component does not add the key inside Locize automatically, while using other simpler Trans or t works fine.
Occurs in react-i18next version
i18next: 19.0.3
react-i18next: 11.2.6
i18next-locize-backend: 3.0.0
To Reproduce
<Alert
color="success"
>
<Trans
i18nKey={'simulatorResults.resultsBloc.summary'}
tOptions={{
context: simulated.isDeferredTypePartial ? 'partial' : 'total',
}}
>
Votre simulation de prĂȘt d'un montant de <b>{prettifyNumber(amount)}âŹ</b> Ă <b>{simulated.interestRate}%</b> d'une durĂ©e
de <b>{simulated.totalDuration} mois</b> dont <b>{simulated.deferredPaymentsQuantity} mois</b> de
différé {simulated.isDeferredTypePartial ? 'partiel' : 'total'} est possible ! Félicitations !<br />
Il nâest pas obligatoire de souscrire Ă une assurance.<br />
Consultez le chapitre 8 du{' '}
<a
href={'#'}
onClick={this.scrollToEbook}
>"Guide du prĂȘt Ă©tudiant"</a>{' '}
pour comprendre dans quels cas l'assurance est utile.
</Trans>
</Alert>
Expected behaviour
The key simulatorResults.resultsBloc.summary should be added to Locize with the content for the default language (fr)
Screenshots

OS (please complete the following information):
Additional context
There is no log in the console about missing translation for the key simulatorResults.resultsBloc.summary` (but I do usually get warnings when a key is not found in Locize so I know it's being created automatically, just not for this particular)
I strongly believe the issue is related to the content being "complex", it's the first time I have to deal with such complex translation and I'm not sure to do it properly.
Looks more like the content is invalid...the interpolations ...nt de <b>{prettifyNumber(amount)}âŹ</b> Ă ... are not correct for using with the Trans component -> https://react.i18next.com/#what-does-my-code-look-like
must be something like ...you have {{count}} unread... <- passing in an object { count: 10 } so we could interpolate those....
I understand. A few things:
Also, beware your usage of count in your examples, because it's really, really confusing.
count seems to be a very special variable that is related with pluralization, I do not understand how I'm supposed to pass my own set of variable, by reading your answer. (or the doc at https://react.i18next.com/latest/trans-component#using-with-react-components)
Could you give me a proper example with one of my variables? Am I supposed to use values? I'm confused.
JSX ...nt de <b>{{ amount: prettifyNumber(amount) }}âŹ</b> Ă ... <- {{}} is passing an object through element children
translation string ...nt de <1>{{ amount }}âŹ</1> Ă ... <- {{}} is interpolation syntax
The samples already show all <0>, interpolation and plural...adding more content inside Trans in a sample just adds to the cognitive overhead in understanding one concept...guess better would be more small samples explaining just one of the above elements...
--- edit
sidenote: formatting the currency would be better using: https://www.i18next.com/translation-function/formatting
I understand your point of view regarding cognitive overhead. Using one example per option would indeed be beneficial IMHO. Having one complex example that shows how all options can play together would also be a good thing though. I'd suggest adding the complex example at the bottom, so that it gets used only by those who are looking for it. Another option is to "hide" it by default, using a collapse or similar. (it's possible in markdown)
Alright, I hadn't thought of using an object through element children, may be what I'm looking for indeed. I'll give it a try.
I tried the following:
<Trans
i18nKey={'simulatorResults.resultsBloc.summary'}
tOptions={{
context: simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL,
}}
>
Votre simulation de prĂȘt d'un montant de <b>{{ amount: prettifyNumber(amount) }}âŹ</b> Ă <b>{{ interestRate: simulated.interestRate }}%</b> d'une durĂ©e
de <b>{{ totalDuration: simulated.totalDuration }} mois</b> dont <b>{{ deferredPaymentsQuantity: simulated.deferredPaymentsQuantity }} mois</b> de
différé [deferredType] est possible ! Félicitations !<br />
Il nâest pas obligatoire de souscrire Ă une assurance.<br />
Consultez le chapitre 8 du{' '}
<a
href={'#'}
onClick={this.scrollToEbook}
>"Guide du prĂȘt Ă©tudiant"</a>{' '}
pour comprendre dans quels cas l'assurance est utile.
</Trans>
I used [deferredType] in order to decrease the complexity (that's what's related to my context). I'll have to translate it properly on Locize by doing so, but I'm forced to do that because I don't understand how to do have a proper default, it's too confusing, so I simplified it for now.
I get exactly the same behaviour as before, the default content is properly displayed on my app, but no key is created in Locize, and there is no warning/error in the console despite debug mode being enabled.
I've been stuck on that particular translation for more than 12h now, I'd love it if you could give me a full example.
Edit: I was not translating the correct string, hence the fact no key was added to Locize... I'll dig in and let you know if there is still an issue or if it was just my dumb mistake.
Now using the correct string, I see the following log in the console stating that the missing translation is added to Locize, but no translation is actually added to Locize. (tried multiple refresh, nothing shows up)
<Trans
i18nKey={'simulatorResults.resultsBloc.recommendedSimulationIsViable.summary'}
tOptions={{
context: simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL,
}}
>
Le prĂȘt de <b>{{ amount: prettifyNumber(amount) }}âŹ</b> Ă <b>{{ interestRate: recommended.interestRate }}%</b> le plus adaptĂ© Ă votre parcours doit
durer <b>{{ totalDuration: recommended.totalDuration }} mois</b> dont <b>{{ deferredPaymentsQuantity: recommended.deferredPaymentsQuantity }} mois </b> de
différé [deferredType].<br />
Il nâest pas obligatoire de souscrire Ă une assurance.<br />
Consultez le chapitre 8 du{' '}
<a
href={'#'}
onClick={this.scrollToEbook}
>"Guide du prĂȘt Ă©tudiant"</a>{' '}
pour comprendre dans quels cas l'assurance est utile.
</Trans>
Logs (browser console):
i18next::translator: missingKey en common simulatorResults.resultsBloc.recommendedSimulationIsViable.summary Le prĂȘt de <1>{{amount}}âŹ</1> Ă <3>{{interestRate}}%</3> le plus adaptĂ© Ă votre parcours doit durer <5>{{totalDuration}} mois</5> dont <7>{{deferredPaymentsQuantity}} mois </7> de diffĂ©rĂ© [deferredType].<br/>Il nâest pas obligatoire de souscrire Ă une assurance.<br/>Consultez le chapitre 8 du <14>"Guide du prĂȘt Ă©tudiant"</14> pour comprendre dans quels cas l'assurance est utile.
POST request payload to https://api.locize.app/used/XXX/latest/fr/common:
[
...
"simulatorResults.resultsBloc.title",
"simulatorResults.resultsBloc.recommendedSimulationIsViable.summary_partial",
"simulatorResults.resultsBloc.recommendedSimulationIsViable.summary"
]
I don't get what's not working, it looks alright, the key looks fine in the payload, it just doesn't show up in the Locize UI
Damn it, it was because I was using en which is not my primary language. The key was correctly added to Locize once switching to fr. đ
Last question now, is how to deal with the default translation properly handling the context value. As I said before, I simplified i by using [deferredType] in the text. What would you recommend in such case?
There is also something else I don't understand. The key was created properly, but the context isn't specified in the key's name.
<Trans
i18nKey={'simulatorResults.resultsBloc.recommendedSimulationIsViable.summary'}
tOptions={{
context: simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL,
}}
>
Le prĂȘt de <b>{{ amount: prettifyNumber(amount) }}âŹ</b> Ă <b>{{ interestRate: recommended.interestRate }}%</b> le plus adaptĂ© Ă votre parcours doit
durer <b>{{ totalDuration: recommended.totalDuration }} mois</b> dont <b>{{ deferredPaymentsQuantity: recommended.deferredPaymentsQuantity }} mois </b> de
différé {simulated.isDeferredTypePartial ? 'partiel' : 'total'}.<br />
Il nâest pas obligatoire de souscrire Ă une assurance.<br />
Consultez le chapitre 8 du{' '}
<a
href={'#'}
onClick={this.scrollToEbook}
>"Guide du prĂȘt Ă©tudiant"</a>{' '}
pour comprendre dans quels cas l'assurance est utile.
</Trans>
I'd expect the key to be named either simulatorResults.resultsBloc.recommendedSimulationIsViable.summary_partial or simulatorResults.resultsBloc.recommendedSimulationIsViable.summary_total, but not just simulatorResults.resultsBloc.recommendedSimulationIsViable.summary because I'm always providing a context value. Looking at the doc https://www.i18next.com/translation-function/context#basic it looks like a "nude" key is always created, but it really doesn't make sense for this use case and creates a translation that isn't supposed to exist and shouldn't be used.
Am I wrong to use the context feature here?
in i18next for plural and context "nude" keys are always treated as the fallback...while for plurals we got an option to create all keys there is no such option for context (as we do not know the options contexts can take)
Okay, I understand that and it makes sense. But what I don't understand is why it only created the "nude" key. It didn't create any context key on Locize.

I believe it should have created at least one context key, like simulatorResults.resultsBloc.recommendedSimulationIsViable.summary_partial, but it didn't đ€
simple the key is simulatorResults.resultsBloc.recommendedSimulationIsViable.summary
the missing logic does not care for context - as it can only create the bare key and the current one...https://github.com/i18next/i18next/blob/master/src/Translator.js#L237
after creating that key a context with other option will never be missing as the unspecific fallback key (bare "nude") exists...so for context a developer has to provide all variations...
Ahhh, that's really not what I had expected. I'm still a bit confused about the behaviour (sorry).
Do you mean that:
If it works as 1), then wouldn't even be easier for me to put the context as part of the key, so that one distinct key exists for each context, but thus can be created automatically by the missing logic?
Something like :
<Trans
i18nKey={`simulatorResults.resultsBloc.recommendedSimulationIsViable.summary_${simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL}`}
>
Instead of
<Trans
i18nKey={'simulatorResults.resultsBloc.recommendedSimulationIsViable.summary'}
tOptions={{
context: simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL,
}}
>
1) a key is only missing if not found anything suitable. anything suitable -> value in fallback, value as singular in case of plural, "nude" fallback key in case of context (so eg. context: male, female -> fallback = other).
2) missing can create plurals as it knows all the plural forms needed. missing can't create all context forms as it does not know them -> they are what the developer defines...
3) as it saved the nude key as best option - that key will never be missing again as it now can perfectly fallback to the "other" / "nude" option
yes you can: <Trans
i18nKey={simulatorResults.resultsBloc.recommendedSimulationIsViable.summary.${simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL}`}
but i would use a.instead of the reserved_`
About 2., missing could create the right context form though instead of creating the nude key, because it knows the context in the default sentence when I have the following:
<Trans
i18nKey={'simulatorResults.resultsBloc.recommendedSimulationIsViable.summary'}
tOptions={{
// context is provided, so the missing could create the key for that context only
context: simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL,
}}
>
If that's really the current behaviour, then I fail to see the interest for the developer to use the context feature (has to create all context variations manually), instead of just using a different key per context (using . instead of reserved _ as you stated).
Why not create the right contextual key as default translation when the context is provided? I don't see any drawback of doing so. (just curious, not expecting it to be "fixed")
eg..context gender: male, female, other -> we could save two of them...all others have to be created anyway using the UI so you get: key_male, key and have to generate key_female manually anyway...sure we could bloat the lib even more and add an option to pass all possible options or make some strict extra check for context...and miss anything not existing...
difference between context and just using a different key => context can fallback to the "other" key
I'm not thinking about something so bloated actually (implementing what you're describing would be "too much" IMHO).
Here is a more in-depth example of what I mean, let's take your example with only male and female options:
const userKind = 'male'; // can be "male" or "female"
<Trans
i18nKey={'welcome'}
tOptions={{
context: userKind,
}}
>
Hello {{ userKind === 'male' ? 'handsome' : 'darling'}}
</Trans>
When i18next comes across that key for the first time (where const userKind = 'male'), it would create the key welcome_male with the default content Hello handsome.
When the same component is loaded for the first time with const userKind = "female" it would then create the key welcome_female with the default content Hello darling.
That's as simple as that, there would be no fallback by default as the translation was never loaded without the userKind context being different from either "male" or "female". And if userKind is falsy, then the fallback key welcome would then be created.
If the needs change later on and we want to add a other option, then the welcome_other will be created automatically as well, it scales correctly.
This behaviour makes more sense to me, the developper wouldn't need to create all contextual keys manually, they would be created as soon as the given context changes for the first time. It would still resolve to the fallback value if it exists. Also, the default value in the source code still makes sense and can be understood. (developer intent can be understood)
What do you think about this different approach?
The problem arises, when the context value passed to i18next is coming from a different source with more dynamic output.
i.e. I want only to catch 2 out of hundreds of context variations and the rest with de âbaseâ key...
When i18next comes across that key for the first time (where const userKind = 'male'), it would create the key welcome_male with the default content Hello handsome.
When the same component is loaded for the first time with const userKind = "female" it would then create the key welcome_female with the default content Hello darling.
Like said...it does not work like this...context always gets a fallback key without the _myContextValue therefore not run the missing again -> changing that is not so easy.
Hello {{ userKind === 'male' ? 'handsome' : 'darling'}} is english and might not apply to other countries / areas in the world...so eg. some language will only use the fallback of "hello there" not gender specificas
you don't like the behaviour...then use key.female, key.male, key.other...works too -> just is not a context / selector usage
The problem arises, when the context value passed to i18next is coming from a different source with more dynamic output.
In such case, a solution would be to tell the Trans component not to create new contextual keys automatically (hence would use the fallback version), I believe (without much background information) this case is the exception rather than the rule so that'd be fine.
But I see where this is going > more complexity. I just wanted to understand your intention behind this choice, as it didn't make sense to me. I understand better now the "whys". Thank you for your inputs! :)
Here is how I eventually implemented my solution:
<Trans
i18nKey={`simulatorResults.resultsBloc.recommendedSimulationIsViable.summary.${simulated.isDeferredTypePartial ? DEFERRED_TYPE_PARTIAL : DEFERRED_TYPE_TOTAL}`}
>
Le prĂȘt de <b>{{ amount: prettifyNumber(amount) }}âŹ</b> Ă <b>{{ interestRate: recommended.interestRate }}%</b> le plus adaptĂ© Ă votre parcours doit
durer <b>{{ totalDuration: recommended.totalDuration }} mois</b> dont <b>{{ deferredPaymentsQuantity: recommended.deferredPaymentsQuantity }} mois </b> de
différé {simulated.isDeferredTypePartial ? 'partiel' : 'total'}.<br />
Il nâest pas obligatoire de souscrire Ă une assurance.<br />
Consultez le chapitre 8 du{' '}
<a
href={'#'}
onClick={this.scrollToEbook}
>"Guide du prĂȘt Ă©tudiant"</a>{' '}
pour comprendre dans quels cas l'assurance est utile.
</Trans>
Which automatically created 2 keys with proper translations for the default language.