i18next / next-i18next

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

Support for Serverless #274

Closed Nelrohd closed 4 years ago

Nelrohd commented 5 years ago

Is your feature request related to a problem? Please describe.

Currently next-i18next does not support serverless. It appears that the i18n-middleware create some troubles when used on lambda #271

Describe the solution you'd like

Not sure exactly what would be the final solution but:

Describe alternatives you've considered

None.

Additional context

Open to suggestion for the work/final solution to be done/achieved.

isaachinman commented 5 years ago

A few things to consider - just my initial thoughts:

I am open to any/all suggestions on this topic, but in general regard it as a major refactor of the project from the ground up. If we can remove our dependency on Express/i18next-express-middleware, I think the package would become a lot more versatile.

However, I am not (currently) doing any work whatsoever with serverless NextJs, and have very little input to add here.

beheh commented 5 years ago

I've gotten next-i18next working in a serverless environment for one of our sites we're building right now (no full source available unfortunately), but it's pretty hacky because we're basically stitching together various parts from next-i18next in a more vanilla i18next setup.

Our approach is that we basically ship the JSON files once to a CDN (with an assetPrefix) and once inside the Lambda "executables". We reference them via import which makes them real Webpack chunks for the CDN and also allows it to pick them up and include them when packaging the serverless file and we're also running express infront of the bundles so the middleware can just be dropped. I can't really recommend this approach, but it can definitely work.

isaachinman commented 5 years ago

@beheh Sounds like you had some fun. Are there any lessons or approaches you'd like to bring back to next-i18next? Being able to support serverless is the eventual goal.

yoeran commented 5 years ago

Stumbled upon this issue because I'm researching for a new NextJS project that should have i18n and is planned to be deployed to Now 2.0. I've not used next-i18next yet, nor am I a serverless/now expert, but if someone has some smart ideas I'm willing to help with the implementation.

isaachinman commented 5 years ago

@yoeran I think the right place to start is rewriting/adapting i18next-express-middleware to use vanilla NodeJs HTTP interfaces instead of relying on Express.

flearsnetwork commented 5 years ago

Would also appreciate it if someone has the knowledge to optimize the package for serverless solutions.

gurkerl83 commented 5 years ago

[RFC] serverless is still open (https://github.com/zeit/next.js/issues/7208). The last entry mentions that a routing middleware is still missing in the current version of Next. Tracking the progress there is a merge request with the title "Example for API routes with middleware" https://github.com/zeit/next.js/pull/7958/files . I do not know anything about the internal of the express middleware currently utilized by next-i18next, but maybe the example shown in the merge request is enough to start a migration.

isaachinman commented 5 years ago

@gurkerl83 That example appears to show how one could use middleware on an API route, not a UI route.

emirotin commented 5 years ago

My request sounds different from the original title but I assume it's related cause it's also about reusing the module outside of next execution context. Sorry if I'm wrong though.

We have a successfully working Next.js project using your next-i18next (thanks!).

Now I also want to create a stand-alone node.js script that would reuse the entire infrastructure we already have and do some interpolations (in the static HTML files if you're curious).

For some reason (I have my brain kinda broken after hours of trying) it doesn't work.

Here's what I'm doing:

const nextConfig = require('next/config');
nextConfig.setConfig(require('./next.config'));

const { i18n } = require('./i18n');

And then trying to use i18n.t directly. I see that it's getting all the proper configs but does not load the data from the JSON files.

And thus an attempt to translate any key returns the key itself.

I'm trying to figure out what else next is doing behind the scene that makes your module fetch the needed JSON data and that I'm missing.


For ref, ./i18n is pretty standard:

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

module.exports = new NextI18Next({
    localePath: 'static-src/locales',
    defaultLanguage: i18nHelper.getDefaultLanguage(),
    otherLanguages: i18nHelper.getOtherLanguages(),
    fallbackLng: i18nHelper.getDefaultLanguage(),
    defaultNS: i18nHelper.getDefaultNamespace(),
    fallbackNS: i18nHelper.getAllNamespaces(),
    ns: i18nHelper.getAllNamespaces(),
    localeSubpaths: 'foreign'
});

And I can confirm that all the i18nHelper methods are returning what expected.

isaachinman commented 5 years ago

@emirotin That's not something we're going to support. Unfortunately you'll need to go it alone. I would suggest simply initialising a normal i18next instance. Note that you can pass your identical next-i18next config object into the i18next.init function.

Let's keep this discussion on topic.

emirotin commented 5 years ago

Sorry for offtopic then. I truly assumed it's about the same root. Just curious what's wrong with my current setup that makes it unusable?

P.S. ~barebones i18next doesn't work for me (yet) too~ normal i18next works!

cryptiklemur commented 5 years ago

It seems possible to spoof this with the following in a custom _app.js

import {handle} from 'i18next-express-middleware';
import {i18n, config} from '../i18n';

export default extends App {
    // ...    
    static async getInitialProps(context) {
        if (context.ctx.req && context.ctx.res) {
            res.set = res.setHeader;
            req.path = context.ctx.asPath;

            const i18nHandler = handle(i18n, {ignoreRoutes: config.ignoreRoutes});
            await new Promise((resolve, reject) => {
                try {
                    i18nHandler(req as any, res as any, resolve);
                } catch (e) {
                    reject(e);
                }
            });
        }
    }
    // ...    
}

Hopefully ZEIT follows through with adding middleware capabilities, and we can easily add this that way

cryptiklemur commented 5 years ago

If this is something you are cool with (and actually works? I'm still testing this), I can throw up a PR to introduce a helper function (plus some documentation)

isaachinman commented 5 years ago

@aequasi Not sure that'd work. The req and res objects that come off Next's context are vanilla HTTP interfaces, not Express req and res interfaces. What have you seen in your testing?

Because there are so many diverse use cases for this package, I'd rather we proceed in the least hacky way possible. We do have support from the Next team and they're aware of the request for serverless middleware.

cryptiklemur commented 5 years ago

The two express specific things (set and path) can easily be "mocked"

set actually needs to be:

req.set = (key, value) => {
    if (value !== undefined) {
        res.setHeader(key, value);
    }
}
cryptiklemur commented 5 years ago

From my testing, this works. I'm not having any issues with server-rendered content

isaachinman commented 5 years ago

To reiterate, I'd rather we proceed in the least hacky way possible. This means we need to:

  1. Wait for serverless middleware support within NextJs
  2. Fork i18next-express-middleware and refactor out the Express dep, and release a new package, eg i18next-node-middleware
  3. Refactor next-i18next accordingly

If people want to roll their own serverless solutions in the meantime, feel free.

samohovets commented 5 years ago

@aequasi nice temporary solution 🤔 But I guess locale subpaths is not working, right?

UPD: yes, server-side locale subpaths is not working (naturally), but it seems fine on the client. I got client-side translations working so far without particularly critical bugs, but it can't work on Zeit Now serverless 🤔

Monkey patching all incoming requests in _app.jsx is not a best way to get things working, so as @isaachinman mentioned - we need to wait for serverless middlware support in Next.js 😞

image

cryptiklemur commented 5 years ago

Zeit compiles everything that it can find from static analysis into a single file. If i had to make an educated guess, the locales are not making it to the deployment, causing the fs.readdirSync to fail. You could look into: https://zeit.co/docs/v2/advanced/builders/overview#including-additional-files

gurkerl83 commented 5 years ago

@aequasi I experienced the same error when a deployment with now get executed. With your last comment, do you mean specifying i18n-resource files within the includeFiles section of now deployment configuration (now.json) will resolve the fs.readdirSync error?

samohovets commented 5 years ago

@gurkerl83 nope, just tested it with both static and custom folder with custom localePath. Tried to use include in now.json, but no luck.

cryptiklemur commented 5 years ago

Regardless of whether or not you want to get this done in a hacky way, doing this research ahead of time makes refactoring take less time, as you know what will have to change.

  1. I still don't think waiting for middleware is necessary, but thats a personal opinion. If 2 is done ahead of time, its easy enough to implement this step once its available, if the work is already done.
  2. You could very easily roll your own version of that into this repository
  3. Did a little more digging into this, with my comments above.

includeFiles added the locales to the deployed code, but you need to go a step further.

On now deployments, process.cwd() isnt the right directory to pull from, and the now team has said to use __dirname instead. Setting localePath to path.join(__dirname, '/static/locales') (or whatever your path is), seems to fix that initial fs.readdirSync issue. This could also be mitigated by forcing the developer to define all namespaces by hand, instead of grabbing it automatically.

After i got past that error, i got stuck on another one for a bit that was a little harder to debug, due to the evals in the repo

Unable to import module 'now__launcher': Error
    at Function.Module._resolveFilename (module.js:547:15)
    at Function.Module._load (module.js:474:25)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at eval (eval at _default (/var/task/page.js:52306:32), <anonymous>:1:1)
    at _default (/var/task/page.js:52306:32)
    at new NextI18Next (/var/task/page.js:201598:51)
    at Object.<anonymous> (/var/task/page.js:56634:27)
    at Object.KbPy (/var/task/page.js:56793:30)
    at __webpack_require__ (/var/task/page.js:23:31)

The eval thats at the line above is:

var _default = function _default(config) {
  if (!_i18next["default"].isInitialized) {
    if (_detectNode["default"]) {
+      var i18nextNodeBackend = eval("require('i18next-node-fs-backend')");
      var i18nextMiddleware = eval("require('i18next-express-middleware')");

      _i18next["default"].use(i18nextNodeBackend);

      if (config.serverLanguageDetection) {
        var serverDetectors = new i18nextMiddleware.LanguageDetector();
        config.customDetectors.forEach(function (detector) {
          return serverDetectors.addDetector(detector);
        });

        _i18next["default"].use(serverDetectors);
      }
// ...

Something about now doesn't like the eval("require('i18next-node-fs-backend')");.

gurkerl83 commented 5 years ago

@aequasi Do you have a fork of next-18next to share respectively an example which demonstrates/summarises the changes you described.

About your last comment, maybe deploying next in serverless mode does not understand the result of eval statements in combination with loading resources through require. Are those statements replaceable through dynamic import statements?

It will be awesome if this library is useable in a serverless deployment mode without the requirement to have a middleware provided by next in the short term. Regular observations of both middlewares (next serverless - for several months in the RFC state) and express i18next-express-middleware (migration to support pure Http instead of express) have currently zero attention.

cryptiklemur commented 5 years ago

I dont. Was able to get there with this:

import * as common from '@static/locales/en/common.json'; // Wherever you have your common locale

export default new NextI18Next({
    browserLanguageDetection: false,
    serverLanguageDetection:  false,
    partialBundledLanguages:  false,
    defaultLanguage:          'en',
    ns:                       ['common'],
    defaultNS:                'common',
    otherLanguages:           ['en'],
    resources:                {
        en: {common},
    },
    localeSubpaths:           'none',
    localePath:               path.join(__dirname, 'static', 'locales'), // Wherever you have your common locale
});
isaachinman commented 5 years ago

For anyone wanting to help: @jamuhl has let us know in https://github.com/i18next/i18next-express-middleware/issues/186 that it should be fairly easy to refactor out the dependency on Express.

This would be a great first step, and will benefit the greater i18next community, not just next-i18next.

isaachinman commented 5 years ago

I've taken a look myself and submitted a PR to remove Express as a dependency from i18next-express-middleware here: https://github.com/i18next/i18next-express-middleware/pull/191.

Reviews, manual QA, and feedback would be much appreciated.

It's kind of strange to retain "middleware" in a vanilla NodeJs environment, but I do think it's the best way forward.

Once this is ready to merge, I think we'll need to deploy an entirely new repo called i18next-middleware (or similar).

From there, the next piece of work will be refactoring the Express dependency out of next-i18next itself. At that point, serverless as well as use with Koa/Hapi should be possible with a little bit of added work from users.

rdewolff commented 5 years ago

Am struggling with this issue and wondering when this will get fixed. What's the workaround ?

isaachinman commented 5 years ago

@rdewolff The problem, approach, and work necessary are all quite well-documented in this issue and the related ones in i18next repos. You can feel free to contribute!

gurkerl83 commented 5 years ago

Hi, just a short update on that issue. The work necessary to fix that issue is

  1. Port the dependency i18next-express-middleware from express to vanilla Http (Node). I have a local branch on which I have worked on for some time.
  2. Small portions of next-18next have to be adjusted, including the file next-i18next-middleware.ts and some others. We can keep the example next application, which is built on top of express or provide a new one. On a local branch of next-18next, those adjustments, including the example, are made. The example supports NextJs in serverless deployment mode, especially for production build with now V2.
  3. Middleware support from Next. I have adjusted the example of next-18next by including a _document file which accesses the document middleware of NextJs which they already provide. The next-18next middleware gets consumed there.

Further experiments have to be conducted to identify pathways around some limitations currently introduced. Some topics have to be discussed, such as the express session handling, integrated into the i18next-express-middleware library we have to loose in a vanilla Http based setup.

What is next? Some investigation in open issues, code cleanup. I will push the changes I have made to i18next-express-middleware and next-18next to different repositories. The naming of repositories was changed because of publishings to Npm required for testing. As soon those things are up, I will prepare those merge requests.

With those adjustments I was able to deploy next-18next example with now V2 in serverless mode (Production build). Some prove...

https://millipede-docs-simple-29i7s3up8.now.sh/

Thx,

rdewolff commented 5 years ago

@rdewolff The problem, approach, and work necessary are all quite well-documented in this issue and the related ones in i18next repos. You can feel free to contribute!

I have tried the solutions provided here and I could not make it work. Tried applying the the i18n config, localeSubpaths seems complaining for not having an object but a string. When commenting that out, I get one step further, but the following error occurs :

/[...]/web/node_modules/next-i18next/dist/commonjs/config/create-config.js:54
        throw new Error("Default namespace not found at ".concat(defaultNSPath));
              ^
Error: Default namespace not found at /[...]/dev/static/locales/en/common.json
    at _default (/[...]/node_modules/next-i18next/dist/commonjs/config/create-config.js:54:15)
    at new NextI18Next (/[...]/node_modules/next-i18next/dist/commonjs/index.js:52:46)
    at Object.<anonymous> (/[...]/i18n.ts:5:14)
    at Module._compile (internal/modules/cjs/loader.js:776:30)
    at Module.m._compile (/[...]/node_modules/ts-node/src/index.ts:473:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:787:10)
    at Object.require.extensions.(anonymous function) [as .ts] ([...]/node_modules/ts-node/src/index.ts:476:12)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)

Is there a fallback to run it without serverless? Am searching the next.js doc but cannot find the info. Even when commenting out target: "serverless" in next-config.js I get the same results.

Tried all the combination I could think of but 👎 - not working. Anyone could provide the source code / sandbox of the working version ? That'd be of great help! Thanks in advance!

michavie commented 5 years ago

Is there any update, another issue to follow status, or package alternative? Thank you!

isaachinman commented 5 years ago

@michaviehauser Still need to get the middleware bit done first. There is no alternative whatsoever, to my knowledge. Feel free to contribute.

martpie commented 5 years ago

👀 [RFC] Custom Routes https://github.com/zeit/next.js/issues/9081

sanishkr commented 5 years ago

Hi, just a short update on that issue. The work necessary to fix that issue is

  1. Port the dependency i18next-express-middleware from express to vanilla Http (Node). I have a local branch on which I have worked on for some time.
  2. Small portions of next-18next have to be adjusted, including the file next-i18next-middleware.ts and some others. We can keep the example next application, which is built on top of express or provide a new one. On a local branch of next-18next, those adjustments, including the example, are made. The example supports NextJs in serverless deployment mode, especially for production build with now V2.
  3. Middleware support from Next. I have adjusted the example of next-18next by including a _document file which accesses the document middleware of NextJs which they already provide. The next-18next middleware gets consumed there.

Further experiments have to be conducted to identify pathways around some limitations currently introduced. Some topics have to be discussed, such as the express session handling, integrated into the i18next-express-middleware library we have to loose in a vanilla Http based setup.

What is next? Some investigation in open issues, code cleanup. I will push the changes I have made to i18next-express-middleware and next-18next to different repositories. The naming of repositories was changed because of publishings to Npm required for testing. As soon those things are up, I will prepare those merge requests.

With those adjustments I was able to deploy next-18next example with now V2 in serverless mode (Production build). Some prove...

https://millipede-docs-simple-29i7s3up8.now.sh/

Thx,

@gurkerl83 Could you please share how you got it running on Now2.0? Sample repo link to your site would be great

BjoernRave commented 5 years ago

👀 [RFC] Custom Routes zeit/next.js#9081

is this middlewares,but with another name, or will it not be possible with this RFC ?

Vadorequest commented 5 years ago

I made Next.js (serverless mode v9.1.2) work with Locize and documented it there: https://stackoverflow.com/questions/55994799/how-to-integrate-next-i18next-nextjs-locize/58782594#58782594

isaachinman commented 5 years ago

Hey @Vadorequest, nice!

However, lots of people use next-i18next with translations that are loaded over network from another CDN/API, and you could also bundle your translations into your serverless deployment, etc.

It's certainly possible with next-i18next, we just need someone to take the lead!

Also, I think it would be great if you wrote about your experience in an article on Medium.

rdewolff commented 5 years ago

+1 for the article on Medium. Thought the same while reading your post on SO.

Vadorequest commented 5 years ago

Hear you, we've got a tech blog on medium at https://medium.com/unly-org/tech/home where you may find some interesting stuff already. But I've got very little time for a medium article right now.

I'm in the process of releasing a Next+Now boilerplate with Locize (and tons of other stuff) production-grade for very quick start, it's not yet ready, been working on it for 2w+. I'll drop the link here when it's released, if you're interested. :)

heikir commented 5 years ago

@gurkerl83 seems to have a working solution. Is there an ETA for the release of his solution and how can others help out to speed up the process?

BjoernRave commented 5 years ago

Also wondering if there is already work being done towards creating a next.js plugin of this to have a clean support for serverless?

isaachinman commented 5 years ago

@BjoernRave It's been a week or two since I checked on the plugins RFC but as far as I know, it's nowhere near ready for third party dev.

Did you have something in mind?

Indeed, a plugin is probably the future of this package, but that doesn't solve the i18next-express-middleware dependency issue.

BjoernRave commented 5 years ago

@isaachinman ah, so at first we need to port i18next-express-middleware to a version which is not depending on express to have a next.js plugin?

Yea, true I guess it's not yet ready for third-party devs, I just can't wait anymore, so I thought maybe someone is already playin around with what the next.js team is providing at this point. There is already a first version of it under an experimental flag

isaachinman commented 5 years ago

Yes, basically we either need:

  1. An i18next middleware which does not depend on Express
  2. A nextjs-express-middleware plugin that extends the NextJs middleware req, res, and a few other things

That being said, I know Tim did prioritise maintaining at least a little bit of parity with commonly-used Express features, so it might end up being pretty much drop-in.

@BjoernRave If you'd like to work on this together, please feel free to email directly.

BjoernRave commented 5 years ago

okay, yea not sure I am much of a help here.

BjoernRave commented 5 years ago

@Vadorequest but that means we need to use locize, right? why is it working with locize, but not without, since your are still using i18next?

BjoernRave commented 5 years ago

@gurkerl83 I would also be very interested if you got something, please share it with us :)

Vadorequest commented 5 years ago

@BjoernRave It's working with Locize and not without because of the backend implementation. I won't go too far into the technicals, but let's just say the Locize implementation was easier to work with. (yet, wasn't THAT easy either, hence the SO post)

But, definitely usable with any tool, the main "difficulty" at hand is to build a universal backend, because Next needs universal compatibility. (node + browser)

isaachinman commented 5 years ago

I would assume that the most desirable way to consume localisation data in serverless deployments is via a third-party CDN, where caching occurs both at the CDN and server levels.

jamuhl commented 5 years ago

@isaachinman @Vadorequest there is a big "drawback" with serverless -> the serverless function does not cache the loaded translations like a hosted express server can -> this means every time the serverless function is called to render a page all the namespaces get loaded from the CDN:

that's why we got that "FAT" warning here: https://github.com/locize/i18next-node-locize-backend#important-advice-for-serverless-environments---aws-lambda-google-cloud-functions-azure-functions-etc