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

switchLocalePath() returns empty string on error 404 #3166

Open
frederikheld opened this issue Oct 7, 2024 · 17 comments
Open

switchLocalePath() returns empty string on error 404 #3166

frederikheld opened this issue Oct 7, 2024 · 17 comments

Comments

@frederikheld
Copy link

frederikheld commented Oct 7, 2024

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: npm@10.2.4
  • Builder: -
  • User Config: compatibilityDate, devtools, ssr, build, nitro, modules, vite, i18n, eslint, security
  • Runtime Modules: (), @nuxtjs/i18n@8.1.0, @nuxtjs/eslint-module@4.1.0, nuxt-security@2.0.0
  • 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

Copy link

github-actions bot commented Oct 7, 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:

@frederikheld
Copy link
Author

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

@BobbieGoede
Copy link
Collaborator

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.

@frederikheld
Copy link
Author

frederikheld commented Oct 7, 2024

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.

@BobbieGoede
Copy link
Collaborator

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

@frederikheld
Copy link
Author

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.

@BobbieGoede
Copy link
Collaborator

BobbieGoede commented Oct 7, 2024

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?

@frederikheld
Copy link
Author

frederikheld commented Oct 7, 2024

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.

@BobbieGoede
Copy link
Collaborator

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 BobbieGoede changed the title switchLocalePath() returns empty string if used on error page switchLocalePath() returns empty string on error 404 Oct 8, 2024
@frederikheld
Copy link
Author

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().

@BobbieGoede
Copy link
Collaborator

BobbieGoede commented Oct 8, 2024

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.

@frederikheld
Copy link
Author

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.

@BobbieGoede
Copy link
Collaborator

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?

@frederikheld
Copy link
Author

frederikheld commented Oct 8, 2024

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.

@BobbieGoede
Copy link
Collaborator

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 #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?

@frederikheld
Copy link
Author

frederikheld commented Oct 8, 2024

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 #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.

@BobbieGoede
Copy link
Collaborator

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.

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

2 participants