Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental.localeDetector and useTranslation fails to load i18n config on initial request #2721

Closed
fakedob opened this issue Jan 21, 2024 · 20 comments

Comments

@fakedob
Copy link

fakedob commented Jan 21, 2024

Environment

Reproduction

Wont be able to provide reproduction, as in reality, it will require access to the host in serverless environment

Describe the bug

I've setup SSR translations with

i18n: {
    experimental: {
      localeDetector: './localeDetector.ts'
    }

In my event handler, I call useTranslation to access the t function, as follows:

export default defineEventHandler(async (ctx, next) => {
  const t = await useTranslation(ctx);
})

All this is working as expected when run in development. In our cloud, we use serverless framework, which scales our application. There is a "cold start" procedure, which runs when the app was either not used for some time, or needs to start a new instance. That is the reason why I am able to hit the issue, as on other build types (or dev), the module loads, prior making the first request directly into the handler, that uses the t function.

If, when the app was called directly on the event handler that uses the useTranslations, the nuxtConfig for i18n is not yet available in the context, thus, resulting in "middleware not initialized, please setup onRequest and onAfterResponse options of createApp with the middleware obtained with defineI18nMiddleware". It also worth mentioning, that there is a period of 1-3 seconds between the initial app load and when the config is available in the context. My best bet is, that the nuxt/i18n module is preparing the config in async, before appending it to the context of the request.

So far I am out of clue, why is this happening. Seems like the event handler gets called, prior the i18n was properly loaded into context.

Additional context

No response

Logs

No response

Copy link

github-actions bot commented Feb 6, 2024

Would you be able to provide a reproduction? 🙏

More info

Why do I need to provide a reproduction?

Reproductions make it possible for us to triage and fix issues quickly with a relatively small team. It helps us discover the source of the problem, and also can reveal assumptions you or we might be making.

What will happen?

If you've provided a reproduction, we'll remove the label and try to reproduce the issue. If we can, we'll mark it as a bug and prioritise it based on its severity and how many people we think it might affect.

If needs reproduction labeled issues don't receive any substantial activity (e.g., new comments featuring a reproduction link), we'll close them. That's not because we don't care! At any point, feel free to comment with a reproduction and we'll reopen it.

How can I create a reproduction?

We have a couple of templates for starting with a minimal reproduction:

👉 Reproduction starter (v8 and higher)
👉 Reproduction starter (edge)

A public GitHub repository is also perfect. 👌

Please ensure that the reproduction is as minimal as possible. See more details in our guide.

You might also find these other articles interesting and/or helpful:

@plushdohn
Copy link

Running into this on a Vercel deployment. First request fails, following requests go through. It's a private repo so sorry for not being able to offer a reproduction yet, I'll see if I can come up with one.

@BobbieGoede
Copy link
Collaborator

Closing this due to inactivity and lack of reproduction, if you're still experiencing this issue please open a new issue with a (minimal) reproduction.

I understand that it's not possible to share private code, or that it's difficult to replicate due to the environment, but for us to be able to debug this issue we would have to put in the time and effort to figure out how to replicate your issue by ourselves.

@BobbieGoede BobbieGoede closed this as not planned Won't fix, can't repro, duplicate, stale Feb 20, 2024
@KyleXie
Copy link

KyleXie commented Mar 11, 2024

Hey, I'm having the same issue here. My app runs on a serverless environment, and this issue happens a lot on "cold start". It works fine on the coming requests when the instance warms up. I'm using @nuxtjs/[email protected].

@AntonGrafton
Copy link

Same here

@bart
Copy link

bart commented Jul 9, 2024

Unfortunately same issue here, also in dev mode

@fakedob
Copy link
Author

fakedob commented Jul 9, 2024

Unfortunately same issue here, also in dev mode

Hey @bart, if it happens in Dev, can you share a small reproduction? Here we all experience this issue only on prod, which makes it impossible to give feedback on the real cause. If this can be tested in dev, then we can see where the error comes from ✌️

@bart
Copy link

bart commented Jul 9, 2024

Sure @fakedob I'm also diving into it already. useTranslation() is checking for event.context.i18n and if this is null it is throwing the error. Here is a reproduction link: https://stackblitz.com/~/github.com/bart/i18n-repro

@bart
Copy link

bart commented Jul 9, 2024

@fakedob Did you have the chance to look into it already?

@bart
Copy link

bart commented Jul 9, 2024

All right!! After digging deep into the module I figured out what the reason for this issue was. Module checks for enableServerIntegration to load server-side middleware that registers i18n there. Problem: When configuring i18n inside of the modules key it doesn't work because experimental gets loaded as undefined. When using the i18n key instead for configuration, it works. Maybe that's also interesting for others who are facing the same issue.

// nuxt.confg.js - not working
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: [["@nuxtjs/i18n", {
    experimental: {
      localeDetector: "./localeDetector.ts",
    },
    defaultLocale: "en",
    locales: [
      {
        code: "en",
        name: "English",
        file: "en.yaml",
      },
    ],
    langDir: "locales",
  }]]
})

// nuxt.config.js - working
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: ["@nuxtjs/i18n"],
  i18n: {
    experimental: {
      localeDetector: "./localeDetector.ts",
    },
    defaultLocale: "en",
    locales: [
      {
        code: "en",
        name: "English",
        file: "en.yaml",
      },
    ],
    langDir: "locales",
  }
})

@BobbieGoede
Copy link
Collaborator

@bart
Thanks for investigating into the source of the issue! This should get resolved by #3020.

@fakedob
Copy link
Author

fakedob commented Jul 23, 2024

All right!! After digging deep into the module I figured out what the reason for this issue was. Module checks for enableServerIntegration to load server-side middleware that registers i18n there. Problem: When configuring i18n inside of the modules key it doesn't work because experimental gets loaded as undefined. When using the i18n key instead for configuration, it works. Maybe that's also interesting for others who are facing the same issue.

// nuxt.confg.js - not working
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: [["@nuxtjs/i18n", {
    experimental: {
      localeDetector: "./localeDetector.ts",
    },
    defaultLocale: "en",
    locales: [
      {
        code: "en",
        name: "English",
        file: "en.yaml",
      },
    ],
    langDir: "locales",
  }]]
})

// nuxt.config.js - working
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: ["@nuxtjs/i18n"],
  i18n: {
    experimental: {
      localeDetector: "./localeDetector.ts",
    },
    defaultLocale: "en",
    locales: [
      {
        code: "en",
        name: "English",
        file: "en.yaml",
      },
    ],
    langDir: "locales",
  }
})

Hey @bart , thank you for your contribution and sorry for my late reply, I was on holiday. Unfortunately, for me it doesnt solve the issue and the key in my nuxt.config is correct. As far as I can understand, the i18n config gets set in an async way, that doesnt wait for the configuration to be setup, prior executing the first response. Based on my experiments, in few ms after the first call, its already set and every next works normally. On my end, this cannot be replicated in normal dev or host environment, it must be serverless host, that has a cold start, which means, the app must be build specifically for this type of hosting and executed in a cold start, to be able to reproduce it. Without knowing more of the internals of how this module works, I cannot say more at this point, I did not had any chance to look deeper into the code, but I am sure it is arround the setup of the i18n initial config being done in async without being awaited.

@nstdspace
Copy link

I just tried to setup server side translations following the documentation on version 8.4.0 - the same error appears:

middleware not initialized, please setup onRequest and onAfterResponse options of createApp with the middleware obtained with defineI18nMiddleware and I have the config as @bart suggests.

Why is this issue closed?

@BobbieGoede
Copy link
Collaborator

@nstdspace
Can you open a new issue with a minimal reproduction? 🙏

@fakedob
Copy link
Author

fakedob commented Aug 19, 2024

@nstdspace

Can you open a new issue with a minimal reproduction? 🙏

Hey @BobbieGoede as in my initial post, I tried to explain the problem does not come from client code. It can only be reproduced in environment that can do cold start as the serveless framework does. If you want, i think i can make you a serverless npm project for you to run and test, but for the i18n module, its basically the initial config as in documentation is enough to reproduce.

My best guess is that the config in the nuxt.config gets loaded async and thats why the initial request fails, as the condig has not been set up.

You cannot reproduce this in dev environment, since you run a dev server and when you hit the api, the config has already been loaded.

In serverless, your server is dead until someone request something from it and the process of the server is being handled differently. Thats why we experience the problem only when deployed on such infrastructure.

@BobbieGoede
Copy link
Collaborator

@fakedob
If you can create a minimal reproduction with instructions on how to run it in a serverless environment (because I haven't done this before), or are able to reproduce the issue by replicating the cold start somehow, that would allow me to investigate the issue further.

From what I can tell the Nuxt config is not loaded asynchronously so I wonder what else it could be 🤔

@phillipmohr
Copy link

phillipmohr commented Nov 13, 2024

I have the same issue in a Vercel environment. I have a webhook which makes an API call and sometimes the API is being called when the "server is cold" and then it fails. When it gets triggered again shortly afterwards, it works. Unfortunately I'm also not sure how I can provide a reproduction but I'm just leaving my Vercel logs here so maybe they help someone. There seems to be an issue in the resolveLocale function but I'm not sure what exactly is going on.

The localeDetector successfully returned the defaultLocale, so this is working.

Using "@nuxtjs/i18n": "^8.3.1"

Webhook --> calls API --> calls function (handleLeadCreation) --> uses translation function --> fails at resolveLocale

SyntaxError: 22
    at createCompileError (file:///var/task/node_modules/@intlify/message-compiler/dist/message-compiler.node.mjs:95:19)
    ... 8 lines matching cause stack trace ...
    at async file:///var/task/chunks/runtime.mjs:3195:19 {
  cause: SyntaxError: 22
      at createCompileError (file:///var/task/node_modules/@intlify/message-compiler/dist/message-compiler.node.mjs:95:19)
      at createCoreError (file:///var/task/node_modules/@intlify/core-base/dist/core-base.mjs:485:12)
      at resolveLocale (file:///var/task/node_modules/@intlify/core-base/dist/core-base.mjs:524:23)
      at getLocale (file:///var/task/node_modules/@intlify/core-base/dist/core-base.mjs:503:11)
      at translate (file:///var/task/node_modules/@intlify/core-base/dist/core-base.mjs:1116:20)
      at translate$1 (file:///var/task/node_modules/@intlify/h3/dist/index.mjs:41:28)
      at handleLeadCreation (file:///var/task/chunks/routes/api/facebook/index.post.mjs:205:23)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async Object.handler (file:///var/task/chunks/routes/api/facebook/index.post.mjs:340:17)
      at async file:///var/task/chunks/runtime.mjs:3195:19 {
    code: 22,
    domain: undefined
  },
  statusCode: 500,
  fatal: false,
  unhandled: false,
  statusMessage: undefined,
  data: undefined
}

My code

async function handleLeadCreation(payload: WebhookPayload<'leadgen'>, event: H3Event<EventHandlerRequest>) {
    try {
        const supabase = serverSupabaseServiceRole<Database>(event)
        const { sendMail } = useNodeMailer()
        const t = await useTranslation(event)
        // fails here
        const subject = t('email.new_lead.subject', { campaign_name: supabaseLead?.fb_campaign_name })

resolveLocale function from the module

function resolveLocale(locale) {
    if (isString(locale)) {
        return locale;
    }
    else {
        if (isFunction(locale)) {
            if (locale.resolvedOnce && _resolveLocale != null) {
                return _resolveLocale;
            }
            else if (locale.constructor.name === 'Function') {
                const resolve = locale();
                if (isPromise(resolve)) {
                    throw createCoreError(CoreErrorCodes.NOT_SUPPORT_LOCALE_PROMISE_VALUE);
                }
                return (_resolveLocale = resolve);
            }
            else {
                throw createCoreError(CoreErrorCodes.NOT_SUPPORT_LOCALE_ASYNC_FUNCTION);
            }
        }
        else {
            throw createCoreError(CoreErrorCodes.NOT_SUPPORT_LOCALE_TYPE);
        }
    }
}

@fakedob
Copy link
Author

fakedob commented Nov 13, 2024

I was able to dig into the problem a little bit more and it seems like the issue is not coming from this plugin, even though maybe there is a way around. I was able to make a simple serverless app that, upon build, should be able to reproduce the cold start, but once I did it, I figured out that the error comes from '@intlify/h3' itself, which is a module used by this lib.

So basically, to reproduce the error, you can simply make a server function like this

import { useTranslation } from '@intlify/h3' export default defineEventHandler(async (event) => { const t = await useTranslation(event); return t('welcome'); });

And you will receive the same output as what is happening in this issue. Further more, in the build package, I traced this function to

nuxt.hook("nitro:config", async (nitroConfig) => { which is a hook called once nuxt prepares its config and then exports the function 'useTranslation' from '@intlify/h3'.

It seems like there is already an open issue - intlify/h3#31 which may be tracked, or you guys can give it a push, as it seems no one is looking after it.

Not sure what else we can do, unless someone who has more knowledge of the internal work of this module tell me a way around.

@phillipmohr
Copy link

@fakedob Thanks for looking further into it! I've left a comment in the issue you've mentioned and hopefully it'll get more attention. In the meantime I'll just don't use the i18n module on the server side :/

I've also heard of an approach to keep the Vercel server alive but didn't have the time to test it out myself. This might fix it:
https://www.youtube.com/watch?v=lj5uiq8HNwk&t=532s&ab_channel=AllAboutPayload

@whoisarjen
Copy link

Exactly same issue for me :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants