i18next / next-i18next

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

Error: appWithTranslation was called without a next-i18next config #990

Closed hectorstudio closed 3 years ago

hectorstudio commented 3 years ago

Describe the bug

Error: appWithTranslation was called without a next-i18next config

Occurs in next-i18next version

I tried the new version - 8.0.1

Steps to reproduce

module.exports = { i18n, }`

const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />

export default appWithTranslation(MyApp)

Screenshots

image

OS (please complete the following information)

Additional context

Add any other context about the problem here.

isaachinman commented 3 years ago

Hi @hectorstudio – unfortunately I'm unable to determine what is going wrong based on the snippets you've copy/pasted. Can you please provide a full repo?

Also just a heads up, regional locales use dashes, not underscores. A correct locale would be en-US.

isaachinman commented 3 years ago

@hectorstudio On second thought, you're probably just not using serverSideTranslations.

hectorstudio commented 3 years ago

I used serverSideTranslationon page-level component. `export const getStaticProps = async ({ locale }) => ({ props: { ...await serverSideTranslations(locale, ['common']), }, })

export default Homepage;`

isaachinman commented 3 years ago

Can you provide a reproducible repo?

BenAllenUK commented 3 years ago

I also have this issue. I think this is because the AWS serverless deployment plugin (https://github.com/serverless-nextjs/serverless-next.js) is not copying in the configuration files to final build packages that get uploaded to lambda. I have tried copying the next-i18next.config.js and next.config.js into the .serverless_nextjs/default-lambda folder post-build but that did not work.

Where does the next-i18next plugin expect the config files to live?

Screenshot 2021-02-25 at 15 36 30

BenAllenUK commented 3 years ago

This is the additional copying script I added:

serverless.yml

# https://github.com/serverless-nextjs/serverless-next.js#readme
omnea:
  component: '@sls-next/serverless-component@1.19.0-alpha.32'
  inputs:
    bucketName: omnea
    build:
      postBuildCommands: ['node post-build.js']

post-build.js:

const fs = require('fs-extra')

console.log('-> Copying locales directory...')
const localeSrc = './public/locales'
const localeDest = './.serverless_nextjs/default-lambda/public/locales'
fs.copySync(localeSrc, localeDest, { recursive: true })

const localeSrc1 = './next-i18next.config.js'
const localeDest1 = './.serverless_nextjs/default-lambda/next-i18next.config.js'
fs.copySync(localeSrc1, localeDest1)

const localeSrc2 = './next.config.js'
const localeDest2 = './.serverless_nextjs/default-lambda/next.config.js'
fs.copySync(localeSrc2, localeDest2)

console.log('Locale directory was copied successfully')
isaachinman commented 3 years ago

Where does the next-i18next plugin expect the config files to live?

In the root dir, as per the documentation.

Let me know if I can assist in any way. Various serverless platforms do tend to do strange things with the fs.

netosha commented 3 years ago

Have same issue, but on my local build (all config files in the root dir)

Props passed to appWithTranslation:

{ _nextI18Next: { initialI18nStore: { ru: [Object], en: [Object] }, initialLocale: 'ru', userConfig: null } }

UPD: next-i18next.config should be always separate file

BenAllenUK commented 3 years ago

I had a chance to look into this a bit further and it seems to be a problem within the generated webpack code as opposed to the file system on the server (well that is also a problem but the code I linked above does resolve that). When the file resolution is called with __webpack_require__ as opposed to require the generated code fails.

I was able to get this to happen locally as well once I'd generated the webpack code for myself.

@isaachinman Are you aware of needing to add the file next-i18next.config.js to any webpack config such that it is included in the compiled code? I'm not greatly familiar with the configuration setup of webpack.

isaachinman commented 3 years ago

@BenAllenUK Not entirely sure what you're asking, do you mind elaborating? The difference between __webpack_require__ and require should not matter – the config file needs to exist on the filesystem.

BenAllenUK commented 3 years ago

Sure.

This is the error I get on the server (after deploying via serverless next js tool to AWS lambda).

2021-03-02T13:38:14.118Z    d38da14f-297f-4dbc-a2cc-98ab7c8ba2e2    ERROR   Unhandled error during request: Error: Cannot find module '/var/task/next-i18next.config.js'
    at webpackEmptyContext (/var/task/pages/[projectSlug]/editor/[articleSlug].js:79610:10)
    at /var/task/pages/[projectSlug]/editor/[articleSlug].js:1348:90
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  code: 'MODULE_NOT_FOUND'
}

Root of project lambda is at /var/task.

I have next.config.js at root and next-i18next.config.js at root. Here's the generated file structure visible from lambdas function code editor:

Screenshot 2021-03-02 at 15 37 00

To confirm I have the following code in pages/[projectSlug]/editor/[articleSlug].tsx:

export async function getServerSideProps({ params, locale }: GetServerSidePropsContext) {
return {
    props: {
      ...(await serverSideTranslations(locale, ['common', 'editor'])),
    },
  }

Contents of `next-i18next.config.js:

const path = require('path')

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'fr'],
  },
  localePath: path.resolve('./public/locales'),
}

So this works in development but once I use the production bundle it fails.

I narrowed the error down to the serverSideTranslations function within my bundled /var/task/pages/[projectSlug]/editor/[articleSlug].js file. Within this function it throws on line 1374.

Screenshot 2021-03-02 at 15 43 10

I walked through this and Promise.resolve("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH))) correctly resolves to the file path of /var/task/next-i18next.config.js. The error is next thrown when __webpack_require__("cgfX")(s) is called. This is a reference to the webpackEmptyContext that we see in the initial stack trace

My earlier comment was to say that if I replace with __webpack_require__("cgfX")(s) with require(s), this loads the file correctly. Obviously this is not a solution, just something I've found that might help me find the solution.

Any advice / things to test here would be greatly apprecaited.

isaachinman commented 3 years ago

Interesting, thanks for a very detailed account.

I think what you're suggesting would involve changing this import to a regular require. Do you want to give that a test, and see if it solves the issue?

BenAllenUK commented 3 years ago

Good suggestion. Unfortunately, this didn't work. At the point in which the node modules get packaged up into one JS file the require also gets replaced with __webpack_require__ so the compiled code is the same.

SeinopSys commented 3 years ago

TIL that it is crucial that the config file looks exactly like the one described in the README. I was getting this exact error scratching my head as to why it's missing, then I realized my config file looks like this:

// next-i18next.config.js
module.exports = {
  locales: ['en', 'hu'],
  defaultLocale: 'en',
};

// next.config.js
const i18n = require('./next-i18next.config.js');
module.exports = { i18n };

instead of this:

// next-i18next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'hu'],
    defaultLocale: 'en',
  },
};

// next.config.js
const { i18n } = require('./next-i18next.config.js');
module.exports = { i18n };

I hadn't realized that was a load-bearing i18n key

BenAllenUK commented 3 years ago

Sounds like another way to get the same error. I confirmed that my next-i18next.config.js is correct and the i18n key import also works in my setup.

const { i18n } = require('./next-i18next.config')

const path = require('path')

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'fr'],
  },
  localePath: path.resolve('./public/locales'),
}
agnese-kerubina commented 3 years ago

@BenAllenUK struggling with similar issue while trying to do the same - build and deploy next-i18next with serverless component. I probably am a step behind you. I think that my issue is that it does not let me build app without next-i18next.config.js. Thus I can't even get to run any postBuildCommands. I run locally serverless build --component myNextApplication and it just gloriously fails. Error points to a file that actually exists.

Theres a couple of issues at serverless component github, but they all seem to have dealt with different localization packages that did not require file before build?

agnese-kerubina commented 3 years ago

@BenAllenUK I dug just as far as you did now. There is indeed something wrong with that __webpack_require__ you pointed out. Not sure what, sorry :(

I have a temporary solution and its sorta ugly - to not to use default configuration file. This package apparently allows to override config within app. So, rename your i18n config file to something else and explicitly use it everywhere. Use this file in next.config.js , override default in appWithTranslation. And make a helper for serverSideTranslations that uses the new config. And it works!

BenAllenUK commented 3 years ago

Hey @Reiiya - thanks for confirming. I'm glad its not just me. I'm still stuck on this unfortunately as well.

Thanks, that workaround makes sense, I may look at doing that but will hold out atm to see if anyone else can pick up where I left off.

isaachinman commented 3 years ago

Closing now, as this seems to simply be a shortcoming of various serverless platforms. Explicitly passing the config into the two functions should work, though.

@BenAllenUK If you do find a solution, please do let us know.

hossisun commented 3 years ago

Why was this one closed? The error still occurs.

agnese-kerubina commented 3 years ago

Why was this one closed? The error still occurs.

Because it was deemed as issue happening on Serverless side (but honestly I'm not exactly sure where it happens). I have not seen anyone actively fixing it there either. From what I've seen, everyone just uses the workaround mentioned. Unfortunately I am among those who just rely on workaround for now and cant jump in the fun :(

gvatsov commented 3 years ago

I got the same error and for me the problem was that I use the serializeConfig: false, option. Once I added the configuration manually to appWithTranslation like this appWithTranslation(MyApp, nextI18NextConfig) it worked. I realize it is not the same config as the reporter's one, but it may help someone.

lednhatkhanh commented 3 years ago

@Reiiya Could you please provide an example for the workaround that you are applying please, I'm facing the exact same issue. Thank you.

Arnarkari93 commented 3 years ago

I had the same error and for me was the case was that I was not sending the correct props down to the pages.

I was sending in an object containing the key _nextI18Next :

// this is wrong
{ 
  _nextI18Next: { 
   initialI18nStore: [Object],
   initialLocale: 'en',
   userConfig: [Object]
 }

instead of the correct object:

// this is expected
{ 
   initialI18nStore: [Object],
   initialLocale: 'en',
   userConfig: [Object]
 }

I was not spreading the ...serverSideTranslations and got confused that was the expected props.

Hopefully, this will help someone.

crathert commented 3 years ago

Adding config to serverSideTranslations fixed it for me. ...(await serverSideTranslations(locale, ['common'], nextI18NextConfig)),

flexboni commented 2 years ago

Adding config to serverSideTranslations fixed it for me. ...(await serverSideTranslations(locale, ['common'], nextI18NextConfig)),

this reference solved my problem @crathert thx^^

yanickrochon commented 1 year ago

Nothing here worked for me. Seriously!

huntharo commented 1 year ago

@BenAllenUK had the right approach. I ended up tracing through the same issue because I didn't see the notes until after I was done :)

Overview

I have a hand-crufted fix in hand, deployed in a real app, details in next section. I do not know how to make this as a patch that we can ship in the library that will work in both non-bundled and bundled cases with the module at the root of a monorepo or in a child package directory. I'm willing to do a Google Meet or other brainstorming / pairing session with anyone that has an idea to try out.

and I believe we can fix this by changing to await import to prevent Webpack from pointing the config file import to the empty dummy using the technique for how next.config.js is avoiding this problem (turns out the next.config.js approach only works because next is external / not bundled).

I am NOT using target: serverless (using this with Next.js 13.1.1) but that does not matter. I have a replacement for target: serverless that I'll be sharing shortly that is even better but does the same bundling of node_modules to prevent enormous deployments and slow starts on Lambda. The solution below will apply go target: serverless though as the problem is just Webpack.

The hand-crufted Fix

next.config.js Block

const path = require('path');

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  webpack: (config, options) => {
    const { dev, isServer } = options;
    if (isServer && config.name === 'server' && !dev) {
      const contextReplacePlugin = new options.webpack.ContextReplacementPlugin(/next-i18next/, './', {
        // Left side (./next-i18next.config.js) is the path that require is looking for at build time
        // Right side (path.resolve(./next-i18next.config.js)) is the path to the file that will be
        // bundled into the server bundle and used at runtime
        './next-i18next.config.js': path.resolve('./next-i18next.config.js'),
      });

      // Put at front of plugins array
      config.plugins.unshift(contextReplacePlugin);
    }

    return config;
  },
};

node_modules/next-i18next/dist/commonjs/serverSideTranslations.js Block

// Make this require no longer an expression so the path stays the same
// for Webpack - This allows us to fix the path with ContextReplacementPlugin
// When the path is wrapped with `path.resolve` it makes it compute one value
// at build time and then a different value at runtime so the `map` that is built
// to find the name at runtime has an entry that does not match the item being
// looked for.  It's possible to hand-fix this map by placing the fully qualified
// path to the file into the map and pointing to the same module number to prove
// that the issue is the difference in paths between build time and runtime.
return _interopRequireWildcard(require(DEFAULT_CONFIG_PATH));

Problem Summary

The Fix

The path.resolve call from DEFAULT_CONFIG_PATH has to be removed as a temporary workaround.

Now... why does this work for the next.js import of next.config.js? It appears that using await import(...) is either a workaround or a legit way to say that we want this imported at runtime, in any case it appears that it stops Webpack from messing this up while still allowing discovery of the path at runtime. Correction: this works for next because next is not bundled so the import expression is evaluated only at runtime.

Applying the Fix Locally

patches/next-i18next+13.0.3.patch Patch File Contents

diff --git a/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js b/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
index 67dc9af..fc5b765 100644
--- a/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
+++ b/node_modules/next-i18next/dist/commonjs/serverSideTranslations.js
@@ -79,9 +79,7 @@ var serverSideTranslations = /*#__PURE__*/function () {
               break;
             }
             _context.next = 9;
-            return Promise.resolve("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH))).then(function (s) {
-              return _interopRequireWildcard(require(s));
-            });
+              return require('../../../../next-i18next.config.js');
           case 9:
             userConfig = _context.sent;
           case 10:

The Line in next-i18next that Breaks Webpack

https://github.com/i18next/next-i18next/blob/6a692ae688d4cb5c95bc8ff0a378f12a6a2fe61c/src/serverSideTranslations.ts#L37

How Next.js Imports the next.config.js at Runtime

This works for next when next is not bundled. But this fails just the same if used in next-i18next as it still uses an expression for the import.

https://github.com/vercel/next.js/blob/70c087e4cf6188d5290b1fe32975b22e17b03b69/packages/next/src/server/config.ts#L922

👉 Why No One Has Confirmed a Fix for This 👈

Locally patching node_modules/next-i18next/dist/commonjs/serverSideTranslations.js and re-running next build will show no change, driving you mad.

The problem is that the node_modules are only compiled once and cached in .next/cache.

If you make a change to a file in node_modules you have to rm -rf .next/cache before you build again, then you will see that the resulting chunk file has your changes.

next-i18next Require of next-i18next.config.js is an Expression

            return Promise.resolve("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH))).then(function (s) {
              return require("".concat(_path["default"].resolve(DEFAULT_CONFIG_PATH));
            });
huntharo commented 1 year ago

I added the example code for the hand-crufted fix to the comment above. I'm not sure about how to make the permanent / shippable fix here, but hopefully others can validate that the solution works for them and/or have some ideas on how to make this shippable.

It actually may be as simple as marking the file external when bundling the server, which kinda stinks because every consumer would need this fix (or we could put the fix into next.js as they have many similar fixes in handleExternals already):

const path = require('path');

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  webpack: (config, options) => {
    const { dev, isServer } = options;
    if (isServer && config.name === 'server' && !dev) {
      // Make some packages external
      config.externals.push({
        path.resolve('./next-i18next.config.js'): `commonjs ${path.resolve('./next-i18next.config.js')}`,
      });
    }

    return config;
  },
};

I have not tried the externals approach yet; it may have the same issue where the path changes at runtime and causes a mismatch.

VadimChorrny commented 7 months ago

I got the same error and for me the problem was that I use the serializeConfig: false, option. Once I added the configuration manually to appWithTranslation like this appWithTranslation(MyApp, nextI18NextConfig) it worked. I realize it is not the same config as the reporter's one, but it may help someone.

yep, this way helped me 🔥