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,…
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.