[]previous and next page

Internationalization with next-intl

next-intl has a counterpart in @shadow-js/i18n: ICU messages typed from the catalog, a locale that is a signal, and a middleware for Dusk that detects the locale,…

For agentsUpdated

next-intl has a counterpart in @shadow-js/i18n: ICU messages typed from the catalog, a locale that is a signal, and a middleware for Dusk that detects the locale, awaits its catalogs and sets <html lang dir>. The guide Internationalization covers the whole package, including its own next-intl, react-intl and i18next migration table; this is the part that maps i18n/routing.ts, the middleware and [locale] segments.

next-intl @shadow-js/i18n
defineRouting({ locales, defaultLocale, localePrefix }) createI18n({ locales, defaultLocale, prefix }) or createLocaleRouting({ locales, defaultLocale, prefix }) on its own; prefix is "as-needed", "always" or "never"
createMiddleware(routing) in middleware.ts createI18nMiddleware(i18n, options) in the middleware array of entry-server.ts; it redirects only on GET and HEAD and has no matcher, so exclude API routes yourself
app/[locale]/ src/routes/[locale]/; validate the param with routing.fromParam(params.locale) and answer notFound() for undefined
Link, usePathname, redirect from createNavigation localePath(path, locale) for the href, routing.resolve(pathname) for the redirect target
getTranslations / useTranslations getEventI18n(event) on the server, useI18n().t in components
generateStaticParams for locales list the locale paths in the prerender option of dusk()
<link rel="alternate" hreflang> by hand i18nHeadTags(i18n, { path, origin }) through head

The URL side is plain functions, so it can be tested without a server. The same three schemes next-intl calls localePrefix behave like this:

The middleware, the hydration state that keeps the first client render identical to the server's, and the [locale] route are shown running against a real Dusk handler in Server rendering with Dusk.