We can discuss the future of the NextJs example here, as it has gotten a lot of attention lately, and has some shortcomings.
Current problems:
Future goals:
Link usage to match this example, thus preventing 404s and fixing #569 withNamespaces HOC that supports tree traversal, and make it abundantly clear that this is the _only_ way to translate content. Fixes issues like #603.I believe the best way forward is a complete rewrite to simplify a few things. The first step is to decide if we want to support language subpaths at all.
Are there any other issues people are aware of currently?
I would like to compile a full list of current issues we are facing here, as well as any new features, or features we plan to remove, before proceeding with any work on the NextJs example.
Only then can we have a clear plan and execute it.
@jamuhl Funny, as we were discussing privately - there _is_ a with-react-i18next example in the NextJs repo. I see you contributed there as well. Is this supposed to be identical/synced with our example here, or different in some way? Should both examples exist simultaneously, or should we deprecate one in favour of the other?
@isaachinman the one on next.js is a lot older (v7) and i planned to update it as soon we got this one here stable (in the past we had just a readme here pointing to the sample on next.js) - but i decided to fix issues here - as i don't follow all the issues on next.js and think issues / questions related to it just pollute the next.js repo.
In the future i would prefer having a link from next.js to the sample here as it would make it easier to be maintained. But yet i'm not sure what the opinion of the next.js team is - as they seem to prefer having all the samples there??!
Important note: This is an example not a boilerplate.
Eventual one problem we have is we try to solve some stuff very convenient by eg. providing the withI18next HOC - which magically solves most needed to be done to work. My bet most people landing here with issues come from another example withApollo, withRedux, withMobx whatever and add the stuff shown in our sample which in most case just won't work without proper adjustments - so eventual we should more focus on showing what needs to be done to solve it (teaching) - instead of trying to hide away the more complicated things...but not completely sure - this is just something that came to my mind in the morning.
I agree with @jamuhl . Teaching users to understand how things work is the hardest and most important thing to be done. So more users could give help to issues, as well as contribute back to the library.
Once users understand how things work basically, ones could customize themselves for their need.
Hi,
I've been testing this sample in my project for a few days, it is mostly working really great, so thank you !
I might have some input regarding a few issues :
Concerning language subpath, in your _app.js, you do use router.replace, but you provide the same parameter to both url and :as . As far as I understand this, this is wrong, you need to provide the actual path to your file in url (meaning /index.js) and the "visual" one in :as (meaning /en/index.js).
The way you do it trigger a server reload since client can't find /en/index.js.
You may want to provide some routing there as well to handle conversion from /post/945 to /post?id=945
I corrected this behavior in my project for changing language quite simply as follow, and the language change is working as I suspect it should (no server reload) :
i18n.on("languageChanged", lng => {
if (process.browser) {
const originalRoute = window.location.pathname;
const correctedVisualPath = originalRoute.replace(//.?//, /${lng}/)
if (correctedVisualPath !== originalRoute) {
let actualPath = correctedVisualPath.replace(//.?//, "/")
if(correctedVisualPath.startsWith(/${lng}/test))
{
actualPath = '/test?search=' + correctedVisualPath.match(/[^/]*?$/)[0];
}
Router.replace(actualPath, correctedVisualPath, {
shallow: true
});
}
}
});
I didn't yet find a way for the similar issue on normal Link, so for now I just write every Link as follow, and it work quite nicely :
<Link url='/mypage' as=/${lng}/mypage><a>Link</a></Link>
I also am experiencing the issue described in #603 . It seems to only happens for the default language and on "first load" : if I run npm run build / start the very first connection won't display the default language, generate a lot of .missing.json. If I hit ctrl-F5, default language is displayed. After that, any connection from any computer is displaying default language correctly.
Finally loading a page with new translation file seems to trigger some kind of clientside rerender, resulting in a "white flash" . I suppose this has something to do with the way translation files are loaded. This doesn't seems to break anything, but it's not "smooth".
Thank you again,
As a rudimentary way of voting, please thumbs up or down this comment to indicate whether or not you think language subpaths belong in the example.
Since your example is easier for you to maintain, you should link from nextjs examples to your example. I suspect the nextjs team will be fine with that since it reduces their maintenance and is a better user experience to see the most up to date example. There is some precedent for that here: https://github.com/zeit/next.js/tree/master/examples/page-transitions
Is there a way to detect broken ssr and throw exception in dev mode? It could be a good idea to consider doing that if it is possible so that people know something is not working as expected before shipping the product to production.
The example _app.js needs a comment linking to a page describing how to add i18next to a project already using something like Apollo. I tried adding i18next to my nextjs apollo project and could not fix the broken ssr despite reading all the relevant issues I could find only hinting I needed to merge something. I finally had to give up and tried adding react-intl example pattern to my apollo project and it worked fine in ssr. If you could try and add i18next to the nextjs with-apollo example and document exactly what it took to get it to work in all the closed "i18next with apollo" tickets that currently don't really contain any resolution, it would help a ton of people be able to use i18next rather than be forced to react-intl.
Hello again @Enalmada. Yes, we all agree the example should be hosted in this repo, and the NextJs team should link here. Just need to confirm that with @timneutkens.
Is there a way to detect broken ssr and throw exception in dev mode
React already does this for you. It'll let you know when the checksum doesn't match.
The example _app.js needs a comment linking to a page describing how to add i18next to a project already using something like Apollo
Couple things here:
_app.js file.Please, let's stay on topic!
Hmmm...I have never seen a checksum error in the broken ssr situations I experienced. Is that something I need to enable or where would I see that? (I am very new to react so apologies if that is a silly question) If it will still be possible to have broken ssr after your enhancements to the example, just consider for a moment if there is something more that can be done in the next version of the example to prevent users from thinking their implementation works and shipping it to production when actually ssr is broken.
It is obviously on topic to call out ways the average user struggles to use the current example to add i18next to an existing project. I am sure you are already aware of the large number of apollo i18next issues, nextjs spectrum threads, stackoverflow links that exist without any solution to the problem. I am optimistic the future example will eliminate the specific problem people are currently having adding i18next to an apollo project, but if not, I am not asking you to add an apollo to the example, just that you consider a link to a solution in the readme to help people in that very common use case find a way through it.
Nextjs Link element also as some edge cases with i18next. You can see this thread here about it. I don't know if that is something that could be improved in the example code or if we need to talk with Nextjs team about it: https://spectrum.chat/?t=b9d1b95e-177b-4447-8cd1-bf2f488e44d5
Oh, and I would highly recommend creating a "next-i18next" project that would abstract out all code and sensible config defaults in example project that users are unlikely to need to ever modify themselves: like i18n.js, getAllNamespaces.js, location of languages, etc. Ideally to get started with next and react-i18next, new users would only need to run "npm -i next-i18next" and add absolute minimum to server.js to initialize with configuration overrides that differ from sensible defaults. Think how awesome it would be that critical fixes/features that you normally would make to the example repos would now automatically roll out to all users as a version update (rather than users having to watch the example repo for bug fixes that you make. Changes that might be critical but they will likely never know about them because they have copied over the original example project files and moved on).
@Enalmada I have written many times that SSR is an advanced topic, and we expect users to understand and be aware of this.
This issue is to discuss the future of the NextJs example. It has nothing to do with Apollo, nor anything to do with some next-i18next repo. Unless your comments are _directly related_ to the example, please post them elsewhere.
Apollo, warnings about SSR breakage, and future projects are all well outside the scope of this example and will be marked as off topic if needed.
My comments are all directly related to the future of the example from my perspective...I get that we both come at things from very different angles. Adding some supporting documentation referencing common issues the examples choose not to cover would be so helpful...I am not sure why the hostility.
Please try and understand the value of abstracting out dependencies and code to make it easier for users to onboard and maintainers to roll out fixes to the "glue" code between react-i18next and consumer projects. This is a pattern that nextjs users are used to: next-seo, next-offline, etc each wrapping sensible defaults and dependencies of things. Doing this would make a huge difference to the way you structure a future example so I just wanted to put it out there as an idea before you started just in case.
For those that want to follow the development of the new example, it can be found here (temporarily):
@Enalmada @isaachinman not read through the full thread -> but just my 50 cents on why it workes with react-intl without issues. => You bundle the translations with your applications - therefore is no async loading (namespace splitting) going on - all synchronous - all easy as piece of cake. Just add the translations in react-intl sample as a lazy import and see everything starts breaking.
So we could just remove i18next-xhr-backend, language-detection -> so we get free from even doing custom stuff in the server.js. All gets simple - no problems.
Just this sample is about adding i18next in production ready way - not make it dead simple for just doing getting started demos by oversimplify the real challenges of SSR and i18n. My problem is what i see in all the errors, issues and questions...those people using next.js have most time not the slightest idea what they are doing - how initialProps work because it's so damn easy to get started with one of the samples.
@isaachinman @Enalmada so i really would say the sample should be rather straight forward to learn what needs to be done - but integrations with all the apollo, mobx, redux, whatever needs to be solved in userland. Extending the react-i18next documentation with samples / hints for doing more complex stuff will be very welcome - but would go to far for the sample.
Edit: I have hopes we could make the v10 sample more expicit on what gets done and less magic -> so devs learn what needs to be done and not get a magic solution they do not really understand and therefore can't make the step including it into other eg. with-apollo sample.
@jamuhl Yes, we're on the same page and have been from the start. It's an example, not a boilerplate. We intentionally do not want to "abstract out code". That would defeat the purpose of an example.
The new example I'm working on should be simpler and more clear, and will contain a _lot_ of comment-based documentation.
It seems like everyone (at least so far) thinks the language subpath functionality should remain in the example, so I will rewrite that as well.
The new example is coming along very nicely, and is in fact nearly done.
@jamuhl I've hit one interesting problem that I need your help with - something we did not even realise was occurring in the original example:
Let's assume we've got a namespace called contact-us that contains some content for the contact page. If a user arrives at the root URL which does not rely on the contact-us namespace, and then the NextJs app navigates to the contact page, the i18next-xhr-backend will indeed fetch the relevant contact-us namespace JSON file. This was clear in the original example.
However, NextJs has no idea about i18next translation JSON network requests, or any of that. The result is that the incoming route (in this case contact-us) will be displayed before the translation data is loaded over the network.
The place to block a page transition while waiting for data is inside the NextJs getInitialProps. We can await any promise. However, I cannot seem to find anything useful to await.
I tried i18n.hasResourceBundle and i18n.getResourceBundle, but both are sync functions that return information about data already in the store.
I tried polling i18n.hasResourceBundle in a loop, but it seems that i18next-xhr-backend doesn't actually start making network requests until the React tree that requires namespaces is rendered, so that won't work either.
In short, it seems we're going to have to trigger network requests manually. It should be trivial, assuming there is a way to do this. Is there?
@jamuhl I was able to come up with a hacky solution that requires one to initialise the i18next-xhr-backend via class construction so that we can have a reference to it and later invoke class methods:
await Promise.all(
i18n.nsFromReactTree.map(ns => {
if (!i18n.hasResourceBundle(lng, ns)) {
return new Promise(resolve => {
i18nextXHRBackend.read(lng, ns, (error, loadedNS) => {
i18n.addResourceBundle(lng, ns, loadedNS);
resolve();
});
});
}
return Promise.resolve();
})
);
Hopefully you can understand this. It's a quick solution and is nasty nested code. Basically it's calling the read method on the in i18next-xhr-backend class instance manually, and then after receiving the JSON, manually calling i18n.addResourceBundle.
This works 100%. However, it's clearly a workaround and I feel it's bug prone and likely to break outright in the future.
Let me know if there's an official way to achieve this that I'm missing, or if you would suggest a different approach.
This is the final hurdle to releasing the new example.
that is very hacky and won't work in production -> as soon you got two components both needed that namespace it will load it twice. i18next handles resource loading very nicely by doing proper deduplication. => https://github.com/i18next/react-i18next/blob/master/src/NamespacesConsumer.js#L116 is like it is solved in the current version
The loadNamespace function has a callback that will be called when translations were already loaded or got loaded.
The render prop or hoc both can be set to wait before rendering their inner content or pass down a prop signaling readyness - if that helps.
Thanks, I'll take a look at loadNamespace tomorrow. I think I tried it, but quickly passed as I didn't realise it did not return a promise. That's good news though.
By the way, none of this is happening inside the HOC, and will take place once per page render - I've refactored things quite a bit.
@isaachinman just had a short look at the sample...looks promising -> like the way initialProps get set explicitly without hiding that magically like in the old hoc
So, the new example is now ready for review.
Some noteworthy points:
_app.js. I thought we could get away with just using _document.js, but it's a bit more complicated than that. The way _app.js is set up, most users should never have to touch/change anything for basic use cases._app.js, the new HOC approach has completed abandoned NamespacesConsumer in favour of withNamespaces, and the I18nextProvider.withNamespaces and I18nextProvider directly out of react-i18next core, were it not for the need for our nsFromReactTree array, which keeps track of all required namespaces, given a React component tree - irrespective of whether components are NextJS pages or not (this is crucial).Link. @timneutkens example was very helpful - we need to parameterise our route params and also pass an as argument to achieve true SPA with multi-langs. In the new example, you can switch languages in a fully-SPA manner without any hard reloads.For anyone that has time, please do review the code, experiment with the app, and let me know about any issues you might find. I've put the example on a free Heroku dyno here (it can take some time to boot).
The basic use flow for the example goes:
_app.js alone unless you know what you're doing.withNamespaces HOC, from lib/withNamespaces.components/Link instead of importing Link from NextJs directly.That's it! Everything else should be straightforward and development should be a more or less "normal" React experience.
@isaachinman looks good to me...one thing I ask myself shouldn't we just add that https://github.com/isaachinman/react-i18next-with-nextjs/blob/master/lib/withNamespaces.js#L23 to the current implementation in the main repo -> does not hurt and for v10 we planned the same to better support SSR out of the box.
@jamuhl Yes, that's exactly what I was thinking as I wrote it. It's just a matter of pushing into a simple array. The thing is, there are definitely use cases where users would want to clear that array - new route, etc.
For this example (and probably in real projects) I decided that clearing the array on a new route doesn't make much sense as it only applies to clientside navigation, and the browser is already going to have the previous-route's namespaces anyways.
Anyways, after getting to know the react-i18next library better, in my opinion I18nextProvider and withNamespaces is the best approach currently available, and nsFromReactTree works flawlessly if you stick to it. I'm not sure how it would work if people are mixing in NamespacesConsumer etc.
@isaachinman fun fact withNamespaces uses NamespacesConsumer: https://github.com/i18next/react-i18next/blob/master/src/withNamespaces.js#L33
So doing same nsFromReactTree in the sample could be done in consumer https://github.com/i18next/react-i18next/blob/master/src/NamespacesConsumer.js#L25 (just in the new way - not using reportNS)
Ah yes, I remember seeing that now. Must have slipped out of my head immediately.
As far as I can tell, the new example solves all open related bugs/issues.
@jamuhl Is there anyone else whose review you'd like to request before we think about merging?
@isaachinman guess we could merge it...and from there improve with the promise based i18next,...
I think the promise-based release would only affect this line of the NextJs example.
But sounds good, I will put this work into a PR soon.
@isaachinman I'm not really a pro i18next config but I had to set fallbackLng to null because in production the front-end always switched back to the DEFAULT_LANGUAGE when the server side language was right. Otherwise, the new example looks great, thank you very much.
The last example failed on my project. Decided to rollback to old working version.
I'm afraid of trying out new things here. Such a lengthly and hopeless hours of debugging without any chance to get things done.
Breaking without any chance for you to fix it. It's this library now.
I'm not sure...
@Nelrohd That sounds like a bug. Can you help me to reproduce that? If setting fallbackLng to null fixes your problem, that potentially sounds like an issue with i18next-browser-languagedetector.
@revskill10 I am sorry to hear you are frustrated. I understand - creating SSR React apps with localisation and code splitting is not an easy task.
We are doing our best to improve the example and help other developers as much as we can.
I will personally help you with your project if you'd like - just email me directly.
@isaachinman @Nelrohd could it be initialLanguage gets not passed down. But we really need some steps to reproduce.
@revskill10 sorry for the frustration - if the old system worked there is no need to update i would say. We just try to make the new sample more like a learning point - looking at the last issues related to next.js and react-i18next most cases where related to changes made and combining other next.js samples without the knowhow to "glue" things together -> the sample here is no boilerplate and no just put everything in at it will not break under any changes made.
@jamuhl Yes, agreed, and I want to take some responsibility for those issues too. @revskill10 That is why I took the time to do a deep dive and rewrite the example from scratch. I spent quite some time on it. I am very confident it is a big improvement over the last example. If you have time to check it out, you might be pleasantly surprised. Regardless, if you do want help or assistance getting things up and running, you can PM me.
@isaachinman @jamuhl Really appreciated all your efforts. A thank you is not enough.
But we should stop all the pain one and for once by introducing a next-i18next package.
I think you have enough deep knowledge to make such thing happen.
Our goal is to minimize the interface to integrate/update to existing project.
Why ?
By introducing an independent package, we have more chance to test it for many use cases without breaking them before installing it. And more next.js discussion should not belong to this project, which is pure React package.
If nextjs is too common, a monorepo style is enough in this case.
My two cents.
@revskill10 I believe this is what @Enalmada was asking for as well. Now that we have discussed and completed the example here, maybe it is time to consider this.
What would a nextjs-i18next package be? There is _way_ more going on than being able to simply do a yarn add nextjs-i18next and being "done". If you look through the new example, you will see how many areas we need to touch. I suppose a package _could_ be possible, but we would need to create a HOC to wrap _app.js so that we have access to getInitialProps, as well as some other things.
@isaachinman @jamuhl
In the config.js
...
detection: {
order: ['header', 'querystring'],
},
...
And set fallbackLng: DEFAULT_LANGUAGE,
In my case, my Google Chrome language settings is fr but DEFAULT_LANGUAGE is en. So what happens is:
cmd + R (back-end refresh)french translationsenglish translations (not good, I should see french)Both my i18n.language and initialLanguage say fr when I debug.
Let me know if you need more info
Note that set fallbackLng to null solves the problem.
@Nelrohd We only have English and German content in the example. What do you mean you see French translations? Do you have a repo I can check out and reproduce this with?
@Nelrohd Regardless of that, why did you change the detection settings? That is most likely your issue. As it says in the codebase:
only change if you understand what you're changing
@isaachinman Thanks for the work, the new example, after some work to make it as I wished, is working allmost perfectly.
small question : is it normal that the language change event is fired at every page change ? seems weird (maybe something I did wrong, I "glued" a few thing on top of i18n)
@isaachinman I setup my own website following the example but I can't share the code. I changed the detection because it's not what I wanted. When I change my settings in my browser, I want it to change right away when I refresh, I'm not sure what the cookie settings brings as value, can you explain this please?
@Nelrohd Please open a separate issue if you believe it's a bug, or post your question on StackOverflow. It sounds like a custom-implementation concern.
I won't open anything since the example works fine for me and I have no idea what to asks, I'm just giving you feedback. I can try to reproduce the issue with the example maybe?
@Eruilz That is a great point, I had not noticed that. I believe it's because initialLanguage is being set on a new page render.
@jamuhl Is this expected/normal? Should we avoid resetting initialLanguage on the clientside?
If so, should be an easy fix.
@Nelrohd If you can reproduce the issue with the example, that would definitely be a bug. In general, use of cookies is the most reliable method I've found of sharing lang state between client and server. It would make sense in your case that your client reverts to English, because it isn't reading headers.
It's probably the cause, I guess not many people will change their chrome language settings.
It's irrespective of your browser settings.
What do you mean? Google Chrome uses language settings to set the languages header
@isaachinman yes...i guess we should run that only once -> https://github.com/i18next/react-i18next/blob/master/src/utils.js#L22
doing that on the hooks version (meant it was in the
Ah, so it's this line that's causing the change function to fire?
Do you think performing a check against multiple initialLanguage calls belongs in react-i18next core, or user land (the example)?
@Nelrohd https://github.com/i18next/react-i18next/blob/master/example/nextjs/config.js#L29 -> caches on cookie and detect is needed to sync -> you still can have the header setting on second position (eg. server not yet getting language via cookie - inital set will be from header)
@isaachinman i would say core - easy fix in utils
Something like:
if (props.initialLanguage && !props.i18n.language) {
props.i18n.changeLanguage(props.initialLanguage);
}
Is that what you had in mind? Can create a quick PR, or if you want to commit directly.
Thanks again @Eruilz, will be fixed shortly.
@isaachinman will set a variable to check it gets set once and only once. Will make a patch version in the next 5min.
let initializedLanguageOnce = false;
let initializedStoreOnce = false;
export function initSSR(props, setIsInitialSSR) {
// nextjs / SSR: getting data from next.js or other ssr stack
if (!initializedStoreOnce && props.initialI18nStore) {
props.i18n.services.resourceStore.data = props.initialI18nStore;
if (setIsInitialSSR) props.i18n.options.isInitialSSR = true;
if (props.i18nOptions) props.i18nOptions.wait = false; // we got all passed down already
initializedStoreOnce = true;
}
if (!initializedLanguageOnce && props.initialLanguage) {
props.i18n.changeLanguage(props.initialLanguage);
initializedLanguageOnce = true;
}
}
looks ok?
Sure, but it's rather confusing for a function named initSSR to be running on the client in the first place.
it's more take from SSR - naming is hard ;)
just published: [email protected]
@Enalmada @revskill10 I've created a potential next-i18next repo here. Can you please add your thoughts about how the project might work to this issue?
We can continue discussion there, instead of here.
damn that was quick ! thx ! 馃憤
edit: can confirm, event isn't fired anymore on page change
I think we have accomplished everything necessary/related to this topic. Future bugs/problems with the example can be opened as new issues.
If anyone is indeed interested in a next-i18next boilerplate/npm package, please do add your suggestions over at the new repo.
Well done! 馃コ馃憤
Hello everyone - I've just published the initial release of next-i18next on npm.
It accomplishes everything the example does, but should be much easier and more flexible to incorporate into your existing projects, or new projects.
If anyone wants to start using the package as an early tester, I would be very appreciative of any feedback.
Also @jamuhl perhaps after a few minor releases over there and a feeling of stability, it could be worthwhile to link to the package from somewhere in the react-i18next repo.
Easier maybe but what makes it "more flexible"? We don't know how next-i18next works internally and because of this I don't see how to tweak its behavior.
Maybe the old example should also be there besides a reference to next-i18next package?
Hello again @germansokolov13. The next-i18next package is entirely open source - you're welcome to read through and see how it works internally. As I mentioned in https://github.com/isaachinman/next-i18next/issues/347, incremental features/changes via PR are very welcome from anyone!
Part of the problem we had with having an example in the react-i18next repo is that it became very difficult and time-consuming to maintain. If you are feeling up to taking on this task, I'm sure @jamuhl would be happy to consider the possibility.
@germansokolov13 @isaachinman sure always open for suggestions...honestly I would suggest @germansokolov13 looking deeper into it by making an example without using next-i18next (getting it right is harder than it seems).
In the past, we just had a sample on react-i18next and there were tons of issues of people applying what they saw in the sample to there own project but some small but very important piece was left out.
So next-i18next gets my full respect - for the work that was put into from @isaachinman and the community behind it and also for what it achieved.
Most helpful comment
As a rudimentary way of voting, please thumbs up or down this comment to indicate whether or not you think language subpaths belong in the example.