
L'internazionalizzazione è ormai un requisito di base nei nostri progetti. All'interno dei nostri applicativi utilizziamo next-intl per orchestrare lingua, caricamento dei messaggi e preferenze dell'utente. Di seguito racconto l'architettura completa, dal boot alla traduzione dei componenti riutilizzabili.
1// apps/app/i18n.ts2import { getRequestConfig } from "next-intl/server";34export const locales = ["en"] as const;5export const defaultLocale = "en";6export const localePrefix = "never";78export default getRequestConfig(async ({ requestLocale }) => {9 let locale = await requestLocale;1011 if (!locales.includes(locale)) {12 locale = defaultLocale;13 }1415 return {16 locale,17 messages: (await import(`./messages/${locale}.json`)).default,18 };19});
Punti chiave:
1// apps/app/next.config.js2import createIntlPlugin from "next-intl/plugin";34const withNextIntl = createIntlPlugin("./i18n.ts");56export default withNextIntl({7 output: "standalone",8});
Il plugin registra i parametri di next-intl lato build, abilita l'estrazione dei messaggi e rende trasparente l'uso di getLocale/getMessages nei server component.
1// apps/app/middleware.ts2import { type NextRequest } from "next/server";3import createMiddleware from "next-intl/middleware";4import { locales, defaultLocale, localePrefix } from "./i18n";56const intlMiddleware = createMiddleware({7 locales,8 defaultLocale,9 localePrefix,10});1112export default function middleware(req: NextRequest) {13 return intlMiddleware(req);14}
Il middleware:
1// apps/app/app/layout.tsx2import { NextIntlClientProvider } from "next-intl";3import { getLocale, getMessages } from "next-intl/server";45export default async function RootLayout({ children }: { children: React.ReactNode }) {6 const locale = await getLocale();7 const messages = await getMessages();89 return (10 <html lang={locale} suppressHydrationWarning>11 <ThemeLayout isMobile={await isMobileDevice()}>12 <body>13 <NextIntlClientProvider14 locale={locale}15 messages={messages}16 >17 <RootLayoutClient>18 {children}19 </RootLayoutClient>20 </NextIntlClientProvider>21 </body>22 </ThemeLayout>23 </html>24 );25}
Recupero le informazioni di lingua direttamente lato server e le inietto nel provider. Qualsiasi componente client (anche in pacchetti riutilizzabili) può così chiamare useTranslations senza boilerplate aggiuntivo.
1// apps/app/messages/en.json2{3 "dashboard": {4 "machinesCount": "{count} machines",5 "dateRange": {6 "last-hour": "Last hour",7 "week-to-date": "Week to date"8 }9 },10 "agentCard": {11 "selectedVariable_one": "1 selected variable",12 "selectedVariable_other": "{count} selected variables"13 }14}
Strategie adottate:
1// apps/app/app/[locale]/(auth)/login/page.tsx2"use client";3import { useTranslations } from "next-intl";45export default function LoginPage() {6 const t = useTranslations("login");78 return (9 <form>10 <label>{t("emailLabel")}</label>11 <input placeholder="name@example.com" />12 <button type="submit">{t("loginButton")}</button>13 </form>14 );15}
useTranslations accetta il namespace (qui login) e restituisce un resolver tipizzato (grazie al literal type dell'oggetto JSON). Per messaggi parametrizzati basta passare un oggetto, es. t("machinesCount", { count }).
1// apps/app/app/not-found.tsx2import { getTranslations } from "next-intl/server";34export default async function NotFound() {5 const t = await getTranslations("notFound");6 return (7 <section>8 <h2>{t("title")}</h2>9 <p>{t("description")}</p>10 </section>11 );12}
getTranslations funziona in qualsiasi server component o azione, così posso localizzare pagine d'errore, metadata o risposte API senza accedere a hook client-side.
Anche un pacchetto UI condiviso (packages/ui/src/user/Component.tsx) può usare useTranslations("user"). Il provider nel layout principale garantisce che i testi siano disponibili indipendentemente da dove venga montato il componente: è essenziale in un monorepo con design system riutilizzabile.
La combinazione Next.js + next-intl consente di controllare il locale a livello centrale, distribuire le traduzioni dinamicamente e riutilizzarle in tutto il monorepo. La struttura a cataloghi JSON e il provider globale semplificano l'aggiunta di altre lingue in futuro.