Next.js Discord

Discord Forum

App Router : i18n and Supabase

Unanswered
California pilchard posted this in #help-forum
Open in Discord
Avatar
California pilchardOP
Okay its maybe a dummy question but how can we manage i18n in web app without slug (like spotify, instagram,...) ? I use Supabase for backend so I can save the language preference for each user. But how set the language of the user in the nextjs app ? And how manage the fact that user can be not logged ? Im new with i18n so I have no idea. My first idea is to use React Context, but I guess I need cookies to store the preference of the user (logged and unlogged) ?

36 Replies

Avatar
Marchy
Spotify handles content by fetching all of the text through GQL on the client, but best practice with next would be to use domain and/or subpath routing so it's cached on the route segment for better performance
Avatar
California pilchardOP
Yeah Im gonna use subpath like [lang]in the doc but in the middlesware make something like, if the user is connected take is favorite language, if not connected, take the one from the Accept-Language header, is a good practice ?
Avatar
Marchy
Yes, next does this automatically based on the Accept-Language header sent by the browser (their system default)
https://nextjs.org/docs/pages/building-your-application/routing/internationalization#automatic-locale-detection
Avatar
California pilchardOP
Because im already use middleware to catch user session with Supabase :
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextRequest, NextResponse } from 'next/server'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  await supabase.auth.getSession()
  return res
}

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

So I guess I can have something with the language
Avatar
Marchy
You could overwrite that in middleware based on user settings
Avatar
California pilchardOP
ChatGPT help me to just think about the way of doing and show me this example :
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextRequest, NextResponse } from 'next/server'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  const session = await supabase.auth.getSession()

  if (session) {
    const userLanguage = await getUserLanguageFromSupabase(session.user.id) // Obtenez la langue de l'utilisateur depuis Supabase
    setLanguageCookie(res, userLanguage)
  } else {
    const acceptLanguage = req.headers.get('Accept-Language')
    const userLanguage = determineUserLanguage(acceptLanguage) // Déterminez la langue préférée en fonction de l'en-tête Accept-Language
    setLanguageCookie(res, userLanguage)
  }

  return res
}

function setLanguageCookie(res, language) {
  res.setHeader('Set-Cookie', `rcmd_locale=${language}; Path=/; HttpOnly; Secure; SameSite=Strict`)
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
Ive check the cookie from Spotify and they store the sp_locale value for the locale
Avatar
Marchy
I don't use supabase, but this looks right in theory
Avatar
California pilchardOP
But for now I have an error in the dictionaries.ts file :
import 'server-only'
 
const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  nl: () => import('./dictionaries/fr.json').then((module) => module.default),
}
 
export const getDictionary = async (locale: string | number) => dictionaries[locale]() => ERROR HERE WITH dictionaries[locale] => 

Element implicitly has an 'any' type because expression of type 'string | number' can't be used to index type '{ en: () => Promise<{ products: { cart: string; }; }>; nl: () => Promise<{ products: { cart: string; }; }>; }'.
  No index signature with a parameter of type 'string' was found on type '{ en: () => Promise<{ products: { cart: string; }; }>; nl: () => Promise<{ products: { cart: string; }; }>; }
Ive try adding interface like that but not working as well :
interface Dictionary {
  [key: string]: () => Promise<{ [key: string]: string }>
}
Avatar
Marchy
The locale gets passed as a param to each page.jsx, it might be easier to just have a content.json in the closest route segment that can be used to manage all the content there
Avatar
California pilchardOP
Ohh ! I was thinking about create a new cookies rcmd_locale but if I can override the NEXT_LOCALE its even better
Its the Pages Router, isnt a problem ?
Avatar
Marchy
Actually, maybe? I don't see it in the /app docs :meow_stare:
it'd be worth a try
same difference either way, just a little bit extra in middleware if it's not automatic
Avatar
California pilchardOP
And when u say override NEXT_LOCALE, is only for a specific website right ? Like isnt gonna chance the lang of all the website ?
Avatar
Marchy
NEXT_LOCALE probably uses the same method as doing it manually in middleware. It would be just for your site, because the cookie is from your domain
it might also just be missing from app docs, in which case I'd be interested if it works so I can add it
Avatar
California pilchardOP
Im gonna try this tomorrow, im gonna sleep rn so i'll keep u informed !
THx again for ur help and sry for my dummy questions 😄
Avatar
Marchy
no such thing as a dumb question, next is a 100% different arch pattern so things happen in different places than you might expect lol
Avatar
California pilchardOP
ahah yeah probably
Btw to use "server-only" we have to enable experimental feature no ? Because in the /app docs they use server-only for dictionaries.ts
or its maybe "user-server"
Avatar
Marchy
'use server' , it was server-only at one point :lolsob:
Avatar
California pilchardOP
Well maybe I can use the plugin next-intl no ? Because I have some difficulties to setup the middleware ahah
Avatar
California pilchardOP
Okay Ive setup the next-intl plugin with server component with this middleware :
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextRequest, NextResponse } from 'next/server'
import createIntlMiddleware from 'next-intl/middleware'

const locales = ['en', 'fr'];

const intlMiddleware = createIntlMiddleware({
  locales,
  defaultLocale: 'en'
})

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  await supabase.auth.getSession()
  return intlMiddleware(req)
}

export const config = {
    matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
Avatar
California pilchardOP
Well no I have to setup my middleware if my user is connected to change the NEXT_LOCALE value
Avatar
California pilchardOP
Ive try this :
export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  const { data: { session } } = await supabase.auth.getSession();
  const { data: user } = await supabase.from('user').select('*').eq('id', session?.user.id).single() as { data: User }
  if (user) {
    console.log('user', user)
    // Mettre à jour le cookie NEXT_LOCALE avec la langue préférée de l'utilisateur
    res.cookies.set(
      {
        name: 'NEXT_LOCALE',
        value: user.language,
        maxAge: 365 * 24 * 60 * 60, // Durée de validité du cookie (1 an)
        path: '/', // Le chemin du cookie
        sameSite: 'strict',
        priority: 'medium'
    });
  }
  return intlMiddleware(req)
}

But in the cookies tabs nothing is change. Ive tried to create a new cookie with differente name and isnt working too
Okay the problem come from the `return intMiddleware(req)`` because if I do that :
return (res)
// return intlMiddleware(req)

I can override cookies. The problem is intMiddleware use req
Avatar
California pilchardOP
I really dont understand with the cookies isnt set with my actual middleware... Because if I change manually the NEXT_LOCALE cookie value in the app tabs the lang change
Image
Avatar
California pilchardOP
Avatar
California pilchardOP
arrhh doesnt work either
Avatar
California pilchardOP
So maybe I have to create a custom intlMiddleware to use the same resvariable ?