Skip to main content

Hướng dẫn đầy đủ về quốc tế hóa Next.js

Thiết lập next-intl với App Router, cấu hình định tuyến theo locale và tự động dịch bằng AI.

1

Cài đặt next-intl

next-intl là một gói duy nhất xử lý định tuyến theo locale, tải thông báo và cung cấp hook dịch cho Next.js App Router.

Terminal
npm install next-intl
Tại sao nên chọn next-intl thay vì next-i18next? next-intl được xây dựng cho App Router và các thành phần máy chủ. next-i18next được thiết kế cho Pages Router nên chỉ hỗ trợ App Router ở mức hạn chế.
2

Tạo cấu hình yêu cầu i18n

Tạo hai tệp: src/i18n/request.ts để tải thông báo và src/i18n/routing.ts để định nghĩa locale. Các tệp này quy định cách next-intl phân giải thông báo và tuyến đường.

src/i18n/routing.ts
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
  locales: ['en', 'de', 'ja', 'es'],
  defaultLocale: 'en',
  localePrefix: 'as-needed',  // /about for en, /de/about for de
});

export const { Link, redirect, usePathname, useRouter } =
  createNavigation(routing);
Turbopack (trình đóng gói mặc định trong Next.js 15) yêu cầu experimental.turbo.resolveAlias trong next.config.js. Nếu thiếu, bạn sẽ gặp lỗi "Couldn't find next-intl config file".
3

Cấu hình middleware

Thêm middleware.ts để phát hiện locale, viết lại URL và chuyển hướng. Middleware chặn mọi yêu cầu và bảo đảm áp dụng đúng locale.

middleware.ts
// middleware.ts  <- Must be in project ROOT, not src/
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)'],
};
middleware.ts PHẢI nằm trong thư mục gốc của dự án, không phải trong src/. Đây là lỗi cấu hình next-intl phổ biến nhất.
4

Thiết lập cấu trúc thư mục [locale]

Chuyển các tuyến của app vào app/[locale]/. Thêm generateStaticParams để tạo trang cho từng locale khi dựng ứng dụng. Cách này tạo cấu trúc URL /en/about, /de/about, v.v.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { routing } from '@/i18n/routing';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}
Bạn PHẢI gọi setRequestLocale(locale) trong mọi page.tsx và layout.tsx có dùng bản dịch. Nếu thiếu, Next.js sẽ quay về kết xuất động và hiệu năng dựng ứng dụng giảm đáng kể.
5

Cập nhật layout gốc

Tải thông báo bằng getMessages() rồi truyền vào NextIntlClientProvider trong layout locale gốc. Đặt thuộc tính html lang theo tham số locale.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import { notFound } from 'next/navigation';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const { locale } = await params;
  if (!routing.locales.includes(locale as any)) notFound();

  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
NextIntlClientProvider yêu cầu prop locale tường minh. Nếu bỏ qua, các thành phần máy khách có thể phát sinh lỗi khó phát hiện.
6

Dùng bản dịch trong thành phần

Thành phần máy chủ dùng getTranslations (async, await). Thành phần máy khách dùng useTranslations (hook). Hãy chọn theo nơi thành phần được kết xuất — thành phần máy chủ không đưa bản dịch vào gói JavaScript.

app/[locale]/page.tsx
// Server Component (default)
import { getTranslations, setRequestLocale } from 'next-intl/server';

export default async function AboutPage({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  setRequestLocale(locale);
  const t = await getTranslations('AboutPage');

  return <h1>{t('title')}</h1>;
}

// Client Component ('use client')
'use client';
import { useTranslations } from 'next-intl';

export default function SearchBar() {
  const t = useTranslations('SearchBar');
  return <input placeholder={t('placeholder')} />;
}
Ưu tiên thành phần máy chủ cho nội dung đã dịch. Cách này không đưa chuỗi dịch vào gói JavaScript máy khách, giúp người dùng tải trang nhanh hơn.
7

Thêm SEO: metadata và hreflang

Dùng generateMetadata để tạo tiêu đề và mô tả trang theo từng locale. Thêm alternates.languages cho các thẻ hreflang để công cụ tìm kiếm phát hiện mọi phiên bản ngôn ngữ của từng trang.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx or any page.tsx
import { getTranslations } from 'next-intl/server';
import { routing } from '@/i18n/routing';

export async function generateMetadata({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'Metadata' });

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      languages: Object.fromEntries(
        routing.locales.map((l) => [l, `/${l}`])
      ),
    },
  };
}
Trên Vercel, quá trình dựng có thể tạo localhost làm URL chính tắc nếu chưa đặt metadataBase. Luôn đặt metadataBase trong layout gốc thành miền production của bạn.
8

Xử lý trang lỗi và trang không tìm thấy

error.tsx và not-found.tsx cần được xử lý riêng vì có thể kết xuất bên ngoài layout locale thông thường. not-found.tsx ở thư mục gốc cần cấu hình trình cung cấp i18n riêng để hiển thị thông báo lỗi đã bản địa hóa.

app/[locale]/error.tsx
// app/[locale]/error.tsx
'use client';
import { useTranslations } from 'next-intl';

export default function Error() {
  const t = useTranslations('Error');
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

// app/not-found.tsx (root level -- needs own provider)
import { routing } from '@/i18n/routing';

export default async function GlobalNotFound() {
  return (
    <html lang={routing.defaultLocale}>
      <body>
        <h1>404 - Page Not Found</h1>
      </body>
    </html>
  );
}
Next.js chỉ kết xuất trang 404 đã bản địa hóa khi mã của bạn gọi tường minh notFound(). Tuyến không xác định và không có trang khớp sẽ hiển thị trang 404 mặc định của Next.js chứ không phải phiên bản đã bản địa hóa.
9

Tự động hóa bản dịch

Sau khi hoàn tất thiết lập i18n, hãy dùng AI để dịch tệp thông báo ngay trong IDE hoặc dùng i18n Agent CLI trong quy trình CI/CD để tự động dịch mỗi lần triển khai.

Terminal
# In your IDE, ask your AI assistant:
> Translate messages/en.json to German, Japanese, and Spanish

✓ messages/de.json created (1.1s)
✓ messages/ja.json created (1.4s)
✓ messages/es.json created (1.0s)
Dùng next-intl-localechain để chuyển dự phòng locale thông minh — người dùng pt-BR sẽ thấy bản dịch pt-PT thay vì quay về tiếng Anh khi không có tiếng Bồ Đào Nha Brazil.

Tự động kiểm soát chất lượng bản dịch

Dùng i18n-validate để phát hiện khóa thiếu và placeholder hỏng trước khi phát hành. Dùng i18n-pseudo để kiểm thử UI bằng bản dịch giả trước khi có bản dịch thật.

Công cụ mã nguồn mở cho i18n Next.js

Các gói mã nguồn mở này giải quyết những vướng mắc phổ biến trong quy trình quốc tế hóa Next.js.

next-intl-localechain

next-intl tiêu chuẩn chuyển thẳng về locale mặc định khi thiếu bản dịch. Người dùng tiếng Bồ Đào Nha Brazil sẽ thấy tiếng Anh dù có bản dịch pt-PT hoàn toàn phù hợp. next-intl-localechain bổ sung chuỗi dự phòng thông minh — công cụ hợp nhất sâu bản dịch từ các locale liên quan để người dùng từng khu vực luôn thấy bản dịch gần nhất hiện có.

src/i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { withLocaleChain } from 'next-intl-localechain';

export default getRequestConfig(withLocaleChain({
  loadMessages: (locale) =>
    import(`../../messages/${locale}.json`).then(m => m.default),
  defaultLocale: 'en'
}));
Tự động hợp nhất sâu bản dịch trong các chuỗi locale
Tích hợp sẵn chuỗi cho tiếng Bồ Đào Nha, Tây Ban Nha, Pháp, Đức và nhiều ngôn ngữ khác
Bỏ qua tệp thông báo bị thiếu mà không gây lỗi
Thiết lập trong một dòng — bao bọc getRequestConfig hiện có
Xem trên GitHub

@i18n-agent/cli

Công cụ dòng lệnh giúp dịch tệp thông báo Next.js mà không cần rời terminal. Bạn có thể dịch trực tiếp, kiểm tra trạng thái tác vụ và tải kết quả xuống. Công cụ hoạt động trong quy trình CI/CD với cơ chế xác thực bằng khóa API để tự động hóa hoàn toàn quy trình bản địa hóa.

Terminal
# Install the CLI
npm install -g @i18n-agent/cli

# Authenticate
i18nagent login

# Translate your message files
i18nagent translate ./messages/en.json --lang de,ja,es

# Or use in CI/CD with an API key
export I18N_AGENT_API_KEY=your-key-here
i18nagent translate ./messages/en.json --lang de,ja,es
Dịch JSON, YAML, PO và các định dạng tệp i18n khác từ terminal
Sẵn sàng cho CI/CD — xác thực qua biến môi trường để tự động hóa quy trình
Theo dõi trạng thái tác vụ, tiếp tục tác vụ lỗi và tải kết quả xuống
Đầu ra JSON cho máy đọc để viết tập lệnh và tự động hóa
Xem trên GitHub

Lỗi thường gặp

"Unable to find next-intl locale"

Middleware không khớp với yêu cầu. Hãy kiểm tra: middleware.ts có nằm ở thư mục gốc dự án không? Mẫu matcher đã loại trừ đúng các tệp tĩnh chưa? Cấu hình định tuyến có chứa locale không?

Kết xuất động ngoài dự kiến

Một trang hoặc layout đang thiếu setRequestLocale(locale). Nếu thiếu, next-intl dùng header/cookie để phát hiện locale, buộc ứng dụng kết xuất động và ngăn tạo tĩnh.

Tuyến song song xung đột với i18n

Tuyến song song (@modal) và tuyến chặn ((.)photo) có các vấn đề tương thích đã biết với phân đoạn động [locale]. Hãy dùng định tuyến dựa trên middleware để khắc phục các mẫu định tuyến nâng cao này.

Chuyển ngôn ngữ làm mất tuyến hiện tại

Khi chuyển locale, hãy giữ nguyên pathname hiện tại bằng usePathname() và chỉ thay phân đoạn locale. Hãy thận trọng với tham số tuyến động — bạn cần phân giải lại chúng cho locale mới.

Cấu trúc tệp đề xuất

Project Structure
my-nextjs-app/
├── middleware.ts              # Locale routing (project root!)
├── next.config.mjs
├── messages/
│   ├── en.json                # Source messages
│   ├── de.json
│   └── ja.json
├── src/
│   ├── i18n/
│   │   ├── request.ts         # Message loading config
│   │   └── routing.ts         # Locale definitions
│   └── app/
│       └── [locale]/
│           ├── layout.tsx     # Root locale layout
│           ├── page.tsx       # Home page
│           ├── error.tsx      # Localized error page
│           ├── not-found.tsx  # Localized 404
│           └── about/
│               └── page.tsx
└── package.json

Dùng thử i18n Agent ngay

Thả tệp bản dịch của bạn vào đây

JSON, YAML, PO, XML, CSV, Markdown, Properties

hoặc nhấp để duyệt

Ngôn ngữ đích

Không cần đăng kýBáo giá tức thì

Chuyển dự phòng locale bằng next-intl-localechain

Khi locale khu vực như pt-BR thiếu khóa dịch, next-intl chuyển thẳng về locale mặc định thay vì kiểm tra locale cha pt trước.

Terminal
npm install next-intl-localechain
Configuration
import { withLocaleChain } from 'next-intl-localechain';

export default withLocaleChain({
  fallbacks: {
    'pt-BR': ['pt', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
  },
  defaultLocale: 'en',
  loadMessages: (locale) => import(`./messages/${locale}.json`),
});

Xem Hướng dẫn chuyển dự phòng locale để biết danh sách đầy đủ các framework được hỗ trợ và 75 chuỗi tích hợp sẵn. Learn more →

Câu hỏi thường gặp