Looks like with18next() HOC in with-react-i18next example is incompatible with _app.js (added in Next v6). Although wrapping each page into the HOC is still possible, this approach has at least these two drawbacks:
_app.js cannot be translated since the components end up _above_ with18next(['ns'])(PageComponent).with18next() creates separate wrapper components on each page (I may be not 100% here).The with-apollo example has been updated recently and ApolloProvider is now a part of _app.js, which is pretty neat. It'd be great if with-react-i18next could undergo a similar transformation. I've just tried to propose this change myself, but am giving up after over three hours 😅
There's one caveat that should be taken into account when implementing the new logic: a collection of namespaces used by a page needs to be passed into _app.js. I suggest that these namespaces can be either sourced from getInitialProps():
export default class MyPage extends Component {
static getInitialProps() {
return { i18nextNamespaces: ['ns1', 'ns2'] };
}
render() {
return <div>{this.props.t('hello')}</div>;
}
}
Alternatively, i18nextNamespaces can be defined in a static property to avoid implementing getInitialProps() in the simplest cases:
export default class MyPage extends Component {
static public i18nextNamespaces = ['ns1', 'ns2'];
render() {
return <div>{this.props.t('hello')}</div>;
}
}
const MyPage = ({t}) => <div>{t('hello')}</div>;
MyPage.i18nextNamespaces = ['ns1', 'ns2'];
export default MyPage;
Ideally, these namespaces should be detected automatically depending on which ones are used down the tree (translate(['ns1'])(Component1), translate(['ns2', 'ns3'])(Component2)). This magic behaviour would be similar to what Apollo does with executing all queries in the tree on the server and building client-side cache based on that. However, I'm not sure how easy it would be to implement this trick in react-i18next.
cc @jamuhl
@kachkaev hm...not yet had a look at the next.js update - as i do not use next myself it's not my main focus - hopefully i get some time to check this out the next days - but do not expect to much right now.
Not sure how easily that parsing of all namespaces in tree could be....not done yet.
Another issue that I witnessed after moving Apollo to _app.ts is to do with the contents of initialI18nStore. For some reason, when a page has got a <Query /> component, initialI18nStore starts containing _all_ namespaces and languages, thus making __NEXT_DATA__ pretty large. I tried investigating this, but again, no solution after about three hours 😅
I believe that this behaviour originates from the fact that withApolloClient calls getDataFromTree(), which in turn renders the app twice. It may be the case that i18nInstance in one of these passes does not find initialI18nStore and thus populates it with all data. I'm not sure why removing <Query /> from a page ‘fixes’ i18nInstance though, because withwithApolloClient inside _app.js is still in place.
Here are a couple of odd things I noticed in addition:
Calling deepFreeze(initialI18nStore) in i18n's getInitialProps causes an exception, even on a page that does not contain Apollo Queries.
/path/to/project/node_modules/i18next/dist/commonjs/utils.js:58
obj[k] = newValue;
^
TypeError: Cannot add property data-demo, object is not extensible
at Object.setPath (/path/to/project/node_modules/i18next/dist/commonjs/utils.js:58:10)
at ResourceStore.addResourceBundle (/path/to/project/node_modules/i18next/dist/commonjs/ResourceStore.js:129:11)
at Connector.loaded (/path/to/project/node_modules/i18next/dist/commonjs/BackendConnector.js:132:18)
at /path/to/project/node_modules/i18next/dist/commonjs/BackendConnector.js:241:14
at /path/to/project/node_modules/i18next/dist/commonjs/BackendConnector.js:180:7
at /path/to/project/node_modules/i18next-node-fs-backend/lib/index.js:125:9
at /path/to/project/node_modules/i18next-node-fs-backend/lib/index.js:87:9
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:511:3)
I was expecting getInitialProps to produce a sufficiently complete version of initialI18nStore, so that it does not need to be changed later. Interestingly, adding console.log in a few bits of the stack trace and removing deepFreeze(initialI18nStore) reveals that _all_ my translation files are loaded on every request 😨
It appears that import * as i18n from "i18next"; works differently in my custom TS server and in the webpack-controlled client. The server imports commonjs while the client uses es distribution of i18next, according to what I see after adding console.log in every flavour of the lib. This means that I18n = i18next.default is null on the server (this variable is used inside lib/withI18next.js
, but things somehow work despite that). I replaced import * as i18n from "i18next"; with import * as i18n from "i18next/index"; to ensure commonjs on both client and server and things kept working 🤷♂️
OMG I need a rest from this 😅
yes...node.js imports commonjs bundle while browser prefers depending on bundler the es build for treeshaking...
not used apollo ever...so i totally have to pass :(
@jamuhl It’d be great if you could have a look at just using i18next in _app.js. If that works, I can nest Apollo into your wrapper and that will be it! In fact, Apollo does need to be inside i18n context, because things like GraphQL endpoint or query params can depend on the current language.
@kachkaev by using i18next in _app.js you mean having a provider / translate HOC which takes all the namespaces specified down the tree and load them in one place (the _app.js) instead of having multiple translate hocs down the tree?
Not sure if i can do much here. This is far from the regular use case done in react.
I guess translating content of _app.js works by wrapping itself into a with-i18next
I tried wrapping MyApp into the HOC like this:
// pages/_app.tsx
import App, { Container } from "next/app";
import { I18n } from "../i18n";
import PageHeader from "../lib/components/PageHeader";
class MyApp extends App {
public props: any;
public render() {
const { Component, pageProps, t } = this.props;
return (
<Container>
<PageLayout>
<PageHeader t={t} /> // <-- this component uses translations from "common" namespace
<Component {...pageProps} />
</PageLayout>
</Container>
);
}
}
export default translate(["common"], {
i18n: I18n,
wait: typeof window !== "undefined",
})(MyApp);
In addition to this, page components are still wrapped into with18next() as before to run getIntialProps logic etc.
In this case, rendering flickers and I get Warning: Did not expect server HTML to contain a <div> in <div>. – that's because the first client-side render is empty because of wait and Next sees a mismatch two trees. If I remove wait, my client-side text does not translate (i.e. I see key as a result of t('key')). Next.js shows another warning: Text content did not match. Server: "Translated Key" Client: "key".
Can there be a way of grabbing the contents of __NEXT_DATA__.initialI18nStore without wait: true? I guess that's what the problem is partially about here.
In any case, it'd be great to see a simple example of translations in _app.tsx where with18next() is around _app.js, not page.ts (just like this has been done for Apollo in https://github.com/zeit/next.js/pull/4420). Things like passing namespaces and linking this with other app wrappers can be done later.
getting no translations means we get no initialI18nStore: https://github.com/i18next/react-i18next/blob/master/src/I18n.js#L19 -> guess you should not use translate but withI18next (https://github.com/zeit/next.js/blob/canary/examples/with-react-i18next/lib/withI18next.js) but idk...
I agreee that it’s likely that i need to go for withI18next in _app.js. I tried this a couple of days ago and gave up after a few hours of trying. There is a chance that i18next needs to be tweaked in order to make it workable so i’m asking for help :–)
I'm open for changes to react-i18next to support that use case - but honestly that's out of things i currently have time for - to much next.js internals for v6. react-i18next works as is for next.js, razzle and custom serverside renderers (so that _app.js support for additional shared layout is awesome - but can be easily solved by moving layout to a custom component and wrap every page with it). Having more comfort for next v6 is out of my personal work scope.
When the layout is declared inside the page component, all dom nodes end up flushed, which may be problematic due to loss of react state or for performance reasons.
Thank you for honestly sharing your thoughts @jamuhl. I’ll ping you if I give the investigation another go and realize that i18next needs tweaking.
I guess I’ll have to move Apollo back to page wrapper for now since i18next does not work well _inside_ it.
I also look forward to using the i18next example in nextjs v6
@imagine10255 @kachkaev this _app.js thing is a little weird - or i do not really understand it to much...a little pain as pageProps are rather inconsistent.
What i came up with - and seems to work - while not overly elegant (@kachkaev i'm sure you could improve that) is using render props and dependent on pageProps has i18n (i guess that's server render) or not using that or the ../i18n -> from root folder:
import App, { Container } from "next/app";
import { I18n as I18nR } from "react-i18next";
import { I18n } from "../i18n";
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<I18nR ns="common" i18n={pageProps && pageProps.i18n || I18n}>
{
(t) => (
<div>
<h1>{t('common:integrates_react-i18next')}</h1>
<Component {...pageProps} />
</div>
)
}
</I18nR>
</Container>
);
}
}
:thinking: Interesting. How do you define pageProps.i18n and how do you pass pre-loaded namespaces to the client via __NEXT_DATA__? I'm also wondering if you have any warning saying that your client-rendered tree is different from the server one – that's what I got stuck with.
Speaking of the behaviour of _app.js, I'm also not sure I understand what's going on in it 😅
@kachkaev no i had no warnings using that code above.
just had no pageProps.i18n on clientside so i took the fallback ../i18n there - everything else seemed to work...
but like you...i'm not to sure how all that _app.js works...and not time to read through all the code ;)
@jamuhl
I implemented your solution above to some success. Everything is fine for production mode, however, I started experiencing issues with the server and client being out of sync in developer mode. My initial assumption is that the i18n instance has changed due to HMR.
Is this something you also encountered?
@areddon as i personally have no project that i work active on - all i got is this small sample...so no - out of not using it extensively i did not had that...
Upon further inspection, it appears that pageProps.i18n and I18n can both be undefined.
how can I18n be undefined - seems i miss something...
It definitly is undefined, as it is followed by an error message ( server only )
TypeError: Cannot read property 'options' of undefined
at new I18n (~node_modules/react-i18next/dist/commonjs/I18n.js:40:47)
That is a case were i18next was not initialized: https://github.com/i18next/react-i18next/blob/master/src/I18n.js#L12
Hi @jamuhl !
I'm having the same trouble than @areddon. I have upgraded Next version, then the console shows:
TypeError: Cannot read property 'options' of undefined
at new I18n (~node_modules/react-i18next/dist/commonjs/I18n.js:56:20)
I think that the problem appears when Next wants to do SSR because the same page are working when I tap on the link to redirect there, but it doesn't work when I refresh the browser on this page.
UPDATE: The problem mentioned above appears on pages where I don't use the HOC.
It's weird because if I don't want to use translations, I can't.
@slorenzo Could you please test:
import App, { Container } from "next/app";
import { I18n as I18nR } from "react-i18next";
import { I18n } from "../i18n";
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<I18nR ns="common" i18n={pageProps && pageProps.i18n || I18n}>
why the I18n from import { I18n } from "../i18n"; is undefined...
Hi @jamuhl , Sorry i am fresh but i also met the issue:
TypeError: Cannot read property 'options' of undefined
at new I18n (${path}\node_modules\react-i18next\dist\commonjs\I18n.js:56:20)
I need to create a page which use getInitialProps to fetch data from my backend. Hence i must use i18n services inside render method... Emmm...
I made a test project which could reproduce this issue, you may find it here, I just changed the way of rendering index page a little bit:
import React from 'react'
import Link from 'next/link'
import PureComponent from '../components/PureComponent'
import ExtendedComponent from '../components/ExtendedComponent'
import ComponentWithTrans from '../components/ComponentWithTrans'
import { withI18next } from '../lib/withI18next'
const TestContent = withI18next(['home', 'common'])(({ t, initialI18nStore }) => (
<div>
<h1>{t('welcome')}</h1>
<p>{t('common:integrates_react-i18next')}</p>
<p>{t('sample_test')}</p>
<div>
<button>{t('sample_button')}</button>
</div>
<PureComponent t={t} />
<ExtendedComponent />
<ComponentWithTrans />
<Link href='/page2'>
<a>{t('link.gotoPage2')}</a>
</Link>
</div>
))
const Test = () => {
return(
<TestContent/>
)
};
export default Test;
This may not be doable cause I think it is a HOC and can not be used in render method as described here.
I am a little confused, could you please give a hand?
Just made a fast check and change in the ../lib/withI18next.js:
There I18n imported from '../i18n' is undefined...idk why we have that I18n and i18nextInstance there but seems to have been related to typescript usage...
finally changing in the HOC
import { translate, loadNamespaces } from 'react-i18next'
import { getInitialProps, I18n, i18nInstance } from '../i18n'
export const withI18next = (namespaces = ['common']) => ComposedComponent => {
const Extended = translate(namespaces, { i18n: I18n || i18nInstance, wait: process.browser })(
ComposedComponent
)
will make it work again...
No idea, evtl. @kachkaev has more insights as he did most of the next.js sample code and exports in i18n.js
👍 It works, thanks a lot @jamuhl
@LiangNex does @jamuhl's suggestion work for you together with other things in app.js (such as apollo client)? If so, could you please update the example?
@kachkaev Sorry but so far I only made the tests on component layer where I'm working on, which works as expected. (I haven't touched anything in app.js, which is setup by next.js by default)
Besides I just start my trip on frontend with next.js, I am afraid I can't do the testing on a high level aspect.
Here are the changes which follows @jamuhl 's suggestion. As he had mentioned that he would make it work again, I guess he might have some ideas in his mind to make it more flexible or something like that.
@LiangNex @kachkaev i will assist as i can - my personal problem is i do not use next myself - so there is no real project i test on...and on the sample given for my "test" cases it works by the above changes in withI18next.js and app.js....but still we get feedback on not being there yet to have it 100% working...
I have the same issue. Is there any other solution for use react-i18next with _app.js ?
There is my code of _app.js
class MyApp extends App {
render() {
const { Component, pageProps, store } = this.props;
return (
<Container>
<Provider store={store}>
<I18nR ns="common" i18n={(pageProps && pageProps.i18n) || i18nInstance}>
{t => (
<div className="Layout">
{t('cta')}
<Header t={t} />
<ScreenClassRender
render={screenClass => (
<div className={'Layout ${isMobile(screenClass) ? 'Fixed' : `''}'}>`
<Component {...pageProps} />
</div>
)}
/>
<Footer t={t} />
</div>
)}
</I18nR>
</Provider>
</Container>
);
}
}
export default withRedux(configureStore)(MyApp);
I wrap other pages with HOC withI18next. I also added fix in HOC (I18n || i18nInstance).
There is my log

So if I remove I18nR from _app it's working. But how can I translate layout?
@MaksRamenskiy interesting...from the log you see client renders before init which results in missingKey -> so the translations are not ready => no initialI18nStore set, no wait
cause:
so i guess if using I18nR in _app.js we need to get initialI18nStore there - just how? As that pageProps seem unreliable...
@MaksRamenskiy @kachkaev @LiangNex @areddon eventual there was an update to _app.js?!!?
https://nextjs.org/docs/#custom-%3Capp%3E
setting pageProps via async getInitialProps() { ... }:
import App, {Container} from 'next/app'
import React from 'react'
export default class MyApp extends App {
static async getInitialProps ({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return {pageProps}
}
render () {
const {Component, pageProps} = this.props
return <Container>
<Component {...pageProps} />
</Container>
}
}
@MaksRamenskiy @LiangNex @imagine10255 @slorenzo @kachkaev
I removed the typescript stuff from i18n.js (having that default and instance export - so there is no mix depending on server or webpack - and made a new sample to checkout here: https://github.com/i18next/react-i18next/tree/master/example/nextjs_withAppJS
Feedback if you still encounter any issues on that with the changes done would be welcome ... if working i will update the sample here - if not PR on the sample would be welcome (or changes so i see the problem - as at my machine all seems to work)
just for credits...i took @LiangNex sample to work on
@jamuhl I tried you example (nextjs_withAppJS) and got error:
Warning: Did not expect server HTML to contain a <div> in <div>.
@MaksRamenskiy i got that from time to time...not sure what that warning means at all
@MaksRamenskiy @jamuhl
I haven't had a chance to test out these changes yet, however that error usually occurs when the server rendered html does not match the client rendered html. It could be a result of using i18next in _app.js or something else within your application. When I get the chance to test on my bare bones implementation, I will let you know if I encounter the same issue.
@MaksRamenskiy @LiangNex @imagine10255 @slorenzo @kachkaev anyone had time to look deeper into - https://github.com/i18next/react-i18next/tree/master/example/nextjs_withAppJS
Would be great if we find a solution to this and update the sample provided here.
This seems to work okay:
https://github.com/ckeeney/with-react-i18next-app
It has some hackish stuff that may or may not be needed now, but I needed to take a break and just wanted to post my progress here.
@ckeeney did you also had a look at: https://github.com/i18next/react-i18next/tree/master/example/nextjs_withAppJS ?!?
There is my solution. But I only use one common namespace.
i18n.js file is same as in nextjs_withAppJS example.
import React from 'react';
import { Provider } from 'react-redux';
import App, { Container } from 'next/app';
import withRedux from 'next-redux-wrapper';
import { I18nextProvider } from 'react-i18next';
import i18nR from '../i18n';
import Header from './../components/Header';
import Footer from './../components/Footer';
import configureStore from './../redux/configureStore';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
const i18nInitialProps = ctx.req ? i18nR.getInitialProps(ctx.req, 'common') : {};
return { pageProps, i18nInitialProps };
}
render() {
const {
Component, pageProps, i18nInitialProps, store,
} = this.props;
const { i18n, initialI18nStore, initialLanguage } = i18nInitialProps;
return (
<Container>
<Provider store={store}>
<I18nextProvider i18n={i18n || i18nR} initialI18nStore={initialI18nStore} initialLanguage={initialLanguage}>
<div>
<Header />
<Component {...pageProps} />
<Footer />
</div>
</I18nextProvider>
</Provider>
</Container>
);
}
}
export default withRedux(configureStore)(MyApp);
Pages wrapped by translate from 'react-i18next'
Most helpful comment
This seems to work okay:
https://github.com/ckeeney/with-react-i18next-app
It has some hackish stuff that may or may not be needed now, but I needed to take a break and just wanted to post my progress here.