Search code examples
next.jslocalizationinternationalizationlocalemiddleware

Next.js internationalisation: all the routes throws "404 not found" for only one locale while I can access the other locale's routes


I'm currently figuring out how the Next.js localisation and internationalisation work. beside the root page.jsx (home page), I have two folders: test and tester -sorry for the confusion- and two locales I want to support which they are: "ar" and "en" before starting the part of the middleware.js I could view all of the pages under any locale, but now after completing the logic of the middleware.js, the root page and /ar/test and /ar/tester become not found, I check the source page to check if there is a lang in <html> tag but there was none. this is not the case for the /en, all the routes working and the locale was detected inside the tag.

I thought may be caused by something related to the browser's default language, so I changed it from Arabic to English but the same thing happened.

directory structure

and here is the next.config file:

/** @type {import('next').NextConfig} */
const nextConfig = {
    i18n: {
        // locales: ['default',"ar", "ar-SA", "en", "en-GB"],
        locales: ['ar', "en-GB"],
        defaultLocale: 'ar',
        localeDetection: false 
    },
    trailingSlash: true,
};

export default nextConfig;

sample of the requests from the console where you can see that for some mysterious reason the /ar is missing from the pathname:

GET /en/ 200 in 1797ms
 GET / 404 in 284ms
 GET / 404 in 280ms
 GET /en/tester/ 200 in 401ms
 GET /tester/ 404 in 284ms

the middleware.js:

import { NextResponse } from "next/server";
import { getLocale } from "./helpers/getLocale"


const localesRegex = /^\/((?!ar\/|en\/|api|_next\/static|_next\/image|auth|favicon.ico|images).*)$/;

export const middleware = (request) => {
    console.log("middleware.js is running");
    const { pathname: url } = request.nextUrl;    

    // is the request to the root page and doesn't have any locale attached? 
    if(localesRegex.test(url)) {
        // attach the locale based on the user's preferences:
        const locale = getLocale(request);

        // rebuild the new request's pathname (url):
        // request.nextUrl.url = `/${locale}${url}`;
        // return NextResponse.redirect(request.nextUrl);
        return NextResponse.redirect(new URL(`/${locale}${url}`, request.nextUrl.origin).href);
    }

    return NextResponse.next();

}
export const config = {
    matcher: ["/^\/((?!ar\/|en\/|api|_next\/static|_next\/image|auth|favicon.ico|images).*)$/"],
  };

getLocale function:

const supportedLocales = ["ar", "en"];
const matchedLocales = [];
// const matchPattern = ["/^(?!\/(ar|en)($|\/)).*/"];
// to get the locale from the request => request.nextUrl.locale

export function getLocale(request){

  // 1) is there any locale ? is it supported ? return it
  if(request.nextUrl.locale && supportedLocales.includes(request.nextUrl.locale)) {
    console.log("there is", request.nextUrl.locale);
    return request.nextUrl.locale;
  }

  // 2) build an array of the preferred locales:
  const userLocals = request.headers.get("accept-language")?.split(",") || [];

  // 3) empty userLocales ? return the matchers and the newUrl attached with /ar (the default)
  if(!userLocals) return "ar";

  // 4) extract the matches, the split(/[-;]/)[0] is used for locales like en-GB-oxendict;q=0.8 or en;q=0.7
  userLocals.map(locale => {
      locale = locale.split(/[-;]/)[0];
      if(supportedLocales.includes(locale) && !matchedLocales.includes(locale)) matchedLocales.push(locale);
  });

  // 5) empty matchedLocales ? return the matchers and the newUrl attached with /ar (the default)
  if(!matchedLocales) return "ar";

  // 6) return the first match:
  return matchedLocales[0];
  }

Solution

  • I've solved the problem !! I don't know the explanation for it so please if anyone comes across this post in the future and can help us to understand what's happened, It would be a delighted to read your thoughts.

    here is what I changed to make it work:

    1- the next.config file was:

    locales: ['ar', "en-GB"],
    defaultLocale: 'ar',
    

    I had a problem with the Arabic language but not with the English because I didn't specify any region so it's now:

    locales: ['ar-SA', "en-GB"],
    defaultLocale: 'ar-SA',
    

    the second thing to change is the middleware's matcher:

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

    and that's it! I hope this post will be some kind of help.