QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.28k stars 59 forks source link

Locale goes back when clicking on nextjs Link component #293

Open huskyjp opened 10 months ago

huskyjp commented 10 months ago

Describe the bug Maybe related to #138 & #140, when I click nextjs <Link> component, it forces to go back to the default locale. To Reproduce Steps to reproduce the behavior:

  1. Go to CodesandBox
  2. Change locale from en -> fr
  3. Click Link component
  4. Always route to default locale en even we pass the locale prop in the

Expected behavior Should keep the locale

About (please complete the following information):

QuiiBz commented 10 months ago

Do you reproduce the issue when running locally? AFAIK this is a bug with CodeSandbox, they don't preserve cookies correctly.

huskyjp commented 10 months ago

@QuiiBz Thanks for the response! Yes it also does the same behavior in my local environment. But I also noticed that this problem is related to the cookies.

The thing is my current middleware.ts has some rewrite condition like below

import { NextRequest } from "next/server"

import { createI18nMiddleware } from "next-international/middleware"

export const I18nMiddleware = createI18nMiddleware({
  locales: ["en", "jp"],
  defaultLocale: "en",
  urlMappingStrategy: "rewriteDefault",
})

export default function middleware(request: NextRequest) {
  const url = request.nextUrl.clone()
  const pathname = url.pathname

  // Check if the URL does not contain any of the specified locales
 // It basically works but did not change the nextjs cookie locale info so when we use `Link` component, it behaves strange.
  // if (!pathname.startsWith("/en/") && !pathname.startsWith("/jp/")) {
  //   url.pathname = `/en${pathname}`

  //   console.log("Redirecting to: ", url.toString())
  //   return NextResponse.rewrite(url)
  // }

  return I18nMiddleware(request)
}

export const config = {
  matcher: ["/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)"],
}

The reason why I added if (!pathname.startsWith("/en/") && !pathname.startsWith("/jp/")) is when we try to dynamically access the path without specifying the default locale (in this case, dynamically typing & changing the URL path from localhost:3000/jp/admin/ to localhost:3000/admin/), nextjs stays the locale as is since the cookie is still the same so I can't basically change the locale from the URL bar.

So basically I want to know if there is a way to handle user can type and dynamically change the locale & route to the correct path from URL bar without above if statement in the middleware.ts?

huskyjp commented 10 months ago

Ok so finally I could manage the cookies correctly via middlware.ts like so.

import { NextRequest, NextResponse } from "next/server"

import { createI18nMiddleware } from "next-international/middleware"

export const I18nMiddleware = createI18nMiddleware({
  locales: ["en", "jp"],
  defaultLocale: "en",
  urlMappingStrategy: "rewriteDefault",
})

export default function middleware(request: NextRequest) {
  const url = request.nextUrl.clone()
  const pathname = url.pathname

  const currentLocale = pathname.startsWith("/jp/") ? "jp" : "en"
  const cookieLocale = request.cookies.get("Next-Locale")?.value || "en"

  // Check if the current locale does not match the cookie locale, then update the cookie and rewrite the URL
  if (currentLocale !== cookieLocale && cookieLocale !== undefined) {
    // Update the cookie to match the current locale
    const response = NextResponse.next()
    response.cookies.set("Next-Locale", currentLocale, { path: "/" })

    // Rewrite to include the default locale if not already present
    if (!pathname.startsWith(`/${currentLocale}/`)) {
      url.pathname = `/${currentLocale}${pathname}`
      const response = NextResponse.rewrite(url)
      response.cookies.set("Next-Locale", currentLocale, { path: "/" })
      return response
    }

    // Special case: If URL explicitly starts with '/en/' (bascially call from /jp/ route to /en/),
    // remove it and call I18nMiddleware
    if (pathname.startsWith("/en/")) {
      url.pathname = pathname.replace("/en", "")
      NextResponse.rewrite(url)
      return I18nMiddleware(request)
    }
    return response
  }

  return I18nMiddleware(request)
}

export const config = {
  matcher: ["/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)"],
}

With this, the cookies should be always align with the current locale. But I found that I still have to explicitly tell nextjs Link component to include locale prefix in the href, otherwise it always navigates me to the pure href link even we include the locale props like below.

For example we are in: http://localhost:3000/jp/admin/settings/

export function PricingTable() {
  const locale = useCurrentLocale()

  console.log("locale : " + locale) < the out put is "locale: jp"

.....some code.....

  <Link href={"/admin/settings/billing/"} locale={locale}>Click me</Link>

After click the Link component, it navigates me to the admin/settings/billing/ and changed the locale to en...

And of course if we specify the locale prefix like, it works correctly and keeps the cookie fine as well.

 <Link href={`/${locale}` + "/admin/settings/billing/} locale={locale}>

Am I missing something?

QuiiBz commented 10 months ago

I'm not able to reproduce the issue using the example in the repo: https://github.com/QuiiBz/next-international/tree/main/examples/next-app

The steps I did:

Does this work on your side, and if no could share the steps to reproduce in this example above? CodeSandbox has issues with cookies so that's why I'd like to reproduce outside of it.

gustaveWPM commented 10 months ago

Hello @QuiiBz

I experiment an issue which could be related to this one.

To reproduce:

Source code: https://github.com/Tirraa/dashboard_rtm

Maybe this is due to my middlewares chain? I think the onClick={() => signIn('discord', { callbackUrl: ROUTES_ROOTS.DASHBOARD })} on the Login button, is redirecting to Discord OAuth "Before" something in next-international which saves the user locale choice...

Btw: I also cheat one rewrite in my middleware, without having any issue about it.