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

Locale goes back when clicking on nextjs Link component

Open huskyjp opened this issue 1 year ago • 5 comments

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 <Link />

Expected behavior Should keep the locale

About (please complete the following information):

  • next-international: "^1.1.4",
  • Next.js version: "^14.0.3"

huskyjp avatar Nov 20 '23 16:11 huskyjp

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

QuiiBz avatar Nov 20 '23 17:11 QuiiBz

@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 avatar Nov 20 '23 17:11 huskyjp

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?

huskyjp avatar Nov 21 '23 08:11 huskyjp

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:

  • Clear cookies
  • Navigate to /, automatically redirected to /en, cookie set to en
  • Click on fr, redirected to /fr, cookie updated to fr
  • Navigate to other pages, the route segment is still fr, cookie is still fr

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.

QuiiBz avatar Nov 21 '23 17:11 QuiiBz

Hello @QuiiBz

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

To reproduce:

  • Open a new private navigation window
  • Go to https://dashboard-rtm.vercel.app/
  • Click on the language switcher, then switch to English/French (depending of your current locale on the website first-visit)
  • Click on the "Login" button of the navbar
  • Login via Discord (I don't keep track of anything)
  • The website locale is out-of-sync

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.

gustaveWPM avatar Nov 29 '23 15:11 gustaveWPM