@jamuhl Hi,
I tried using your with-react-i18next example with typescript. It's giving me errors. Do you think you could help me debug?
here's my gist: https://gist.github.com/lifeiscontent/f23b5ea2221f11f8f0cfc11a0764e01f
and I'm getting the error: Cannot read property 'resourceStore' of undefined
I think it may be due to how you were using a i18nInstance and the i18next module.
caused by this line: https://gist.github.com/lifeiscontent/f23b5ea2221f11f8f0cfc11a0764e01f#file-i18n-ts-L38
looks like you render before init was called.
@jamuhl I think it might be something deeper. I'm thinking the way react-i18next is exported isn't done properly for both the client and the server to consume the files in a proper manner.
the fact that in your example you have to use i18next.default in the export is what leads me to believe it.
server is node.js and will use the commonjs build...while client depending on what gusto of build tool it uses uses a umd or es6 build...
I think the problem is that you are passing i18n, not I18n here: https://gist.github.com/lifeiscontent/f23b5ea2221f11f8f0cfc11a0764e01f#file-withi18next-L5-L8
The argument needs to be a class, but it is a node module.
I also spent quite a lot of time chasing this issue and refactored the example in this PR: https://github.com/zeit/next.js/pull/3959
More specifically, see this line: https://github.com/zeit/next.js/pull/3959/files#diff-d60fa1ce8485089e8383d76b6033d1ecR5
Something like that?
This sample does not work.
i18n.js
const i18next = require('i18next')
const XHR = require('i18next-xhr-backend')
const LanguageDetector = require('i18next-browser-languagedetector')
const options = {
fallbackLng: 'en',
load: 'languageOnly', // we only provide en, de -> no region specific locals like en-US, de-DE
// have a common namespace used around the full app
ns: ['common'],
defaultNS: 'common',
debug: process.env.NODE_ENV !== 'production',
saveMissing: true,
// whitelist: ['en'],
interpolation: {
escapeValue: false, // not needed for react!!
formatSeparator: ',',
format: (value, format, lng) => {
if (format === 'uppercase') return value.toUpperCase()
return value
}
}
}
const i18nInstance = i18next
// for browser use xhr backend to load translations and browser lng detector
if (process.browser) {
i18nInstance
.use(XHR)
// .use(Cache)
.use(LanguageDetector)
}
// initialize if not already initialized
if (!i18nInstance.isInitialized) i18nInstance.init(options)
// a simple helper to getInitialProps passed on loaded i18n data
const getInitialProps = (req, namespaces) => {
if (!namespaces) namespaces = i18nInstance.options.defaultNS
if (typeof namespaces === 'string') namespaces = [namespaces]
req.i18n.toJSON = () => null // do not serialize i18next instance and send to client
const initialI18nStore = {}
req.i18n.languages.forEach((l) => {
initialI18nStore[l] = {}
namespaces.forEach((ns) => {
initialI18nStore[l][ns] = (req.i18n.services.resourceStore.data[l] || {})[ns] || {}
})
})
return {
i18n: req.i18n, // use the instance on req - fixed language on request (avoid issues in race conditions with lngs of different users)
initialI18nStore,
initialLanguage: req.i18n.language
}
}
module.exports = {
getInitialProps,
i18nInstance,
I18n: i18next.default
}
lib/withI18next.js
import { translate, loadNamespaces } from 'react-i18next'
import { getInitialProps, I18n } from '../i18n'
export const withI18next = (namespaces = ['common']) => ComposedComponent => {
const Extended = translate(namespaces, { i18n: I18n, wait: process.browser })(
ComposedComponent
)
Extended.getInitialProps = async (ctx) => {
const composedInitialProps = ComposedComponent.getInitialProps
? await ComposedComponent.getInitialProps(ctx)
: {}
const i18nInitialProps = ctx.req
? getInitialProps(ctx.req, namespaces)
: await loadNamespaces({
components: [{ props: { namespaces } }],
i18n: I18n,
});
return {
...composedInitialProps,
...i18nInitialProps
}
}
return Extended
}
component.js
import { withI18next } from '../lib/withI18next'
...
@observer
class Index extends React.Component {
...
}
...
export default withRoot(withI18next(['home', 'common'], { wait: true })(withStyles(styles)(Index)));
@kachkaev I am using the Next.js example, and I cannot figure out how the translations are loaded during an SSR.
When looking at getInitialProps:
Where does req.i18n get set?
Is it here koa.use(i18nextMiddleware.getHandler(i18n))?
At what point does req.i18n.services.resourceStore.data get filled?
Also, you set i18n to i18nInstance, but they are the same aren't they? Isn't it a singelton?
i18n and i18nInstance may not be the same depending on how you resolve modules, which is worth noting if you want your code to work both in SSR and on the client. Not sure about other questions though – I'm not an expert in next.js and i18next :(
My issue was that I am using koa, and koa-i18next-middleware, which attaches i18n to ctx.request, instead of ctx.req from the example.
const i18nInitialProps = ctx.req
? getInitialProps(ctx.req, namespaces)
: await loadNamespaces({
components: [{props: {namespaces}}],
i18n,
})
Need to add ctx.request.i18n to ctx.req.
Also, i18n is a singleton, so no need for the i18nInstance stuff.
koa-i18next-middleware does run i18n.cloneInstance though. I'm not sure why...
cloneInstance is run to assert you get a fixed language for that request (avoiding async mixup of languages).
req.i18n gets set in the middleware.handler (at least for express)
resourceStore.data gets set via (fs) backend on init
i18n and i18nInstance is something i never experienced myself -> might be more a typescript issue
@timneutkens i guess this can be closed - solved in https://github.com/i18next/react-i18next/tree/master/example/nextjs (which get merged back to here soon)
You're on 🔥 @jamuhl ❤️
Most helpful comment
I think the problem is that you are passing
i18n, notI18nhere: https://gist.github.com/lifeiscontent/f23b5ea2221f11f8f0cfc11a0764e01f#file-withi18next-L5-L8The argument needs to be a class, but it is a node module.
I also spent quite a lot of time chasing this issue and refactored the example in this PR: https://github.com/zeit/next.js/pull/3959
More specifically, see this line: https://github.com/zeit/next.js/pull/3959/files#diff-d60fa1ce8485089e8383d76b6033d1ecR5