Questa pagina ha un modello di applicazione disponibile.
Pose una domanda e ottieni un riassunto del documento facendo riferimento a questa pagina e al provider AI di tua scelta
Integrando il server MCP Intlayer al tuo assistente AI, puoi recuperare tutti i documenti direttamente da ChatGPT, DeepSeek, Cursor, VSCode, ecc.
Vedi la documentazione del server MCPIl contenuto di questa pagina è stato tradotto con un'IA.
Vedi l'ultima versione del contenuto originale in ingleseSe hai un’idea per migliorare questa documentazione, non esitare a contribuire inviando una pull request su GitHub.
Collegamento GitHub alla documentazioneCopia il Markdown del documento nella porta-documenti
Traduci il tuo sito Next.js 15 usando next-intl con Intlayer | Internazionalizzazione (i18n)
Questa guida ti accompagna attraverso le best practice di next-intl in un'app Next.js 15 (App Router) e mostra come sovrapporre Intlayer per una gestione robusta delle traduzioni e automazione.
Vedi il confronto in next-i18next vs next-intl vs Intlayer.
- Per i junior: segui le sezioni passo-passo per ottenere un'app multilingue funzionante.
- Per gli sviluppatori di livello medio: presta attenzione all'ottimizzazione del payload e alla separazione server/client.
- Per i senior: nota la generazione statica, il middleware, l'integrazione SEO e i hook di automazione.
Cosa tratteremo:
- Configurazione e struttura dei file
- Ottimizzazione del caricamento dei messaggi
- Uso di componenti client e server
- Metadata, sitemap, robots per SEO
- Middleware per il routing delle localizzazioni
- Aggiungere Intlayer sopra (CLI e automazione)
Configura la tua applicazione usando next-intl
Installa le dipendenze di next-intl -
npm install next-intl.├── locales│ ├── en│ │ ├── common.json│ │ └── about.json│ ├── fr│ │ ├── common.json│ │ └── about.json│ └── es│ ├── common.json│ └── about.json└── src ├── i18n.ts ├── middleware.ts ├── app │ └── [locale] │ ├── layout.tsx │ └── about │ └── page.tsx └── components ├── ClientComponentExample.tsx └── ServerComponent.tsxConfigurazione e caricamento dei contenuti
Carica solo i namespace di cui le tue rotte hanno bisogno e valida le localizzazioni in anticipo. Mantieni i componenti server sincroni quando possibile e invia al client solo i messaggi necessari.
Copiare il codice nella clipboard
import { getRequestConfig } from "next-intl/server";import { notFound } from "next/navigation";export const locales = ["en", "fr", "es"] as const;export const defaultLocale = "en" as const;async function loadMessages(locale: string) { // Carica solo i namespace di cui il tuo layout/pagine hanno bisogno const [common, about] = await Promise.all([ import(`../locales/${locale}/common.json`).then((m) => m.default), import(`../locales/${locale}/about.json`).then((m) => m.default), ]); return { common, about } as const;}export default getRequestConfig(async ({ locale }) => { if (!locales.includes(locale as any)) notFound(); return { messages: await loadMessages(locale), };});Copiare il codice nella clipboard
import type { ReactNode } from "react";import { locales } from "@/i18n";import { getLocaleDirection, unstable_setRequestLocale,} from "next-intl/server";export const dynamic = "force-static";export function generateStaticParams() { return locales.map((locale) => ({ locale }));}export default async function LocaleLayout({ children, params,}: { children: ReactNode; params: { locale: string };}) { const { locale } = params; // Imposta la locale attiva per questa richiesta di rendering server (RSC) unstable_setRequestLocale(locale); const dir = getLocaleDirection(locale); return ( <html lang={locale} dir={dir}> <body>{children}</body> </html> );}Copiare il codice nella clipboard
import { getTranslations, getMessages, getFormatter } from "next-intl/server";import { NextIntlClientProvider } from "next-intl";import pick from "lodash/pick";import ServerComponent from "@/components/ServerComponent";import ClientComponentExample from "@/components/ClientComponentExample";export const dynamic = "force-static";export default async function AboutPage({ params,}: { params: { locale: string };}) { const { locale } = params; // I messaggi vengono caricati lato server. Invia al client solo ciò che è necessario. const messages = await getMessages(); const clientMessages = pick(messages, ["common", "about"]); // Traduzioni/formatting strettamente lato server const tAbout = await getTranslations("about"); const tCounter = await getTranslations("about.counter"); const format = await getFormatter(); const initialFormattedCount = format.number(0); return ( <NextIntlClientProvider locale={locale} messages={clientMessages}> <main> <h1>{tAbout("title")}</h1> <ClientComponentExample /> <ServerComponent formattedCount={initialFormattedCount} label={tCounter("label")} increment={tCounter("increment")} /> </main> </NextIntlClientProvider> );}Utilizzo in un componente client
Prendiamo un esempio di un componente client che rende un contatore.
Traduzioni (forma riutilizzata; caricale nei messaggi next-intl come preferisci)
Copiare il codice nella clipboard
{ "counter": { "label": "Counter", "increment": "Increment" }}Copiare il codice nella clipboard
{ "counter": { "label": "Contatore", "increment": "Incrementa" }}Componente client
Copiare il codice nella clipboard
"use client";import React, { useState } from "react";import { useTranslations, useFormatter } from "next-intl";const ClientComponentExample = () => { // Ambito direttamente all'oggetto annidato const t = useTranslations("about.counter"); const format = useFormatter(); const [count, setCount] = useState(0); return ( <div> <p>{format.number(count)}</p> <button aria-label={t("label")} onClick={() => setCount((count) => count + 1)} > {t("increment")} </button> </div> );};Non dimenticare di aggiungere il messaggio "about" nei messaggi client della pagina (includi solo i namespace di cui il tuo client ha effettivamente bisogno).
Utilizzo in un componente server
Questo componente UI è un componente server e può essere renderizzato sotto un componente client (pagina → client → server). Mantienilo sincrono passando stringhe pre-calcolate.
Copiare il codice nella clipboard
type ServerComponentProps = { formattedCount: string; label: string; increment: string;};const ServerComponent = ({ formattedCount, label, increment,}: ServerComponentProps) => { return ( <div> <p>{formattedCount}</p> <button aria-label={label}>{increment}</button> </div> );};Note:
- Calcola formattedCount lato server (es. const initialFormattedCount = format.number(0)).
- Evita di passare funzioni o oggetti non serializzabili ai componenti server.
Copiare il codice nella clipboard
import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations } from "next-intl/server";function localizedPath(locale: string, path: string) { return locale === defaultLocale ? path : "/" + locale + path;}export async function generateMetadata({ params,}: { params: { locale: string };}): Promise<Metadata> { const { locale } = params; const t = await getTranslations({ locale, namespace: "about" }); const url = "/about"; const languages = Object.fromEntries( locales.map((locale) => [locale, localizedPath(locale, url)]) ); return { title: t("title"), description: t("description"), alternates: { canonical: localizedPath(locale, url), languages: { ...languages, "x-default": url }, }, };}// ... Resto del codice della paginaCopiare il codice nella clipboard
import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const formatterLocalizedPath = (locale: string, path: string) => locale === defaultLocale ? origin + path : origin + "/" + locale + path;export default function sitemap(): MetadataRoute.Sitemap { const aboutLanguages = Object.fromEntries( locales.map((l) => [l, formatterLocalizedPath(l, "/about")]) ); return [ { url: formatterLocalizedPath(defaultLocale, "/about"), lastModified: new Date(), changeFrequency: "mensile", priority: 0.7, alternates: { languages: aboutLanguages }, }, ];}Copiare il codice nella clipboard
import type { MetadataRoute } from "next";import { locales, defaultLocale } from "@/i18n";const origin = "https://example.com";const withAllLocales = (path: string) => [ path, ...locales .filter((locale) => locale !== defaultLocale) .map((locale) => "/" + locale + path),];export default function robots(): MetadataRoute.Robots { const disallow = [ ...withAllLocales("/dashboard"), ...withAllLocales("/admin"), ]; return { rules: { userAgent: "*", allow: ["/"], disallow }, host: origin, sitemap: origin + "/sitemap.xml", };}Middleware per il routing della localizzazione
Aggiungi un middleware per gestire il rilevamento della lingua e il routing:
Copiare il codice nella clipboard
import createMiddleware from "next-intl/middleware";import { locales, defaultLocale } from "@/i18n";export default createMiddleware({ locales: [...locales], defaultLocale, localeDetection: true,});export const config = { // Escludi API, internals di Next e risorse statiche matcher: ["/((?!api|_next|.*\\..*).*)"],};Best practices
- Imposta html lang e dir: In src/app/[locale]/layout.tsx, calcola dir tramite getLocaleDirection(locale) e imposta <html lang={locale} dir={dir}>.
- Dividi i messaggi per namespace: Organizza i JSON per locale e namespace (es. common.json, about.json).
- Minimizza il payload client: Nelle pagine, invia solo i namespace necessari a NextIntlClientProvider (es. pick(messages, ['common', 'about'])).
- Preferisci pagine statiche: Esporta export const dynamic = 'force-static' e genera parametri statici per tutte le locales.
- Componenti server sincroni: Passa stringhe precomputate (etichette tradotte, numeri formattati) invece di chiamate async o funzioni non serializzabili.
Implementa Intlayer sopra next-intl
Installa le dipendenze di intlayer:
npm install intlayer @intlayer/sync-json-plugin -DCrea il file di configurazione di intlayer:
Copiare il codice nella clipboard
import { type IntlayerConfig, Locales } from "intlayer";import { syncJSON } from "@intlayer/sync-json-plugin";const config: IntlayerConfig = { internationalization: { locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], defaultLocale: Locales.ENGLISH, }, ai: { apiKey: process.env.OPENAI_API_KEY, }, plugins: [ // Mantieni la struttura delle cartelle per namespace sincronizzata con Intlayer syncJSON({ source: ({ key, locale }) => `./locales/${locale}/${key}.json`, }), ],};export default config;Aggiungi gli script in package.json:
Copiare il codice nella clipboard
{ "scripts": { "i18n:fill": "intlayer fill", "i18n:test": "intlayer test" }}Note:
- intlayer fill: utilizza il tuo provider AI per completare le traduzioni mancanti in base alle localizzazioni configurate.
- intlayer test: verifica la presenza di traduzioni mancanti o non valide (usalo in CI).
Puoi configurare argomenti e provider; vedi Intlayer CLI.