Closed Robloche closed 5 years ago
1.) you call init before calling render -> does not mean init was done before you called render!!! (init is async)
https://github.com/i18next/i18next/blob/master/src/Translator.js#L345 calls https://github.com/i18next/i18next/blob/master/src/i18next.js#L296 ...your namespace not yet loaded
It's the same logic that tests for ready in react-i18next -> did you set useSuspense: false ?!?
In fact, I tried to call render
after the promise is fullfilled and got the same result...
I don't know useSuspense
. I'll check it out...
If useSuspense is false and you got somewhere a withTranslation or useTranslation loading a namespace - you also run into this
Can you paste your i18next.init options and the component accessing the xxx key throwing that warning
wondering...tests are passing for that feature...might be I miss something: https://github.com/i18next/i18next/blob/master/test/i18next.hasLoadedNamespace.spec.js
I'm in the middle of something else but plan to get back to this as soon as possible. I'll let you know what I find...
Might be I found some edgecase...might be the next update today will solve this false positive...
Will ping you again with new versions to test...
Can you retry with:
react-i18next@11.0.0 i18next@18.0.0
I tried with:
react-i18next@11.0.0 i18next@18.0.0
and got the same warnings.
But I still haven't looked at useSuspense
.
a codesandbox for reproduction might help...
useSuspense
set to true
or false
doesn't change the outcome.
Will need a sample to reproduce...as I don't get this on the example...
The warning is displayed by i18next.js, in resolve()
:
With usedNS='translation'
Then, I delved a little bit and I think, the issue is that lang is fr-FR
at this point although i18n was initialized with fr
.
In resolve
, there's this:
codes.forEach(function (code) {
With codes=['fr-FR', 'fr']
.
So, this fails with fr-FR
, hence the warning.
And it succeeds with fr
, hence my correctly localized strings.
Is it a bug on your side or am I using it the wrong way?
No, it's the behaviour you get based on: https://www.i18next.com/principles/fallback#language-fallback
If language is set to fr-FR
it will try to fallback to fr
in cases unable to resolve in fr-FR
. If not possible it will finally fallback to the defined fallback language.
But still can't reproduce...
the check checks if loading is not pending: https://github.com/i18next/i18next/blob/master/src/i18next.js#L340
having no success loading fr-FR it still sets loading state to -1: https://github.com/i18next/i18next/blob/master/src/BackendConnector.js#L94
I upgraded this morning to i18next@18.0.1 and it's fixed. The warnings are gone.
Thanks!
If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project.
If you liked my support / work - I would enjoy a coffee sponsored using the “💜Sponsor Button”.
There are many ways to help this project :pray:
i18next 19.0.1 react-i18next 11.2.5
same false warnings.
@zmnv are you sure they are false warnings...?
If so please provide a codesandbox for reproduction 🙏
@jamuhl
Web CRA
Initialization in index.js https://gitlab.com/zmnv/localization-example/blob/master/src/index.js
i18next initialization https://gitlab.com/zmnv/localization-example/blob/master/src/i18n/index.js
i18next configuration https://gitlab.com/zmnv/localization-example/blob/master/src/i18n/i18nextOptions.js
Add Resources example https://gitlab.com/zmnv/localization-example/blob/master/src/Calibration/locales/index.js
Using inside component https://gitlab.com/zmnv/localization-example/blob/master/src/Calibration/index.js
https://gist.github.com/zmnv/53729aace40af35e429e10691ee9e218
Actually I need to use i18next with React Native.
I tried some different configurations of i18next
and react-i18next
.
All time I get warnings, BUT I see translated strings in render(); It works but gives me warnings.
Open App:
Open Screen with HOC withTranslation('calibration')(Screen)
also used with useSuspense: false
the same result.
PS: I try to copy your examples from https://github.com/i18next/react-i18next/blob/master/example/react-native/App.js. Please update packages. Also I see that warnings...
@zmnv there is a) no language set and b) useSuspense: false
-> doing check the ready flag you get: https://react.i18next.com/latest/usetranslation-hook#not-using-suspense and as long that is false - do not access t
the react-native sample is very basic working on passed in resources on init...
@jamuhl ooohhhhh, I thought fallbackLng
was enough. I just used i18next without language detector.
When I added languageDetector
like in:
https://github.com/i18next/react-i18next/blob/master/example/react-native/App.js#L15
it's worked without false warnings.
thank you!
@jamuhl not sure if this is documented somewhere, I ran into the same scenario. Using i18next
without a language detector and without setting the lng
option thinking the fallbackLng
would be enough.
Thank you @zmnv for posting your answer, I've set the lng
option attribute instead of using a languageDetector
and it all worked.
@andresilveirah honestly I'm not sure where to add this in the docs - for people running into this...thought it's rather logical you got to set lng
if not using a detector (fallbackLng is only fallback to current lng detected or set - as it says)
@jamuhl you might be right, maybe it should be straight forward but maybe not 🤷♂ I guess it'll be up to you to keep track if this question come up over and over again and update the doc to be over explicative, or just ignore if it's only two people :)
I have a use case where this false warning might still be present, my i18next
initialization is:
i18n
.use(initReactI18next)
.use(i18nextXHRBackend)
.use(intervalPlural)
.init(
{
lng,
fallbackLng: [defaultLng],
debug: isDebug,
ns: ["common"],
defaultNS: "common",
saveMissing: true,
saveMissingTo: "current",
backend: {
loadPath:
loadPathBaseUrl + "/i18n/res/{{lng}}/{{ns}}.json",
crossDomain: true
},
},
() => {
// init callback, I can't put the code in `i18n.on("languageChanged", ...)` here because
// when this init callback is executed it's too late and the React components have already rendered...
// I need to execute some code which depends on the "i18n.t" function before rendering the first time and each time the language changes...
}
);
// Can't put this code in the "init" callback,
// as the "init" callback seems to be executed only after the React components render,
// but in my case I need to do some setup before rendering the suspended components,
// therefore I put all my code in this event handler...
i18n.on("languageChanged", lng => {
ref.lng = lng;
// I need to execute some initialization functions which depend on the i18next "i18n" object
// each time the language changes and THE VERY FIRST time
// when the first language is loaded from the backend BUT BEFORE rendering the suspended components.
// The namespace is loaded at this point,
// and both "thousandSeparator" and "decimalSeparator" are translated correctly,
// but i18next complains about them not being loaded (though, they are)
// and outputs the warning in the console:
//
// i18next::translator: key "format.thousandSeparator" for namespace "common"
// for languages "en" won't get resolved as namespace was not yet loaded...
//
const thousandSeparator = i18n.t('format.thousandSeparator')
const decimalSeparator = i18n.t('format.decimalSeparator')
Both `thousandSeparator, decimalSeparator`
initNumberFormatting(thousandSeparator, decimalSeparator)
});
I have put my code using the t
function within the "languageChanged" event callback, but I still see the warning, even though the translations are loaded and i18n is ready at that point and i18n.t('format.thousandSeparator')
and i18n.t('format.decimalSeparator')
work correctly and return me the translated text.
Right now, I can patch my code using:
... skipping i18n.init() code ...
...
i18n.on("languageChanged", lng => {
// Manually setting `isInitialized` to true makes the warning disappear.
i18n.isInitialized = true; // <----------
...
initNumberFormatting(thousandSeparator, decimalSeparator)
});
This way the warning disappears, but I really think that this case is treated as a false positive by i18next, what do you think?
Thanks!
@tonix-tuft if that works - it just avoids the warning - not fixing the problem
do you use either Suspense or handle the ready state given by the withTranslation or useTranslation?
@andresilveirah just to come back to your isssue - a warning was added for the case no detector was used and no lng set...
That's really cool @jamuhl , appreciate the effort.
@jamuhl I am using Suspense, yeah, that's a workaround, but I don't see the warning this way... Otherwise is there a way to execute some code using the ready i18next
instance (as soon as the namespaces for the given language are fully loaded) that will run before the suspended React components will render for the very first time?
init
's callback is executed after and does not cover this use case...
Thank you very much!
@tonix-tuft do you use the backend as is - or add translations partial manually? Must be something special - normally I would expect the i18next.init call to return before the withTranslation/useTranslation get there state set to ready by loading needed namespaces and check for those translations available...
There is currently only a few ways I could think of this happening:
ns: [...]
that is longer than actually needed namespaces on first renderWould you be able to produce a codesandbox reproducing this behaviour in your app? That would help a lot...🙏
I am using the XHR backend plugin to load all the translations through AJAX.
Yes, I load several namespaces initially:
ns: ["common", "chat", "user", "input", "stats", "order"]
Because otherwise if I e.g. remove "user"
and "input"
from ns: [...]
and then at some point during rendering one of my React components uses t("user: ...")
or t("common: ...")
, i18next
won't load the missing namespaces...
And also, if I change the language with i18n.changeLanguage(...)
, i18next won't load user
and input
namespaces and the keys using those namespaces with the t
function with will not be translated (e.g. t("user:some key")
will return "some key"
instead of the translated string, because i18next didn't load the user
namespace)...
But that's another story.
If you look at your fiddle here you should see the warning in the console (I attach a screenshot): https://jsfiddle.net/jamuhl/ferfywyf/
HTML code:
<script src="https://unpkg.com/i18next/i18next.js"></script>
<script src="https://unpkg.com/i18next-xhr-backend/i18nextXHRBackend.js"></script>
<script src="https://unpkg.com/i18next-browser-languagedetector/i18nextBrowserLanguageDetector.js"></script>
<div style="height: 150px">
<button onclick="i18next.changeLanguage('en')">
english
</button>
<button onclick="i18next.changeLanguage('de')">
german
</button>
<hr />
<div id="title"></div>
<button id="saveBtn"></button>
<hr />
<div id="info"></div>
</div>
JS code:
// import i18next from 'i18next';
i18next
.use(i18nextXHRBackend)
.use(i18nextBrowserLanguageDetector)
.init({
fallbackLng: 'en',
debug: true,
ns: ['special', 'common'],
defaultNS: 'special',
backend: {
// load from i18next-gitbook repo
loadPath: 'https://raw.githubusercontent.com/i18next/i18next-gitbook/master/locales/{{lng}}/{{ns}}.json',
crossDomain: true
}
}, function(err, t) {
// init set content
updateContent();
});
// just set some content and react to language changes
// could be optimized using vue-i18next, jquery-i18next, react-i18next, ...
function updateContent() {
document.getElementById('title').innerHTML = i18next.t('title', { what: 'i18next' });
document.getElementById('saveBtn').innerHTML = i18next.t('common:button.save', { count: Math.floor(Math.random()*2+1) });
document.getElementById('info').innerHTML = `detected user language: "${i18next.language}" --> loaded languages: "${i18next.languages.join(', ')}"`;
}
function changeLng(lng) {
i18next.changeLanguage(lng);
}
i18next.on('languageChanged', () => {
//i18next.isInitialized = true; // If you uncomment this line, the warning disappears...
updateContent();
});
Try to uncomment //i18next.isInitialized = true;
within i18next.on('languageChanged', () => {
and you will see that the warning disappears.
When using multiple namespaces in one file like t("common: ...")
you have to add that in withTranslation/useTranslation: https://react.i18next.com/latest/usetranslation-hook#loading-namespaces and all will work
The jsfiddle is old - and suffers an issue...renders initially twice - once on the init callback and once on the languageChanged event coming before init is done:
https://jsfiddle.net/jamuhl/ferfywyf/523/
i18next.on('languageChanged', () => {
if (i18next.isInitialized) updateContent();
});
would correct that sample - might add a check for isInitialized to the react check - but not 100% that was once there and removed out of a reason...
Guess had todo with https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md#1012
Nope nothing todo with it...
https://github.com/i18next/react-i18next/blob/master/src/useTranslation.js#L42 why does it render and not throw the Suspense on your code? Are you sure you did not set useSuspense: false
?
Yes I am sure, useSuspense
is not even set in my code, so it defaults to true
I guess.
Anyway, shouldn't i18next.isInitialized
be true when languageChanged
's callback is executed for the very first time? When the callback is executed, I see the translations are loaded and i18n.t("any key")
works, so it follows that i18next
is initialized from the client code's point of view... What do you think?
languageChanged
is an event - not a callback
and there you access t to early:
i18n.on("languageChanged", lng => {
ref.lng = lng;
// I need to execute some initialization functions which depend on the i18next "i18n" object
// each time the language changes and THE VERY FIRST time
// when the first language is loaded from the backend BUT BEFORE rendering the suspended components.
// The namespace is loaded at this point,
// and both "thousandSeparator" and "decimalSeparator" are translated correctly,
// but i18next complains about them not being loaded (though, they are)
// and outputs the warning in the console:
//
// i18next::translator: key "format.thousandSeparator" for namespace "common"
// for languages "en" won't get resolved as namespace was not yet loaded...
//
const thousandSeparator = i18n.t('format.thousandSeparator')
const decimalSeparator = i18n.t('format.decimalSeparator')
Both `thousandSeparator, decimalSeparator`
initNumberFormatting(thousandSeparator, decimalSeparator)
});
change that to:
i18n.on("languageChanged initialized",() => {
if (!i18n.isInitialized) return;
ref.lng = i18n.language;
// I need to execute some initialization functions which depend on the i18next "i18n" object
// each time the language changes and THE VERY FIRST time
// when the first language is loaded from the backend BUT BEFORE rendering the suspended components.
// The namespace is loaded at this point,
// and both "thousandSeparator" and "decimalSeparator" are translated correctly,
// but i18next complains about them not being loaded (though, they are)
// and outputs the warning in the console:
//
// i18next::translator: key "format.thousandSeparator" for namespace "common"
// for languages "en" won't get resolved as namespace was not yet loaded...
//
const thousandSeparator = i18n.t('format.thousandSeparator')
const decimalSeparator = i18n.t('format.decimalSeparator')
Both `thousandSeparator, decimalSeparator`
initNumberFormatting(thousandSeparator, decimalSeparator)
});
I know that languageChanged
is an event XD.
languageChanged
's callback = The callback of languageChanged
.
I cannot use if (!i18n.isInitialized) return;
because that formatting will not be initialized when the page loads and I end up with unformatted content, only if I change the language with i18n.changeLanguage("another-LNG")
then i18next executes the callback of languageChanged
and this time formatting is set properly.
I need to set up formatting by executing this code:
const thousandSeparator = i18n.t('format.thousandSeparator')
const decimalSeparator = i18n.t('format.decimalSeparator')
initNumberFormatting(thousandSeparator, decimalSeparator)
Before any component using the useTranslation()
hook is rendered.
The callback passed to i18n.init
as a second parameter does not work because at the time it's executed, the React components using useTranslation()
have already been rendered and if I set the formatting in the init
callback, the formatting will be available only if change the language, but I need the formatting as soon as translations are loaded from the backend when the page loads.
The only way I found to do it seems to use the following code:
i18n.init(...); // Init i18next.
// Registrer "languageChanged" event handler/callback
i18n.on("languageChanged", lng => {
//if (!i18n.isInitialized) return; // Can't use this line because otherwise formatting won't be set on initial rendering when the namespaces for the initial language are loaded.
ref.lng = lng;
//i18n.isInitialized = true; // With this line commented out, i18next outputs the warning, but `i18n.t('format.thousandSeparator')` returns the correct string for the user's language...
const thousandSeparator = i18n.t('format.thousandSeparator')
const decimalSeparator = i18n.t('format.decimalSeparator')
initNumberFormatting(thousandSeparator, decimalSeparator)
});
I hope I was clear. Thank you again!
i18n.on("languageChanged initialized",() => {
initialized event is bound too this way!!! But might be too late - not tested -> but as rendering is delayed until isInitialized the settings should be done
So guess - you can keep your "hacky" way to avoid the warning (knowing what you do - so it does not hurt). Not sure how to avoid this edge case - as there is no way to delay the languageChanged
event to be called after init is done...
I cannot share you the source code, but I can attach a screenshot to make you understand better the issue I am facing.
This is the application I am building:
The React components that render the percentage values require i18n formatting and the formatting itself depends on the initialized i18next instance (because I need to call e.g. i18n.t("format.decimalSeparator")
when I set up formatting).
On page load, i18next
is initialized this way:
i18n
.use(initReactI18next)
.use(i18nextXHRBackend)
.use(intervalPlural)
.init({
lng, // A global variable, generated server-side.
fallbackLng: [defaultLng], // Same as "lng".
ns: ["ns1", "ns2", "ns3", ... , "nsN"],
defaultNS: "ns1",
backend: ... // XHR backend configuration
},
() => {
// init callback.
}
);
i18n.on("languageChanged", lng => {
// If I remove this patch, i18next displays the not initialized warning,
// though I suppose that i18next is initialized at this point when it executes this code.
//* Patch
const isInitialized = i18n.isInitialized;
i18n.isInitialized = true;
//*/
// Init i18n formatting. This code will run when the page loads and i18next
// has loaded the translations for the initial language ("lng") downloading
// the files from the backend, as well as when I programmatically call `i18n.changeLanguage("new-LNG")`.
const thousandSeparator = i18n.t('format.thousandSeparator');
const decimalSeparator = i18n.t('format.decimalSeparator');
initNumberFormatting(thousandSeparator, decimalSeparator);
// Resetting `i18n.isInitalized` to its previous value.
//* Patch
i18n.isInitialized = isInitialized;
//*/
});
The above code works, and once the page loads and i18next
has loaded the translations, the languageChanged
's callback is executed and my formatting is initialized and only after the formatting initialization then the suspended React components render and the percentage values are formatted as they should:
Now, if I move this code:
// Init i18n formatting.
const thousandSeparator = i18n.t('format.thousandSeparator');
const decimalSeparator = i18n.t('format.decimalSeparator');
initNumberFormatting(thousandSeparator, decimalSeparator);
from the languageChanged
event callback and put it within the init
callback of i18next
:
i18n
.use(initReactI18next)
.use(i18nextXHRBackend)
.use(intervalPlural)
.init({
lng, // A global variable, generated server-side.
fallbackLng: [defaultLng], // Same as "lng".
ns: ["ns1", "ns2", "ns3", ... , "nsN"],
defaultNS: "ns1",
backend: ... // XHR backend configuration
},
() => {
// i18next init callback.
const thousandSeparator = i18n.t('format.thousandSeparator');
const decimalSeparator = i18n.t('format.decimalSeparator');
initNumberFormatting(thousandSeparator, decimalSeparator);
}
);
i18n.on("languageChanged", lng => {
// Execute only when the language changes through `i18n.changeLanguage()` while using the app,
// and NOT when the page loads (when `i18n.isInitialized === false`).
if (!i18n.isInitialized) return;
// Init i18n formatting. This code will run ONLY when I programmatically call
// `i18n.changeLanguage("new-LNG")`, and not when the page loads for the first time
// and i18next has loaded the translations.
const thousandSeparator = i18n.t('format.thousandSeparator');
const decimalSeparator = i18n.t('format.decimalSeparator');
initNumberFormatting(thousandSeparator, decimalSeparator);
});
With the above code, when the page loads, the formatting is not initialized on time, it's rather initialized too late when the suspended React components have already been rendered, so you can see formatting is missing (in my case, if initNumberFormatting(thousandSeparator, decimalSeparator)
isn't called before the components render, percentage values will not have the decimal part):
So the only way to achieve what I want is to use the first option and place all my initialization within the "languageChanged" event callback and setting i18n.isInitialized = false
there to suppress the misleading warning (again, I assume that when the "languageChanged" callback is executed, it means that i18next is ready to be used and therefore is initialized).
I hope I was esplicative and clear. Let me know.
Thank you very much!
The implementation is suboptimal...taking separators from translations is not the best option...chicken egg problem
1) not all languages separate thousand 2) formatting should be done by Intl or a major lib doing this correctly 3) use: https://www.i18next.com/translation-function/formatting
Also bind the https://www.i18next.com/overview/api#oninitialized beside language change -> https://github.com/i18next/react-i18next/blob/master/src/useTranslation.js#L42 might work
There is no need to explain over and over the https://github.com/i18next/react-i18next/issues/977#issuecomment-579922061 still is valid
Thank you @jamuhl, I got it. I was using accounting-js
to format numbers: http://openexchangerates.github.io/accounting.js/
This library needs to know the thousand and decimal separator in advance:
accounting.settings = {
currency: {
symbol : "$", // default currency symbol is '$'
format: "%s%v", // controls output: %s = symbol, %v = value/number (can be object: see below)
decimal : ".", // decimal point separator
thousand: ",", // thousands separator
precision : 2 // decimal places
},
number: {
precision : 0, // default precision on numbers is 0
thousand: ",",
decimal : "."
}
}
I took a look at the https://www.i18next.com/translation-function/formatting link you provided and there's a lib called numeral.js which seems to be pretty interesting.
But even that library needs to know the separators to use in advance (http://numeraljs.com/#locales):
// load a locale
numeral.register('locale', 'fr', {
delimiters: {
thousands: ' ',
decimal: ','
},
abbreviations: {
thousand: 'k',
million: 'm',
billion: 'b',
trillion: 't'
},
ordinal : function (number) {
return number === 1 ? 'er' : 'ème';
},
currency: {
symbol: '€'
}
});
// switch between locales
numeral.locale('fr');
What do you advise in order to format numbers and currency in an i18n format to support all languages?
Thank you!
Thank you Jan! No more questions :)
@tonix-tuft hope I was able to help...
Today I modified the code again and here is what I ended up with:
i18n.on("initialized", () => {
initFormatting();
});
i18n.on("languageChanged", lng => {
if (!i18n.isInitialized) return;
initFormatting();
});
function initFormatting() {
// Init i18n formatting. This code will run ONLY when I programmatically call
// `i18n.changeLanguage("new-LNG")`, and not when the page loads for the first time
// and i18next has loaded the translations.
const thousandSeparator = i18n.t('format.thousandSeparator');
const decimalSeparator = i18n.t('format.decimalSeparator');
initNumberFormatting(thousandSeparator, decimalSeparator);
}
This way everything works, thanks again!
If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project.
If you liked my support / work - I would enjoy a coffee sponsored using the “:purple_heart:Sponsor Button”.
There are many ways to help this project :pray:
I'm having the same issue like render is getting loaded before i18n. and here is the init code for i18n,
.init( { fallbackLng: "en", debug: true, interpolation: { escapeValue: false // not needed for react as it escapes by default }, react: { useSuspense: false } }, () => { // init callback. }
I'm not really sure what to add in the callback funciton to supress the warnings, please help me out in resolving this issue
Describe the bug I'm having a warning that I think is a false positive. I get the thousands of the following warning:
i18next.js:27 i18next::translator: key "xxx" for namespace "translation" won't get resolved as namespace was not yet loaded This means something IS WRONG in your application setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!
I think it's a false positive for 3 reasons:
init()
before callingReactDOM.render()
.Before all the warnings, the console prints this:
Occurs in following versions npm 6.12.0 react-i18next 10.13.2
OS (please complete the following information):