i18next / next-i18next

The easiest way to translate your NextJs apps.
https://next.i18next.com
MIT License
5.62k stars 762 forks source link

react-i18next:: i18n.languages were undefined or empty undefined #374

Closed mehmetnyarar closed 5 years ago

mehmetnyarar commented 5 years ago

Describe the bug

I get the the following error:

[ wait ]  compiling ...
react-i18next:: i18n.languages were undefined or empty undefined

which appears only on the server (I mean in the output of the terminal, it doesn’t appear on the browser console).

Occurs in next-i18next version

"next": "^8.1.0", "i18next": "^17.0.6", "next-i18next": "^0.44.0",

Steps to reproduce

I followed every step described in the readme except keySeparator (because I have keys like Status.ONGOING)

Below is my configuration:

import NextI18Next from 'next-i18next'

const instance = new NextI18Next({
  defaultLanguage: 'en',
  otherLanguages: ['ru'],
  keySeparator: false
})

export default instance

Expected behaviour

This doesn’t look like a critical error, though I’m wondering the cause.

Screenshots

OS (please complete the following information)

Additional context

oteka21 commented 5 years ago

i think it can be fix by adding the preload key to the options object

module.exports = new NextI18Next({
  preload: ['fr'],
  otherLanguages: ['fr','en', 'es'],
  browserLanguageDetection: true,
  fallbackLng: 'fr',
  debug: true
})

options documentation

isaachinman commented 5 years ago

@oteka21 Please see the source.

rdewolff commented 5 years ago

Same issue here with latest versions. Site seems to render with:

lng: 'en',

as @truongtx9 proposed above. What's this ? Is this a bug or a non documented feature ?

isaachinman commented 5 years ago

@rdewolff As stated previously, that will break language detection.

sebaseek commented 5 years ago

Facing the same issue, there is a way to solve this without breaking the language detection?

harmvandendorpel commented 5 years ago

+1

isaachinman commented 5 years ago

there is a way to solve this without breaking the language detection?

Yes, either await the promise returned by the i18next init call, or use the callback.

StarpTech commented 5 years ago

@isaachinman

Yes, either await the promise returned by the i18next init call, or use the callback.

so do you know a solution or what is the state here? How can I help?

isaachinman commented 5 years ago

@StarpTech The working assumption is that it's a race condition on startup. The only way we can arrive at a non-breaking-change solution is if we can find a way to initialise an i18next instance synchronously.

bethrezen commented 5 years ago

If that helps, let me describe my solution to the problem. In my case I've seen such errors when there were requests to non existing URLS(404) on /static/. And here's the logic:

  1. We have our next-i18next/middleware
  2. next-i18next/middleware adds another middleware i18next-express-middleware
  3. i18next-express-middleware checks ignoreRoutes option
  4. /static/ is ignored, so i18next-express-middleware is not running at all - it just calls next() function argument, so express.js server continues
  5. Then next.js can't find static file and tries to show Error page (_error.js)
  6. In my case _error.js has namespacesRequired, but it is rendered within a Document that has react components that need translations
  7. But we can't render them because i18next-express-middleware didn't provide anything because /static/ is ignored route.

So here is solution in my case - change ignoreRoutes to empty array:

const NextI18Next = require('next-i18next').default;

const { localeSubpaths } = require('next/config').default().publicRuntimeConfig;

const localeSubpathVariations = {
  none: {},
  foreign: {
    fr: 'fr',
    en: 'en',
  },
  all: {
    en: 'en',
    fr: 'fr',
  },
};

module.exports = new NextI18Next({
  defaultLanguage: 'ru',
  otherLanguages: ['fr', 'en'],
  localeSubpaths: localeSubpathVariations[localeSubpaths],
  debug: true,
  // initImmediate: true,
  // load: 'all',
  ignoreRoutes: [],
});

@isaachinman not sure if there's any race condition in i18next-express-middleware(cuz there's no async and loadLanguages calls next() only on callback), but please check my solution if it closes this issue

isaachinman commented 5 years ago

Setting ignoreRoutes to an empty array is not a solution. That config option is there for a reason. For any users that use localeSubpaths, their static and _next traffic would be erroneously redirected, breaking the entire app.

For anyone wishing to help on this issue: please take a deep dive before bumping or offering suggestions!

MelleB commented 5 years ago

@isaachinman Not sure if it is of any help, but for me the problem seems to occur when I'm having a custom route, e.g.:

custom route:

  router.get('/password-renew/:passwordResetSlug', helmet.noCache(), (req, res) => {
    const actualPage = '/password-renew';
    const queryParams = { ...req.query, passwordResetSlug: req.params.passwordResetSlug }
    return app.render(req, res, actualPage, queryParams);
  })

page:

class RenewPasswordPage extends Component {
  static async getInitialProps(ctx) {
    const namespacesRequired = ['public']
    const passwordResetSlug = ctx.query.passwordResetSlug
    return { namespacesRequired, passwordResetSlug }
  }

  render() {
    // ...
  }
}

export default withTranslation('public')(RenewPasswordPage)
qd-qd commented 5 years ago

Not sure if I'm helping but I caught this error from my side too and I temporary fix it by adding languages propriety to i18n object.

const languages = ['fr-FR', 'en-GB'];

const options = {
  defaultLanguage: 'fr-FR',
  otherLanguages: ['en-GB'],
  ...
};

const NextI18NextInstance = new NextI18Next(options);

NextI18NextInstance.i18n.languages = languages;

module.exports = NextI18NextInstance;
MelleB commented 5 years ago

Using @jonathangiamp worked after adding the fallbackLng config property.

StarpTech commented 5 years ago

Correct me if I'm wrong but if the root issue is really the way how we bootstrap i18n then we should release a breaking change to fix that correctly.

isaachinman commented 5 years ago

@StarpTech If and when it is determined that we cannot initialise an i18next instance synchronously, then yes, we'd need to release a breaking change.

This is a very good example of a use case where we definitely want to init an instance synchronously, as our application cannot serve any requests until it's ready.

@jamuhl Any ideas about what we might be doing wrong here? Any opinion about construction/initialisation? Funny that this issue has become so popular.

StarpTech commented 5 years ago

@isaachinman please add "bug" label

jamuhl commented 5 years ago

@isaachinman would be happy to help with this...but I do not get the issue

but why is that a problem in production...a server starts up and things will be ready on first request...or even delay start of server and start it inside the init callback...

for serverless add a middleware that asserts init is done before calling next should do the trick (just use the initialized event and flag on i18next.)

ricardo-cantu commented 5 years ago

Looks like this might happen more often when you have a custom _error that uses t() or the hoc. Next will compile a _error page with no req, thus no i18n object that has the correct lang detected. I removed the custom _error in another project I’m working on and I have not seen the warning again.

isaachinman commented 5 years ago

Finally took some time to dig into this. The issue of awaiting the initialisation of the i18next instance is a very trivial one, and I can provide an easy, non-breaking way for users to do that if they want to. However, I think that that race condition is causing a very low percentage of the error reports we are seeing here.

The main problem is indeed with 404s, primarily in development. This is because a browser makes a request for a dev resource which used to exist, but now has been cleared by HMR. Eg:

http://localhost:3000/_next/static/webpack/f15dd305991bb90f87e1.hot-update.json

This request goes through the next-i18next middleware first, and as far as we are concerned, this is a request for a static resource and has a statusCode of 200 (has not been modified yet). The default behaviour here is to exclude static resources from i18next-express-middleware, thus req.i18n shouldn't exist on this request.

The request then gets passed to NextJs:

const handle = app.getRequestHandler()
server.get('*', (req, res) => handle(req, res))

At this point the request is determined to be a 404, and the status code is changed. At that point in time, NextJs wants to render an error page, but does so without a redirect (naturally), so the next-i18next middleware never gets called again, and we do not have a chance to populate the i18n instance.

That NextJs logic can be found here.

(It's noteworthy that this code path is different than "normal", non-resource, 404s which next-i18next handles just fine.)

The curious part is how react-i18next ever gets to the point of calling hasLoadedNamespace, as i18n.isInitialized is a precondition of that call, and i18n shouldn't exist on that req at all. Still not sure about this, but it's relatively unimportant.

In general this is a bit of a chicken-and-egg problem to solve. The easy thing to do is to remove the passing of ignoreRoutes to i18next-express-middleware, seen here.

The actual redirect logic is protected by isI18nRoute which uses the exact same ignoreRoutes array, so we actually don't have to worry about static resources being redirected when users have enabled localeSubpaths.

However, this solution would mean that we are processing all our static resource requests through the i18next middleware, just on the off chance that one of them results in a 404. Perhaps this is actually the right thing to do? No idea if this has perf implications, but it probably does.

On a side note, I have no idea why the NextJs team have decided to return an HTML doc for a 404 on a JSON resource - that doesn't seem right to me. Not sure if that's configurable.

Apologies for a long post, but that's the entire story. We have all the information necessary to work on this, it's just a matter of discussing amongst ourselves what makes the most sense.

Open for discussion.

pdandradeb commented 5 years ago

Thanks to OP and @isaachinman for taking time to check this issue.

Finally took some time to dig into this. The issue of awaiting the initialisation of the i18next instance is a very trivial one, and I can provide an easy, non-breaking way for users to do that if they want to. However, I think that that race condition is causing a very low percentage of the error reports we are seeing here.

What would be the quick-fix?

Thanks

isaachinman commented 5 years ago

There is no "quick-fix" in user land, it needs to happen within the source code.

GraxMonzo commented 5 years ago

I get this problem when I use public/static path instead of static/locales. Removing public folder and setting up localePath to default value fixes the problem

My locales folder:

.
└── public
    └── static
        └── locales
            ├── ru
            |   └── common.json
            └── kk
                └── common.json

Instance:

const NextI18NextInstance = new NextI18Next({
  localePath: 'public/static/locales',
  preload: ['ru'],
  defaultLanguage: 'ru',
  otherLanguages: ['kk'],
  localeSubpaths: {
    ru: 'ru',
    kk: 'kk'
  }
});
mercteil commented 5 years ago

@GraxMonzo

localePath: typeof window === 'undefined' ? 'public/static/locales' : 'static/locales'