App Router không có i18n config sẵn như Pages Router; bạn dựng routing bằng dynamic segment [locale] + middleware để phát hiện/redirect ngôn ngữ.
app/[locale]/layout.tsx
app/[locale]/page.tsxMiddleware đọc Accept-Language hoặc cookie, redirect / → /vi. generateStaticParams liệt kê các locale để build tĩnh.
next-intl xây sẵn lớp này và bổ sung:
- Middleware locale-detection + routing cấu hình sẵn (prefix /vi, /en).
- API dịch dùng được trong Server Components: getTranslations() (async, server) và useTranslations() (client) — quan trọng vì hầu hết i18n lib cũ chỉ chạy client.
- Format số/ngày/plural theo Intl chuẩn, type-safe message keys.
import { useTranslations } from 'next-intl'
function Title() {
const t = useTranslations('Home')
return <h1>{t('title')}</h1>
}Lưu ý: giữ message ở Server Component khi có thể để không gửi toàn bộ từ điển xuống client — chỉ Client Component nào cần mới nhận phần của nó.
App Router has no built-in i18n config like Pages Router did; you build routing with a dynamic [locale] segment + middleware to detect/redirect the language.
app/[locale]/layout.tsx
app/[locale]/page.tsxMiddleware reads Accept-Language or a cookie and redirects / → /vi. generateStaticParams lists the locales for static builds.
next-intl builds this layer for you and adds:
- Pre-configured locale-detection middleware + routing (prefixes /vi, /en).
- Translation APIs usable in Server Components: getTranslations() (async, server) and useTranslations() (client) — important since most older i18n libs are client-only.
- Number/date/plural formatting via standard Intl, type-safe message keys.
import { useTranslations } from 'next-intl'
function Title() {
const t = useTranslations('Home')
return <h1>{t('title')}</h1>
}Note: keep messages in Server Components when possible so you don't ship the entire dictionary to the client — only the Client Components that need messages receive their slice.