@intlayer/docs 7.3.3 → 7.3.4

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.
@@ -18,120 +18,203 @@ slugs:
18
18
  - compiler-vs-declarative-i18n
19
19
  ---
20
20
 
21
- # Lập Luận Ủng Hộ và Phản Đối i18n Dựa Trên Trình Biên Dịch
21
+ # Lập Luận Ủng Hộ và Phản Đối i18n Dựa trên Trình Biên Dịch
22
22
 
23
- Nếu bạn đã xây dựng các ứng dụng web hơn một thập kỷ, bạn sẽ biết rằng Quốc tế hóa (i18n) luôn là một điểm gây khó khăn. Đây thường là công việc không ai muốn làm — trích xuất chuỗi, quản lý các tệp JSON, và lo lắng về các quy tắc số nhiều.
23
+ Nếu bạn đã xây dựng các ứng dụng web hơn một thập kỷ, bạn biết rằng Quốc tế hóa (i18n) luôn là một điểm gây khó khăn. Đây thường là công việc không ai muốn làm — trích xuất chuỗi, quản lý các tập tin JSON, và lo lắng về các quy tắc số nhiều.
24
24
 
25
- Gần đây, một làn sóng mới các công cụ i18n "dựa trên trình biên dịch" đã xuất hiện, hứa hẹn sẽ làm biến mất những khó khăn này. Lời mời gọi thật hấp dẫn: **Chỉ cần viết văn bản trong các component của bạn, và để công cụ build lo phần còn lại.** Không cần khóa, không cần import, chỉ đơn giản là ma thuật.
25
+ Gần đây, một làn sóng mới của các công cụ i18n **"dựa trên trình biên dịch"** đã xuất hiện, hứa hẹn sẽ làm biến mất nỗi đau này. Lời chào hàng rất hấp dẫn: **Chỉ cần viết văn bản trong các component của bạn, và để công cụ build lo phần còn lại.** Không cần key, không cần import, chỉ phép thuật.
26
26
 
27
- Nhưng như với tất cả các trừu tượng trong kỹ thuật phần mềm, ma thuật luôn đi kèm với một cái giá.
27
+ Nhưng như với tất cả các trừu tượng trong kỹ thuật phần mềm, phép thuật luôn đi kèm với một cái giá.
28
28
 
29
- Trong bài blog này, chúng ta sẽ khám phá sự chuyển dịch từ các thư viện khai báo sang các phương pháp dựa trên trình biên dịch, những khoản nợ kiến trúc ẩn mà chúng mang lại, và lý do tại sao cách "nhàm chán" có thể vẫn là cách tốt nhất cho các ứng dụng chuyên nghiệp.
29
+ Trong bài blog này, chúng ta sẽ khám phá sự chuyển dịch từ các thư viện khai báo sang các phương pháp dựa trên trình biên dịch, các khoản nợ kiến trúc ẩn mà chúng mang lại, và lý do tại sao cách "nhàm chán" có thể vẫn là cách tốt nhất cho các ứng dụng chuyên nghiệp.
30
30
 
31
- ## Lịch Sử Ngắn Gọn về Dịch Thuật
31
+ ## Mục lục
32
32
 
33
- Để hiểu chúng ta đang ở đâu, chúng ta phải nhìn lại nơi chúng ta đã bắt đầu.
33
+ <TOC/>
34
34
 
35
- Khoảng năm 2011–2012, cảnh quan JavaScript rất khác biệt. Các bundler như chúng ta biết ngày nay (Webpack, Vite) chưa tồn tại hoặc còn trong giai đoạn sơ khai. Chúng ta phải dán các script lại với nhau ngay trong trình duyệt. Trong thời kỳ này, các thư viện như **i18next** đã ra đời.
35
+ ## Lược Sử Ngắn Gọn về Quốc Tế Hóa
36
36
 
37
- Chúng giải quyết vấn đề theo cách duy nhất thể vào thời điểm đó: **Từ điển thời gian chạy (Runtime Dictionaries)**. Bạn tải một đối tượng JSON khổng lồ vào bộ nhớ, và một hàm sẽ tra cứu các khóa ngay lập tức. Cách này đáng tin cậy, rõ ràng và hoạt động ở mọi nơi.
37
+ Để hiểu được vị trí hiện tại, chúng ta phải nhìn lại nơi chúng ta đã bắt đầu.
38
38
 
39
- Tiến tới ngày nay. Chúng ta các trình biên dịch mạnh mẽ (SWC, các bundler dựa trên Rust) thể phân tích Cây Pháp Trừu Tượng (AST) trong vài mili giây. Sức mạnh này đã sinh ra một ý tưởng mới: _Tại sao chúng ta phải quản khóa thủ công? Tại sao trình biên dịch không thể nhìn thấy đoạn văn bản "Hello World" thay thế nó cho chúng ta?_
39
+ Khoảng năm 2011–2012, cảnh quan JavaScript rất khác biệt. Các bundler như chúng ta biết ngày nay (Webpack, Vite) chưa tồn tại hoặc còn trong giai đoạn khai. Chúng ta đang dán các script lại với nhau trong trình duyệt. Trong thời kỳ này, các thư viện như **i18next** đã ra đời.
40
+
41
+ Chúng giải quyết vấn đề theo cách duy nhất có thể vào thời điểm đó: **Từ điển thời gian chạy (Runtime Dictionaries)**. Bạn tải một đối tượng JSON khổng lồ vào bộ nhớ, và một hàm sẽ tra cứu các key ngay lập tức. Nó đáng tin cậy, rõ ràng và hoạt động ở mọi nơi.
42
+
43
+ Tiến tới ngày nay. Chúng ta có các trình biên dịch mạnh mẽ (SWC, các bundler dựa trên Rust) có thể phân tích Cây Cú Pháp Trừu Tượng (AST) trong vài mili giây. Sức mạnh này đã sinh ra một ý tưởng mới: _Tại sao chúng ta phải quản lý key thủ công? Tại sao trình biên dịch không thể chỉ nhìn thấy văn bản "Hello World" và thay thế nó cho chúng ta?_
40
44
 
41
45
  Và thế là i18n dựa trên trình biên dịch ra đời.
42
46
 
43
- ## Sức Hấp Dẫn của Trình Biên Dịch (Cách Tiếp Cận "Phép Thuật")
47
+ > **Ví dụ về i18n dựa trên trình biên dịch:**
48
+ >
49
+ > - Paraglide (Các module được tree-shaken biên dịch mỗi thông điệp thành một hàm ESM nhỏ gọn để các bundler có thể tự động loại bỏ các locale và key không sử dụng. Bạn nhập các thông điệp dưới dạng hàm thay vì tra cứu key chuỗi.)
50
+ > - LinguiJS (Trình biên dịch macro-thành-hàm, viết lại các macro thông điệp như `<Trans>` thành các lời gọi hàm JS thuần túy tại thời điểm build. Bạn có cú pháp ICU/MessageFormat với footprint runtime rất nhỏ.)
51
+ > - Lingo.dev (Tập trung tự động hóa quy trình localization bằng cách chèn nội dung đã dịch trực tiếp trong quá trình build ứng dụng React của bạn. Nó có thể tự động tạo bản dịch sử dụng AI và tích hợp trực tiếp vào CI/CD.)
52
+ > - Wuchale (Trình tiền xử lý ưu tiên Svelte, trích xuất văn bản nội tuyến trong các file .svelte và biên dịch thành các hàm dịch không bao bọc. Nó tránh sử dụng key chuỗi và tách biệt hoàn toàn logic trích xuất nội dung khỏi runtime chính của ứng dụng.)
53
+ > - Intlayer (Trình biên dịch / CLI trích xuất phân tích các component của bạn, tạo các từ điển kiểu, và có thể tùy chọn viết lại mã để sử dụng nội dung Intlayer rõ ràng. Mục tiêu là sử dụng trình biên dịch để tăng tốc độ trong khi giữ một lõi khai báo, không phụ thuộc vào framework.)
54
+
55
+ > **Ví dụ về i18n khai báo:**
56
+ >
57
+ > - i18next / react-i18next / next-i18next (Tiêu chuẩn công nghiệp trưởng thành sử dụng từ điển JSON tại runtime và hệ sinh thái plugin rộng lớn)
58
+ > - react-intl (Một phần của thư viện FormatJS, tập trung vào cú pháp thông điệp ICU tiêu chuẩn và định dạng dữ liệu nghiêm ngặt)
59
+ > - next-intl (Tối ưu hóa đặc biệt cho Next.js với tích hợp cho App Router và React Server Components)
60
+ > - vue-i18n / @nuxt/i18n (Giải pháp tiêu chuẩn trong hệ sinh thái Vue cung cấp các khối dịch ở cấp component và tích hợp phản ứng chặt chẽ)
61
+ > - svelte-i18n (Một lớp bao nhẹ quanh các store của Svelte để dịch phản ứng tại runtime)
62
+ > - angular-translate (Thư viện dịch động kế thừa dựa trên tra cứu key tại runtime thay vì gộp vào thời gian build)
63
+ > - angular-i18n (Phương pháp gốc của Angular, ahead-of-time, gộp các file XLIFF trực tiếp vào template trong quá trình build)
64
+ > - Tolgee (Kết hợp mã khai báo với một SDK trong ngữ cảnh để chỉnh sửa "click-to-translate" trực tiếp trong giao diện người dùng)
65
+ > - Intlayer (Phương pháp theo từng component, sử dụng các file khai báo nội dung cho phép tree-shaking gốc và kiểm tra TypeScript)
66
+
67
+ ## Trình Biên Dịch Intlayer
68
+
69
+ Mặc dù **Intlayer** là một giải pháp cơ bản khuyến khích một **phương pháp khai báo** cho nội dung của bạn, nó bao gồm một trình biên dịch để giúp tăng tốc phát triển hoặc hỗ trợ tạo mẫu nhanh.
70
+
71
+ Trình biên dịch Intlayer duyệt qua AST (Cây Cú Pháp Trừu Tượng) của các component React, Vue hoặc Svelte của bạn, cũng như các file JavaScript/TypeScript khác. Vai trò của nó là phát hiện các chuỗi ký tự cứng nhắc và trích xuất chúng vào các khai báo `.content` riêng biệt.
72
+
73
+ > Để biết thêm chi tiết, hãy xem tài liệu: [Tài liệu Trình Biên Dịch Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/docs/vi/compiler.md)
74
+
75
+ ## Sức Hút của Trình Biên Dịch (Phương Pháp "Phép Thuật")
44
76
 
45
- Có một lý do khiến cách tiếp cận mới này đang trở thành xu hướng. Đối với một nhà phát triển, trải nghiệm này thật tuyệt vời.
77
+ Có một lý do khiến phương pháp mới này đang trở nên phổ biến. Đối với một nhà phát triển, trải nghiệm này thật tuyệt vời.
46
78
 
47
79
  ### 1. Tốc Độ và "Dòng Chảy"
48
80
 
49
- Khi bạn đang tập trung, việc dừng lại để nghĩ tên biến (`home_hero_title_v2`) sẽ làm gián đoạn dòng chảy công việc của bạn. Với cách tiếp cận trình biên dịch, bạn chỉ cần gõ `<p>Welcome back</p>` và tiếp tục. Không có sự cản trở nào.
81
+ Khi bạn đang tập trung cao độ, việc dừng lại để nghĩ tên biến có ý nghĩa (`home_hero_title_v2`) sẽ làm gián đoạn dòng chảy công việc của bạn. Với phương pháp trình biên dịch, bạn chỉ cần gõ `<p>Welcome back</p>` và tiếp tục. Sự cản trở bằng không.
50
82
 
51
83
  ### 2. Nhiệm Vụ Cứu Hộ Di Sản
52
84
 
53
- Hãy tưởng tượng bạn thừa kế một codebase khổng lồ với 5.000 component và không có bản dịch nào. Việc bổ sung hệ thống dựa trên khóa thủ công sẽ là một cơn ác mộng kéo dài hàng tháng. Công cụ dựa trên trình biên dịch hoạt động như một chiến lược cứu hộ, ngay lập tức trích xuất hàng ngàn chuỗi mà bạn không cần phải chạm vào bất kỳ file nào thủ công.
85
+ Hãy tưởng tượng bạn thừa kế một codebase khổng lồ với 5.000 component và không có bản dịch nào. Việc cải tạo lại hệ thống này bằng cách sử dụng hệ thống key thủ công sẽ là một cơn ác mộng kéo dài hàng tháng. Một công cụ dựa trên trình biên dịch sẽ đóng vai trò như một chiến lược cứu hộ, ngay lập tức trích xuất hàng nghìn chuỗi mà bạn không cần phải động tay vào bất kỳ file nào một cách thủ công.
54
86
 
55
- ### 3. Thời Đại AI
87
+ ### 3. Kỷ Nguyên AI
56
88
 
57
- Đây là một lợi ích hiện đại mà chúng ta không nên bỏ qua. Các trợ lý lập trình AI (như Copilot hoặc ChatGPT) tự nhiên tạo ra JSX/HTML tiêu chuẩn. Chúng không biết sơ đồ khóa dịch thuật cụ thể của bạn.
89
+ Đây là một lợi ích hiện đại mà chúng ta không nên bỏ qua. Các trợ lý lập trình AI (như Copilot hoặc ChatGPT) tự nhiên tạo ra JSX/HTML tiêu chuẩn. Chúng không biết sơ đồ key dịch cụ thể của bạn.
58
90
 
59
- - **Khai báo:** Bạn phải viết lại đầu ra của AI để thay thế văn bản bằng các khóa.
91
+ - **Khai báo:** Bạn phải viết lại đầu ra của AI để thay thế văn bản bằng các key.
60
92
  - **Trình biên dịch:** Bạn chỉ cần sao chép-dán mã của AI, và nó hoạt động ngay.
61
93
 
62
94
  ## Kiểm Tra Thực Tế: Tại Sao "Phép Thuật" Lại Nguy Hiểm
63
95
 
64
- Mặc dù "phép thuật" rất hấp dẫn, nhưng sự trừu tượng này lại bị rò rỉ. Dựa vào công cụ xây dựng để hiểu ý định của con người sẽ tạo ra sự mong manh về kiến trúc.
96
+ Mặc dù "phép thuật" rất hấp dẫn, nhưng sự trừu tượng này lại bị rò rỉ. Việc dựa vào một công cụ build để hiểu ý định của con người sẽ tạo ra sự mong manh về mặt kiến trúc.
97
+
98
+ ### Sự Mong Manh Dựa Trên Heuristic (Trò Chơi Đoán)
99
+
100
+ Trình biên dịch phải đoán đâu là nội dung và đâu là mã. Điều này dẫn đến các trường hợp biên giới mà bạn cuối cùng phải "đấu tranh" với công cụ.
101
+
102
+ Hãy xem xét các tình huống sau:
103
+
104
+ - Có nên trích xuất `<span className="active"></span>` không? (Nó là một chuỗi, nhưng có thể là một class).
105
+ - Có nên trích xuất `<span status="pending"></span>` không? (Nó là một giá trị prop).
106
+ - Có nên trích xuất `<span>{"Hello World"}</span>` không? (Nó là một biểu thức JS).
107
+ - Có nên trích xuất `<span>Hello {name}. How are you?</span>` không? (Nội suy phức tạp).
108
+ - Có nên trích xuất `<span aria-label="Image of cat"></span>` không? (Thuộc tính truy cập cần được dịch).
109
+ - Có phải `<span data-testid="my-element"></span>` được trích xuất không? (Các ID kiểm thử KHÔNG nên được dịch).
110
+ - Có phải `<MyComponent errorMessage="An error occurred" />` được trích xuất không?
111
+ - Có phải `<p>This is a paragraph{" "}\n containing multiple lines</p>` được trích xuất không?
112
+ - Có phải kết quả hàm `<p>{getStatusMessage()}</p>` được trích xuất không?
113
+ - Có phải `<div>{isLoading ? "The page is loading" : <MyComponent/>} </div>` được trích xuất không?
114
+ - Có phải một ID sản phẩm như `<span>AX-99</span>` được trích xuất không?
115
+
116
+ Bạn không thể tránh khỏi việc phải thêm các chú thích cụ thể (như `// ignore-translation`, hoặc các props cụ thể như `data-compiler-ignore="true"`) để ngăn chặn việc làm hỏng logic ứng dụng của bạn.
117
+
118
+ ### Intlayer xử lý sự phức tạp này như thế nào?
119
+
120
+ Intlayer sử dụng một phương pháp kết hợp để phát hiện xem một trường có nên được trích xuất để dịch hay không, cố gắng giảm thiểu các kết quả dương tính giả:
65
121
 
66
- ### 1. Sự Mong Manh Dựa Trên Phán Đoán (Trò Chơi Đoán)
122
+ 1. **Phân tích AST:** kiểm tra loại phần tử ( dụ: phân biệt giữa một `reactNode`, một `label`, hoặc một prop `title`).
123
+ 2. **Nhận diện mẫu:** Nó phát hiện xem chuỗi có được viết hoa hay có chứa khoảng trắng hay không, gợi ý rằng đó có khả năng là văn bản dễ đọc cho con người thay vì một định danh mã.
67
124
 
68
- Trình biên dịch phải đoán đâu nội dung và đâu là mã.
125
+ ### Giới hạn cứng của dữ liệu động
69
126
 
70
- - `className="active"` được dịch không? một chuỗi.
71
- - `status="pending"` được dịch không?
72
- - Có phải `<MyComponent errorMessage="An error occurred" />` được dịch không?
73
- - Có phải một ID sản phẩm như `"AX-99"` được dịch không?
127
+ Việc trích xuất của trình biên dịch dựa vào **phân tích tĩnh**. phải nhìn thấy chuỗi ký tự nguyên văn trong mã của bạn để tạo ra một ID ổn định.
128
+ Nếu API của bạn trả về một chuỗi mã lỗi như `server_error`, bạn không thể dịch nó bằng trình biên dịch vì trình biên dịch không biết chuỗi đó tồn tại vào thời điểm xây dựng. Bạn buộc phải xây dựng một hệ thống thứ cấp "chỉ chạy thời gian thực" chỉ dành cho dữ liệu động.
74
129
 
75
- Bạn không thể tránh khỏi việc "đấu tranh" với trình biên dịch, thêm các chú thích cụ thể (như `// ignore-translation`) để ngăn nó phá vỡ logic ứng dụng của bạn.
130
+ ### Thiếu phân đoạn (chunking)
76
131
 
77
- ### 2. Giới Hạn Cứng của Dữ Liệu Động
132
+ Một số trình biên dịch không phân đoạn bản dịch theo từng trang. Nếu trình biên dịch của bạn tạo ra một tệp JSON lớn cho mỗi ngôn ngữ (ví dụ: `./lang/en.json`, `./lang/fr.json`, v.v.), bạn có khả năng sẽ tải nội dung từ tất cả các trang của mình cho một trang được truy cập duy nhất. Ngoài ra, mỗi thành phần sử dụng nội dung của bạn có thể sẽ được khởi tạo với nhiều nội dung hơn mức cần thiết, điều này có thể gây ra các vấn đề về hiệu suất.
78
133
 
79
- Việc trích xuất của trình biên dịch dựa trên **phân tích tĩnh**. phải nhìn thấy chuỗi tự nguyên văn trong của bạn để tạo ra một ID ổn định.
80
- Nếu API của bạn trả về một chuỗi mã lỗi như `server_error`, bạn không thể dịch nó bằng trình biên dịch vì trình biên dịch không biết chuỗi đó tồn tại tại thời điểm xây dựng. Bạn buộc phải xây dựng một hệ thống "chỉ chạy thời gian thực" phụ trợ chỉ dành cho dữ liệu động.
134
+ Cũng hãy cẩn thận khi tải bản dịch của bạn một cách động. Nếu điều này không được thực hiện, bạn sẽ tải nội dung cho tất cả các ngôn ngữ ngoài ngôn ngữ hiện tại.
81
135
 
82
- ### 3. "Bùng Nổ Chunk"Hiện Tượng Thác Nước Mạng
136
+ > Để minh họa vấn đề, hãy xem xét một trang web có 10 trang 10 ngôn ngữ (tất cả đều 100% độc nhất). Bạn sẽ tải nội dung cho 99 trang bổ sung (10 × 10 - 1).
83
137
 
84
- Để cho phép tree-shaking, các công cụ trình biên dịch thường chia nhỏ bản dịch theo từng component.
138
+ ### "Sự bùng nổ chunk" Hiện tượng thác mạng
85
139
 
86
- - **Hệ quả:** Một trang hiển thị với 50 component nhỏ thể kích hoạt **50 yêu cầu HTTP riêng biệt** cho các đoạn dịch nhỏ. Ngay cả với HTTP/2, điều này tạo ra một chuỗi yêu cầu mạng khiến ứng dụng của bạn cảm thấy chậm chạp so với việc tải một gói ngôn ngữ duy nhất được tối ưu hóa.
140
+ Để giải quyết vấn đề chunking, một số giải pháp cung cấp chunking theo thành phần, hoặc thậm chí theo từng khóa. Tuy nhiên, vấn đề chỉ được giải quyết một phần. Điểm bán hàng của những giải pháp này thường nói "Nội dung của bạn được tree-shaken."
87
141
 
88
- ### 4. Chi phí hiệu năng khi chạy
142
+ Thật vậy, nếu bạn tải nội dung một cách tĩnh, giải pháp của bạn sẽ tree-shake nội dung không sử dụng, nhưng bạn vẫn sẽ kết thúc với nội dung từ tất cả các ngôn ngữ được tải cùng với ứng dụng của bạn.
89
143
 
90
- Để làm cho bản dịch phản ứng (để chúng cập nhật ngay lập tức khi bạn chuyển đổi ngôn ngữ), trình biên dịch thường chèn các hook quản trạng thái vào _mọi_ component.
144
+ Vậy tại sao không tải một cách động? Đúng vậy, trong trường hợp đó bạn sẽ tải nhiều nội dung hơn mức cần thiết, nhưng điều đó không phải không những đánh đổi.
91
145
 
92
- - **Chi phí:** Nếu bạn render một danh sách gồm 5.000 mục, bạn sẽ khởi tạo 5.000 hook `useState` `useEffect` chỉ để xử văn bản. Điều này tiêu tốn bộ nhớ chu kỳ CPU các thư viện khai báo (thường sử dụng một Context provider duy nhất) thể tiết kiệm được.
146
+ Việc tải nội dung một cách động sẽ cô lập mỗi phần nội dung trong một chunk riêng biệt, chỉ được tải khi component đó được render. Điều này có nghĩa là bạn sẽ thực hiện một yêu cầu HTTP cho mỗi khối văn bản. Có 1.000 khối văn bản trên trang của bạn? → 1.000 yêu cầu HTTP đến máy chủ của bạn. để hạn chế thiệt hại tối ưu thời gian render lần đầu của ứng dụng, bạn sẽ cần chèn nhiều ranh giới Suspense hoặc Skeleton Loaders.
93
147
 
94
- ## Cái bẫy: Bị khóa nhà cung cấp
148
+ > Lưu ý: Ngay cả với Next.js và SSR, các component của bạn vẫn sẽ được hydrate sau khi tải, vì vậy các yêu cầu HTTP vẫn sẽ được thực hiện.
95
149
 
96
- Đây thể coi khía cạnh nguy hiểm nhất của i18n dựa trên trình biên dịch.
150
+ Giải pháp? Áp dụng một giải pháp cho phép khai báo nội dung theo phạm vi, như `i18next`, `next-intl`, hoặc `intlayer` làm.
97
151
 
98
- Trong một thư viện khai báo, nguồn của bạn chứa ý định ràng. Bạn sở hữu các khóa. Nếu bạn chuyển đổi thư viện, bạn chỉ cần thay đổi phần import.
152
+ > Lưu ý: `i18next` `next-intl` yêu cầu bạn quản thủ công việc nhập namespace / messages cho từng trang để tối ưu kích thước bundle. Bạn nên sử dụng công cụ phân tích bundle như `rollup-plugin-visualizer` (vite), `@next/bundle-analyzer` (next.js), hoặc `webpack-bundle-analyzer` (React CRA / Angular / v.v.) để phát hiện xem bạn đang làm phình bundle với các bản dịch không sử dụng hay không.
99
153
 
100
- Trong cách tiếp cận dựa trên trình biên dịch, **mã nguồn của bạn chỉ là văn bản tiếng Anh.** "Logic dịch thuật" chỉ tồn tại bên trong cấu hình plugin xây dựng.
101
- Nếu thư viện đó ngừng được duy trì, hoặc nếu bạn vượt quá khả năng của nó, bạn sẽ bị mắc kẹt. Bạn không thể dễ dàng "eject" vì bạn không có bất kỳ khóa dịch nào trong mã nguồn của mình. Bạn sẽ phải tự tay viết lại toàn bộ ứng dụng để di chuyển sang giải pháp khác.
154
+ ### Chi phí hiệu năng khi chạy
102
155
 
103
- ## Mặt khác: Rủi ro của phương pháp khai báo
156
+ Để làm cho các bản dịch phản ứng (để chúng cập nhật ngay lập tức khi bạn chuyển đổi ngôn ngữ), trình biên dịch thường chèn các hook quản lý trạng thái vào mỗi component.
104
157
 
105
- Công bằng nói, cách khai báo truyền thống cũng không hoàn hảo. những "cạm bẫy" riêng.
158
+ - **Chi phí:** Nếu bạn render một danh sách gồm 5.000 mục, bạn sẽ khởi tạo 5.000 hook `useState` `useEffect` chỉ dành cho văn bản. React phải xác định và render lại tất cả 5.000 consumer cùng lúc. Điều này gây ra một block lớn trên "Main Thread", làm đóng băng giao diện người dùng trong quá trình chuyển đổi. Điều này tiêu tốn bộ nhớ và chu kỳ CPU mà các thư viện khai báo (thường sử dụng một Context provider duy nhất) có thể tiết kiệm được.
106
159
 
107
- 1. **Địa ngục Namespace:** Bạn thường phải quản thủ công các tệp JSON nào sẽ được tải (`common.json`, `dashboard.json`, `footer.json`). Nếu bạn quên một tệp, người dùng sẽ thấy các khóa thô.
108
- 2. **Tải quá mức:** Nếu không cấu hình cẩn thận, rất dễ vô tình tải _tất cả_ các khóa dịch của bạn cho _tất cả_ các trang ngay lần tải đầu tiên, làm phình to kích thước gói của bạn.
109
- 3. **Trôi đồng bộ:** Thông thường các khóa vẫn còn trong file JSON dù component sử dụng chúng đã bị xóa. Các file dịch của bạn sẽ ngày càng phình to, chứa đầy các "khóa ma."
160
+ > Lưu ý rằng vấn đề này tương tự đối với các framework khác ngoài React.
161
+
162
+ ## Cạm bẫy: Bị khóa nhà cung cấp
163
+
164
+ Hãy cẩn thận khi chọn một giải pháp i18n cho phép trích xuất hoặc di chuyển các khóa dịch.
165
+
166
+ Trong trường hợp sử dụng thư viện khai báo, mã nguồn của bạn rõ ràng chứa ý định dịch thuật: đây là các khóa của bạn, và bạn kiểm soát chúng. Nếu bạn muốn thay đổi thư viện, bạn thường chỉ cần cập nhật phần import.
167
+
168
+ Với cách tiếp cận trình biên dịch, mã nguồn của bạn có thể chỉ là văn bản tiếng Anh thuần túy, không có dấu vết của logic dịch thuật: mọi thứ đều được ẩn trong cấu hình công cụ xây dựng. Nếu plugin đó không còn được duy trì hoặc bạn muốn thay đổi giải pháp, bạn có thể bị mắc kẹt. Không có cách dễ dàng để “thoát ra”: không có khóa sử dụng được trong mã của bạn, và bạn có thể cần phải tạo lại tất cả bản dịch cho thư viện mới.
169
+
170
+ Một số giải pháp cũng cung cấp dịch vụ tạo bản dịch. Hết tín dụng? Hết bản dịch.
171
+
172
+ Trình biên dịch thường băm văn bản (ví dụ, `"Hello World"` -> `x7f2a`). Các tệp dịch của bạn trông như `{ "x7f2a": "Hola Mundo" }`. Cạm bẫy: Nếu bạn chuyển đổi thư viện, thư viện mới sẽ thấy `"Hello World"` và tìm khóa đó. Nó sẽ không tìm thấy vì tệp dịch của bạn đầy các giá trị băm (`x7f2a`).
173
+
174
+ ### Ràng Buộc Nền Tảng
175
+
176
+ Bằng cách chọn phương pháp dựa trên trình biên dịch, bạn tự ràng buộc mình vào nền tảng cơ bản. Ví dụ, một số trình biên dịch không có sẵn cho tất cả các bundler (như Vite, Turbopack hoặc Metro). Điều này có thể làm cho việc di chuyển trong tương lai trở nên khó khăn, và bạn có thể cần áp dụng nhiều giải pháp để bao phủ tất cả các ứng dụng của mình.
177
+
178
+ ## Mặt Khác: Rủi Ro của Cách Tiếp Cận Khai Báo
179
+
180
+ Công bằng mà nói, cách tiếp cận khai báo truyền thống cũng không hoàn hảo. Nó có một số "cạm bẫy" riêng.
181
+
182
+ 1. **Địa Ngục Namespace:** Bạn thường phải quản lý thủ công các tệp JSON nào sẽ được tải (`common.json`, `dashboard.json`, `footer.json`). Nếu bạn quên một tệp, người dùng sẽ thấy các khóa thô.
183
+ 2. **Tải quá mức:** Nếu không cấu hình cẩn thận, rất dễ vô tình tải _tất cả_ các khóa dịch cho _tất cả_ các trang ngay lần tải đầu tiên, làm phình to kích thước gói của bạn.
184
+ 3. **Trôi đồng bộ:** Thường xảy ra tình trạng các khóa vẫn còn trong file JSON dù thành phần sử dụng chúng đã bị xóa. Các file dịch của bạn sẽ ngày càng lớn vô hạn, chứa đầy các "khóa ma."
110
185
 
111
186
  ## Giải pháp Trung gian của Intlayer
112
187
 
113
- Đây là nơi các công cụ như **Intlayer** cố gắng đổi mới. Intlayer hiểu rằng dù compiler rất mạnh mẽ, nhưng phép thuật ngầm lại nguy hiểm.
188
+ Đây là nơi các công cụ như **Intlayer** đang cố gắng đổi mới. Intlayer hiểu rằng mặc dù compiler rất mạnh mẽ, nhưng phép thuật ngầm (implicit magic) lại nguy hiểm.
114
189
 
115
- Intlayer cung cấp một **lệnh `transform`** độc đáo. Thay vì chỉ làm phép thuật trong bước build ẩn, thực sự thể **viết lại component của bạn**. quét văn bản thay thế bằng các khai báo nội dung ràng trong codebase của bạn.
190
+ Intlayer cung cấp một phương pháp kết hợp, cho phép bạn tận dụng lợi thế của cả hai cách tiếp cận: quản nội dung khai báo, đồng thời tương thích với compiler của để tiết kiệm thời gian phát triển.
191
+
192
+ Ngay cả khi bạn không sử dụng trình biên dịch Intlayer, Intlayer cũng cung cấp lệnh `transform` (cũng có thể truy cập thông qua tiện ích mở rộng VSCode). Thay vì chỉ thực hiện phép thuật trong bước xây dựng ẩn, nó thực sự có thể **viết lại mã thành phần của bạn**. Nó quét văn bản của bạn và thay thế nó bằng các khai báo nội dung rõ ràng trong codebase của bạn.
116
193
 
117
194
  Điều này mang lại cho bạn lợi ích của cả hai thế giới:
118
195
 
119
- 1. **Độ chi tiết:** Bạn giữ bản dịch gần với các component của mình (cải thiện tính mô-đun và tree-shaking).
120
- 2. **An toàn:** Bản dịch trở thành mã rõ ràng, không phải phép thuật ẩn trong thời gian build.
121
- 3. **Không bị khóa:** Vì mã được chuyển đổi thành cấu trúc khai báo tiêu chuẩn trong repo của bạn, bạn không giấu logic trong một plugin webpack.
196
+ 1. **Độ chi tiết:** Bạn giữ các bản dịch gần với các thành phần của mình (cải thiện tính mô-đun và tree-shaking).
197
+ 2. **An toàn:** Bản dịch trở thành mã rõ ràng, không phải phép thuật ẩn trong thời gian xây dựng.
198
+ 3. **Không bị khóa:** Vì mã được chuyển đổi thành cấu trúc khai báo trong repo của bạn, bạn có thể dễ dàng nhấn tab, hoặc sử dụng copilot của IDE để tạo các khai báo nội dung, bạn không giấu logic trong plugin webpack.
122
199
 
123
200
  ## Kết luận
124
201
 
125
- Vậy, bạn nên chọn gì?
202
+ Vậy, bạn nên chọn phương án nào?
203
+
204
+ **Nếu bạn đang xây dựng MVP, hoặc muốn tiến nhanh:**
205
+ Phương pháp dựa trên compiler là một lựa chọn hợp lý. Nó cho phép bạn tiến rất nhanh. Bạn không cần phải lo lắng về cấu trúc file hay các khóa. Bạn chỉ cần xây dựng. Nợ kỹ thuật sẽ là vấn đề của "Bạn trong tương lai."
206
+
207
+ **Nếu bạn là một Junior Developer, hoặc không quan tâm đến tối ưu hóa:**
208
+ Nếu bạn muốn ít quản lý thủ công nhất, phương pháp dựa trên compiler có thể là tốt nhất. Bạn sẽ không cần phải xử lý các khóa hay file dịch thuật - chỉ cần viết văn bản, và compiler sẽ tự động hóa phần còn lại. Điều này giảm thiểu công sức thiết lập và các lỗi i18n phổ biến liên quan đến các bước thủ công.
126
209
 
127
- **Nếu bạn một Junior Developer, một Nhà sáng lập đơn lẻ, hoặc đang xây dựng MVP:**
128
- Phương pháp dựa trên compiler là một lựa chọn hợp lệ. cho phép bạn di chuyển cực kỳ nhanh. Bạn không cần phải lo lắng về cấu trúc file hay các khóa. Bạn chỉ cần xây dựng. Nợ kỹ thuật vấn đề của "Bạn trong tương lai."
210
+ **Nếu bạn đang quốc tế hóa một dự án hiện đã bao gồm hàng ngàn component cần tái cấu trúc:**
211
+ Một phương pháp dựa trên trình biên dịch có thể là một lựa chọn thực tế đây. Giai đoạn trích xuất ban đầu có thể tiết kiệm hàng tuần hoặc hàng tháng công việc thủ công. Tuy nhiên, hãy cân nhắc sử dụng một công cụ như lệnh `transform` của Intlayer, có thể trích xuất chuỗi và chuyển đổi chúng thành các khai báo nội dung khai báo rõ ràng. Điều này mang lại cho bạn tốc độ tự động hóa trong khi vẫn duy trì sự an toàn và khả năng di động của phương pháp khai báo. Bạn sẽ được lợi ích của cả hai thế giới: di chuyển nhanh ban đầu mà không gây ra nợ kiến trúc lâu dài.
129
212
 
130
213
  **Nếu bạn đang xây dựng một Ứng dụng Chuyên nghiệp, Cấp Doanh nghiệp:**
131
214
  Phép thuật thường là một ý tưởng tồi. Bạn cần kiểm soát.
132
215
 
133
216
  - Bạn cần xử lý dữ liệu động từ backend.
134
- - Bạn cần đảm bảo hiệu suất trên các thiết bị cấu hình thấp (tránh hiện tượng hook bị quá tải).
217
+ - Bạn cần đảm bảo hiệu suất trên các thiết bị cấu hình thấp (tránh hiện tượng quá tải hook).
135
218
  - Bạn cần đảm bảo rằng bạn không bị khóa vào một công cụ build cụ thể mãi mãi.
136
219
 
137
220
  Đối với các ứng dụng chuyên nghiệp, **Quản lý Nội dung Khai báo** (như Intlayer hoặc các thư viện đã được thiết lập) vẫn là tiêu chuẩn vàng. Nó tách biệt các mối quan tâm của bạn, giữ cho kiến trúc của bạn sạch sẽ, và đảm bảo rằng khả năng đa ngôn ngữ của ứng dụng không phụ thuộc vào một trình biên dịch "hộp đen" đoán ý định của bạn.
@@ -2,7 +2,7 @@
2
2
  createdAt: 2025-11-24
3
3
  updatedAt: 2025-11-24
4
4
  title: 编译器与声明式国际化的对比
5
- description: 探讨“魔法”编译器驱动国际化与显式声明式内容管理之间的架构权衡。
5
+ description: 探讨“魔法”编译器驱动的国际化与显式声明式内容管理之间的架构权衡。
6
6
  keywords:
7
7
  - Intlayer
8
8
  - 国际化
@@ -22,23 +22,55 @@ slugs:
22
22
 
23
23
  如果你从事网页应用开发超过十年,你会知道国际化(i18n)一直是一个难点。它通常是没人愿意做的任务——提取字符串、管理 JSON 文件,以及处理复数规则。
24
24
 
25
- 最近,一波新的“编译器驱动”国际化工具出现了,承诺让这份痛苦消失。它们的卖点很诱人:**只需在组件中编写文本,构建工具会处理剩下的一切。**无需键名,无需导入,纯粹是魔法。
25
+ 最近,一波新的**“基于编译器”的国际化(i18n)工具**涌现,承诺让这份痛苦消失。它们的宣传非常诱人:**只需在组件中编写文本,构建工具会处理剩下的一切。**无需键名,无需导入,纯粹是魔法。
26
26
 
27
27
  但正如软件工程中的所有抽象一样,魔法是有代价的。
28
28
 
29
- 在这篇博客文章中,我们将探讨从声明式库向编译器驱动方法的转变,它们引入的隐藏架构债务,以及为什么“无聊”的方式可能仍然是专业应用的最佳选择。
29
+ 在这篇博客文章中,我们将探讨从声明式库向基于编译器方法的转变,它们引入的隐藏架构债务,以及为什么"无聊"的方式可能仍然是专业应用的最佳选择。
30
30
 
31
- ## 翻译的简史
31
+ ## 目录
32
32
 
33
- 要理解我们现在的位置,必须回顾我们从哪里开始。
33
+ <TOC/>
34
34
 
35
- 大约在2011年至2012年,JavaScript的生态环境截然不同。我们现在熟知的打包工具(如Webpack、Vite)当时还不存在或处于初期阶段。那时,我们是在浏览器中将脚本拼接在一起。在那个时代,像**i18next**这样的库诞生了。
35
+ ## 国际化的简史
36
36
 
37
- 它们以当时唯一可行的方式解决了问题:**运行时字典**。你将一个庞大的JSON对象加载到内存中,然后通过函数动态查找键。它可靠、明确,并且在任何地方都能工作。
37
+ 要理解我们现在所处的位置,必须回顾我们从哪里开始。
38
38
 
39
- 快进到今天。我们拥有强大的编译器(如SWC、基于Rust的打包工具),能够在毫秒级别解析抽象语法树(AST)。这种能力催生了一个新想法:_为什么我们还要手动管理键?为什么编译器不能直接看到文本“Hello World”并替我们替换它?_
39
+ 大约在2011年至2012年,JavaScript的生态环境截然不同。我们今天所熟知的打包工具(如Webpack、Vite)还不存在,或者还处于初期阶段。那时,我们是在浏览器中手动拼接脚本。在那个时代,像**i18next**这样的库诞生了。
40
40
 
41
- 于是,基于编译器的i18n诞生了。
41
+ 它们以当时唯一可行的方式解决了问题:**运行时字典**。你将一个庞大的JSON对象加载到内存中,然后通过函数动态查找键。这种方式可靠、明确,并且在任何地方都能工作。
42
+
43
+ 快进到今天。我们拥有强大的编译器(如SWC、基于Rust的打包工具),它们可以在毫秒级别解析抽象语法树(AST)。这种能力催生了一个新想法:_为什么我们还要手动管理键?为什么编译器不能直接识别文本“Hello World”,并替我们替换它?_
44
+
45
+ 于是,基于编译器的国际化(i18n)诞生了。
46
+
47
+ > **基于编译器的 i18n 示例:**
48
+ >
49
+ > - Paraglide(经过 Tree-shaking 的模块,将每条消息编译成一个小型的 ESM 函数,使打包工具能够自动剔除未使用的语言和键。你导入消息作为函数,而不是通过字符串键查找。)
50
+ > - LinguiJS(宏到函数的编译器,在构建时将类似 `<Trans>` 的消息宏重写为普通的 JS 函数调用。你可以使用 ICU/MessageFormat 语法,且运行时开销非常小。)
51
+ > - Lingo.dev(专注于自动化本地化流程,在构建 React 应用时直接注入翻译内容。它可以使用 AI 自动生成翻译,并直接集成到 CI/CD 流程中。)
52
+ > - Wuchale(以 Svelte 为先的预处理器,提取 .svelte 文件中的内联文本并将其编译为零包装的翻译函数。它避免使用字符串键,并将内容提取逻辑完全与主应用运行时分离。)
53
+ > - Intlayer(编译器 / 提取 CLI,解析你的组件,生成类型化字典,并可选择性地重写代码以使用显式的 Intlayer 内容。目标是在保持声明式、框架无关核心的同时,利用编译器提升开发效率。)
54
+
55
+ > **声明式 i18n 示例:**
56
+ >
57
+ > - i18next / react-i18next / next-i18next(成熟的行业标准,使用运行时 JSON 字典和丰富的插件生态系统)
58
+ > - react-intl(FormatJS 库的一部分,专注于标准 ICU 消息语法和严格的数据格式化)
59
+ > - next-intl(专为 Next.js 优化,集成了 App Router 和 React Server Components)
60
+ > - vue-i18n / @nuxt/i18n(标准的 Vue 生态系统解决方案,提供组件级翻译块和紧密的响应式集成)
61
+ > - svelte-i18n(围绕 Svelte stores 的轻量级封装,用于响应式运行时翻译)
62
+ > - angular-translate(传统的动态翻译库,依赖运行时键查找而非构建时合并)
63
+ > - angular-i18n(Angular 原生的预编译方法,在构建时将 XLIFF 文件直接合并到模板中)
64
+ > - Tolgee(结合声明式代码和上下文 SDK,实现 UI 中的“点击翻译”编辑)
65
+ > - Intlayer(基于每个组件的方法,使用内容声明文件,实现原生的 tree-shaking 和 TypeScript 校验)
66
+
67
+ ## Intlayer 编译器
68
+
69
+ 虽然 **Intlayer** 本质上是一种鼓励对内容采取 **声明式方法** 的解决方案,但它包含了一个编译器,以帮助加快开发速度或促进快速原型设计。
70
+
71
+ Intlayer 编译器会遍历你的 React、Vue 或 Svelte 组件的 AST(抽象语法树),以及其他 JavaScript/TypeScript 文件。它的作用是检测硬编码字符串,并将它们提取到专门的 `.content` 声明中。
72
+
73
+ > 更多详情,请查阅文档:[Intlayer 编译器文档](https://github.com/aymericzip/intlayer/blob/main/docs/docs/zh/compiler.md)
42
74
 
43
75
  ## 编译器的魅力(“魔法”方法)
44
76
 
@@ -46,92 +78,143 @@ slugs:
46
78
 
47
79
  ### 1. 速度与“流畅感”
48
80
 
49
- 当你进入状态时,停下来思考一个变量名(如 `home_hero_title_v2`)会打断你的思路。使用编译器方法时,你只需输入 `<p>Welcome back</p>`,然后继续前进。摩擦为零。
81
+ 当你进入状态时,停下来思考一个语义化的变量名(如 `home_hero_title_v2`)会打断你的思路。使用编译器方法,你只需输入 `<p>Welcome back</p>`,然后继续前进。摩擦为零。
50
82
 
51
83
  ### 2. 旧代码救援任务
52
84
 
53
- 想象一下,继承了一个拥有5000个组件且没有任何翻译的大型代码库。用手动基于键的系统来改造它,将是一场持续数月的噩梦。基于编译器的工具则像一场救援行动,能够瞬间提取成千上万的字符串,而你无需手动触碰任何文件。
85
+ 想象一下,继承了一个拥有5000个组件且没有任何翻译的大型代码库。用手动基于键的系统来改造它,将是一场持续数月的噩梦。基于编译器的工具则作为一种救援策略,能够即时提取成千上万的字符串,而你无需手动触碰任何文件。
54
86
 
55
87
  ### 3. AI 时代
56
88
 
57
- 这是一个我们不应忽视的现代优势。AI 编码助手(如 Copilot 或 ChatGPT)自然生成标准的 JSX/HTML。它们并不知道你特定的翻译键方案。
89
+ 这是一个我们不应忽视的现代优势。AI 编码助手(如 Copilot 或 ChatGPT)自然生成标准的 JSX/HTML。它们并不知道你特定的翻译键模式。
58
90
 
59
91
  - **声明式(Declarative):** 你必须重写 AI 的输出,将文本替换为键。
60
92
  - **编译器(Compiler):** 你只需复制粘贴 AI 的代码,它就能直接工作。
61
93
 
62
94
  ## 现实检验:为什么“魔法”是危险的
63
95
 
64
- 虽然“魔法”很吸引人,但抽象层会泄漏。依赖构建工具去理解人类意图会引入架构上的脆弱性。
96
+ 虽然“魔法”很吸引人,但抽象层会泄露。依赖构建工具来理解人类意图会引入架构上的脆弱性。
97
+
98
+ ### 启发式脆弱性(猜测游戏)
99
+
100
+ 编译器必须猜测什么是内容,什么是代码。这会导致一些边缘情况,最终你会发现自己在“与工具斗争”。
101
+
102
+ 考虑以下场景:
103
+
104
+ - `<span className="active"></span>` 会被提取吗?(它是字符串,但很可能是类名)。
105
+ - `<span status="pending"></span>` 会被提取吗?(它是一个属性值)。
106
+ - `<span>{"Hello World"}</span>` 会被提取吗?(它是一个 JS 表达式)。
107
+ - `<span>Hello {name}. How are you?</span>` 会被提取吗?(插值很复杂)。
108
+ - `<span aria-label="Image of cat"></span>` 会被提取吗?(无障碍属性需要翻译)。
109
+ - `<span data-testid="my-element"></span>` 会被提取吗?(测试 ID 不应被翻译)。
110
+ - `<MyComponent errorMessage="An error occurred" />` 会被提取吗?
111
+ - `<p>This is a paragraph{" "}\n containing multiple lines</p>` 会被提取吗?
112
+ - `<p>{getStatusMessage()}</p>` 函数结果会被提取吗?
113
+ - `<div>{isLoading ? "The page is loading" : <MyComponent/>} </div>` 会被提取吗?
114
+ - 像 `<span>AX-99</span>` 这样的产品 ID 会被提取吗?
115
+
116
+ 你不可避免地会添加特定注释(如 `// ignore-translation`,或特定属性如 `data-compiler-ignore="true"`)来防止破坏你的应用逻辑。
117
+
118
+ ### Intlayer 如何处理这种复杂性?
119
+
120
+ Intlayer 使用混合方法来检测字段是否应被提取用于翻译,旨在尽量减少误报:
65
121
 
66
- ### 1. 启发式脆弱性(猜测游戏)
122
+ 1. **AST 分析:** 它检查元素类型(例如,区分 `reactNode`、`label` 或 `title` 属性)。
123
+ 2. **模式识别:** 它检测字符串是否首字母大写或包含空格,这表明它更可能是人类可读的文本,而非代码标识符。
67
124
 
68
- 编译器必须猜测什么是内容,什么是代码。
125
+ ### 动态数据的硬性限制
69
126
 
70
- - `className="active"` 会被翻译吗?它是一个字符串。
71
- - `status="pending"` 会被翻译吗?
72
- - `<MyComponent errorMessage="An error occurred" />` 会被翻译吗?
73
- - 像 `"AX-99"` 这样的产品 ID 会被翻译吗?
127
+ 编译器提取依赖于**静态分析**。它必须在代码中看到字面字符串,才能生成稳定的 ID。
128
+ 如果您的 API 返回一个错误代码字符串,比如 `server_error`,您无法通过编译器进行翻译,因为编译器在构建时并不知道该字符串的存在。您被迫为动态数据构建一个二级的“仅运行时”系统。
74
129
 
75
- 你不可避免地会与编译器“斗争”,添加特定注释(如 `// ignore-translation`)以防止它破坏你的应用逻辑。
130
+ ### 缺乏分块
76
131
 
77
- ### 2. 动态数据的硬性限制
132
+ 某些编译器不会按页面对翻译内容进行分块。如果您的编译器为每种语言生成一个大型 JSON 文件(例如 `./lang/en.json`、`./lang/fr.json` 等),那么访问单个页面时,您很可能会加载所有页面的内容。此外,每个使用这些内容的组件可能会被注入远多于所需的内容,可能导致性能问题。
78
133
 
79
- 编译器提取依赖于**静态分析**。它必须在代码中看到字面字符串才能生成稳定的 ID。
80
- 如果你的 API 返回一个错误代码字符串,比如 `server_error`,你无法用编译器翻译它,因为编译器在构建时并不知道该字符串的存在。你被迫为动态数据构建一个次级的“仅运行时”系统。
134
+ 还要注意动态加载翻译内容。如果不这样做,你将会加载当前语言之外的所有语言内容。
81
135
 
82
- ### 3. “块爆炸”和网络瀑布效应
136
+ > 为了说明这个问题,考虑一个有10个页面和10种语言(全部100%独特)的站点。你将会加载另外99个页面的内容(10 × 10 - 1)。
83
137
 
84
- 为了支持 tree-shaking,编译器工具通常会按组件拆分翻译内容。
138
+ ### “Chunk爆炸”和网络瀑布效应
85
139
 
86
- - **后果:** 一个页面视图中如果有 50 个小组件,可能会触发 **50 个独立的 HTTP 请求** 来获取微小的翻译片段。即使使用 HTTP/2,这也会造成网络瀑布效应,使你的应用相比加载单个优化语言包时显得响应迟缓。
140
+ 为了解决chunking问题,一些解决方案提供了按组件甚至按键的chunking。然而,这个问题只是部分解决了。这些解决方案的卖点通常是说“你的内容会被tree-shake”。
87
141
 
88
- ### 4. 运行时性能开销
142
+ 实际上,如果你静态加载内容,你的解决方案会tree-shake未使用的内容,但你仍然会加载所有语言的内容到你的应用中。
89
143
 
90
- 为了让翻译具有响应性(以便切换语言时能即时更新),编译器通常会在 _每个_ 组件中注入状态管理钩子。
144
+ 那么为什么不动态加载呢?是的,在这种情况下你会加载比必要内容更多的内容,但这并非没有权衡。
91
145
 
92
- - **代价:** 如果你渲染一个包含5000个条目的列表,你实际上是在为文本初始化5000个 `useState` `useEffect` 钩子。这会消耗大量内存和CPU周期,而声明式库(通常只使用一个 Context 提供者)则能节省这些资源。
146
+ 动态加载内容会将每一块内容隔离到它自己的代码块中,只有在组件渲染时才会加载。这意味着你每个文本块都会发起一次 HTTP 请求。页面上有 1000 个文本块?→ 你将向服务器发起 1000 HTTP 请求。为了限制影响并优化应用的首次渲染时间,你需要插入多个 Suspense 边界或骨架加载器(Skeleton Loaders)。
147
+
148
+ > 注意:即使使用 Next.js 和 SSR,组件在加载后仍会被水合(hydrated),因此 HTTP 请求仍然会被发起。
149
+
150
+ 解决方案?采用允许声明作用域内容声明的方案,比如 `i18next`、`next-intl` 或 `intlayer`。
151
+
152
+ > 注意:`i18next` 和 `next-intl` 需要你为每个页面手动管理命名空间 / 消息导入,以优化你的包大小。你应该使用类似 `rollup-plugin-visualizer`(vite)、`@next/bundle-analyzer`(next.js)或 `webpack-bundle-analyzer`(React CRA / Angular / 等)这样的包分析工具来检测是否有未使用的翻译污染了你的包。
153
+
154
+ ### 运行时性能开销
155
+
156
+ 为了使翻译具有响应性(以便切换语言时能即时更新),编译器通常会向每个组件注入状态管理钩子。
157
+
158
+ - **代价:** 如果你渲染一个包含 5,000 个项目的列表,你实际上是在为文本初始化 5,000 个 `useState` 和 `useEffect` 钩子。React 必须同时识别并重新渲染所有这 5,000 个消费者。这会导致巨大的“主线程”阻塞,在切换语言时冻结 UI。这会消耗大量内存和 CPU 周期,而声明式库(通常使用单一 Context 提供者)则能节省这些资源。
159
+
160
+ > 注意,这个问题在 React 以外的其他框架中也类似。
93
161
 
94
162
  ## 陷阱:供应商锁定
95
163
 
96
- 这可以说是基于编译器的国际化中最危险的方面。
164
+ 选择 i18n 解决方案时要小心,确保它允许提取或迁移翻译键。
97
165
 
98
- 在声明式库中,你的源代码包含明确的意图。你拥有翻译键。如果你切换库,只需更改导入即可。
166
+ 在声明式库的情况下,您的源代码明确包含了您的翻译意图:这些就是您的键,您可以控制它们。如果您想更换库,通常只需要更新导入即可。
99
167
 
100
- 而在基于编译器的方法中,**你的源代码只是英文文本。** “翻译逻辑”仅存在于构建插件的配置中。
101
- 如果该库停止维护,或者你超出了它的能力范围,你就会陷入困境。你无法轻易“弹出”,因为你的源代码中没有任何翻译键。你必须手动重写整个应用程序才能迁移。
168
+ 而采用编译器方法时,您的源代码可能只是纯英文文本,没有任何翻译逻辑的痕迹:所有内容都隐藏在构建工具配置中。如果该插件不再维护或您想更换解决方案,您可能会陷入困境。没有简单的“弹出”方式:您的代码中没有可用的键,您可能需要为新库重新生成所有翻译。
169
+
170
+ 一些解决方案还提供翻译生成服务。没有更多积分了?就没有更多翻译了。
171
+
172
+ 编译器通常会对文本进行哈希处理(例如,`"Hello World"` -> `x7f2a`)。你的翻译文件看起来像 `{ "x7f2a": "Hola Mundo" }`。陷阱在于:如果你更换了库,新库会看到 `"Hello World"` 并查找该键。但它找不到,因为你的翻译文件充满了哈希值(`x7f2a`)。
173
+
174
+ ### 平台锁定
175
+
176
+ 通过选择基于编译器的方法,你会将自己锁定在底层平台上。例如,某些编译器并非适用于所有打包工具(如 Vite、Turbopack 或 Metro)。这可能会使未来的迁移变得困难,你可能需要采用多种解决方案来覆盖所有应用程序。
102
177
 
103
178
  ## 另一面:声明式方法的风险
104
179
 
105
180
  公平地说,传统的声明式方法也并不完美。它有自己的一些“坑”。
106
181
 
107
- 1. **命名空间地狱:** 你经常需要手动管理加载哪些 JSON 文件(如 `common.json`、`dashboard.json`、`footer.json`)。如果忘记加载其中一个,用户就会看到原始的键名。
108
- 2. **过度获取:** 如果配置不当,很容易在初始加载时意外加载所有页面的所有翻译键,导致包体积膨胀。
109
- 3. **同步漂移(Sync Drift):** 通常情况下,某些键会在对应组件被删除很久之后仍然保留在 JSON 文件中。你的翻译文件会无限增长,充满了“僵尸键”。
182
+ 1. **命名空间地狱:** 你经常需要手动管理加载哪些 JSON 文件(`common.json`、`dashboard.json`、`footer.json`)。如果你忘记加载其中一个,用户就会看到原始的键名。
183
+ 2. **过度获取(Over-fetching):** 如果配置不当,很容易在初始加载时意外加载所有页面的所有翻译键,导致包体积膨胀。
184
+ 3. **同步漂移(Sync Drift):** 通常情况下,某些键会在使用它们的组件被删除后仍留在 JSON 文件中。你的翻译文件会无限增长,充满“僵尸键”。
110
185
 
111
186
  ## Intlayer 的折中方案
112
187
 
113
- 这正是像 **Intlayer** 这样的工具试图创新的地方。Intlayer 理解编译器虽然强大,但隐式的魔法是危险的。
188
+ 这正是像 **Intlayer** 这样的工具试图创新的地方。Intlayer 理解虽然编译器功能强大,但隐式魔法是危险的。
114
189
 
115
- Intlayer 提供了一个独特的 **`transform` 命令**。它不仅仅是在隐藏的构建步骤中做魔法,而是可以实际**重写你的组件代码**。它扫描你的文本,并用显式的内容声明替换代码库中的文本。
190
+ Intlayer 提供了一种混合方法,让你能够同时享受两种方法的优势:声明式内容管理,同时兼容其编译器以节省开发时间。
191
+
192
+ 即使你不使用 Intlayer 编译器,Intlayer 也提供了一个 `transform` 命令(也可以通过 VSCode 扩展访问)。它不仅仅是在隐藏的构建步骤中做魔法,而是可以实际**重写你的组件代码**。它会扫描你的文本,并将其替换为代码库中显式的内容声明。
116
193
 
117
194
  这让你同时拥有两者的优点:
118
195
 
119
- 1. **粒度控制(Granularity):** 你可以将翻译内容保持在组件附近(提升模块化和 tree-shaking 效果)。
120
- 2. **安全性:** 翻译变成了显式代码,而不是隐藏的构建时魔法。
121
- 3. **无锁定:** 由于代码被转换为仓库内的标准声明式结构,你不会将逻辑隐藏在 webpack 插件中。
196
+ 1. **细粒度控制:** 你可以将翻译内容保持在组件附近(提升模块化和 tree-shaking 效果)。
197
+ 2. **安全性:** 翻译变成显式代码,而不是隐藏的构建时魔法。
198
+ 3. **无锁定:** 由于代码被转换成你仓库中的声明式结构,你可以轻松按下 Tab 键,或使用 IDE 的 copilot 来生成内容声明,而不是将逻辑隐藏在 webpack 插件中。
122
199
 
123
200
  ## 结论
124
201
 
125
- 那么,你应该选择哪种方案?
202
+ 那么,你应该选择哪种方式呢?
203
+
204
+ **如果你正在构建 MVP,或者想快速推进:**
205
+ 基于编译器的方法是一个有效的选择。它允许你非常快速地开发。你不需要担心文件结构或键值。你只需构建即可。技术债务是“未来的你”需要解决的问题。
206
+
207
+ **如果你是初级开发者,或者不在意优化:**
208
+ 如果你想减少手动管理,基于编译器的方法可能是最好的。你不需要自己处理键或翻译文件——只需编写文本,编译器会自动完成剩下的工作。这减少了设置工作量和与手动步骤相关的常见国际化错误。
126
209
 
127
- **如果你是初级开发者、独立创始人,或者正在构建 MVP:**
128
- 基于编译器的方法是一个有效的选择。它允许你非常快速地开发。你无需担心文件结构或键值。你只需构建。技术债务是“未来的你”的问题。
210
+ **如果你正在对一个已有数千个组件需要重构的现有项目进行国际化:**
211
+ 基于编译器的方法在这里可以是一个务实的选择。初始的提取阶段可以节省数周甚至数月的手动工作。然而,建议考虑使用像 Intlayer 的 `transform` 命令这样的工具,它可以提取字符串并将其转换为显式的声明式内容声明。这让你既能享受自动化的速度,又能保持声明式方法的安全性和可移植性。你可以两者兼得:快速的初始迁移而不会带来长期的架构债务。
129
212
 
130
- **如果你正在构建专业的企业级应用:**
131
- 魔法通常不是一个好主意。你需要掌控。
213
+ **如果你正在构建一个专业的企业级应用程序:**
214
+ 魔法通常不是一个好主意。你需要控制。
132
215
 
133
216
  - 你需要处理来自后端的动态数据。
134
- - 您需要确保在低端设备上的性能(避免钩子爆炸)。
135
- - 您需要确保不会永远被锁定在特定的构建工具中。
217
+ - 你需要确保在低端设备上的性能(避免钩子爆炸)。
218
+ /// 你需要确保不会永远被锁定在特定的构建工具中。
136
219
 
137
- 对于专业应用,**声明式内容管理**(如 Intlayer 或成熟的库)仍然是黄金标准。它将关注点分离,保持架构清晰,并确保您的应用多语言能力不依赖于“黑盒”编译器去猜测您的意图。
220
+ 对于专业应用,**声明式内容管理**(如 Intlayer 或成熟的库)仍然是黄金标准。它将关注点分离,保持架构清晰,并确保你的应用多语言能力不依赖于“黑盒”编译器去猜测你的意图。
@@ -13,6 +13,9 @@ slugs:
13
13
  - doc
14
14
  - why
15
15
  history:
16
+ - version: 7.3.1
17
+ date: 2025-11-27
18
+ changes: Release Compiler
16
19
  - version: 5.8.0
17
20
  date: 2025-08-19
18
21
  changes: Update comparative table
@@ -229,6 +232,7 @@ This approach allows you to:
229
232
  | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
230
233
  | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/frameworks.png?raw=true) | **Cross-Frameworks Support**<br><br>Intlayer is compatible with all major frameworks and libraries, including Next.js, React, Vite, Vue.js, Nuxt, Preact, Express, and more. |
231
234
  | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/javascript_content_management.png?raw=true) | **JavaScript-Powered Content Management**<br><br>Harness the flexibility of JavaScript to define and manage your content efficiently. <br><br> - [Content declaration](https://intlayer.org/doc/concept/content) |
235
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/compiler.jpg?raw=true" alt="Feature" width="700"> | **Compiler**<br><br>The Intlayer Compiler extract automatically the content from the components and generate the dictionary files.<br><br> - [Compiler](https://intlayer.org/doc/compiler) |
232
236
  | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/per_locale_content_declaration_file.png?raw=true) | **Per-Locale Content Declaration File**<br><br>Speed up your development by declaring your content once, before auto generation.<br><br> - [Per-Locale Content Declaration File](https://intlayer.org/doc/concept/per-locale-file) |
233
237
  | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true) | **Type-Safe Environment**<br><br>Leverage TypeScript to ensure your content definitions and code are error-free, while also benefiting from IDE autocompletion.<br><br> - [TypeScript configuration](https://intlayer.org/doc/environment/vite-and-react#configure-typescript) |
234
238
  | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/config_file.png?raw=true) | **Simplified Setup**<br><br>Get up and running quickly with minimal configuration. Adjust settings for internationalization, routing, AI, build, and content handling with ease. <br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |