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

[Example needed] i18n with Next.js 13 and app directory #41980

Closed
1 task done
jimjamdev opened this issue Oct 27, 2022 · 92 comments
Closed
1 task done

[Example needed] i18n with Next.js 13 and app directory #41980

jimjamdev opened this issue Oct 27, 2022 · 92 comments
Labels
area: app App directory (appDir: true) examples Issue/PR related to examples

Comments

@jimjamdev
Copy link

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
Platform: win32
Arch: x64
Version: Windows 10 Home
Binaries:
Node: 16.15.0
npm: N/A
Yarn: N/A
pnpm: N/A
Relevant packages:
next: 13.0.1-canary.0
eslint-config-next: 13.0.0
react: 18.2.0
react-dom: 18.2.0

What browser are you using? (if relevant)

Chrome

How are you deploying your application? (if relevant)

Local

Describe the Bug

Setting up i18n test in next.config as follows:

experimental: {
    appDir: true
  },
i18n: {
    locales: ['en', 'fr'],
    defaultLocale: 'en',
    localeDetection: true
  },

I've deleted pages and added a few files into the new /app folder to test

Creating a simple component like so:

import { ReactNode } from 'react';

export default function Layout({ locale, children, ...rest }: {
  children: ReactNode;
  locale?: string;
}) {
  console.log('rest', rest);
  return <main className="layout">
    <header><h3 style={{ color: 'tomato' }}>{locale || 'nope'}</h3></header>
    {children}
  </main>;
}

I'm not seeing any locale information provided via the props. I was thinking this would be provided on the server side for rendering locale specific data on the server.

Expected Behavior

I would expect to be passed the current locale for use within sever components layouts/pages.

Link to reproduction

https://github.com/jimmyjamieson/nextjs-13-ts

To Reproduce

npm dev, check output of props, locale in layout

@jimjamdev jimjamdev added the bug Issue was opened via the bug report template. label Oct 27, 2022
@aej11a
Copy link

aej11a commented Oct 27, 2022

Hi @jimmyjamieson - it's noted in the beta docs here that they're not planning i18n support in /app

Have you tried something like this? /app/[locale]/<all project files/subfolders here>
That should give you a similar system to the old /pages routing behavior.

And you could probably use middleware to enable/disable specific locales.

@jimjamdev
Copy link
Author

jimjamdev commented Oct 27, 2022

@aej11a Yeah, I've tried that, but that just gives a 404.
I also tried [...locale] and [[...locale]] which works for direct pages. But sub folders will throw an Catch-all must be the last part of the URL. error

I would be of benefit of allowing sub-folders under a catch-all to make internationalization easier. Will have to wait for the v13 docs to see what they suggest regarding middleware.

update: It also seems middleware isn't being loaded

@leonbe02
Copy link

Middleware only seems to work for routes under /pages, anything under /app does not run your middleware.
We have the use-case where our default locale (en-us) does not have a subdirectory i.e. /about vs /en-us/about so there's not an easy way to replicate the behavior of the old i18n feature from <= Next 12
Would love to hear from one of the Nextjs devs on how they foresee i18n routing being implemented on Next 13+

@johnkahn
Copy link

Middleware seems to execute correctly if you keep the pages directory. My setup has all my pages in the app directory, and then I have a pages folder with just a .gitkeep file in it. That seems to be enough to get Next to run the middleware file for all routes in the app folder.

There's an open issue for middleware not working when the pages folder is removed. There's also a placeholder menu item on the beta docs site for middleware, so I would assume there is probably gonna be a new method in the future for adding middleware in the app folder.

@jimjamdev
Copy link
Author

@johnkahn yeah, adding my 404.tsx into the pages directory has middleware working now and can access everything in the request.

@siinghd
Copy link

siinghd commented Oct 28, 2022

@jimmyjamieson How did you manage to solve the issue? Everytime i'm getting redirected to default locale and can't do nothing with the middleware.

@guttenbergovitz
Copy link

actually added [langcode]/test/bunga while having locales de-DE and en-GB and obviously it was 404ing. but to my suprise /en-GB/en-GB/test/bunga and /de-DE/de-DE/bunga worked fine... any ideas?

@jimjamdev
Copy link
Author

@guttenbergovitz I would maybe report that as a bug.

@jimjamdev
Copy link
Author

@siinghd You can see in my public repo above what I currently have.

@minnyww
Copy link

minnyww commented Nov 1, 2022

how to solve this issue ?

@guttenbergovitz
Copy link

@guttenbergovitz I would maybe report that as a bug.

Well... The issue is I am not 100% convinced it is a bug not the default behaviour I do not understand yet. 🤔

@jimjamdev
Copy link
Author

jimjamdev commented Nov 1, 2022

@guttenbergovitz in my repo above it seems to work. My middleware isn't exactly feature complete and messy, but the redirect is working. Links need the locale to work - or possibly wrap next/link. But you can manually add /en/sub and see it gets the correct page

@nbouvrette
Copy link
Contributor

Have you tried something like this? /app/[locale]/<all project files/subfolders here>

I tried this and it works on my end. I also didn't configure the i18n values in the config since its clearly no longer supported. Basically, I created my own config file like this (called it locales.json):

{
  "i18n": {
    "locales": ["en-US", "fr-CA"],
    "defaultLocale": ["en-US"]
  }
}

Then at the root of the app directory I created a [locale] directory where I can decide which locale will be valid, with something like this:

type I18nConfig = {
  i18n: {
    locales: string[];
    defaultLocale: string;
  };
};

export default async function Layout({
  children,
  params,
}: PageProps) {
  const locale = params.locale as string;
  const locales = (i18nConfig as unknown as I18nConfig).i18n.locales.map(
    (locale) => locale.toLowerCase()
  );
  if (!locale || !locales.includes(locale)) {
    return null;
  }
  return (
    <div>
      <div>Locale: {params.locale}</div>
      <div>{children}</div>
    </div>
  );
}

Of course, this is very basic and raw (I was just testing options). I would probably expect more mature packages to be released soon to handle this instead of relying on Next.js.

I myself maintain a Next.js i18n package that relies heavily on pages and the locale config and I suspect it will take a while before I can adapt my package to the app directory because of how many changes there are.

@jimjamdev
Copy link
Author

Have you tried something like this? /app/[locale]/<all project files/subfolders here>

I tried this and it works on my end. I also didn't configure the i18n values in the config since its clearly no longer supported. Basically, I created my own config file like this (called it locales.json):

{
  "i18n": {
    "locales": ["en-US", "fr-CA"],
    "defaultLocale": ["en-US"]
  }
}

Then at the root of the app directory I created a [locale] directory where I can decide which locale will be valid, with something like this:

type I18nConfig = {
  i18n: {
    locales: string[];
    defaultLocale: string;
  };
};

export default async function Layout({
  children,
  params,
}: PageProps) {
  const locale = params.locale as string;
  const locales = (i18nConfig as unknown as I18nConfig).i18n.locales.map(
    (locale) => locale.toLowerCase()
  );
  if (!locale || !locales.includes(locale)) {
    return null;
  }
  return (
    <div>
      <div>Locale: {params.locale}</div>
      <div>{children}</div>
    </div>
  );
}

Of course, this is very basic and raw (I was just testing options). I would probably expect more mature packages to be released soon to handle this instead of relying on Next.js.

I myself maintain a Next.js i18n package that relies heavily on pages and the locale config and I suspect it will take a while before I can adapt my package to the app directory because of how many changes there are.

You might be better just doing the check and any redirects in the middleware itself.

@aldabil21
Copy link

Hi @jimmyjamieson

I was testing to implement i18n with Next13, there are some limitations, I've looked into your repo. the limitations I found still exists, please correct me:

  1. This is only handle the case where user hit the home route, what about if a user hit somehwere else like domain.com/some/path ? it will 404. So you would tweak the matcher to run on each route something like matcher: "/:path*", in this case, you will also need to filter requests somehow cuz you wouldn't want to care about chunks & static requests... Have you got into this?

  2. How you would reach the locale inside a nested component? this approach seems encourage prop drilling? Although this approached is somewhat based on 41745#. So as a work around, I tried to wrap a provider in the root app to be able to provide locale with a hook, but that will result in turning most of the app into client components, which is not what Next 13 is about, server component first and by default.

Playground in this repo, any input is appreciated

@jimjamdev
Copy link
Author

Yeah you're right, this is only for home. You'd have to call it for all requests and modify the redirect path to include the locale in front of the initial url.

I've still to look into the other issue, but I understand what you mean. I thought about merging the locale data with the params props on page/layout and always returning it there

@aldabil21
Copy link

I've somewhat reached an initial solution, I managed to use i18next with fs-loader, and could keep an instance of i18next in the server. Not sure how good/bad this solution be, anyone can give an input to this repo would appreciate it https://github.com/aldabil21/next13-i18n

What I've learned on the way:

  1. Trying to use generateStaticParams as mentioned here 41745# will not work at all, adding generateStaticParams in the layout will prevent you from using any "next/navigation" or "next/headers" in any nested routes, a vague error message will show: Error: Dynamic server usage: .... Maybe Next team will make some changes about it in future?
  2. There is no way to know the current path on the server. known the current path only available in client-component hooks such as "usePathname".
  3. This solution is server-component only, once you use "use client" it will not work, cuz of the fs-backend for i18next.

@leerob leerob changed the title Next 13 i18n. How do we get the current locale? [Planned, not yet implemented] i18n with Next.js 13 and app directory Nov 5, 2022
@leerob
Copy link
Member

leerob commented Nov 5, 2022

Just want to follow up here and say an example for how to use i18n with the app directory is planned, we just haven't made it yet!

We will be providing more guidance on solutions for internationalized routing inside app. With the addition of generateStaticParams, which can be used inside layouts, this will provide improved flexibility for handling routing when paired with Middleware versus the existing i18n routing features in pages/.

@leerob leerob changed the title [Planned, not yet implemented] i18n with Next.js 13 and app directory [Example needed] i18n with Next.js 13 and app directory Nov 5, 2022
@timneutkens timneutkens added area: app App directory (appDir: true) examples Issue/PR related to examples area: documentation and removed bug Issue was opened via the bug report template. labels Nov 6, 2022
@aej11a
Copy link

aej11a commented Nov 6, 2022

Hey @leerob ! Is there any chance you or the team could briefly describe what that solution will look like, even if the example isn't implemented yet?

The "official word" on i18n is the biggest open question I've found holding back my team from starting incremental adoption of /app - need localization but also want to make sure we don't invest too much in the wrong approach :)

Thanks for everything!

Edit: really glad the new approach h will allow more flexible i18n, I've had a few clients who need more flexible URL structures than the default /pages system allows

@timneutkens
Copy link
Member

timneutkens commented Nov 7, 2022

@aej11a the example will have pretty much the same features but leverages other features to achieve the intended result so that you get much more flexibility, the most common feedback around the i18n feature has been "I want to do x in a different way" so it made more sense to leave it out of the built-in features and instead provide an example to achieve the same.

I can't share the full example yet because there's a few changes needed in order to mirror the incoming locale matching.

The short of it is:

  • Middleware handles incoming request matching to a locale. This is better than what we have today because it could only run on /, whereas middleware runs on all navigations.
  • Root layout with generateStaticParams:
// app/[lang]/layout.ts
export function generateStaticParams() {
  return [{ lang: 'en' }, { lang: 'nl' }, { lang: 'de' }]
}

export default function Layout({children, params}) {
  return <html lang={params.lang}>
	<head></head>
    <body>{children}</body>
  </html>
}
  • Reading lang in page/layout:
// app/[lang]/page.ts
export default function Page({params}) {
  return <h1>Hello from {params.lang}</h1>
}
  • Using additional dynamic route parameters as static:
// app/[lang]/blog/[slug]/page.js
export function generateStaticParams({params}) {
  // generateStaticParams below another generateStaticParams is called once for each value above it
  const lang = params.lang
  const postSlugsByLanguage = getPostSlugsByLanguage(lang)
  return postsByLanguage.map(slug => {
    // Both params have to be returned.
    return {
      lang,
	  slug
    }
  })
}

export default function BlogPage({params}) {
  return <h1>Dashboard in Language: {params.lang}</h1>
}

@oezi
Copy link

oezi commented Nov 8, 2022

@timneutkens thanks a lot for your example. one thing missing is the actual code for "Reading lang in page/layout:", as it's showing the axact same code block as for the point before ("Root layout with generateStaticParams:").
Most likely just an error when copying your code to this post, but could be confusing for people reading this - maybe you can fix that.

@timneutkens
Copy link
Member

@oezi good catch! I copied the wrong block from my notes indeed, fixed!

@amannn
Copy link
Contributor

amannn commented Jan 13, 2023

@leerob My apologies, I didn't know about the caching section in the docs and should have checked—thanks for pointing this out! I've edited my comment above to be accurate.

@prashantchothani
Copy link

prashantchothani commented Jan 15, 2023

We now have documentation and examples: https://beta.nextjs.org/docs/guides/internationalization

Hi @leerob

Thanks for the example.Our requirement is as follows:

  1. For locale en we do not need a locale segment in pathname. Like it should be domain.com/flights
  2. For locale de (and all other locales) we need a locale segment. Lit it should be domain/com/de/flights

How can this be achieved ? Since [lang] folder is above all the folders in your example, i am unable to figure this out.

@iljamulders
Copy link

@prashantchothani
We have the exact same situation!

Our plan is to create a middleware that:

  1. Redirects requests with /en/* to /*
  2. Rewrites requests that don't start with /de/ to /en/*

This way the user sees domain.com/flights but the middleware updates it server side to domain.com/en/flights

@prashantchothani
Copy link

@iljamulders

Thanks a lot. I will try doing this. If you succeed please could you let me know the code snippet for this ?

And i think, you need to redirect request with /* without any [lang] to /en/* i guess. What you mentioned is other way around.

@iljamulders
Copy link

@prashantchothani

No. I've created a quick example:
https://github.com/iljamulders/nextjs-i18n

Here you have a default locale, e.g. the url's are
http://localhost:3000/
http://localhost:3000/de
http://localhost:3000/products
http://localhost:3000/de/products

Trying to access http://localhost:3000/en/products will result in a redirect to http://localhost:3000/products

middleware.ts

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

const defaultLocale = 'en'
let locales = ['de']

export function middleware(request: NextRequest) {
  // Check if there is any supported locale in the pathname
  const pathname = request.nextUrl.pathname

  // Check if the default locale is in the pathname
  if (pathname.startsWith(`/${defaultLocale}/`) || pathname === `/${defaultLocale}`) {
    // e.g. incoming request is /en/products
    // The new URL is now /products
    return NextResponse.redirect(
      new URL(pathname.replace(`/${defaultLocale}`, pathname === `/${defaultLocale}` ? '/' : ''), request.url)
    )
  }

  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  )

  if (pathnameIsMissingLocale) {
    // We are on the default locale
    // Rewrite so Next.js understands

    // e.g. incoming request is /products
    // Tell Next.js it should pretend it's /en/products
    return NextResponse.rewrite(
      new URL(`/${defaultLocale}${pathname}`, request.url)
    )
  }
}

export const config = {
  matcher: [
    // Skip all internal paths (_next)
    '/((?!api|_next/static|_next/image|assets|favicon.ico).*)',
  ],
}

@l4b4r4b4b4
Copy link

l4b4r4b4b4 commented Jan 16, 2023

I tried to setup a NextJS 13 project including internationalisation with the example on Github provided by Vercel.
Unfortunately, this did break loading of local images.
An additional check in middleware.ts, if the requested resource is an image, solves the issue though.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

import { i18n } from "./i18n-config";

import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";

function getLocale(request: NextRequest): string | undefined {
  // Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));

  // Use negotiator and intl-localematcher to get best locale
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();
  // @ts-ignore locales are readonly
  const locales: string[] = i18n.locales;
  return matchLocale(languages, locales, i18n.defaultLocale);
}

export function middleware(request: NextRequest) {
  // if (!request.headers.get("accept")?.includes("image")) {
  // Skip next internal and image requests
  if (
    request.nextUrl.pathname.startsWith("/_next") ||
    request.headers.get("accept")?.includes("image")
  )
    return;

  // Check if there is any supported locale in the pathname
  const pathname = request.nextUrl.pathname;
  const pathnameIsMissingLocale = i18n.locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  // Let's redirect if there is no locale
  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);
    return NextResponse.redirect(
      new URL(`/${locale}/${pathname}`, request.url)
    );
  }
}

export const config = {
  // We can enable redirect just from root
  // matcher: "/"
};

It seems I can finally start migrating NextJS projects to the app directory and make use of all the awesome new features! :)

@l4b4r4b4b4
Copy link

image

Check my answer above. This solves the issue with loading local images with relative paths.

@aej11a
Copy link

aej11a commented Jan 16, 2023

Hi everyone, over the last few days I wrote a guide based on the official examples provided showing how to set up localized routing, automatic locale detection, static generation, and content translation.

Here's the link, I hope it helps! I learned a lot writing it: https://dev.to/ajones_codes/the-ultimate-guide-to-internationalization-i18n-in-nextjs-13-ed0

(Not sure if I'm allowed to post this here - there's no self-promotion other than a single Twitter link at the end of the article, but I'd be happy to take down the link if needed!)

@TCM-dev
Copy link

TCM-dev commented Jan 18, 2023

Hi everyone, over the last few days I wrote a guide based on the official examples provided showing how to set up localized routing, automatic locale detection, static generation, and content translation.

Here's the link, I hope it helps! I learned a lot writing it: https://dev.to/ajones_codes/the-ultimate-guide-to-internationalization-i18n-in-nextjs-13-ed0

(Not sure if I'm allowed to post this here - there's no self-promotion other than a single Twitter link at the end of the article, but I'd be happy to take down the link if needed!)

While this should definitely work (many guides out there are applying the same concepts but with packages such as react-i18next) there is still something that's a no go for me, you have to pass the language parameter as a prop to child components and even to the useTranslation hook (if you use it), there is no easy way to retrieve the current locale otherwise. Also it looks like on server side you cannot use singletons for this use case (every guides shows a custom useTranslation hook which init a new instance of react-i18next for example)

I know the split between server and client components has to increase complexity but this looks very unpractical to me. Should this be adressed by external packages directly or is there a need for developments on nextjs to make it as efficient to develop with as before too ?

@KurtGokhan
Copy link

KurtGokhan commented Jan 18, 2023

@TCM-dev I see that createServerContext is used specifically for this case in this tweet by @sebmarkbage . You may want to try it. Although, I am confused who implements this. Afaik, React didn't implement server contexts yet (or it may be experimental). Not sure if it is ready to be used in production yet.

@nayaabkhan
Copy link

Hey fellows! We have an existing Next.js 12 site which uses i18n with pages. This setup seems to be interfering with app/[locale]/* as prescribed by official examples and many others.

localhost/en goes to 404 but localhost/en/en works. This means we cannot migrate to the app folder piece-by-piece.

Same was pointed out by @fernandojbf and @guttenbergovitz earlier in the following posts. I'm not sure if that was addressed already. If so, can someone link the solution? If not, is this issue open somewhere else separately so that I could track?

Thanks a lot in advance.

@amannn
Copy link
Contributor

amannn commented Jan 23, 2023

you have to pass the language parameter as a prop to child components and even to the useTranslation hook (if you use it), there is no easy way to retrieve the current locale otherwise (…) I know the split between server and client components has to increase complexity but this looks very unpractical to me.

I agree to this, that's the reason why I went for a SSR-only approach for now. By doing that there's a workaround and the locale can be retrieved deeply from within the component tree by reading from a cookie or header.

I see that createServerContext is used specifically for this case in this tweet by @sebmarkbage

I experimented with it, but unfortunately couldn't find a reliable way on how to use it with Next.js: #44222. I'm also not sure where a good place to add it would be, since there's no natural root like _app anymore in the app directory (e.g. head.js vs. layout.js).

@balazsorban44
Copy link
Member

Hi everyone, since we have an official guide and example, I think the original request has been fulfilled on this issue, so I am closing it.

We understand if you have specific/requirements or questions for your specific use case. If you think something is missing from the guide/example above, you can open a new issue or add feedback directly on the guide page via Preview Comments, and we will review them. Thanks!

@tholder
Copy link

tholder commented Feb 2, 2023

This example didn't work for me. I keep getting a static to dynamic error https://nextjs.org/docs/messages/app-static-to-dynamic-error

I'd also like to table domain based locale detection being an important feature. Currently we use next-18next and it works well with pages, using an edge function to re-route to a domain based on locale. This keeps Urls clean of locale e.g. know /en_GB/ etc.

@DerianAndre
Copy link

DerianAndre commented Feb 16, 2023

Hi everyone, since we have an official guide and example, I think the original request has been fulfilled on this issue, so I am closing it.

We understand if you have specific/requirements or questions for your specific use case. If you think something is missing from the guide/example above, you can open a new issue or add feedback directly on the guide page via Preview Comments, and we will review them. Thanks!

Hello!, I'm getting this error with electron + nextjs with that example. Any chances someone has worked on this?

TypeError: Invalid URL: http://undefined:undefined/?__nextDefaultLocale=

@damianricobelli
Copy link

Hi, after many attempts, I can't get a good result in my use case: I'm trying to use [lang] as indicated in the documentation, but I need one of the segments to be with SSR in the fetching (cache: no-store + revalidate: 0). The problem is that whenever I try to load the [lang]/my-ssr-segment, Next JS warns me that in runtime it tried to change from static to dynamic.

What should I do in this case?

@eric-burel
Copy link
Contributor

@damianricobelli take a look at this issue maybe and feel free to comment: #44712
It depends on how you setup your layout I think

@Rover420
Copy link

Rover420 commented Mar 8, 2023

#41980 (comment)

It doesn't work for me. The main problem I've got right now and can't get past is those images... Better approach I found is to check if it's image request like this:

if(request.headers.get("sec-fetch-dest") === 'image')

but I'm not able to return the image anyway. How to return proper image response?

Or maybe that's because I've got the images in /public/logo/...

@Amir-Mirkamali
Copy link

We now have documentation and examples: https://beta.nextjs.org/docs/guides/internationalization

The main problem is that i want an optional parameter for default language (Like previous version of next.js) and required for other languages, All solutions didnt support this feature yet.
Completely disapointed.

@amannn
Copy link
Contributor

amannn commented Mar 13, 2023

@Amir-Mirkamali

The main problem is that i want an optional parameter for default language (Like previous version of next.js) and required for other languages, All solutions didnt support this feature yet.

You can solve this with a rewrite when the default locale matches.

The next-intl middleware does this by default: https://next-intl-docs.vercel.app/docs/next-13/server-components#routing-with-a-locale-prefix-default

@l4b4r4b4b4
Copy link

l4b4r4b4b4 commented Mar 14, 2023

#41980 (comment)

It doesn't work for me. The main problem I've got right now and can't get past is those images... Better approach I found is to check if it's image request like this:

if(request.headers.get("sec-fetch-dest") === 'image')

but I'm not able to return the image anyway. How to return proper image response?

Or maybe that's because I've got the images in /public/logo/...

I came up with a different approach, since I missed a comment in the provided middleware.ts.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

import { i18n } from "./i18n-config";

import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";
import { images } from "@/typings.d";

function getLocale(request: NextRequest): string | undefined {
  // Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));

  // Use negotiator and intl-localematcher to get best locale
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();
  // @ts-ignore locales are readonly
  const locales: string[] = i18n.locales;
  return matchLocale(languages, locales, i18n.defaultLocale);
}

export function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname;
  // Skip next internal and image requests
  // if (
  //   request.headers.get("accept")?.includes("image")
  // )
  //   return;

  // // `/_next/` and `/api/` are ignored by the watcher, but we need to ignore files in `public` manually.
  // // If you have one
  if (
    [
      ...Array.from(images).map(
        (path) => path
      ),
    ].includes(pathname)
  )
    return;

  // Check if there is any supported locale in the pathname
  const pathnameIsMissingLocale = i18n.locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  // Redirect if there is no locale
  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    // e.g. incoming request is /products
    // The new URL is now /en-US/products
    return NextResponse.redirect(
      new URL(`/${locale}/${pathname}`, request.url)
    );
  }
}

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

where images is an array of relative image paths like so:

export const images = ["/images/image2.png","/logos/logo1.svg"]

@leandromatos
Copy link

About the images and other files on the public directory, look at this code below:

https://nextjs.org/docs/advanced-features/i18n-routing

const PUBLIC_FILE = /\.(.*)$/

if (
  req.nextUrl.pathname.startsWith('/_next') ||
  req.nextUrl.pathname.includes('/api/') ||
  PUBLIC_FILE.test(req.nextUrl.pathname)
) {
  return
}

I tried, and everything worked very well.

@l4b4r4b4b4
Copy link

About the images and other files on the public directory, look at this code below:

https://nextjs.org/docs/advanced-features/i18n-routing

const PUBLIC_FILE = /\.(.*)$/

if (
  req.nextUrl.pathname.startsWith('/_next') ||
  req.nextUrl.pathname.includes('/api/') ||
  PUBLIC_FILE.test(req.nextUrl.pathname)
) {
  return
}

I tried, and everything worked very well.

could you share a demo repo / codepen?

@leandromatos
Copy link

leandromatos commented Mar 18, 2023

@l4b4r4b4b4 Here is my middleware.ts. My project is a private application. Let me know if you have any questions, and I will try to help you.

import { match as matchLocale } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'

import { i18n } from './i18n-config'

function getLocale(request: NextRequest): string | undefined {
  // Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {}
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value))

  // Use negotiator and intl-localematcher to get best locale
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages()
  const locales: string[] = i18n.locales.slice()

  return matchLocale(languages, locales, i18n.defaultLocale)
}

export function middleware(request: NextRequest) {
  // Skip next internal and image requests
  if (
    request.nextUrl.pathname.startsWith('/_next') ||
    request.nextUrl.pathname.includes('/api/') ||
    /\.(.*)$/.test(request.nextUrl.pathname)
  ) {
    return
  }

  // Check if there is any supported locale in the pathname
  const pathname = request.nextUrl.pathname
  const pathnameIsMissingLocale = i18n.locales.every(
    locale => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  )

  // Let's redirect if there is no locale
  if (pathnameIsMissingLocale) {
    const locale = getLocale(request)

    return NextResponse.redirect(new URL(`/${locale}/${pathname}`, request.url))
  }
}

export const config = {
  // Matcher ignoring `/_next/` and `/api/`
  matcher: ['/((?!api|_next/static|_next/image).*)'],
}

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) examples Issue/PR related to examples
Projects
None yet
Development

No branches or pull requests