Skip to content
Advertisement

How to enforce i18n locale slugs and achieve i18n consistency upon reload in Next.js?

I’m using next-translate. By default, my routes are recognized as follows:

/about         <---
/de/about
/es/about

but I’d like to enforce a locale for all paths:

/en/about      <---
/de/about
/es/about

Here’s my config:

next.config.js

const nextTranslate = require('next-translate');

module.exports = nextTranslate({
    ...
    i18n: {
        localeDetection: false,
        locales: ['en', 'de', 'es'],
        defaultLocale: 'en',
    }
});

i18n.js

module.exports = {
    locales: ['en', 'de', 'es'],
    defaultLocale: 'en',
    pages: {
        '*': ['common']
    },
    interpolation: {
        prefix: '${',
        suffix: '}',
    },
    loadLocaleFrom: (locale, namespace) =>
        import(`./translations/${locale}/${namespace}`).then((m) => m.default),
}

Note that I also have a lang change component that persists the NEXT_LOCALE cookie. As such, I would expect that when I access /about and my NEXT_LOCALE cookie had previously been set to de, the router would redirect me to /de/about. But it does not. It stays at /about and rewrites the cookie to en

Here’s current pages folder structure:

...
pages/
  _app.tsx
  _document.tsx
  about.tsx
  ...

Do I need to restructure it to this?

pages/
  _app.tsx
  _document.tsx
  [lang]/         <---
    about.tsx
    ...

If so, what would be the next step?

  • Parse the preferred locale through useRouter()
  • Parse the NEXT_LOCALE cookie
  • Parse the lang slug

and then decide which has higher precedence? Where should I do that? In _app.tsx / some HOC?

Do I need any rewrites or redirects in my next.config.js or should I handle these dynamically through Router.push?

Advertisement

Answer

The fact that the persisted NEXT_LOCALE cookie doesn’t automatically redirect based on its value is because you have explicitly disabled it by setting localeDetection: false. This affects the header-based redirection as well as the cookie-based one.

Simply removing it from your next.config.js should solve that issue.


There’s no built-in way to force the default locale on all paths. However, there are a couple of workarounds that could help with prefixing the default locale on the URL.

Workaround #1: Set default locale to default, and redirect in middleware

As documented in the i18n Routing documentation, add a new “dummy” locale named default and set it as the default locale. Note that this locale won’t actually be used, it will simply allow us to have the en locale always prefixed on its paths.

// next.config.js
module.exports = {
    i18n: {
        locales: ['default', 'en', 'de', 'es'],
        defaultLocale: 'default'
    }
}

Next, create a middleware file to redirect to the /en prefix if a request is made to the default locale.

// middleware.js
import { NextResponse } from 'next/server'

const PUBLIC_FILE = /.(.*)$/

export function middleware(request) {
    const shouldHandleLocale = !PUBLIC_FILE.test(request.nextUrl.pathname)
        && !request.nextUrl.pathname.includes('/api/') 
        && request.nextUrl.locale === 'default'

    if (shouldHandleLocale) {
        const url = request.nextUrl.clone()
        url.pathname = `/en${request.nextUrl.pathname}`
        return NextResponse.redirect(url)
    }

    return undefined
}

Workaround #2: Shallow routing to prefixed path on the client-side

Alternatively, you could check for the default locale and set it explicitly in the URL on first mount through router.push.

Let’s assume the following custom useDefaultLocale hook that abstracts the logic to be re-used.

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';

export const useDefaultLocale = () => {
    const router = useRouter();

    useEffect(() => {
        if (router.locale === router.defaultLocale) {
            router.push(`/${router.locale}${router.asPath}`, undefined, {
                locale: false,
                shallow: true // Optionally add this if you don't want to rerun data fetching methods
            });
        }
    }, [router.asPath]);
};

Which could then be used in your pages or _app.js.

import { useDefaultLocale } from '<path-to>/use-default-locale';

const AboutPage = () => {
    useDefaultLocale()

    return <>About Page</>;
};
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement