Đặt câu hỏi và nhận tóm tắt tài liệu bằng cách tham chiếu trang này và nhà cung cấp AI bạn chọn
Lịch sử phiên bản
- "Thêm so sánh sao GitHub"v8.9.818/5/2026
- "Khởi tạo benchmark"v8.7.56/1/2026
Nội dung của trang này đã được dịch bằng AI.
Xem phiên bản mới nhất của nội dung gốc bằng tiếng AnhNếu bạn có ý tưởng để cải thiện tài liệu này, vui lòng đóng góp bằng cách gửi pull request trên GitHub.
Liên kết GitHub tới tài liệuSao chép Markdown của tài liệu vào bộ nhớ tạm
Thư viện i18n cho TanStack Start - Báo cáo Benchmark 2026
Trang này là báo cáo benchmark cho các giải pháp i18n trên TanStack Start.
Mục lục
Benchmark tương tác
Tham chiếu kết quả:
Xem dữ liệu benchmark đầy đủ
Xem kho lưu trữ benchmark đầy đủ tại đây.
Giới thiệu
Các giải pháp quốc tế hóa là một trong những phụ thuộc (dependency) nặng nề nhất trong một ứng dụng React. Trên TanStack Start, rủi ro chính là gửi đi nội dung không cần thiết: các bản dịch cho các trang khác và các ngôn ngữ khác trong gói bundle của một route duy nhất.
Khi ứng dụng của bạn phát triển, vấn đề đó có thể nhanh chóng làm tăng vọt lượng JavaScript được gửi đến máy khách và làm chậm quá trình điều hướng.
Trong thực tế, đối với các triển khai ít được tối ưu hóa nhất, một trang đa ngôn ngữ có thể nặng hơn nhiều lần so với phiên bản không có i18n.
Tác động khác là đối với trải nghiệm phát triển (DX): cách bạn khai báo nội dung, các kiểu dữ liệu, tổ chức namespace, tải động và tính phản ứng khi ngôn ngữ thay đổi.
TL;DR
- Intlayer: Cung cấp hiệu năng tốt nhất và kích thước bundle nhỏ nhất (v8.7.12) cho TanStack Start.
- react-i18next & use-intl: Các lựa chọn thay thế thuần thục với hệ sinh thái lớn, nhưng nặng hơn đáng kể và phức tạp hơn để tối ưu hóa.
- Paraglide: Ý tưởng tree-shaking sáng tạo nhưng không hoạt động trong thực tế. DX phức tạp và chi phí phản ứng trên TanStack Start.
- Cần tránh: General Translation (GT) và Lingo.dev do các vấn đề hiệu năng nghiêm trọng, giới hạn hạn mức AI và bị ràng buộc vào nhà cung cấp (vendor lock-in).
Kiểm tra ứng dụng của bạn
Để nhanh chóng phát hiện các vấn đề rò rỉ i18n, tôi đã thiết lập một trình quét miễn phí có sẵn tại đây.
Vấn đề
Hai đòn bẩy là cần thiết để hạn chế chi phí của một ứng dụng đa ngôn ngữ:
- Chia tách nội dung theo trang / namespace để bạn không tải toàn bộ từ điển khi không cần dùng đến.
- Tải đúng ngôn ngữ một cách linh hoạt, chỉ khi cần thiết.
Hiểu các hạn chế kỹ thuật của các phương tiếp cận này:
Tải động (Dynamic loading)
Nếu không có tải động, hầu hết các giải pháp giữ các thông điệp trong bộ nhớ ngay từ lần render đầu tiên, điều này thêm vào chi phí đáng kể cho các ứng dụng có nhiều route và ngôn ngữ.
Với tải động, bạn chấp nhận một sự đánh đổi: ít mã JS ban đầu hơn, nhưng đôi khi có thêm một yêu cầu khi chuyển đổi ngôn ngữ.
Chia tách nội dung (Content splitting)
Các cú pháp được xây dựng xung quanh const t = useTranslation() + t('a.b.c') rất thuận tiện nhưng thường khuyến khích việc giữ các đối tượng JSON lớn tại thời điểm chạy (runtime). Mô hình đó khiến việc loại bỏ mã thừa (tree-shaking) trở nên khó khăn trừ khi thư viện cung cấp một chiến lược chia tách thực sự theo từng trang.
Phương pháp đo lường
Đối với benchmark này, chúng tôi đã so sánh các thư viện sau:
Base App(Không sử dụng thư viện i18n)react-intlayer(v8.7.12)react-i18next(v17.0.2)use-intl(v4.9.1)@lingui/core(v5.3.0)@inlang/paraglide-js(v2.15.1)@tolgee/react(v7.0.0)react-intl(v10.1.1)wuchale(v0.22.11)gt-react(vlatest)lingo.dev(v0.133.9)
Framework được sử dụng là TanStack Start với một ứng dụng đa ngôn ngữ gồm 10 trang và 10 ngôn ngữ.
Chúng tôi đã so sánh bốn chiến lược tải:
Mở bảng trong một cửa sổ bật lên để xem toàn bộ nội dung dữ liệu một cách rõ ràng
| Chiến lược | Không có namespace (global) | Có namespace (scoped) |
|---|---|---|
| Tải tĩnh | Static: Mọi thứ nằm trong bộ nhớ khi khởi động. | Scoped static: Chia tách theo namespace; mọi thứ được tải khi khởi động. |
| Tải động | Dynamic: Tải theo yêu cầu cho mỗi ngôn ngữ. | Scoped dynamic: Tải chi tiết theo từng namespace và ngôn ngữ. |
Tóm tắt chiến lược
- Static: Đơn giản; không có độ trễ mạng sau lần tải đầu tiên. Nhược điểm: kích thước gói bundle lớn.
- Dynamic: Giảm bớt gánh nặng ban đầu (lazy-loading). Lý tưởng khi bạn có nhiều ngôn ngữ.
- Scoped static: Giữ cho mã được tổ chức (tách biệt logic) mà không cần các yêu cầu mạng bổ sung phức tạp.
- Scoped dynamic: Phương pháp tốt nhất cho việc chia tách mã (code splitting) và hiệu năng. Giảm thiểu bộ nhớ bằng cách chỉ tải những gì cần thiết cho chế độ xem hiện tại và ngôn ngữ đang hoạt động.
Sao GitHub
Sao GitHub là một chỉ số mạnh mẽ về mức độ phổ biến của dự án, sự tin tưởng của cộng đồng và mức độ phù hợp 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, 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, các ngôi sao giúp so sánh sức hút giữa các lựa chọn thay thế và cung cấp thông tin chi tiết về sự phát triển của hệ sinh thái.
Kết quả chi tiết
1 - Các giải pháp cần tránh
Một số giải pháp, chẳng hạn như gt-react hoặc lingo.dev, rõ ràng là những lựa chọn nên tránh xa. Chúng kết hợp việc phụ thuộc vào nhà cung cấp với việc làm ô nhiễm mã nguồn của bạn. Tệ hơn nữa: mặc dù dành nhiều giờ cố gắng triển khai chúng, tôi chưa bao giờ làm cho chúng hoạt động bình thường trên TanStack Start (tương tự như gt-next trên Next.js).
Các vấn đề gặp phải:
(General Translation) (gt-react@latest):
- Đối với một ứng dụng khoảng 110kb,
gt-reactcó thể thêm vào hơn 440kb dữ liệu dư thừa (mức độ quan sát được trên triển khai Next.js trong cùng một benchmark). - Thông báo
Quota Exceeded, please upgrade your planngay trong lần xây dựng đầu tiên với General Translation. - Các bản dịch không được render; tôi nhận được lỗi
Error: <T> used on the client-side outside of <GTProvider>, dường như là một lỗi trong thư viện đó. - Trong khi triển khai gt-tanstack-start-react, tôi cũng gặp phải một vấn đề với thư viện:
does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, khiến ứng dụng bị hỏng. Sau khi báo cáo, người duy trì đã khắc phục nó trong vòng 24 giờ. - Các thư viện này sử dụng một anti-pattern thông qua hàm
initializeGT(), ngăn cản gói bundle loại bỏ mã thừa một cách sạch sẽ.
(Lingo.dev) ([email protected]):
- Vượt quá hạn mức AI (hoặc chặn phụ thuộc server), khiến việc xây dựng / production gặp rủi ro nếu không trả tiền.
- Trình biên dịch đã bỏ sót gần 40% nội dung đã dịch. Tôi đã phải viết lại tất cả các cấu trúc
.mapthành các khối component phẳng để nó hoạt động. - CLI của họ có nhiều lỗi và hay tự ý đặt lại tệp cấu hình không vì lý do gì.
- Khi xây dựng, nó đã xóa hoàn toàn các tệp JSON được tạo ra khi có nội dung mới được thêm vào. Kết quả là, bạn có thể kết thúc với việc chỉ một vài khóa xóa sạch hàng trăm khóa hiện có.
- Tôi đã gặp vấn đề về tính phản ứng với thư viện trên TanStack Start: khi ngôn ngữ thay đổi, tôi phải ép buộc render lại provider để nó hoạt động.
2 - Các giải pháp thử nghiệm
(Wuchale) ([email protected]):
Ý tưởng đằng sau Wuchale rất thú vị nhưng vẫn chưa phải là một giải pháp khả thi. Tôi đã gặp vấn đề về tính phản ứng với thư viện này và phải ép buộc render lại provider để ứng dụng hoạt động trên TanStack Start. Tài liệu cũng khá mơ hồ, khiến việc làm quen khó khăn hơn.
3 - Các giải pháp chấp nhận được
(Paraglide) (@inlang/[email protected]):
Paraglide mang đến một phương pháp tiếp cận sáng tạo và được cân nhắc kỹ lưỡng. Dù vậy, trong benchmark này, khả năng loại bỏ mã thừa mà công ty họ quảng cáo đã không hoạt động cho triển khai Next.js hoặc cho TanStack Start của tôi. Quy trình làm việc và trải nghiệm phát triển cũng phức tạp hơn các tùy chọn khác. Cá nhân tôi không phải là fan của việc phải tạo lại các tệp JS trước mỗi lần đẩy mã, điều này tạo ra rủi ro xung đột merge liên tục cho các nhà phát triển qua PR.
Lưu ý về paraglide: giải pháp này đưa mã vào mã nguồn của bạn cho các lần nhập; kết quả là, chỉ số 'lib size' trong báo cáo benchmark gần như bằng 0. Việc tạo mã (Code generation) là một điều tốt, vì hàm được sử dụng sẽ chỉ bao gồm logic cần thiết (tiền tố ở mọi nơi so với không có tiền tố, cookie so với lưu trữ, v.v.). So với đó, Intlayer thực hiện việc lọc này thông qua việc đưa các biến môi trường vào bản dựng để buộc trình đóng gói (bundler) thực hiện tree-shake nội dung tùy thuộc vào logic. Nhờ đó, paraglide và intlayer cuối cùng là những giải pháp nhẹ hơn từ 6 đến 10 lần so with i18next hoặc next-intl.
(Tolgee) (@tolgee/[email protected]):
Tolgee giải quyết được nhiều vấn đề đã đề cập trước đó. Tôi thấy việc bắt đầu với Tolgee khó khăn hơn so với các công cụ khác có phương pháp tiếp cận tương tự. Nó không cung cấp tính an toàn kiểu dữ liệu, điều này cũng khiến việc phát hiện các khóa bị thiếu tại thời điểm biên dịch trở nên khó khăn hơn nhiều. Tôi đã phải bao bọc các API của Tolgee bằng các API của riêng mình để thêm tính năng phát hiện khóa bị thiếu.
Trên TanStack Start, tôi cũng gặp vấn đề về tính phản ứng: khi ngôn ngữ thay đổi, tôi phải ép buộc provider render lại và đăng ký vào các sự kiện thay đổi ngôn ngữ để việc tải ở một ngôn ngữ khác hoạt động chính xác.
(use-intl) ([email protected]):
use-intl là phần "intl" hợp thời nhất trong hệ sinh thái React (cùng họ với next-intl) và thường được các trợ lý AI thúc đẩy, nhưng theo quan điểm của tôi thì điều đó là sai lầm trong một thiết lập ưu tiên hiệu năng. Bắt đầu khá đơn giản. Trong thực tế, quá trình tối ưu hóa và hạn chế rò rỉ khá phức tạp. Tương tự, việc kết hợp tải động + tạo namespace + các kiểu dữ liệu TypeScript làm chậm quá trình phát triển rất nhiều.
Trên TanStack Start, bạn tránh được các cạm bẫy đặc thù của Next.js (setRequestLocale, render tĩnh), nhưng vấn đề cốt lõi là như nhau: nếu không có kỷ luật nghiêm ngặt, gói bundle sẽ nhanh chóng mang theo quá nhiều thông điệp và việc bảo trì namespace cho từng route trở nên mệt mỏi.
(react-i18next) ([email protected]):
react-i18next có lẽ là tùy chọn phổ biến nhất vì nó là một trong những giải pháp đầu tiên phục vụ nhu cầu i18n cho các ứng dụng JavaScript. Nó cũng có một bộ plugin cộng đồng rộng lớn cho các vấn đề cụ thể.
Tuy nhiên, nó có cùng những nhược điểm chính như các ngăn công nghệ được xây dựng trên t('a.b.c'): tối ưu hóa là có thể nhưng rất tốn thời gian, và các dự án lớn có rủi ro rơi vào các thực hành xấu (namespace + tải động + kiểu dữ liệu).
Các định dạng thông báo cũng khác nhau: use-intl sử dụng ICU MessageFormat, trong khi i18next sử dụng định dạng riêng của mình - điều này làm phức tạp thêm công cụ hoặc quá trình di chuyển nếu bạn trộn lẫn chúng.
(Lingui) (@lingui/[email protected]):
Lingui thường được khen ngợi. Cá nhân tôi thấy quy trình làm việc xung quanh lingui extract / lingui compile phức tạp hơn các phương pháp tiếp cận khác, mà không có ưu điểm rõ ràng trong benchmark TanStack Start này. Tôi cũng nhận thấy cú pháp không nhất quán gây nhầm lẫn cho AI (ví dụ: t(), t'', i18n.t(), <Trans>).
(react-intl) ([email protected]):
react-intl là một triển khai hiệu năng từ nhóm Format.js. Trải nghiệm phát triển vẫn còn rườm rà: const intl = useIntl() + intl.formatMessage({ id: "xx.xx" }) thêm vào sự phức tạp, công việc JavaScript bổ sung và ràng buộc instance i18n toàn cục vào nhiều nút trong cây React.
4 - Các khuyến nghị
Benchmark TanStack Start này không có đối trọng trực tiếp cho next-translate (Next.js plugin + getStaticProps). Đối với các nhóm thực sự muốn một API t() với một hệ sinh thái chín muồi, react-i18next và use-intl vẫn là những lựa chọn "hợp lý", nhưng hãy chuẩn bị đầu tư nhiều thời gian tối ưu hóa để tránh rò rỉ.
(Intlayer) ([email protected]):
Tôi sẽ không đích thân đánh giá react-intlayer vì tính khách quan, vì đây là giải pháp của riêng tôi.
Ghi chú cá nhân
Ghi chú này mang tính cá nhân và không ảnh hưởng đến kết quả benchmark. Tuy nhiên, trong thế giới i18n, bạn thường thấy sự đồng thuận xung quanh một mẫu như const t = useTranslation('xx') + <>{t('xx.xx')}</> cho nội dung dịch.
Trong các ứng dụng React, việc đưa một hàm vào dưới dạng một ReactNode, theo quan điểm của tôi, là một anti-pattern. Nó cũng thêm vào sự phức tạp có thể tránh được và chi phí thực thi JavaScript (ngay cả khi hầu như không nhận thấy).