@intlayer/docs 8.7.4 → 8.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/id/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/pl/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/vi/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/dist/cjs/generated/docs.entry.cjs +60 -0
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +60 -0
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/types/generated/docs.entry.d.ts +3 -0
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/docs/ar/benchmark/index.md +29 -0
- package/docs/ar/benchmark/nextjs.md +227 -0
- package/docs/ar/benchmark/tanstack.md +193 -0
- package/docs/ar/intlayer_with_tanstack.md +0 -2
- package/docs/de/benchmark/index.md +29 -0
- package/docs/de/benchmark/nextjs.md +227 -0
- package/docs/de/benchmark/tanstack.md +193 -0
- package/docs/en/benchmark/___NOTE.md +82 -0
- package/docs/en/benchmark/___nextjs.md +195 -0
- package/docs/en/benchmark/___tanstack.md +187 -0
- package/docs/en/benchmark/index.md +29 -0
- package/docs/en/benchmark/nextjs.md +228 -0
- package/docs/en/benchmark/tanstack.md +217 -0
- package/docs/en-GB/benchmark/index.md +29 -0
- package/docs/en-GB/benchmark/nextjs.md +228 -0
- package/docs/en-GB/benchmark/tanstack.md +193 -0
- package/docs/es/benchmark/index.md +29 -0
- package/docs/es/benchmark/nextjs.md +226 -0
- package/docs/es/benchmark/tanstack.md +193 -0
- package/docs/fr/benchmark/index.md +29 -0
- package/docs/fr/benchmark/nextjs.md +227 -0
- package/docs/fr/benchmark/tanstack.md +193 -0
- package/docs/hi/benchmark/index.md +29 -0
- package/docs/hi/benchmark/nextjs.md +227 -0
- package/docs/hi/benchmark/tanstack.md +193 -0
- package/docs/id/benchmark/index.md +29 -0
- package/docs/id/benchmark/nextjs.md +227 -0
- package/docs/id/benchmark/tanstack.md +193 -0
- package/docs/id/intlayer_with_react_native+expo.md +0 -2
- package/docs/it/benchmark/index.md +29 -0
- package/docs/it/benchmark/nextjs.md +227 -0
- package/docs/it/benchmark/tanstack.md +193 -0
- package/docs/ja/benchmark/index.md +29 -0
- package/docs/ja/benchmark/nextjs.md +227 -0
- package/docs/ja/benchmark/tanstack.md +193 -0
- package/docs/ko/benchmark/index.md +29 -0
- package/docs/ko/benchmark/nextjs.md +227 -0
- package/docs/ko/benchmark/tanstack.md +193 -0
- package/docs/ko/intlayer_with_tanstack.md +0 -2
- package/docs/pl/benchmark/index.md +29 -0
- package/docs/pl/benchmark/nextjs.md +227 -0
- package/docs/pl/benchmark/tanstack.md +193 -0
- package/docs/pt/benchmark/index.md +29 -0
- package/docs/pt/benchmark/nextjs.md +227 -0
- package/docs/pt/benchmark/tanstack.md +193 -0
- package/docs/ru/benchmark/index.md +29 -0
- package/docs/ru/benchmark/nextjs.md +227 -0
- package/docs/ru/benchmark/tanstack.md +193 -0
- package/docs/tr/benchmark/index.md +29 -0
- package/docs/tr/benchmark/nextjs.md +227 -0
- package/docs/tr/benchmark/tanstack.md +193 -0
- package/docs/uk/benchmark/index.md +29 -0
- package/docs/uk/benchmark/nextjs.md +227 -0
- package/docs/uk/benchmark/tanstack.md +193 -0
- package/docs/vi/benchmark/index.md +29 -0
- package/docs/vi/benchmark/nextjs.md +227 -0
- package/docs/vi/benchmark/tanstack.md +193 -0
- package/docs/zh/benchmark/index.md +29 -0
- package/docs/zh/benchmark/nextjs.md +227 -0
- package/docs/zh/benchmark/tanstack.md +193 -0
- package/package.json +6 -6
- package/src/generated/docs.entry.ts +60 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Giải pháp i18n tốt nhất cho Next.js năm 2026 - Báo cáo Benchmark
|
|
5
|
+
description: So sánh các thư viện quốc tế hóa (i18n) cho Next.js như next-intl, next-i18next và Intlayer. Báo cáo hiệu năng chi tiết về kích thước gói bundle, rò rỉ dữ liệu và tính phản ứng.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- hiệu năng
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- nextjs
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Khởi tạo benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Thư viện i18n cho Next.js — Báo cáo Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Trang này là báo cáo benchmark cho các giải pháp i18n trên Next.js.
|
|
28
|
+
|
|
29
|
+
## Mục lục
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark tương tác
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="nextjs" vertical/>
|
|
36
|
+
|
|
37
|
+
## Tham chiếu kết quả:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md
|
|
47
|
+
|
|
48
|
+
Xem kho lưu trữ benchmark đầy đủ [tại đây](https://github.com/intlayer-org/benchmark-i18n).
|
|
49
|
+
|
|
50
|
+
## Giới thiệu
|
|
51
|
+
|
|
52
|
+
Các thư viện quốc tế hóa có tác động nặng nề đến ứng dụng của bạn. Rủi ro chính là tải nội dung cho mọi trang và mọi ngôn ngữ ngay cả khi người dùng chỉ truy cập một trang duy nhất.
|
|
53
|
+
|
|
54
|
+
Khi ứng dụng của bạn phát triển, kích thước gói bundle có thể tăng theo cấp số nhân, điều này có thể làm giảm hiệu năng một cách rõ rệt.
|
|
55
|
+
|
|
56
|
+
Ví dụ, trong những trường hợp xấu nhất, sau khi triển khai đa ngôn ngữ, trang của bạn có thể nặng hơn gần 4 lần.
|
|
57
|
+
|
|
58
|
+
Một tác động khác của các thư viện i18n là làm chậm quá trình phát triển. Việc chuyển đổi các component thành nội dung đa ngôn ngữ trên nhiều ngôn ngữ khác nhau rất tốn thời gian.
|
|
59
|
+
|
|
60
|
+
Vì bài toán này khó, nên có nhiều giải pháp tồn tại — một số tập trung vào trải nghiệm phát triển (DX), một số khác vào hiệu năng hoặc khả năng mở rộng, v.v.
|
|
61
|
+
|
|
62
|
+
Intlayer cố gắng tối ưu hóa trên tất cả các khía cạnh này.
|
|
63
|
+
|
|
64
|
+
## Kiểm tra ứng dụng của bạn
|
|
65
|
+
|
|
66
|
+
Để làm rõ các vấn đề này, tôi đã xây dựng một trình quét miễn phí mà bạn có thể thử [tại đây](https://intlayer.org/i18n-seo-scanner).
|
|
67
|
+
|
|
68
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
69
|
+
|
|
70
|
+
## Vấn đề
|
|
71
|
+
|
|
72
|
+
Có hai cách chính để hạn chế tác động của một ứng dụng đa ngôn ngữ lên gói bundle của bạn:
|
|
73
|
+
|
|
74
|
+
- Chia nhỏ tệp JSON (hoặc nội dung) của bạn thành nhiều tệp / biến / namespace để công cụ đóng gói (bundler) có thể loại bỏ mã thừa (tree-shake) cho nội dung không dùng đến trên một trang cụ thể.
|
|
75
|
+
- Tải nội dung trang một cách linh hoạt (dynamic load) chỉ theo ngôn ngữ của người dùng.
|
|
76
|
+
|
|
77
|
+
Các hạn chế kỹ thuật đối với các phương pháp này:
|
|
78
|
+
|
|
79
|
+
**Tải động (Dynamic loading)**
|
|
80
|
+
|
|
81
|
+
Ngay cả khi bạn khai báo các route như `[locale]/page.tsx`, với Webpack hoặc Turbopack, và ngay cả khi `generateStaticParams` được định nghĩa, bundler vẫn không coi `locale` là một hằng số tĩnh. Điều đó có nghĩa là nó có thể kéo nội dung của tất cả các ngôn ngữ vào từng trang. Cách chính để hạn chế điều này là tải nội dung thông qua import động (ví dụ: `import('./locales/${locale}.json')`).
|
|
82
|
+
|
|
83
|
+
Điều xảy ra tại thời điểm build là Next.js tạo ra một gói bundle JS cho mỗi ngôn ngữ (ví dụ: `./locales_vi_12345.js`). Sau khi trang web được gửi đến máy khách, khi trang chạy, trình duyệt thực hiện thêm một yêu cầu HTTP cho tệp JS cần thiết (ví dụ: `./locales_vi_12345.js`).
|
|
84
|
+
|
|
85
|
+
> Một cách khác để giải quyết vấn đề tương tự là sử dụng `fetch()` để tải JSON động. Đó là cách `Tolgee` hoạt động khi JSON nằm trong thư mục `/public`, hoặc `next-translate`, dựa vào `getStaticProps` để tải nội dung. Quy trình là giống nhau: trình duyệt thực hiện thêm một yêu cầu HTTP để tải tài nguyên.
|
|
86
|
+
|
|
87
|
+
**Chia tách nội dung (Content splitting)**
|
|
88
|
+
|
|
89
|
+
Nếu bạn sử dụng cú pháp như `const t = useTranslation()` + `t('object-của-tôi.sub-object.key-của-tôi')`, toàn bộ tệp JSON thường phải nằm trong gói bundle để thư viện có thể phân tích và tìm kiếm khóa (key). Phần lớn nội dung đó sau đó được gửi đi ngay cả khi nó không được sử dụng trên trang.
|
|
90
|
+
|
|
91
|
+
Để giảm thiểu điều này, một số thư viện yêu cầu bạn khai báo theo từng trang những namespace nào cần tải — ví dụ: `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
|
|
92
|
+
|
|
93
|
+
Ngược lại, `Paraglide` thêm một bước bổ sung trước khi build để chuyển đổi tệp JSON thành các biểu tượng phẳng như `const en_my_var = () => 'giá trị của tôi'`. Về lý thuyết, điều đó cho phép loại bỏ mã thừa cho nội dung không dùng đến trên trang. Như chúng ta sẽ thấy, phương pháp đó vẫn có những nhược điểm đánh đổi.
|
|
94
|
+
|
|
95
|
+
Cuối cùng, `Intlayer` áp dụng một tối ưu hóa tại thời điểm build để `useIntlayer('key-của-tôi')` được thay thế trực tiếp bằng nội dung tương ứng.
|
|
96
|
+
|
|
97
|
+
## Phương pháp đo lường
|
|
98
|
+
|
|
99
|
+
Đối với benchmark này, chúng tôi đã so sánh các thư viện sau:
|
|
100
|
+
|
|
101
|
+
- `Base App` (Không sử dụng thư viện i18n)
|
|
102
|
+
- `next-intlayer` (v8.7.5)
|
|
103
|
+
- `next-i18next` (v16.0.5)
|
|
104
|
+
- `next-intl` (v4.9.1)
|
|
105
|
+
- `@lingui/core` (v5.3.0)
|
|
106
|
+
- `next-translate` (v3.1.2)
|
|
107
|
+
- `next-international` (v1.3.1)
|
|
108
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
109
|
+
- `tolgee` (v7.0.0)
|
|
110
|
+
- `@lingo.dev/compiler` (v0.4.0)
|
|
111
|
+
- `wuchale` (v0.22.11)
|
|
112
|
+
- `gt-next` (v6.16.5)
|
|
113
|
+
|
|
114
|
+
Tôi đã sử dụng phiên bản `Next.js` `16.2.4` với App Router.
|
|
115
|
+
|
|
116
|
+
Tôi đã xây dựng một ứng dụng đa ngôn ngữ với **10 trang** và **10 ngôn ngữ**.
|
|
117
|
+
|
|
118
|
+
Tôi đã so sánh **bốn chiến lược tải**:
|
|
119
|
+
|
|
120
|
+
| Chiến lược | Không có namespace (global) | Có namespace (scoped) |
|
|
121
|
+
| :----------- | :-------------------------------------------------- | :--------------------------------------------------------------------------- |
|
|
122
|
+
| **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. |
|
|
123
|
+
| **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ữ. |
|
|
124
|
+
|
|
125
|
+
## Tóm tắt chiến lược
|
|
126
|
+
|
|
127
|
+
- **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.
|
|
128
|
+
- **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ữ.
|
|
129
|
+
- **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.
|
|
130
|
+
- **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.
|
|
131
|
+
|
|
132
|
+
### Những gì tôi đã đo lường:
|
|
133
|
+
|
|
134
|
+
Tôi đã chạy cùng một ứng dụng đa ngôn ngữ trên trình duyệt thực tế cho mỗi công nghệ, sau đó ghi lại những gì thực sự truyền qua mạng và thời gian thực hiện. Kích thước được báo cáo **sau khi nén web thông thường**, vì điều đó gần với những gì người dùng thực sự tải xuống hơn là số lượng mã nguồn thô.
|
|
135
|
+
|
|
136
|
+
- **Kích thước thư viện quốc tế hóa**: Sau khi đóng gói, loại bỏ mã thừa và thu gọn mã, kích thước của thư viện i18n là kích thước của các provider (ví dụ: `NextIntlClientProvider`) + mã của các hook (ví dụ: `useTranslations`) trong một component trống. Nó không bao gồm việc tải các tệp bản dịch. Điều này trả lời cho câu hỏi thư viện "đắt đỏ" như thế nào trước khi nội dung của bạn tham gia vào.
|
|
137
|
+
|
|
138
|
+
- **JavaScript trên mỗi trang**: Đối với mỗi route benchmark, trình duyệt tải bao nhiêu mã script cho lần truy cập đó, được tính trung bình trên các trang trong bộ thử nghiệm (và trên các ngôn ngữ mà báo cáo tổng hợp chúng). Các trang nặng là các trang chậm.
|
|
139
|
+
|
|
140
|
+
- **Rò rỉ từ các ngôn ngữ khác (Leakage)**: Đó là nội dung của cùng một trang nhưng ở ngôn ngữ khác mà vô tình được tải vào trang đang kiểm tra. Nội dung này là không cần thiết và nên tránh (ví dụ: nội dung trang `/fr/about` nằm trong gói bundle của trang `/en/about`).
|
|
141
|
+
|
|
142
|
+
- **Rò rỉ từ các route khác**: Ý tưởng tương tự cho **các màn hình khác** trong ứng dụng: liệu văn bản của chúng có đi kèm khi bạn chỉ mở một trang (ví dụ: nội dung trang `/en/about` nằm trong gói bundle của trang `/en/contact`). Điểm số cao cho thấy việc chia tách yếu hoặc các gói bundle quá rộng.
|
|
143
|
+
|
|
144
|
+
- **Kích thước gói bundle trung bình cho component**: Các thành phần giao diện người dùng (UI) phổ biến được đo lường **từng cái một** thay vì ẩn bên trong một con số khổng lồ của ứng dụng. Nó cho thấy liệu việc quốc tế hóa có âm thầm làm phồng các component hàng ngày hay không. Ví dụ, nếu component của bạn render lại, nó sẽ tải toàn bộ dữ liệu đó từ bộ nhớ. Việc đính kèm một tệp JSON khổng lồ vào bất kỳ component nào giống như việc kết nối một kho lưu trữ lớn các dữ liệu không dùng đến sẽ làm chậm hiệu năng của component của bạn.
|
|
145
|
+
|
|
146
|
+
- **Khả năng phản ứng khi chuyển đổi ngôn ngữ**: Tôi chuyển đổi ngôn ngữ bằng bộ điều khiển của chính ứng dụng và đo thời gian cho đến khi trang được chuyển đổi rõ ràng — những gì một khách truy cập sẽ nhận thấy, không phải là một bước vi mô trong phòng thí nghiệm.
|
|
147
|
+
|
|
148
|
+
- **Công việc render sau khi thay đổi ngôn ngữ**: Một theo dõi hẹp hơn: giao diện mất bao nhiêu công sức để vẽ lại cho ngôn ngữ mới sau khi việc chuyển đổi đang diễn ra. Hữu ích khi thời gian "cảm nhận được" và chi phí framework khác biệt.
|
|
149
|
+
|
|
150
|
+
- **Thời gian tải trang ban đầu**: Từ lúc điều hướng cho đến khi trình duyệt coi trang đã được tải đầy đủ cho các kịch bản tôi đã kiểm tra. Tốt để so sánh khởi động lạnh (cold start).
|
|
151
|
+
|
|
152
|
+
- **Thời gian Hydration**: Khi ứng dụng hiển thị nó, khách hàng mất bao lâu để chuyển đổi mã HTML từ máy chủ thành thứ mà bạn thực sự có thể nhấp vào. Dấu gạch ngang trong các bảng có nghĩa là việc triển khai đó không cung cấp một con số hydration đáng tin cậy trong benchmark này.
|
|
153
|
+
|
|
154
|
+
## Kết quả chi tiết
|
|
155
|
+
|
|
156
|
+
### 1 — Các giải pháp cần tránh
|
|
157
|
+
|
|
158
|
+
Một số giải pháp, chẳng hạn như `gt-next` hoặc `lingo.dev`, rõ ràng là tốt nhất nên tránh. Chúng kết hợp việc phụ thuộc vào nhà cung cấp (vendor lock-in) với việc làm ô nhiễm mã nguồn của bạn. Mặc dù đã dành nhiều giờ để thử triển khai chúng, tôi chưa bao giờ làm cho chúng hoạt động bình thường — kể cả trên TanStack Start hay Next.js.
|
|
159
|
+
|
|
160
|
+
Các vấn đề gặp phải:
|
|
161
|
+
|
|
162
|
+
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
|
+
|
|
164
|
+
- Đối với một ứng dụng 110kb, `gt-react` thêm vào hơn 440kb dữ liệu dư thừa.
|
|
165
|
+
- Thông báo `Quota Exceeded, please upgrade your plan` ngay trong lần xây dựng đầu tiên với General Translation.
|
|
166
|
+
- Các bản dịch không được hiển trị; 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.
|
|
167
|
+
- Trong khi triển khai **gt-tanstack-start-react**, tôi cũng gặp phải một [vấn đề](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) 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 vấn đề này, người duy trì đã khắc phục nó trong vòng 24 giờ.
|
|
168
|
+
- Thư viện ngăn cản việc render tĩnh các trang Next.js.
|
|
169
|
+
|
|
170
|
+
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
171
|
+
|
|
172
|
+
- Vượt quá hạn mức AI, khiến việc xây dựng bị đình trệ hoàn toàn — vì vậy bạn không thể đưa lên production nếu không trả tiền.
|
|
173
|
+
- 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 `.map` thành các khối component phẳng để nó hoạt động.
|
|
174
|
+
- CLI của họ có rất nhiều lỗi và hay tự ý đặt lại tệp cấu hình không vì lý do gì.
|
|
175
|
+
- 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à, một số ít khóa có thể xóa sạch hơn 300 khóa hiện có.
|
|
176
|
+
|
|
177
|
+
### 2 — Các giải pháp thử nghiệm
|
|
178
|
+
|
|
179
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
180
|
+
|
|
181
|
+
Ý tưởng đằng sau `Wuchale` rất thú vị nhưng vẫn chưa khả thi. Tôi đã gặp vấn đề về tính phản ứng và phải ép buộc render lại provider để ứng dụng hoạt động. Tài liệu cũng khá mơ hồ, khiến việc làm quen khó khăn hơn.
|
|
182
|
+
|
|
183
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
184
|
+
|
|
185
|
+
`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 (tree-shaking) mà công ty họ quảng cáo đã không hoạt động cho cấu hình Next.js hoặc TanStack Start của tôi. Quy trình làm việc và trải nghiệm phát triển (DX) phức tạp hơn các tùy chọn khác.
|
|
186
|
+
Cá nhân tôi không thích việc phải tạo lại các tệp JS trước mỗi lần đẩy mã (push), điều này tạo ra rủi ro xung đột merge liên tục qua PR. Công cụ này cũng có vẻ tập trung vào Vite hơn là Next.js.
|
|
187
|
+
Cuối cùng, so với các giải pháp khác, Paraglide không sử dụng store (ví dụ: React context) để lấy ngôn ngữ hiện tại nhằm render nội dung. Đối với mỗi nút (node) được phân tích, nó sẽ yêu cầu ngôn ngữ từ localStorage / cookie v.v. Điều này dẫn đến việc thực thi logic không cần thiết ảnh hưởng đến tính phản ứng của component.
|
|
188
|
+
|
|
189
|
+
### 3 — Các giải pháp chấp nhận được
|
|
190
|
+
|
|
191
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
192
|
+
|
|
193
|
+
`Tolgee` giải quyết được nhiều vấn đề đã đề cập trước đó. Tôi thấy việc áp dụng nó khó hơn so với các công cụ tương tự. Nó không cung cấp tính an toàn kiểu dữ liệu (type safety), đ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. Tôi đã phải bao bọc các hàm của Tolgee bằng các hàm của riêng mình để thêm tính năng phát hiện khóa bị thiếu.
|
|
194
|
+
|
|
195
|
+
**(Next Intl)** (`next-intl@4.9.1`):
|
|
196
|
+
|
|
197
|
+
`next-intl` là tùy chọn hợp thời nhất và là tùy chọn mà các trợ lý AI thúc đẩy nhiều nhất, nhưng theo quan điểm của tôi thì điều đó là sai lầm. Bắt đầu rất dễ dàng. Trong thực tế, việc tối ưu hóa để hạn chế rò rỉ rất phức tạp. 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. Gói bundle cũng khá nặng (~13kb cho `NextIntlClientProvider` + `useTranslations`, tức là gấp hơn 2 lần `next-intlayer`). **next-intl** từng ngăn cản việc render tĩnh các trang Next.js. Nó cung cấp một trình hỗ trợ mang tên `setRequestLocale()`. Điều đó dường như đã được giải quyết một phần cho các tệp tập trung như `en.json` / `fr.json`, nhưng việc render tĩnh vẫn thất bại khi nội dung được chia thành các namespace như `en/shared.json` / `fr/shared.json` / `es/shared.json`.
|
|
198
|
+
|
|
199
|
+
**(Next I18next)** (`next-i18next@16.0.5`):
|
|
200
|
+
|
|
201
|
+
`next-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 i18n đầu tiên cho các ứng dụng JavaScript. Nó có nhiều plugin cộng đồng. Nó có cùng những nhược điểm chính như `next-intl`. Gói bundle đặc biệt nặng (~18kb cho `I18nProvider` + `useTranslation`, gấp khoảng 3 lần `next-intlayer`).
|
|
202
|
+
|
|
203
|
+
Các định dạng thông báo cũng khác nhau: `next-intl` sử dụng ICU MessageFormat, trong khi `i18next` sử dụng định dạng riêng của mình.
|
|
204
|
+
|
|
205
|
+
**(Next International)** (`next-international@1.3.1`):
|
|
206
|
+
|
|
207
|
+
`next-international` cũng giải quyết các vấn đề trên nhưng không khác nhiều so với `next-intl` hay `next-i18next`. Nó bao gồm `scopedT()` cho các bản dịch cụ thể theo namespace, nhưng việc sử dụng nó về cơ bản không ảnh hưởng đến kích thước gói bundle.
|
|
208
|
+
|
|
209
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
210
|
+
|
|
211
|
+
`Lingui` thường được khen ngợi. Cá nhân tôi thấy quy trình `lingui extract` / `lingui compile` phức tạp hơn các lựa chọn thay thế, mà không có ưu điểm rõ ràng. 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>`).
|
|
212
|
+
|
|
213
|
+
### 4 — Các khuyến nghị
|
|
214
|
+
|
|
215
|
+
**(Next Translate)** (`next-translate@3.1.2`):
|
|
216
|
+
|
|
217
|
+
`next-translate` là khuyến nghị chính của tôi nếu bạn thích một API theo kiểu `t()`. Nó vận hành thanh thoát thông qua `next-translate-plugin`, tải các namespace qua `getStaticProps` với một trình tải Webpack / Turbopack. Nó cũng là tùy chọn nhẹ nhất ở đây (~2.5kb). Đối với việc phân namespace, việc định nghĩa các namespace theo từng trang hoặc route trong cấu hình được cân nhắc kỹ lưỡng và dễ bảo trì hơn so với các lựa chọn thay thế chính như **next-intl** hay **next-i18next**. Ở phiên bản `3.1.2`, tôi nhận thấy rằng việc render tĩnh không hoạt động; Next.js đã quay trở lại việc render động.
|
|
218
|
+
|
|
219
|
+
**(Intlayer)** (`next-intlayer@8.7.5`):
|
|
220
|
+
|
|
221
|
+
Tôi sẽ không đích thân đánh giá `next-intlayer` vì tính khách quan, vì đây là giải pháp của riêng tôi.
|
|
222
|
+
|
|
223
|
+
### Ghi chú cá nhân
|
|
224
|
+
|
|
225
|
+
Ghi chú này mang tính cá nhân và không ảnh hưởng đến kết quả benchmark. Trong thế giới i18n, bạn thường thấy sự đồng thuận xung quanh mẫu `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>`.
|
|
226
|
+
|
|
227
|
+
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).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Giải pháp i18n tốt nhất cho TanStack Start năm 2026 - Báo cáo Benchmark
|
|
5
|
+
description: So sánh các thư viện quốc tế hóa cho TanStack Start như react-i18next, use-intl và Intlayer. Báo cáo hiệu năng chi tiết về kích thước gói bundle, rò rỉ dữ liệu và tính phản ứng.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- tanstack
|
|
11
|
+
- hiệu năng
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- tanstack
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-tanstack-start-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Khởi tạo benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Thư viện i18n cho TanStack Start — Báo cáo Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Trang này là báo cáo benchmark cho các giải pháp i18n trên TanStack Start.
|
|
28
|
+
|
|
29
|
+
## Mục lục
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark tương tác
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
36
|
+
|
|
37
|
+
## Tham chiếu kết quả:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md
|
|
47
|
+
|
|
48
|
+
Xem kho lưu trữ benchmark đầy đủ [tại đây](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Giới thiệu
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
60
|
+
## Kiểm tra ứng dụng của bạn
|
|
61
|
+
|
|
62
|
+
Để 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](https://intlayer.org/i18n-seo-scanner).
|
|
63
|
+
|
|
64
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
65
|
+
|
|
66
|
+
## Vấn đề
|
|
67
|
+
|
|
68
|
+
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ữ:
|
|
69
|
+
|
|
70
|
+
- 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.
|
|
71
|
+
- Tải đúng ngôn ngữ một cách linh hoạt, chỉ khi cần thiết.
|
|
72
|
+
|
|
73
|
+
Hiểu các hạn chế kỹ thuật của các phương tiếp cận này:
|
|
74
|
+
|
|
75
|
+
**Tải động (Dynamic loading)**
|
|
76
|
+
|
|
77
|
+
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ữ.
|
|
78
|
+
|
|
79
|
+
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ữ.
|
|
80
|
+
|
|
81
|
+
**Chia tách nội dung (Content splitting)**
|
|
82
|
+
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
## Phương pháp đo lường
|
|
86
|
+
|
|
87
|
+
Đối với benchmark này, chúng tôi đã so sánh các thư viện sau:
|
|
88
|
+
|
|
89
|
+
- `Base App` (Không sử dụng thư viện i18n)
|
|
90
|
+
- `react-intlayer` (v8.7.5-canary.0)
|
|
91
|
+
- `react-i18next` (v17.0.2)
|
|
92
|
+
- `use-intl` (v4.9.1)
|
|
93
|
+
- `@lingui/core` (v5.3.0)
|
|
94
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
95
|
+
- `tolgee` (v7.0.0)
|
|
96
|
+
- `react-intl` (v10.1.1)
|
|
97
|
+
- `wuchale` (v0.22.11)
|
|
98
|
+
- `gt-react` (vlatest)
|
|
99
|
+
- `lingo.dev` (v0.133.9)
|
|
100
|
+
|
|
101
|
+
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ữ**.
|
|
102
|
+
|
|
103
|
+
Chúng tôi đã so sánh **bốn chiến lược tải**:
|
|
104
|
+
|
|
105
|
+
| Chiến lược | Không có namespace (global) | Có namespace (scoped) |
|
|
106
|
+
| :----------- | :-------------------------------------------------- | :--------------------------------------------------------------------------- |
|
|
107
|
+
| **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. |
|
|
108
|
+
| **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ữ. |
|
|
109
|
+
|
|
110
|
+
## Tóm tắt chiến lược
|
|
111
|
+
|
|
112
|
+
- **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.
|
|
113
|
+
- **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ữ.
|
|
114
|
+
- **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.
|
|
115
|
+
- **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.
|
|
116
|
+
|
|
117
|
+
## Kết quả chi tiết
|
|
118
|
+
|
|
119
|
+
### 1 — Các giải pháp cần tránh
|
|
120
|
+
|
|
121
|
+
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).
|
|
122
|
+
|
|
123
|
+
Các vấn đề gặp phải:
|
|
124
|
+
|
|
125
|
+
**(General Translation)** (`gt-react@latest`):
|
|
126
|
+
|
|
127
|
+
- Đối với một ứng dụng khoảng 110kb, `gt-react` có 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).
|
|
128
|
+
- Thông báo `Quota Exceeded, please upgrade your plan` ngay trong lần xây dựng đầu tiên với General Translation.
|
|
129
|
+
- 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 đó.
|
|
130
|
+
- Trong khi triển khai **gt-tanstack-start-react**, tôi cũng gặp phải một [vấn đề](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) 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ờ.
|
|
131
|
+
- 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ẽ.
|
|
132
|
+
|
|
133
|
+
**(Lingo.dev)** (`lingo.dev@0.133.9`):
|
|
134
|
+
|
|
135
|
+
- 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.
|
|
136
|
+
- 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 `.map` thành các khối component phẳng để nó hoạt động.
|
|
137
|
+
- 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ì.
|
|
138
|
+
- 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ó.
|
|
139
|
+
- 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.
|
|
140
|
+
|
|
141
|
+
### 2 — Các giải pháp thử nghiệm
|
|
142
|
+
|
|
143
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
144
|
+
|
|
145
|
+
Ý 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.
|
|
146
|
+
|
|
147
|
+
### 3 — Các giải pháp chấp nhận được
|
|
148
|
+
|
|
149
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
150
|
+
|
|
151
|
+
`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.
|
|
152
|
+
|
|
153
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
154
|
+
|
|
155
|
+
`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.
|
|
156
|
+
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
**(use-intl)** (`use-intl@4.9.1`):
|
|
160
|
+
|
|
161
|
+
`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.
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
**(react-i18next)** (`react-i18next@17.0.2`):
|
|
166
|
+
|
|
167
|
+
`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ể.
|
|
168
|
+
|
|
169
|
+
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).
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
174
|
+
|
|
175
|
+
`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>`).
|
|
176
|
+
|
|
177
|
+
**(react-intl)** (`react-intl@10.1.1`):
|
|
178
|
+
|
|
179
|
+
`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.
|
|
180
|
+
|
|
181
|
+
### 4 — Các khuyến nghị
|
|
182
|
+
|
|
183
|
+
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ỉ.
|
|
184
|
+
|
|
185
|
+
**(Intlayer)** (`react-intlayer@8.7.5-canary.0`):
|
|
186
|
+
|
|
187
|
+
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.
|
|
188
|
+
|
|
189
|
+
### Ghi chú cá nhân
|
|
190
|
+
|
|
191
|
+
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.
|
|
192
|
+
|
|
193
|
+
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).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-20
|
|
4
|
+
title: i18n 库基准测试
|
|
5
|
+
description: 了解 Intlayer 在性能和打包体积方面与其他 i18n 库的对比情况。
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- tanstack
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
history:
|
|
17
|
+
- version: 8.7.5
|
|
18
|
+
date: 2026-01-06
|
|
19
|
+
changes: "初始化基准测试"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Benchmark - 报告
|
|
23
|
+
|
|
24
|
+
Benchmark Bloom 是一套性能基准测试,用于衡量 i18n(国际化)库在多种 React 框架与加载策略下的真实影响。
|
|
25
|
+
|
|
26
|
+
各框架的详细报告与技术文档见下方:
|
|
27
|
+
|
|
28
|
+
- [**Next.js 基准测试报告**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/benchmark/nextjs.md)
|
|
29
|
+
- [**TanStack Start 基准测试报告**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/benchmark/tanstack.md)
|