React-i18next: Trans component with complex children does not add the key to Locize using backend

Created on 7 Jan 2020  Â·  20Comments  Â·  Source: i18next/react-i18next

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
image

OS (please complete the following information):

  • Device: [e.g. MBP 2018 13"] Mac OS
  • Browser: [e.g. Chrome 70.0.3538.77] Chrome lastest

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.

All 20 comments

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:

  • Can a complex example (similar to this one) be added to the documentation of the Trans component? (for react-i18next), good examples save everyone's time, especially yours.
  • Why is there no error or warning thrown to my face when doing this, that would help me figure out this by myself? It would be a good thing to throw warnings when unexpected is used within the Trans component.

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.

image

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:

  1. the missing logic does not handle the context and will always create the nude key whatever context is used, and therefore the developper has to manually create the contextual keys manually?
  2. or that the missing logic always create a nude key whatever context is provided the first time, but then creates contextual keys if the nude key already exists? (that would make no sense, so I guess it's 1)

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pwiszowaty0 picture pwiszowaty0  Â·  4Comments

dawsbot picture dawsbot  Â·  4Comments

Jessidhia picture Jessidhia  Â·  4Comments

tankpower1 picture tankpower1  Â·  3Comments

ok2ju picture ok2ju  Â·  3Comments