Describe the bug
I have an SSR app with three languages: ES, EN, DE.
In the first load, it will render the default language, EN.
When I select one of the other languages, let's say ES, it will trigger the Suspense fallback.
The strange behavior appears when I select the third language, DE. In this case, it doesn't trigger the fallback, despite it's loading the required namespaces.
This only happens when I try to use not-default namespaces (in my example, alternate ns).
Here is a PR with the updated example where you can see this issue: https://github.com/i18next/react-i18next/pull/1030
Occurs in react-i18next version
"react-i18next": "^11.2.7",
To Reproduce
Steps to reproduce the behavior:
Expected behaviour
Either suspense gets triggered in all the switches or it doesn't trigger for any of them.
(I would like the second option for my case, but if it's configurable I don't mind).
Screenshots
N/A
OS (please complete the following information):
Untested but seems like testing for loaded here: https://github.com/i18next/react-i18next/blob/master/src/utils.js#L80 fails...as adding resources via initialStore does not set the backend load state: https://github.com/i18next/react-i18next/blob/master/src/utils.js#L57
Changing https://github.com/i18next/react-i18next/blob/master/src/utils.js#L80 to
if (loadNotPending(lng, ns) && (!fallbackLng || loadNotPending(lastLng, ns) ||聽i18n.hasResourceBundle(lastLng, ns))) return true;
might solve this (yet not sure it's the best solution). Could you please test if this helps?
An alternative would be adding the load state by looping lng-ns here: https://github.com/i18next/react-i18next/blob/master/src/useSSR.js#L15
Tried that change, the fallback is still being triggered :(
Regarding your second idea, I believe that the problem is that something is not being initialized on SSR but it's initialized after the first language change. Is that what you are referring to? On a language change you loop through all the lng-ns? Even the ones that you are not changing to?
On SSR we only send the current language to the user (in this case, the default one, EN) so if I do a loop, all the other languages (DE, ES) should have a load state "pending".
I referred to eg. serverside renders in ES (not passing down EN) -> EN is the fallbackLng -> so changing to DE would trigger the suspense as neither DE nor EN are there
Could also be removing the language detector and not setting an initial language
i18n.init({ lng: 'en'}) (only defining a fallbackLng) causes the issue....hard to tell...really would have to debug through it...
Ok, in my case serverside renders EN and I'm passing down EN so the store gets populate with the EN lng.
Tried with i18n.init({ lng: 'en'}) in addition to the fallback but the behavior is still the same.
Will continue debugging this, it must be some initialization..
Watch out for two things:
1) what triggers the the update: https://github.com/i18next/react-i18next/blob/master/src/useTranslation.js#L70 --> should only be a valid languageChanged event EN -> ES
2) why we get a false ready state here: https://github.com/i18next/react-i18next/blob/master/src/useTranslation.js#L43 based on the code here: https://github.com/i18next/react-i18next/blob/master/src/utils.js#L43
I think I got it, if I don't populate the store with the SSR language (EN), I see the fallback only for the EN language (on load basically) but not when changing to the others, so probably we are missing something when hydrating the store.
Do you remember any particular step that we do when using the backend? The only difference I can find is that it uses the addResourceBundle to add the data to the internal store.
https://github.com/i18next/i18next/blob/master/src/BackendConnector.js#L90
So maybe this https://github.com/i18next/react-i18next/blob/master/src/useSSR.js#L16 it's the problem.
Found it, if I set the i18n.options.ns to the namespaces in the initialI18nStore, changing the language won't trigger the suspense fallback.
Hacky test:
if (initialI18nStore && !i18n.initializedStoreOnce) {
i18n.services.resourceStore.data = initialI18nStore;
+ i18n.options.ns = Object.keys(initialI18nStore['en']);
i18n.initializedStoreOnce = true;
i18n.isInitialized = true;
}
Which is the same the addNamespaces is doing for all the data loaded through the backend connector https://github.com/i18next/i18next/blob/master/src/ResourceStore.js#L16-L20
Ok, that seems to make sense...i18n.options.ns basically is a current list of all namespaces (on init it contains all to load initial and during usage we add namespaces lazy loaded on demand)...
so in this case a languageChange is triggered but the list is incomplete - so not all namespaces get loaded for the new language. The useTranslation will trigger a check on languageChanged event but see a namespace is missing triggering a load for that which throws the Promise and triggers Suspense...
Ok...will need to extract the list of namespaces there and append them to the list of namespaces...
awesome finding...thanks a lot for digging that deep 馃憦
could you please test if this would work (more generic code):
// nextjs / SSR: getting data from next.js or other ssr stack
if (initialI18nStore && !i18n.initializedStoreOnce) {
i18n.services.resourceStore.data = initialI18nStore;
// add namespaces to the config - so a languageChange call loads all namespaces needed
i18n.options.ns = Object.values(initialI18nStore).reduce((mem, lngResources) => {
Object.keys(lngResources).forEach(ns => {
if (mem.indexOf(ns) < -1) mem.push(ns);
});
return mem;
}, i18n.options.ns);
i18n.initializedStoreOnce = true;
i18n.isInitialized = true;
}
if that works I will publish an update containing this
Any update on this?
change was published in [email protected]
@jamuhl I'm sorry for the delay, I couldn't find the time last week to check :(
Your proposed code works fine.
Thanks for fixing this!