i18n icon indicating copy to clipboard operation
i18n copied to clipboard

`switchLocalePath()` returns empty string on error 404

Open frederikheld opened this issue 1 year ago β€’ 17 comments

Environment


  • Operating System: Linux
  • Node Version: v20.11.0
  • Nuxt Version: 3.13.2
  • CLI Version: 3.13.2
  • Nitro Version: 2.9.7
  • Package Manager: [email protected]
  • Builder: -
  • User Config: compatibilityDate, devtools, ssr, build, nitro, modules, vite, i18n, eslint, security
  • Runtime Modules: (), @nuxtjs/[email protected], @nuxtjs/[email protected], [email protected]
  • Build Modules: -

Reproduction

<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

const { availableLocales } = useI18n()

const localeSelectorItems = computed(() => {
  return availableLocales.map((loc: string) => {
    return {
      locale: loc
    }
  })
})
</script>

<template>
  <NuxtLink
    v-for="item in localeSelectorItems"
    :key="item.locale"
    :to="switchLocalePath(item.locale)"
  >
    {{ item.locale }}
  </NuxtLink>
</template>

Describe the bug

According to nuxt docs, the error.vue page to generate a custom error page has to be located outside of the pages directory in the src directory side by side with App.vue.

If I use the switchLocalePath() function on this page, it will always return a empty string instead of the actual locale path.

I'm using the same piece of code in src/error.vue and in src/pages/index.vue, so I can say for sure that it should work.

Additional context

No response

Logs

No response

frederikheld avatar Oct 07 '24 12:10 frederikheld

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:

github-actions[bot] avatar Oct 07 '24 13:10 github-actions[bot]

So what's missing from the code I already posted?

frederikheld avatar Oct 07 '24 20:10 frederikheld

You have provided a code snippet but your issue could be caused by a variety of factors such as config and project structure. A minimal reproduction (stackblitz or repository) with steps on how to reproduce your issue would allow me to actually debug what's happening.

BobbieGoede avatar Oct 07 '24 20:10 BobbieGoede

https://stackblitz.com/edit/bobbiegoede-nuxt-i18n-starter-za36nq?file=components%2FLangSwitcher.vue

I used your starter template. I copy & pasted the code from app.vue into error.vue and it already shows --> locale switcher links are broken

Both pages use the same LangSwitcher.vue component provided by the starter template.

frederikheld avatar Oct 07 '24 20:10 frederikheld

Hmm I see what you mean, what do you expect switchLocalePath to return in this case?

BobbieGoede avatar Oct 07 '24 20:10 BobbieGoede

The same as everywhere else. I use the same top bar for all pages, so I expect the language switcher to work everywhere the same. The error is just a different kind of content, but I don't see that page any different from the other pages. And I wonder why Nuxt does.

frederikheld avatar Oct 07 '24 20:10 frederikheld

The language switcher would keep working if you add a catch all route/page, though going to a non existent route would not trigger an error anymoreπŸ€” The core issue is not it being an error page but the trying to resolve a translated version of a non existent route. Would that fit your use case?

BobbieGoede avatar Oct 07 '24 20:10 BobbieGoede

Ah, that makes sense.

TBH I'm not a fan of all the crude magic going on inside Nuxt. In Vue I would just have defined a catchall route in the router. But as Nuxt is stubborn about being different, I think that the nuxt-i18n module should conform to that stubbornness.

At least the Nuxt error page takes care of sending the right http status code to the client. I suppose that I would have to create all that my self with the catchall approach? And how would I create such a catchall route in Nuxt?

Important context: I'm using Nuxt just for SSG. No Nitro, I'm just dumping the contents of .output/public to static webhosting. I don't know if that would work with a catchall route but it works with error.vue.

frederikheld avatar Oct 07 '24 22:10 frederikheld

The catch all route would only be used for non existent pages, check the docs on how to make one here https://nuxt.com/docs/guide/directory-structure/pages#catch-all-route.

The language switcher will keep working on any error page as long as the page actually exists for there to be localized variants of those, errors other than 404 should render the error.vue page.

BobbieGoede avatar Oct 08 '24 09:10 BobbieGoede

catch all route would only be used for non existent pages

But a catchall is different from an error page. Catchall will send 200 for pages that should send 404.

I'll look into that approach, should be possible to mimick a real error page using setResponseStatus().

frederikheld avatar Oct 08 '24 12:10 frederikheld

But a catchall is different from an error page. Catchall will send 200 for pages that should send 404.

I agree they're different, but it would resolve the issue of the language switcher not working.

It seems like the underlying behavior is clear for this issue, language switching does not work on non existent pages combined with Nuxt considering the error page not to be a 'page', does that sound right? For issues/questions about Nuxt specific behaviors you'll have better luck opening an issue on the Nuxt repository or asking in the Discord.

BobbieGoede avatar Oct 08 '24 12:10 BobbieGoede

I agree they're different, but it would resolve the issue of the language switcher not working.

The package ist called nuxt-i18n. So I expect it to work with Nuxt. Not doing things the Nuxt way to make the package work is an anti-pattern. There should at least be some error or note in the docs that explains the behavior. Just silently returning an empty string is a bug that is hard to catch.

frederikheld avatar Oct 08 '24 15:10 frederikheld

So what do you expect switchLocalePath to return on a non existent path (404)? The error 'page' is not a real route or page, so what would you consider the localized equivalent of no path?

BobbieGoede avatar Oct 08 '24 15:10 BobbieGoede

I want to be able to switch languages, even on the error page. If the user is on the non-existing route /en/foo and they switch to de, the route should be/de/foo. It doesn't matter if the route exists within Nuxt or not. It's the route that is visible in the url bar, that's what the user sees.

EDIT: and maybe /en/foo even was a broken link that should have been /de/foo, so the route might exist in one locale but not in the other. If there are no route localization rules specified in i18n.pages, I would expect it to stupidly swap out the locale part of the path, no matter if the route exists or not.

frederikheld avatar Oct 08 '24 17:10 frederikheld

and maybe /en/foo even was a broken link that should have been /de/foo, so the route might exist in one locale but not in the other. If there are no route localization rules specified in i18n.pages, I would expect it to stupidly swap out the locale part of the path, no matter if the route exists or not.

If a route exists in one language then it does work the way you describe, which ironically is actually an issue for other users, see https://github.com/nuxt-modules/i18n/issues/2782.

You can switch language fine on the error page (using setLocale for example), you're just not getting a localized path from switchLocalePath since there's no path to localize.

I would expect it to stupidly swap out the locale part of the path, no matter if the route exists or not.

If you're on a non existent page, like /foo, would you expect router.resolve({ query: { bar: '123' } }) (resolves current route with query) to return /foo?bar=123?

BobbieGoede avatar Oct 08 '24 18:10 BobbieGoede

If a route exists in one language then it does work the way you describe, which ironically is actually an issue for other users, see https://github.com/nuxt-modules/i18n/issues/2782.

I see the confusion here. But however you want to solve this, it should be consistent from the users' point of view.

You can switch language fine on the error page (using setLocale for example), you're just not getting a localized path from switchLocalePath since there's no path to localize.

But how would this work with prefixed routes?

Imagine the user to be on the non-existing route /en/foo but they want to read that page in German, so they switch the language. Would the url still be /en/foo but in German? This doesn't make sense, because for all other pages it would switch to /de/foo.

From the users' POV, the url of an existing and non-existing route should follow the same rules. Everything else is just confusing. The only difference between existing and non-existing routes is on the technical side.

I think the high-level question that causes the confusion is: do we expect that a page exists in every locale? I would think so. If there's no translation, the page will show it's content in the fallback-locale.

So we can also assume that every page that does not exist in one locale doesn't exist in all others. So a 404 in English will also be a 404 in German.

frederikheld avatar Oct 08 '24 19:10 frederikheld

If you want localized routing to work, you need to be on an actual route. I suggest adding a 404 page and redirecting to it instead of showing the error 'page', or alternatively remove the language switcher on the error page and provide a link to a proper route. There's a reason definePageMeta does not work on the error page, and you can't resolve a route to the error page, so this is actually in line with Nuxt's behavior of the error page not working as a typical page/route.

Getting the localized version of the current page is not straightforward, which is why internally we rely on route names as these don't get localized. This guarantees an actual page is returned or nothing is returned but also requires the current page to exists, the same applies for Vue Router except it throws an error instead of an empty string.

If we matched the behavior of Vue Router's route resolution we would only resolve non-existent routes when given a path (you can do this with localePath):

router.resolve('/non-existent') // => { path: '/non-existent', ... }
router.resolve({ path: '/non-existent' })  // => { path: '/non-existent', ... }

If you're on a non-existent page/path, and try to resolve the current path with other query params or params:

// on /non-existent
router.resolve({ query: { foo: '123' } }) // => throws error
router.resolve({ param: { foo: '123' } })  // => throws error

Swapping out the locale as if it were a parameter would not be consistent with route resolution, the page would be broken entirely.

The switchLocalePath function is also used internally to create the SEO tags, having functioning links and meta tags pointing to non-existent pages is detrimental to your ranking in search engines.

I'm willing to consider changing the route resolution if enough people agree with your suggested behaviors, but it would need to be made in a next major version. In the meantime you will have to implement your own fallback resolution or one of the suggestions I have made.

BobbieGoede avatar Oct 08 '24 20:10 BobbieGoede