next-translate icon indicating copy to clipboard operation
next-translate copied to clipboard

getT on API route on Vercel (serverless)

Open Fivedark opened this issue 4 years ago • 11 comments

First: Thanks for this great lib!

I'm using next-translate @ 1.0.1 and next @ 10.0.5, and I have trouble to use getT on my API route.

The following error occurred in production on Vercel, when calling the API: ERROR TypeError: Cannot read property 'loadLocaleFrom' of undefined

The API is called serverless on Vercel which mean the NodeJS global didn't contain the i18nConfig, which leads to this error message.

I tried to set the config to global inside the API route:

import getT from 'next-translate/getT'

import i18n from '../../i18n.json'

const handler = async (
  req,
  res,
) => {
  global.i18nConfig = i18n
  const t = await getT(req.query.__nextLocale, 'api')

  const error = t('error')

  res.statusCode = 200
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ error }))
}

export default handler

The error disappears, but I get only the translation key instead of the translated text.

You can reproduce this on your local machine: Build and start Next and call the API directly without any page load, because the page load will add the config to global.

Any ideas how to resolve this?

Fivedark avatar Jan 31 '21 15:01 Fivedark

Can confirm that this bug exists, it virtually makes next-translate impossible to use on an API.

Here's a stacktrace I'm getting in Vercel (just trying to be helpful):

2021-02-07T09:11:37.129Z	6aba0774-c6e7-487b-9f6c-3d20f2172049	ERROR	TypeError: Cannot read property 'loadLocaleFrom' of undefined
    at /var/task/node_modules/next-translate/lib/cjs/getT.js:60:37
    at step (/var/task/node_modules/next-translate/lib/cjs/getT.js:33:23)
    at Object.next (/var/task/node_modules/next-translate/lib/cjs/getT.js:14:53)
    at /var/task/node_modules/next-translate/lib/cjs/getT.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/var/task/node_modules/next-translate/lib/cjs/getT.js:4:12)
    at getT (/var/task/node_modules/next-translate/lib/cjs/getT.js:48:12)
    at handler (/var/task/.next/serverless/pages/api/tools.js:345:77)
    at apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:8:7)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
2021-02-07T09:11:37.130Z	6aba0774-c6e7-487b-9f6c-3d20f2172049	ERROR	TypeError: Cannot read property 'loadLocaleFrom' of undefined
    at /var/task/node_modules/next-translate/lib/cjs/getT.js:60:37
    at step (/var/task/node_modules/next-translate/lib/cjs/getT.js:33:23)
    at Object.next (/var/task/node_modules/next-translate/lib/cjs/getT.js:14:53)
    at /var/task/node_modules/next-translate/lib/cjs/getT.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/var/task/node_modules/next-translate/lib/cjs/getT.js:4:12)
    at getT (/var/task/node_modules/next-translate/lib/cjs/getT.js:48:12)
    at handler (/var/task/.next/serverless/pages/api/tools.js:345:77)
    at apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:8:7)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
2021-02-07T09:11:37.130Z	6aba0774-c6e7-487b-9f6c-3d20f2172049	ERROR	Unhandled Promise Rejection 	{"errorType":"Runtime.UnhandledPromiseRejection"
,"errorMessage":"TypeError: Cannot read property 'loadLocaleFrom' of undefined"
,"reason":{"errorType":"TypeError"
,"errorMessage":"Cannot read property 'loadLocaleFrom' of undefined"
,"stack":["TypeError: Cannot read property 'loadLocaleFrom' of undefined"
,"    at /var/task/node_modules/next-translate/lib/cjs/getT.js:60:37"
,"    at step (/var/task/node_modules/next-translate/lib/cjs/getT.js:33:23)"
,"    at Object.next (/var/task/node_modules/next-translate/lib/cjs/getT.js:14:53)"
,"    at /var/task/node_modules/next-translate/lib/cjs/getT.js:8:71"
,"    at new Promise (<anonymous>)"
,"    at __awaiter (/var/task/node_modules/next-translate/lib/cjs/getT.js:4:12)"
,"    at getT (/var/task/node_modules/next-translate/lib/cjs/getT.js:48:12)"
,"    at handler (/var/task/.next/serverless/pages/api/tools.js:345:77)"
,"    at apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:8:7)"
,"    at processTicksAndRejections (internal/process/task_queues.js:97:5)"]}
,"promise":{}
,"stack":["Runtime.UnhandledPromiseRejection: TypeError: Cannot read property 'loadLocaleFrom' of undefined"
,"    at process.<anonymous> (/var/runtime/index.js:35:15)"
,"    at process.emit (events.js:326:22)"
,"    at processPromiseRejections (internal/process/promises.js:209:33)"
,"    at processTicksAndRejections (internal/process/task_queues.js:98:32)"]}

joeriharleman avatar Feb 07 '21 09:02 joeriharleman

Thanks to @aralroca and @Fivedark :)

I was having the exact same issue and after applying the solution above from @Fivedark, everything is working as expected now. For the locale, I'm passing the value to my api function. Here's my code in case it could help anyone else:

// I18N
import getT from 'next-translate/getT'
import i18n from '../i18n'

const SENDGRID_API = 'https://api.sendgrid.com/v3/mail/send'

const sendEmail = async ({ name, email, message, locale }) => {
  // I18N
  global.i18nConfig = i18n
  const t = await getT(locale, 'contact')

  /* Send to client */
  await fetch(SENDGRID_API, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,
        'Accept-Language': `${locale}`
      },
      body: JSON.stringify({
        personalizations: [
          {
            to: [
              {
                email: email
              }
            ],
            subject: t('EmailSubject')
          }
        ],
        from: {
          email: '[email protected]',
          name: 'Guy Dumais'
        },
        content: [
          {
            type: 'text/html',
            value: `${t('Thank You')} <b><a href='mailto:${email}'>${name}</a></b>,<br/><br/>${t('EmailConfirmation')}<br/><br/>Message:<br/>${message}`
          }
        ]
      })
  })
    
}

export default sendEmail

guydumais avatar Apr 12 '21 02:04 guydumais

any idea how make it work guys ? i wont re edit my all backend work to front end :S

kotsh23 avatar May 29 '21 17:05 kotsh23

It is also happening on localhost, not only Vercel. It happens when the first call made on the server is an api call.

I mean that, when I start the server, if I load a page in the browser, the subsequent api calls work fine, and global.i18nConfig is correctly loaded. But, if the first call made to the server is an API call, then global.i18nConfig is undefined and I have this error Cannot read property 'loadLocaleFrom' of undefined.

slevy85 avatar Jul 08 '21 09:07 slevy85

A simple workaround would be to load it manually:

import i18n from '../i18n'

global.i18nConfig = i18n

Before solving it, I would like to rethink the way of consuming the configuration...

aralroca avatar Jul 08 '21 11:07 aralroca

@aralroca with the workaround I only get the keys and not the values returned.

And can the workaround be added once in the next.js project or do we have to add this on every api route?

goellner avatar Jul 16 '21 08:07 goellner

@goellner No, the workaround will need to be added in every api route where you use getT.

The following patch works for me until this issue is resolved:

// monkey-patches.ts
import getT from "next-translate/getT";
import i18n from "../i18n";

export const ensureAvailabilityOfGetT = () => {
  global.i18nConfig = i18n;
  return getT;
};

and then:

// some-api-route.ts

import { ensureAvailabilityOfGetT } from "../../../utils/api/utils/monkey-patches";

const parsedLocale = "..."; 

const getT = ensureAvailabilityOfGetT();
const errorsT = await getT(parsedLocale, "errors");
const feedbackT = await getT(parsedLocale, "feedback");

// do your thing

Once getT from next-translate works properly, refactoring will be a piece of cake.

jzzfs avatar Jul 29 '21 18:07 jzzfs

@goellner I had the same problem than you, only keys where return and not the values, it was because my i18n.js file didn't contain loadLocaleFrom which is needed by getT, I had to add it.

At first I had only locales, defaultLocale and pages, when I added loadLocaleFrom it worked :

{
  locales: [ 'en' ],
  defaultLocale: 'en',
  pages: { '*': [ 'common' ] },
  loadLocaleFrom: (lang, ns) =>
    import(`./locales/${lang}/${ns}.json`).then((m) => m.default),
}

slevy85 avatar Aug 16 '21 15:08 slevy85

It's a shame that this bug/feature isn't implemented in a timely manner. Can you think about it again?

marc-on-github avatar Jan 11 '22 10:01 marc-on-github

@Marc-Hoch the project is open-source, feel free to implement and do a PR

aralroca avatar Jan 17 '22 09:01 aralroca

@aralroca

This is what I ended up doing:

import getT_ from "next-translate/getT";

import i18n from "path/to/i18n_config";

export const getT = (locale?: string, namespace?: string | string[]) => {
  global.i18nConfig = i18n;
  
  
  // This part in particular fixes a problem that happens only when
  // I start the next server, and before any other navigation, I send a request to an API route
  // that is supposed to return some localised text.
  if (global?.__NEXT_TRANSLATE__?.config) {
    if (!global.__NEXT_TRANSLATE__.config.loadLocaleFrom) {
      global.__NEXT_TRANSLATE__.config.loadLocaleFrom = i18n.loadLocaleFrom;
    }
  }

  return getT_(locale, namespace);
};

brunoscopelliti avatar Oct 22 '23 18:10 brunoscopelliti