Nhận thông báo về các bản phát hành sắp tới của Intlayer
    Ngày tạo:2025-08-23Cập nhật lần cuối:2025-09-29

    next-i18next VS next-intl VS intlayer | Quốc tế hóa Next.js (i18n)

    next-i18next VS next-intl VS intlayer

    Hãy cùng xem xét những điểm tương đồng và khác biệt giữa ba lựa chọn i18n cho Next.js: next-i18next, next-intl và Intlayer.

    Đây không phải là một hướng dẫn đầy đủ. Đây là một so sánh giúp bạn lựa chọn.

    Chúng tôi tập trung vào Next.js 13+ App Router (với React Server Components) và đánh giá:

    tóm tắt: Cả ba đều có thể địa phương hóa một ứng dụng Next.js. Nếu bạn muốn nội dung theo phạm vi component, kiểu TypeScript nghiêm ngặt, kiểm tra khóa thiếu trong thời gian build, từ điển được tree-shaking, và App Router + trợ giúp SEO hàng đầu, thì Intlayer là lựa chọn toàn diện và hiện đại nhất.
    Một sự nhầm lẫn thường gặp của các nhà phát triển là nghĩ rằng next-intl là phiên bản Next.js của react-intl. Không phải vậy, next-intl được duy trì bởi Amann, trong khi react-intl được duy trì bởi FormatJS.

    Tóm tắt ngắn gọn

    • next-intl - Định dạng thông điệp nhẹ, đơn giản với hỗ trợ Next.js vững chắc. Các catalog tập trung là phổ biến; trải nghiệm nhà phát triển (DX) đơn giản, nhưng an toàn và bảo trì quy mô lớn phần lớn vẫn là trách nhiệm của bạn.
    • next-i18next - i18next trong bộ dạng Next.js. Hệ sinh thái trưởng thành và các tính năng qua plugin (ví dụ: ICU), nhưng cấu hình có thể dài dòng và các catalog có xu hướng tập trung khi dự án phát triển.
    • Intlayer - Mô hình nội dung tập trung vào component cho Next.js, kiểu TypeScript nghiêm ngặt, kiểm tra trong thời gian build, tree-shaking, middleware tích hợp & trợ giúp SEO, tùy chọn Visual Editor/CMS, và dịch thuật hỗ trợ AI.

    Library GitHub Stars Total Commits Last Commit First Version NPM Version NPM Downloads
    aymericzip/intlayer GitHub Repo stars GitHub commit activity Last Commit April 2024 npm npm downloads
    amannn/next-intl GitHub Repo stars GitHub commit activity Last Commit Nov 2020 npm npm downloads
    i18next/i18next GitHub Repo stars GitHub commit activity Last Commit Jan 2012 npm npm downloads
    i18next/next-i18next GitHub Repo stars GitHub commit activity Last Commit Nov 2018 npm npm downloads
    Các huy hiệu được cập nhật tự động. Các ảnh chụp nhanh sẽ thay đổi theo thời gian.

    So sánh Tính năng Song song (Tập trung vào Next.js)

    Tính năng next-intlayer (Intlayer) next-intl next-i18next
    Bản dịch Gần Thành phần ✅ Có, nội dung được đặt gần với từng thành phần ❌ Không ❌ Không
    Tích hợp TypeScript ✅ Nâng cao, tự động tạo kiểu nghiêm ngặt ✅ Tốt ⚠️ Cơ bản
    Phát hiện bản dịch thiếu ✅ Tô sáng lỗi TypeScript và cảnh báo/lỗi trong thời gian biên dịch ⚠️ Dự phòng thời gian chạy ⚠️ Dự phòng thời gian chạy
    Nội dung phong phú (JSX/Markdown/components) ✅ Hỗ trợ trực tiếp ❌ Không thiết kế cho các node phong phú ⚠️ Hạn chế
    Dịch thuật hỗ trợ AI ✅ Có, hỗ trợ nhiều nhà cung cấp AI. Có thể sử dụng bằng API key của bạn. Xem xét ngữ cảnh ứng dụng và phạm vi nội dung ❌ Không ❌ Không
    Trình chỉnh sửa trực quan ✅ Có, Trình chỉnh sửa trực quan cục bộ + CMS tùy chọn; có thể tách nội dung codebase ra ngoài; có thể nhúng ❌ Không / có sẵn qua các nền tảng bản địa hóa bên ngoài ❌ Không / có sẵn qua các nền tảng bản địa hóa bên ngoài
    Định tuyến bản địa hóa ✅ Có, hỗ trợ các đường dẫn bản địa hóa sẵn có (hoạt động với Next.js & Vite) ✅ Tích hợp sẵn, App Router hỗ trợ phân đoạn [locale] ✅ Tích hợp sẵn
    Tạo Đường Dẫn Động ✅ Có ✅ Có ✅ Có
    Phân số nhiều ✅ Mẫu dựa trên liệt kê ✅ Tốt ✅ Tốt
    Định dạng (ngày tháng, số, tiền tệ) ✅ Bộ định dạng tối ưu (Intl ở tầng dưới) ✅ Tốt (trợ giúp Intl) ✅ Tốt (trợ giúp Intl)
    Định dạng nội dung ✅ .tsx, .ts, .js, .json, .md, .txt, (.yaml đang phát triển) ✅ .json, .js, .ts ⚠️ .json
    Hỗ trợ ICU ⚠️ Đang phát triển ✅ Có ⚠️ Qua plugin (i18next-icu)
    Trợ giúp SEO (hreflang, sitemap) ✅ Công cụ tích hợp sẵn: trợ giúp cho sitemap, robots.txt, metadata ✅ Tốt ✅ Tốt
    Hệ sinh thái / Cộng đồng ⚠️ Nhỏ hơn nhưng đang phát triển nhanh và phản ứng tốt ✅ Tốt ✅ Tốt
    Kết xuất phía máy chủ & Thành phần máy chủ ✅ Có, tối ưu cho SSR / React Server Components ⚠️ Hỗ trợ ở cấp trang nhưng cần truyền các hàm t trên cây thành phần cho các thành phần máy chủ con ⚠️ Hỗ trợ ở cấp trang nhưng cần truyền các hàm t trên cây thành phần cho các thành phần máy chủ con
    Tree-shaking (chỉ tải nội dung được sử dụng) ✅ Có, theo từng component tại thời điểm build thông qua các plugin Babel/SWC ⚠️ Một phần ⚠️ Một phần
    Tải lười (Lazy loading) ✅ Có, theo từng locale / từng từ điển ✅ Có (theo từng route/theo từng locale), cần quản lý namespace ✅ Có (theo từng route/theo từng locale), cần quản lý namespace
    Loại bỏ nội dung không sử dụng ✅ Có, theo từ điển tại thời điểm build ❌ Không, có thể quản lý thủ công bằng cách quản lý namespace ❌ Không, có thể quản lý thủ công bằng cách quản lý namespace
    Quản lý dự án lớn ✅ Khuyến khích mô-đun, phù hợp với hệ thống thiết kế ✅ Mô-đun với thiết lập ✅ Mô-đun với thiết lập
    Kiểm tra bản dịch thiếu (CLI/CI) ✅ CLI: npx intlayer content test (kiểm tra thân thiện với CI) ⚠️ Không tích hợp sẵn; tài liệu đề xuất npx @lingual/i18n-check ⚠️ Không tích hợp sẵn; dựa vào công cụ i18next / runtime saveMissing

    Giới thiệu

    Next.js cung cấp hỗ trợ tích hợp cho routing quốc tế hóa (ví dụ: các đoạn locale). Nhưng tính năng đó không tự động thực hiện việc dịch thuật. Bạn vẫn cần một thư viện để hiển thị nội dung đã được bản địa hóa cho người dùng.

    Có nhiều thư viện i18n tồn tại, nhưng trong thế giới Next.js hiện nay, có ba thư viện đang được ưa chuộng: next-i18next, next-intl và Intlayer.


    Kiến trúc & khả năng mở rộng

    • next-intl / next-i18next: Mặc định sử dụng danh mục tập trung theo từng locale (cộng với namespace trong i18next). Hoạt động tốt ban đầu, nhưng thường trở thành một bề mặt chia sẻ lớn với sự phụ thuộc ngày càng tăng và sự thay đổi nhiều của các key.
    • Intlayer: Khuyến khích sử dụng từ điển theo từng component (hoặc theo từng tính năng) đặt cùng vị trí với mã nguồn mà chúng phục vụ. Điều này giảm tải nhận thức, dễ dàng sao chép/di chuyển các phần UI, và giảm xung đột giữa các nhóm. Nội dung không sử dụng cũng dễ dàng được phát hiện và loại bỏ.

    Tại sao điều này quan trọng: Trong các codebase lớn hoặc các thiết lập hệ thống thiết kế, nội dung mô-đun có khả năng mở rộng tốt hơn so với các danh mục đơn khối.


    Kích thước gói & phụ thuộc

    Sau khi xây dựng ứng dụng, bundle là JavaScript mà trình duyệt sẽ tải để hiển thị trang. Do đó, kích thước bundle rất quan trọng đối với hiệu suất ứng dụng.

    Có hai thành phần quan trọng trong bối cảnh bundle của ứng dụng đa ngôn ngữ:

    • Mã ứng dụng
    • Nội dung được trình duyệt tải

    Mã ứng dụng

    Tầm quan trọng của mã ứng dụng trong trường hợp này là rất nhỏ. Cả ba giải pháp đều hỗ trợ tree-shaking, nghĩa là các phần mã không sử dụng sẽ không được bao gồm trong bundle.

    Dưới đây là so sánh kích thước bundle JavaScript được trình duyệt tải cho một ứng dụng đa ngôn ngữ với ba giải pháp.

    Nếu chúng ta không cần bất kỳ bộ định dạng nào trong ứng dụng, danh sách các hàm được xuất sau khi tree-shaking sẽ là:

    • next-intlayer: useIntlayer, useLocale, NextIntlClientProvider, (Kích thước bundle là 180.6 kB -> 78.6 kB (gzip))
    • next-intl: useTranslations, useLocale, NextIntlClientProvider, (Kích thước bundle là 101.3 kB -> 31.4 kB (gzip))
    • next-i18next: useTranslation, useI18n, I18nextProvider, (Kích thước bundle là 80.7 kB -> 25.5 kB (gzip))

    Các hàm này chỉ là các wrapper quanh React context/state, vì vậy tổng ảnh hưởng của thư viện i18n lên kích thước bundle là rất nhỏ.

    Intlayer hơi lớn hơn một chút so với next-intlnext-i18next vì nó bao gồm nhiều logic hơn trong hàm useIntlayer. Điều này liên quan đến tích hợp markdown và intlayer-editor.

    Nội dung và Bản dịch

    Phần này thường bị các nhà phát triển bỏ qua, nhưng hãy xem xét trường hợp một ứng dụng gồm 10 trang với 10 ngôn ngữ. Giả sử mỗi trang chứa 100% nội dung duy nhất để đơn giản hóa phép tính (trong thực tế, nhiều nội dung bị trùng lặp giữa các trang, ví dụ: tiêu đề trang, đầu trang, chân trang, v.v.).

    Một người dùng muốn truy cập trang /fr/about sẽ tải nội dung của một trang trong một ngôn ngữ nhất định. Bỏ qua việc tối ưu hóa nội dung có nghĩa là tải tới 8.200% ((1 + (((10 trang - 1) × (10 ngôn ngữ - 1)))) × 100) nội dung của ứng dụng một cách không cần thiết. Bạn có thấy vấn đề không? Ngay cả khi nội dung này chỉ là văn bản, và trong khi bạn có thể ưu tiên tối ưu hóa hình ảnh trên trang web của mình, bạn đang gửi đi nội dung thừa khắp toàn cầu và khiến máy tính của người dùng phải xử lý nó một cách vô ích.

    Hai vấn đề quan trọng:

    • Phân tách theo route:

      Nếu tôi đang ở trang /about, tôi không muốn tải nội dung của trang /home
    • Phân tách theo locale:

      Nếu tôi đang ở trang /fr/about, tôi không muốn tải nội dung của trang /en/about

    Một lần nữa, cả ba giải pháp đều nhận thức được những vấn đề này và cho phép quản lý các tối ưu hóa này. Sự khác biệt giữa ba giải pháp là trải nghiệm nhà phát triển (DX).

    next-intlnext-i18next sử dụng phương pháp tập trung để quản lý bản dịch, cho phép phân tách JSON theo locale và theo các tệp con. Trong next-i18next, chúng ta gọi các tệp JSON là 'namespaces'; next-intl cho phép khai báo các messages. Trong intlayer, chúng ta gọi các tệp JSON là 'dictionaries'.

    • Trong trường hợp của next-intl, giống như next-i18next, nội dung được tải ở cấp độ trang/bố cục, sau đó nội dung này được tải vào một context provider. Điều này có nghĩa là nhà phát triển phải tự quản lý các file JSON sẽ được tải cho mỗi trang.
    Trong thực tế, điều này ngụ ý rằng các nhà phát triển thường bỏ qua tối ưu hóa này, ưu tiên tải toàn bộ nội dung trong context provider của trang để đơn giản.
    • Trong trường hợp của intlayer, toàn bộ nội dung được tải trong ứng dụng. Sau đó một plugin (@intlayer/babel / @intlayer/swc) sẽ đảm nhiệm việc tối ưu gói bằng cách chỉ tải nội dung được sử dụng trên trang. Do đó, nhà phát triển không cần phải tự quản lý các từ điển sẽ được tải. Điều này cho phép tối ưu tốt hơn, dễ bảo trì hơn và giảm thời gian phát triển.

    Khi ứng dụng phát triển (đặc biệt khi nhiều nhà phát triển cùng làm việc trên ứng dụng), việc quên xóa nội dung không còn sử dụng trong các tệp JSON là điều thường gặp.

    Lưu ý rằng tất cả JSON đều được tải trong mọi trường hợp (next-intl, next-i18next, intlayer).

    Đây là lý do tại sao cách tiếp cận của Intlayer hiệu quả hơn: nếu một component không còn được sử dụng, từ điển của nó sẽ không được tải vào bundle.

    Cách thư viện xử lý fallback cũng rất quan trọng. Giả sử ứng dụng mặc định là tiếng Anh, và người dùng truy cập trang /fr/about. Nếu bản dịch tiếng Pháp bị thiếu, chúng ta sẽ sử dụng fallback tiếng Anh.

    Trong trường hợp của next-intlnext-i18next, thư viện yêu cầu tải JSON liên quan đến locale hiện tại, nhưng cũng phải tải JSON của locale dự phòng. Do đó, giả sử tất cả nội dung đã được dịch, mỗi trang sẽ tải 100% nội dung không cần thiết. Ngược lại, intlayer xử lý fallback ngay trong thời gian xây dựng từ điển. Vì vậy, mỗi trang sẽ chỉ tải nội dung được sử dụng.

    Lưu ý: Để tối ưu gói bundle sử dụng intlayer, bạn cần thiết lập tùy chọn importMode: 'dynamic' trong file intlayer.config.ts của bạn. Và đảm bảo plugin @intlayer/babel / @intlayer/swc đã được cài đặt (được cài đặt mặc định khi sử dụng vite-intlayer).

    Dưới đây là ví dụ về tác động của việc tối ưu kích thước bundle sử dụng intlayer trong ứng dụng vite + react:

    Gói tối ưu hóa Gói không tối ưu hóa
    gói tối ưu hóa gói không tối ưu hóa

    TypeScript & an toàn

    next-i18next

    • Kiểu cơ bản cho các hook. kiểu khóa nghiêm ngặt yêu cầu công cụ/cấu hình bổ sung.

    next-intl

    • Hỗ trợ TypeScript vững chắc, nhưng các khóa không được kiểu nghiêm ngặt theo mặc định. bạn sẽ duy trì các mẫu an toàn một cách thủ công.

    intlayer

    • Tạo kiểu nghiêm ngặt từ nội dung của bạn. Tự động hoàn thành trong IDElỗi thời gian biên dịch phát hiện lỗi chính tả và khóa thiếu trước khi triển khai.

    Tại sao điều này quan trọng: Kiểu mạnh giúp chuyển lỗi sang bên trái (CI/build) thay vì bên phải (runtime).


    Xử lý dịch thiếu

    next-i18next

    • Dựa vào fallback thời gian chạy. Build không bị lỗi.

    next-intl

    • Dựa vào fallback thời gian chạy. Build không bị lỗi.

    intlayer

    • Phát hiện trong thời gian build với cảnh báo/lỗi cho các locale hoặc key bị thiếu.

    Tại sao điều này quan trọng: Phát hiện thiếu sót trong quá trình build giúp ngăn chặn các chuỗi 'undefined' xuất hiện trong môi trường production.


    Định tuyến, middleware & chiến lược URL

    next-i18next

    • Cho phép định tuyến theo ngôn ngữ. Nhưng middleware không được tích hợp sẵn.

    next-intl

    • Cho phép định tuyến theo ngôn ngữ.
    • Cung cấp middleware.

    intlayer

    • Cho phép định tuyến theo ngôn ngữ.
    • Cung cấp middleware.

    Tại sao điều này quan trọng: Giúp cải thiện SEO và khả năng khám phá, cũng như trải nghiệm người dùng.


    Đồng bộ với Server Components (RSC)

    next-i18next

    • Hỗ trợ các server component cho trang và layout.
    • Không cung cấp API đồng bộ cho các thành phần server con.

    next-intl

    • Hỗ trợ các thành phần server trang và bố cục.
    • Không cung cấp API đồng bộ cho các thành phần server con.

    intlayer

    • Hỗ trợ các thành phần server trang và bố cục.
    • Cung cấp API đồng bộ cho các thành phần server con.

    Tại sao điều này quan trọng: Hỗ trợ thành phần server là một tính năng then chốt của Next.js 13+, giúp cải thiện hiệu suất. Việc truyền props như locale hoặc hàm t từ thành phần cha xuống các thành phần server con làm cho các thành phần của bạn kém tái sử dụng hơn.


    Tích hợp với các nền tảng bản địa hóa (TMS)

    Các tổ chức lớn thường dựa vào Hệ thống Quản lý Dịch thuật (TMS) như Crowdin, Phrase, Lokalise, Localizely, hoặc Localazy.

    • Tại sao các công ty quan tâm

      • Hợp tác & vai trò: Có nhiều bên tham gia: nhà phát triển, quản lý sản phẩm, người dịch, người đánh giá, đội ngũ marketing.
      • Quy mô & hiệu quả: dịch thuật liên tục, đánh giá trong ngữ cảnh.
    • next-intl / next-i18next

      • Thường sử dụng danh mục JSON tập trung, nên việc xuất/nhập với TMS rất đơn giản.
      • Hệ sinh thái trưởng thành và có ví dụ/tích hợp cho các nền tảng trên.
    • Intlayer

      • Khuyến khích từ điển phân tán, theo từng component và hỗ trợ nội dung TypeScript/TSX/JS/JSON/MD.
      • Điều này cải thiện tính mô-đun trong mã, nhưng có thể làm cho việc tích hợp TMS dạng plug-and-play trở nên khó khăn hơn khi một công cụ mong đợi các tệp JSON phẳng, tập trung.
      • Intlayer cung cấp các lựa chọn thay thế: dịch thuật hỗ trợ AI (sử dụng khóa nhà cung cấp của bạn), một Trình chỉnh sửa trực quan/CMS, và các quy trình làm việc CLI/CI để phát hiện và điền trước các khoảng trống.
    Lưu ý: next-intli18next cũng chấp nhận các catalog TypeScript. Nếu nhóm của bạn lưu trữ các thông điệp trong các tệp .ts hoặc phân quyền chúng theo tính năng, bạn có thể gặp phải sự cản trở tương tự với TMS. Tuy nhiên, nhiều thiết lập next-intl vẫn tập trung trong thư mục locales/, điều này giúp việc chuyển đổi sang JSON cho TMS dễ dàng hơn một chút.

    Trải nghiệm nhà phát triển

    Phần này thực hiện so sánh sâu giữa ba giải pháp. Thay vì xem xét các trường hợp đơn giản, như được mô tả trong tài liệu 'bắt đầu' cho mỗi giải pháp, chúng ta sẽ xem xét một trường hợp sử dụng thực tế, tương tự hơn với một dự án thực tế.

    Cấu trúc ứng dụng

    Cấu trúc ứng dụng rất quan trọng để đảm bảo khả năng bảo trì tốt cho codebase của bạn.

    .├── i18n.ts├── locales│   ├── en│   │  ├── home.json│   │  └── navbar.json│   ├── fr│   │  ├── home.json│   │  └── navbar.json│   └── es│      ├── home.json│      └── navbar.json└── src    ├── middleware.ts    ├── app    │   ├── i18n    │   │   └── server.ts    │   └── [locale]    │       └── home.tsx    └── components        └── Navbar            └── index.tsx

    So sánh

    • next-intl / next-i18next: Danh mục tập trung (JSON; namespaces/messages). Cấu trúc rõ ràng, tích hợp tốt với các nền tảng dịch thuật, nhưng có thể dẫn đến nhiều chỉnh sửa chéo file khi ứng dụng phát triển.
    • Intlayer: Từ điển .content.{ts|js|json} theo từng component, đặt cùng vị trí với component. Dễ dàng tái sử dụng component và suy luận cục bộ; thêm các file và dựa vào công cụ xây dựng thời gian biên dịch.

    Cài đặt và Tải Nội dung

    Như đã đề cập trước đó, bạn phải tối ưu cách mỗi file JSON được nhập vào code của bạn. Cách thư viện xử lý việc tải nội dung rất quan trọng.

    src/i18n.ts
    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) {  // Chỉ tải các namespace mà layout/trang của bạn cần  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),  };});
    src/app/[locale]/layout.tsx
    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: Promise<{ locale: string }>;}) {  const { locale } = await params;  // Đặt locale yêu cầu đang hoạt động cho lần render server này (RSC)  unstable_setRequestLocale(locale);  const dir = getLocaleDirection(locale);  return (    <html lang={locale} dir={dir}>      <body>{children}</body>    </html>  );}
    src/app/[locale]/about/page.tsx
    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: Promise<{ locale: string }>;}) {  const { locale } = await params;  // Các thông điệp được tải phía server. Chỉ đẩy những gì cần thiết cho client.  const messages = await getMessages();  const clientMessages = pick(messages, ["common", "about"]);  // Dịch/định dạng nghiêm ngặt phía 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>  );}

    So sánh

    Cả ba đều hỗ trợ tải nội dung và providers theo từng locale.

    • Với next-intl/next-i18next, bạn thường tải các messages/namespace được chọn theo từng route và đặt providers ở nơi cần thiết.

    • Với Intlayer, thêm phân tích tại thời điểm build để suy luận việc sử dụng, điều này có thể giảm thiểu việc cấu hình thủ công và cho phép sử dụng một provider gốc duy nhất.

    Chọn giữa kiểm soát rõ ràng và tự động hóa dựa trên sở thích của nhóm.

    Sử dụng trong một component phía client

    Hãy lấy ví dụ về một component phía client hiển thị bộ đếm.

    Bản dịch (dạng dữ liệu được tái sử dụng; tải chúng vào các thông điệp next-intl theo cách bạn muốn)

    locales/vi/about.json
    {  "counter": {    "label": "Bộ đếm",    "increment": "Tăng"  }}
    locales/fr/about.json
    {  "counter": {    "label": "Compteur",    "increment": "Incrémenter"  }}

    Component phía client

    src/components/ClientComponentExample.tsx
    "use client";import React, { useState } from "react";import { useTranslations, useFormatter } from "next-intl";const ClientComponentExample = () => {  // Phạm vi trực tiếp đến đối tượng lồng nhau  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>  );};
    Đừng quên thêm thông điệp "about" vào thông điệp client của trang

    So sánh

    • Định dạng số

      • next-i18next: không có useNumber; sử dụng Intl.NumberFormat (hoặc i18next-icu).
      • next-intl: useFormatter().number(value).
      • Intlayer: tích hợp sẵn useNumber().
    • Khóa (Keys)

      • Giữ cấu trúc lồng nhau (about.counter.label) và phạm vi hook của bạn tương ứng (useTranslation("about") + t("counter.label") hoặc useTranslations("about.counter") + t("label")).
    • Vị trí file

      • next-i18next yêu cầu JSON ở public/locales/{lng}/{ns}.json.
      • next-intl linh hoạt; tải thông điệp theo cách bạn cấu hình.
      • Intlayer lưu nội dung trong các từ điển TS/JS và giải quyết theo key.

    Sử dụng trong một server component

    Chúng ta sẽ lấy ví dụ về một component giao diện người dùng (UI). Component này là một server component, và nên có khả năng được chèn như một con của client component. (page (server component) -> client component -> server component). Vì component này có thể được chèn như một con của client component, nó không thể là async.

    src/components/ServerComponent.tsx
    type ServerComponentProps = {  t: (key: string) => string; // hàm dịch theo key  locale: string; // ngôn ngữ hiện tại  count: number; // số đếm  formatter: Intl.NumberFormat;};const ServerComponent = ({  t,  locale,  count,  formatter,}: ServerComponentProps) => {  const formatted = formatter.format(count);  return (    <div>      <p>{formatted}</p>      <button aria-label={t("counter.label")}>{t("counter.increment")}</button>    </div>  );};export default ServerComponent;

    Vì component phía server không thể là async, bạn cần truyền các bản dịch và hàm formatter dưới dạng props.

    Trong trang / layout của bạn:

    • import { getTranslations, getFormatter } from "next-intl/server";
    • const t = await getTranslations("about.counter");
    • const formatter = await getFormatter().then((formatter) => formatter.number());
    Intlayer cung cấp các hook an toàn cho server thông qua next-intlayer/server. Để hoạt động, useIntlayeruseNumber sử dụng cú pháp giống hook, tương tự như các hook phía client, nhưng dựa vào ngữ cảnh server (IntlayerServerProvider) ở bên dưới.

    Metadata / Sitemap / Robots

    Dịch nội dung là điều tuyệt vời. Nhưng mọi người thường quên rằng mục tiêu chính của quốc tế hóa là làm cho trang web của bạn trở nên dễ nhìn thấy hơn trên toàn thế giới. I18n là một đòn bẩy tuyệt vời để cải thiện khả năng hiển thị trang web của bạn.

    Dưới đây là danh sách các thực hành tốt liên quan đến SEO đa ngôn ngữ.

    • đặt thẻ meta hreflang trong thẻ <head> > Nó giúp các công cụ tìm kiếm hiểu được những ngôn ngữ nào có trên trang
    • liệt kê tất cả các bản dịch trang trong sitemap.xml sử dụng schema XML http://www.w3.org/1999/xhtml >
    • đừng quên loại trừ các trang có tiền tố khỏi robots.txt (ví dụ: /dashboard, và /fr/dashboard, /es/dashboard) >
    • sử dụng component Link tùy chỉnh để chuyển hướng đến trang được địa phương hóa nhất (ví dụ: bằng tiếng Pháp <a href="/fr/about">A propos</a>) >

    Các nhà phát triển thường quên tham chiếu đúng các trang của họ theo từng ngôn ngữ.

    src/app/[locale]/about/layout.tsx
    import type { Metadata } from "next";import { locales, defaultLocale } from "@/i18n";import { getTranslations } from "next-intl/server";const localizedPath = (locale: string, path: string) => {  return locale === defaultLocale ? path : "/" + locale + path;};type GenerateMetadataParams = {  params: Promise<{    locale: string;  }>;};export const generateMetadata = async ({  params,}: GenerateMetadataParams): Promise<Metadata> => {  const { locale } = await 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"), // tiêu đề trang    description: t("description"), // mô tả trang    alternates: {      canonical: localizedPath(locale, url), // đường dẫn chuẩn      languages: { ...languages, "x-default": url }, // các ngôn ngữ thay thế, bao gồm mặc định    },  };};// ... Phần còn lại của mã trang
    src/app/sitemap.ts
    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 const sitemap = (): MetadataRoute.Sitemap => {  const aboutLanguages = Object.fromEntries(    locales.map((l) => [l, formatterLocalizedPath(l, "/about")])  );  return [    {      url: formatterLocalizedPath(defaultLocale, "/about"),      lastModified: new Date(),      changeFrequency: "monthly",      priority: 0.7,      alternates: { languages: aboutLanguages },    },  ];};
    src/app/robots.ts
    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 const robots = (): MetadataRoute.Robots => {  const disallow = [    ...withAllLocales("/dashboard"),    ...withAllLocales("/admin"),  ];  return {    rules: { userAgent: "*", allow: ["/"], disallow },    host: origin,    sitemap: origin + "/sitemap.xml",  };};  path,  ...locales    .filter((locale) => locale !== defaultLocale)    .map((locale) => "/" + locale + path),];export const robots = (): MetadataRoute.Robots => {  const disallow = [    ...withAllLocales("/dashboard"),    ...withAllLocales("/admin"),  ];  return {    rules: { userAgent: "*", allow: ["/"], disallow },    host: origin,    sitemap: origin + "/sitemap.xml",  };};
    Intlayer cung cấp một hàm getMultilingualUrls để tạo các URL đa ngôn ngữ cho sitemap của bạn.

    Middleware cho định tuyến locale

    Thêm middleware để xử lý phát hiện locale và định tuyến:

    src/middleware.ts
    import createMiddleware from "next-intl/middleware";import { locales, defaultLocale } from "@/i18n";export default createMiddleware({  locales: [...locales],  defaultLocale,  localeDetection: true,});export const config = {  // Bỏ qua API, các phần nội bộ của Next và tài nguyên tĩnh  matcher: ["/((?!api|_next|.*\\..*).*)"],};

    Danh sách kiểm tra thiết lập và các thực hành tốt

    • Thiết lập thuộc tính html langdir: Trong src/app/[locale]/layout.tsx, tính toán dir thông qua getLocaleDirection(locale) và thiết lập <html lang={locale} dir={dir}>.
    • Phân tách thông điệp theo namespace: Tổ chức JSON theo từng locale và namespace (ví dụ, common.json, about.json).
    • Giảm thiểu payload trên client: Trên các trang, chỉ gửi các namespace cần thiết đến NextIntlClientProvider (ví dụ, pick(messages, ['common', 'about'])).
    • Ưu tiên các trang tĩnh: Xuất export const dynamic = 'force-static' và tạo các tham số tĩnh cho tất cả các locales.
    • Các thành phần server đồng bộ: Giữ cho các thành phần server đồng bộ bằng cách truyền các chuỗi đã được tính toán trước (nhãn đã dịch, số đã được định dạng) thay vì các cuộc gọi async hoặc các hàm không thể tuần tự hóa.

    Và người chiến thắng là…

    Không đơn giản. Mỗi lựa chọn đều có những đánh đổi. Đây là cách tôi nhìn nhận:

    next-i18next

    • trưởng thành, đầy đủ tính năng, nhiều plugin cộng đồng, nhưng chi phí thiết lập cao hơn. Nếu bạn cần hệ sinh thái plugin của i18next (ví dụ: các quy tắc ICU nâng cao qua plugin) và đội ngũ của bạn đã quen với i18next, chấp nhận cấu hình nhiều hơn để có sự linh hoạt.

    next-intl

    • đơn giản nhất, nhẹ, ít quyết định bắt buộc hơn. Nếu bạn muốn một giải pháp tối giản, bạn thoải mái với các danh mục tập trung, và ứng dụng của bạn có quy mô nhỏ đến trung bình.

    Intlayer

    • được xây dựng cho Next.js hiện đại, với nội dung mô-đun, an toàn kiểu, công cụ hỗ trợ, và ít mã mẫu hơn. Nếu bạn đánh giá cao nội dung phạm vi thành phần, TypeScript nghiêm ngặt, đảm bảo tại thời điểm xây dựng, tree-shaking, và công cụ định tuyến/SEO/trình soạn thảo đầy đủ tính năng - đặc biệt cho Next.js App Router, hệ thống thiết kế và các codebase lớn, mô-đun.

    Nếu bạn ưu tiên thiết lập tối giản và chấp nhận một số cấu hình thủ công, next-intl là lựa chọn tốt. Nếu bạn cần tất cả các tính năng và không ngại sự phức tạp, next-i18next sẽ phù hợp. Nhưng nếu bạn muốn một giải pháp hiện đại, có thể mở rộng, mô-đun với các công cụ tích hợp sẵn, Intlayer hướng đến việc cung cấp cho bạn điều đó ngay khi sử dụng.

    Lựa chọn thay thế cho các nhóm doanh nghiệp: Nếu bạn cần một giải pháp đã được chứng minh hoạt động hoàn hảo với các nền tảng bản địa hóa đã được thiết lập như Crowdin, Phrase, hoặc các hệ thống quản lý dịch thuật chuyên nghiệp khác, hãy cân nhắc next-intl hoặc next-i18next vì hệ sinh thái trưởng thành và các tích hợp đã được kiểm chứng của chúng.
    Lộ trình tương lai: Intlayer cũng dự định phát triển các plugin hoạt động trên nền tảng các giải pháp i18nextnext-intl. Điều này sẽ mang lại cho bạn những lợi thế của Intlayer về tự động hóa, cú pháp và quản lý nội dung trong khi vẫn giữ được tính bảo mật và ổn định do các giải pháp đã được thiết lập này cung cấp trong mã ứng dụng của bạn.

    GitHub STARs

    Sao trên GitHub là một chỉ số mạnh mẽ cho thấy mức độ phổ biến của dự án, sự tin tưởng của cộng đồng và tính liên quan lâu dài. Mặc dù không phải là thước đo trực tiếp về chất lượng kỹ thuật, nhưng chúng phản ánh số lượng nhà phát triển thấy dự án hữu ích, theo dõi tiến trình của nó và có khả năng áp dụng nó. Để ước tính giá trị của một dự án, sao giúp so sánh mức độ thu hút giữa các lựa chọn thay thế và cung cấp cái nhìn sâu sắc về sự phát triển của hệ sinh thái.

    Biểu đồ Lịch sử Sao


    Kết luận

    Cả ba thư viện đều thành công trong việc cốt lõi hóa localization. Sự khác biệt là bạn phải làm bao nhiêu công việc để đạt được một thiết lập vững chắc, có thể mở rộng trong Next.js hiện đại:

    • Với Intlayer, nội dung mô-đun, TypeScript nghiêm ngặt, an toàn thời gian xây dựng, gói tree-shaken, và App Router + công cụ SEO hàng đầumặc định, không phải là gánh nặng.
    • Nếu đội ngũ của bạn coi trọng khả năng bảo trì và tốc độ trong một ứng dụng đa ngôn ngữ, hướng thành phần, Intlayer cung cấp trải nghiệm toàn diện nhất hiện nay.

    Tham khảo tài liệu 'Tại sao chọn Intlayer?' để biết thêm chi tiết.