@intlayer/docs 8.6.10 → 8.7.0-canary.1
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/ar/i18n_using_next-i18next.md +1 -1
- package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/de/i18n_using_next-i18next.md +1 -1
- package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/en/i18n_using_next-i18next.md +1 -1
- package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/en-GB/i18n_using_next-i18next.md +1 -1
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/es/i18n_using_next-i18next.md +1 -1
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/fr/i18n_using_next-i18next.md +1 -1
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/hi/i18n_using_next-i18next.md +1 -1
- package/blog/id/i18n_using_next-i18next.md +1 -1
- package/blog/id/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/it/i18n_using_next-i18next.md +1 -1
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ja/i18n_using_next-i18next.md +1 -1
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ko/i18n_using_next-i18next.md +1 -1
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/pl/i18n_using_next-i18next.md +1 -1
- package/blog/pl/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/pt/i18n_using_next-i18next.md +1 -1
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ru/i18n_using_next-i18next.md +1 -1
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/tr/i18n_using_next-i18next.md +1 -1
- package/blog/uk/i18n_using_next-i18next.md +1 -1
- package/blog/uk/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/vi/i18n_using_next-i18next.md +1 -1
- package/blog/vi/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/zh/i18n_using_next-i18next.md +1 -1
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/docs/ar/bundle_optimization.md +454 -0
- package/docs/ar/intlayer_with_next-i18next.md +1 -1
- package/docs/ar/intlayer_with_next-intl.md +1 -1
- package/docs/ar/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ar/intlayer_with_tanstack.md +45 -68
- package/docs/bn/bundle_optimization.md +454 -0
- package/docs/cs/bundle_optimization.md +454 -0
- package/docs/de/bundle_optimization.md +454 -0
- package/docs/de/intlayer_with_next-i18next.md +1 -1
- package/docs/de/intlayer_with_next-intl.md +1 -1
- package/docs/de/intlayer_with_tanstack+solid.md +24 -5
- package/docs/de/intlayer_with_tanstack.md +45 -68
- package/docs/en/bundle_optimization.md +36 -8
- package/docs/en/intlayer_with_next-i18next.md +1 -1
- package/docs/en/intlayer_with_next-intl.md +1 -1
- package/docs/en/intlayer_with_tanstack+solid.md +24 -5
- package/docs/en/intlayer_with_tanstack.md +45 -68
- package/docs/en-GB/bundle_optimization.md +454 -0
- package/docs/en-GB/intlayer_with_next-i18next.md +1 -1
- package/docs/en-GB/intlayer_with_next-intl.md +1 -1
- package/docs/en-GB/intlayer_with_tanstack+solid.md +24 -5
- package/docs/en-GB/intlayer_with_tanstack.md +47 -70
- package/docs/es/bundle_optimization.md +454 -0
- package/docs/es/intlayer_with_next-i18next.md +1 -1
- package/docs/es/intlayer_with_next-intl.md +1 -1
- package/docs/es/intlayer_with_tanstack+solid.md +24 -5
- package/docs/es/intlayer_with_tanstack.md +45 -68
- package/docs/fr/bundle_optimization.md +454 -0
- package/docs/fr/intlayer_with_next-i18next.md +1 -1
- package/docs/fr/intlayer_with_next-intl.md +1 -1
- package/docs/fr/intlayer_with_tanstack+solid.md +24 -5
- package/docs/fr/intlayer_with_tanstack.md +45 -68
- package/docs/hi/bundle_optimization.md +454 -0
- package/docs/hi/intlayer_with_next-i18next.md +1 -1
- package/docs/hi/intlayer_with_next-intl.md +1 -1
- package/docs/hi/intlayer_with_tanstack+solid.md +24 -5
- package/docs/hi/intlayer_with_tanstack.md +45 -68
- package/docs/id/bundle_optimization.md +454 -0
- package/docs/id/intlayer_with_next-i18next.md +1 -1
- package/docs/id/intlayer_with_next-intl.md +1 -1
- package/docs/id/intlayer_with_tanstack+solid.md +24 -5
- package/docs/id/intlayer_with_tanstack.md +45 -68
- package/docs/it/bundle_optimization.md +454 -0
- package/docs/it/intlayer_with_next-i18next.md +1 -1
- package/docs/it/intlayer_with_next-intl.md +1 -1
- package/docs/it/intlayer_with_tanstack+solid.md +24 -5
- package/docs/it/intlayer_with_tanstack.md +45 -68
- package/docs/ja/bundle_optimization.md +454 -0
- package/docs/ja/intlayer_with_next-i18next.md +1 -1
- package/docs/ja/intlayer_with_next-intl.md +1 -1
- package/docs/ja/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ja/intlayer_with_tanstack.md +45 -36
- package/docs/ko/bundle_optimization.md +454 -0
- package/docs/ko/intlayer_with_next-i18next.md +1 -1
- package/docs/ko/intlayer_with_next-intl.md +1 -1
- package/docs/ko/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ko/intlayer_with_tanstack.md +45 -68
- package/docs/nl/bundle_optimization.md +454 -0
- package/docs/pl/bundle_optimization.md +454 -0
- package/docs/pl/intlayer_with_next-i18next.md +1 -1
- package/docs/pl/intlayer_with_next-intl.md +1 -1
- package/docs/pl/intlayer_with_tanstack+solid.md +24 -5
- package/docs/pl/intlayer_with_tanstack.md +45 -68
- package/docs/pt/bundle_optimization.md +454 -0
- package/docs/pt/intlayer_with_next-i18next.md +1 -1
- package/docs/pt/intlayer_with_next-intl.md +1 -1
- package/docs/pt/intlayer_with_tanstack+solid.md +24 -5
- package/docs/pt/intlayer_with_tanstack.md +45 -68
- package/docs/ru/bundle_optimization.md +454 -0
- package/docs/ru/intlayer_with_next-i18next.md +1 -1
- package/docs/ru/intlayer_with_next-intl.md +1 -1
- package/docs/ru/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ru/intlayer_with_tanstack.md +45 -68
- package/docs/tr/bundle_optimization.md +454 -0
- package/docs/tr/intlayer_with_next-i18next.md +1 -1
- package/docs/tr/intlayer_with_next-intl.md +1 -1
- package/docs/tr/intlayer_with_tanstack+solid.md +24 -5
- package/docs/tr/intlayer_with_tanstack.md +45 -68
- package/docs/uk/bundle_optimization.md +454 -0
- package/docs/uk/intlayer_with_next-i18next.md +1 -1
- package/docs/uk/intlayer_with_next-intl.md +1 -1
- package/docs/uk/intlayer_with_tanstack+solid.md +24 -5
- package/docs/uk/intlayer_with_tanstack.md +45 -68
- package/docs/ur/bundle_optimization.md +454 -0
- package/docs/vi/bundle_optimization.md +454 -0
- package/docs/vi/intlayer_with_next-i18next.md +1 -1
- package/docs/vi/intlayer_with_next-intl.md +1 -1
- package/docs/vi/intlayer_with_tanstack+solid.md +24 -5
- package/docs/vi/intlayer_with_tanstack.md +45 -68
- package/docs/zh/bundle_optimization.md +454 -0
- package/docs/zh/intlayer_with_next-i18next.md +1 -1
- package/docs/zh/intlayer_with_next-intl.md +1 -1
- package/docs/zh/intlayer_with_tanstack+solid.md +24 -5
- package/docs/zh/intlayer_with_tanstack.md +45 -68
- package/package.json +7 -7
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2025-11-25
|
|
3
|
+
updatedAt: 2026-04-08
|
|
4
|
+
title: Tối ưu hóa kích thước Bundle i18n & Hiệu suất
|
|
5
|
+
description: Giảm kích thước bundle ứng dụng bằng cách tối ưu hóa nội dung quốc tế hóa (i18n). Tìm hiểu cách tận dụng tree shaking và lazy loading cho từ điển với Intlayer.
|
|
6
|
+
keywords:
|
|
7
|
+
- Tối ưu hóa Bundle
|
|
8
|
+
- Tự động hóa nội dung
|
|
9
|
+
- Nội dung động
|
|
10
|
+
- Intlayer
|
|
11
|
+
- Next.js
|
|
12
|
+
- JavaScript
|
|
13
|
+
- React
|
|
14
|
+
slugs:
|
|
15
|
+
- doc
|
|
16
|
+
- concept
|
|
17
|
+
- bundle-optimization
|
|
18
|
+
history:
|
|
19
|
+
- version: 8.7.0
|
|
20
|
+
date: 2026-04-08
|
|
21
|
+
changes: "Thêm các tùy chọn `minify` và `purge` vào cấu hình build"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Tối ưu hóa kích thước Bundle i18n & Hiệu suất
|
|
25
|
+
|
|
26
|
+
Một trong những thách thức phổ biến nhất với các giải pháp i18n truyền thống dựa trên tệp JSON là quản lý kích thước nội dung. Nếu các nhà phát triển không tách nội dung thành các không gian tên (namespaces) theo cách thủ công, người dùng thường phải tải xuống các bản dịch cho mọi trang và có thể là mọi ngôn ngữ chỉ để xem một trang duy nhất.
|
|
27
|
+
|
|
28
|
+
Ví dụ, một ứng dụng có 10 trang được dịch sang 10 ngôn ngữ có thể dẫn đến việc người dùng tải xuống nội dung của 100 trang, mặc dù họ chỉ cần **một** trang (trang hiện tại bằng ngôn ngữ hiện tại). Điều này dẫn đến lãng phí băng thông và thời gian tải chậm hơn.
|
|
29
|
+
|
|
30
|
+
**Intlayer giải quyết vấn đề này thông qua tối ưu hóa tại thời điểm build.** Nó phân tích mã của bạn để phát hiện từ điển nào thực sự được sử dụng cho mỗi thành phần và chỉ chèn lại nội dung cần thiết vào bundle của bạn.
|
|
31
|
+
|
|
32
|
+
## Mục lục
|
|
33
|
+
|
|
34
|
+
<TOC />
|
|
35
|
+
|
|
36
|
+
## Quét bundle của bạn
|
|
37
|
+
|
|
38
|
+
Phân tích bundle là bước đầu tiên để xác định các tệp JSON "nặng" và cơ hội tách mã (code-splitting). Các công cụ này tạo ra một sơ đồ cây (treemap) trực quan về mã đã biên dịch của ứng dụng, cho phép bạn thấy chính xác thư viện nào đang tiêu tốn nhiều không gian nhất.
|
|
39
|
+
|
|
40
|
+
<Tabs>
|
|
41
|
+
<Tab value="vite">
|
|
42
|
+
|
|
43
|
+
### Vite / Rollup
|
|
44
|
+
|
|
45
|
+
Vite sử dụng Rollup dưới nền t cả. Plugin `rollup-plugin-visualizer` tạo ra một tệp HTML tương tác hiển thị kích thước của mọi mô-đun trong biểu đồ của bạn.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install -D rollup-plugin-visualizer
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```typescript fileName="vite.config.ts"
|
|
52
|
+
import { defineConfig } from "vite";
|
|
53
|
+
import { visualizer } from "rollup-plugin-visualizer";
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
visualizer({
|
|
58
|
+
open: true, // Tự động mở báo cáo trong trình duyệt của bạn
|
|
59
|
+
filename: "stats.html",
|
|
60
|
+
gzipSize: true,
|
|
61
|
+
brotliSize: true,
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
</Tab>
|
|
68
|
+
<Tab value="nextjs (turbopack)">
|
|
69
|
+
|
|
70
|
+
### Next.js (Turbopack)
|
|
71
|
+
|
|
72
|
+
Đối với các dự án sử dụng App Router và Turbopack, Next.js cung cấp một trình phân tích thử nghiệm tích hợp sẵn mà không cần thêm phụ thuộc.
|
|
73
|
+
|
|
74
|
+
```bash packageManager='npm'
|
|
75
|
+
npx next experimental-analyze
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```bash packageManager='yarn'
|
|
79
|
+
yarn next experimental-analyze
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```bash packageManager='pnpm'
|
|
83
|
+
pnpm next experimental-analyze
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash packageManager='bun'
|
|
87
|
+
bun next experimental-analyze
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
</Tab>
|
|
91
|
+
<Tab value="nextjs (Webpack)">
|
|
92
|
+
|
|
93
|
+
### Next.js (Webpack)
|
|
94
|
+
|
|
95
|
+
Nếu bạn đang sử dụng trình đóng gói Webpack mặc định trong Next.js, hãy sử dụng trình phân tích bundle chính thức. Kích hoạt nó bằng cách đặt biến môi trường trong quá trình build của bạn.
|
|
96
|
+
|
|
97
|
+
```bash packageManager='npm'
|
|
98
|
+
npm install -D @next/bundle-analyzer
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```bash packageManager='yarn'
|
|
102
|
+
yarn add -D @next/bundle-analyzer
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```bash packageManager='pnpm'
|
|
106
|
+
pnpm add -D @next/bundle-analyzer
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash packageManager='bun'
|
|
110
|
+
bun add -d @next/bundle-analyzer
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```javascript fileName="next.config.js"
|
|
114
|
+
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|
115
|
+
enabled: process.env.ANALYZE === "true",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
module.exports = withBundleAnalyzer({
|
|
119
|
+
// Cấu hình Next.js của bạn
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Sử dụng:**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
ANALYZE=true npm run build
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</Tab>
|
|
130
|
+
<Tab value="Webpack (CRA / Angular / etc)">
|
|
131
|
+
|
|
132
|
+
### Webpack Tiêu chuẩn
|
|
133
|
+
|
|
134
|
+
Đối với Create React App (ejected), Angular hoặc các thiết lập Webpack tùy chỉnh, hãy sử dụng tiêu chuẩn ngành `webpack-bundle-analyzer`.
|
|
135
|
+
|
|
136
|
+
```bash packageManager='npm'
|
|
137
|
+
npm install -D webpack-bundle-analyzer
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash packageManager='yarn'
|
|
141
|
+
yarn add -D webpack-bundle-analyzer
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```bash packageManager='pnpm'
|
|
145
|
+
pnpm add -D webpack-bundle-analyzer
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```bash packageManager='bun'
|
|
149
|
+
bun add -d webpack-bundle-analyzer
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```typescript fileName="webpack.config.ts
|
|
153
|
+
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
|
154
|
+
|
|
155
|
+
export default {
|
|
156
|
+
plugins: [
|
|
157
|
+
new BundleAnalyzerPlugin({
|
|
158
|
+
analyzerMode: "static",
|
|
159
|
+
reportFilename: "bundle-analyzer.html",
|
|
160
|
+
openAnalyzer: false,
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</Tab>
|
|
167
|
+
</Tabs>
|
|
168
|
+
|
|
169
|
+
## Cách thức hoạt động
|
|
170
|
+
|
|
171
|
+
Intlayer sử dụng **cách tiếp cận theo từng thành phần**. Không giống như các tệp JSON toàn cục, nội dung của bạn được xác định bên cạnh hoặc bên trong các thành phần của bạn. Trong quá trình build, Intlayer sẽ:
|
|
172
|
+
|
|
173
|
+
1. **Phân tích** mã của bạn để tìm các cuộc gọi `useIntlayer`.
|
|
174
|
+
2. **Xây dựng** nội dung từ điển tương ứng.
|
|
175
|
+
3. **Thay thế** cuộc gọi `useIntlayer` bằng mã được tối ưu hóa dựa trên cấu hình của bạn.
|
|
176
|
+
|
|
177
|
+
Điều này đảm bảo rằng:
|
|
178
|
+
|
|
179
|
+
- Nếu một thành phần không được nhập, nội dung của nó sẽ không được bao gồm trong bundle (Loại bỏ mã chết - Dead Code Elimination).
|
|
180
|
+
- Nếu một thành phần được tải chậm (lazy-loaded), nội dung của nó cũng được tải chậm.
|
|
181
|
+
|
|
182
|
+
## Thiết lập theo Nền tảng
|
|
183
|
+
|
|
184
|
+
<Tabs>
|
|
185
|
+
<Tab value="nextjs">
|
|
186
|
+
|
|
187
|
+
### Next.js
|
|
188
|
+
|
|
189
|
+
Next.js yêu cầu plugin `@intlayer/swc` để xử lý quá trình chuyển đổi, vì Next.js sử dụng SWC cho các lần build.
|
|
190
|
+
|
|
191
|
+
> Plugin này không được cài đặt theo mặc định vì các plugin SWC vẫn đang được thử nghiệm cho Next.js. Nó có thể thay đổi trong tương lai.
|
|
192
|
+
|
|
193
|
+
```bash packageManager="npm"
|
|
194
|
+
npm install -D @intlayer/swc
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```bash packageManager="yarn"
|
|
198
|
+
yarn add -D @intlayer/swc
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```bash packageManager="pnpm"
|
|
202
|
+
pnpm add -D @intlayer/swc
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```bash packageManager="bun"
|
|
206
|
+
bun add -d @intlayer/swc
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Sau khi cài đặt, Intlayer sẽ tự động phát hiện và sử dụng plugin này.
|
|
210
|
+
|
|
211
|
+
</Tab>
|
|
212
|
+
<Tab value="vite">
|
|
213
|
+
|
|
214
|
+
### Vite
|
|
215
|
+
|
|
216
|
+
Vite sử dụng plugin `@intlayer/babel` được bao gồm như một phụ thuộc của `vite-intlayer`. Quá trình tối ưu hóa được bật theo mặc định. Không cần làm gì thêm.
|
|
217
|
+
|
|
218
|
+
</Tab>
|
|
219
|
+
<Tab value="webpack">
|
|
220
|
+
|
|
221
|
+
### Webpack
|
|
222
|
+
|
|
223
|
+
Để bật tối ưu hóa bundle với Intlayer trên Webpack, bạn cần cài đặt và cấu hình plugin Babel (`@intlayer/babel`) hoặc SWC (`@intlayer/swc`) thích hợp.
|
|
224
|
+
|
|
225
|
+
```bash packageManager="npm"
|
|
226
|
+
npm install -D @intlayer/babel
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```bash packageManager="yarn"
|
|
230
|
+
yarn add -D @intlayer/babel
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```bash packageManager="pnpm"
|
|
234
|
+
pnpm add -D @intlayer/babel
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```bash packageManager="bun"
|
|
238
|
+
bun add -d @intlayer/babel
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```typescript fileName="babel.config.js"
|
|
242
|
+
const {
|
|
243
|
+
getOptimizePluginOptions,
|
|
244
|
+
intlayerOptimizeBabelPlugin,
|
|
245
|
+
} = require("@intlayer/babel");
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
plugins: [[intlayerOptimizeBabelPlugin, getOptimizePluginOptions()]],
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
</Tab>
|
|
253
|
+
</Tabs>
|
|
254
|
+
|
|
255
|
+
## Cấu hình
|
|
256
|
+
|
|
257
|
+
Bạn có thể kiểm soát cách Intlayer tối ưu hóa bundle của mình thông qua thuộc tính `build` trong tệp `intlayer.config.ts`.
|
|
258
|
+
|
|
259
|
+
```typescript fileName="intlayer.config.ts"
|
|
260
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
261
|
+
|
|
262
|
+
const config: IntlayerConfig = {
|
|
263
|
+
internationalization: {
|
|
264
|
+
locales: [Locales.ENGLISH, Locales.FRENCH],
|
|
265
|
+
defaultLocale: Locales.ENGLISH,
|
|
266
|
+
},
|
|
267
|
+
dictionary: {
|
|
268
|
+
importMode: "dynamic",
|
|
269
|
+
},
|
|
270
|
+
build: {
|
|
271
|
+
/**
|
|
272
|
+
* Nén (minify) các từ điển để giảm kích thước bundle.
|
|
273
|
+
*/
|
|
274
|
+
minify: true;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Loại bỏ (purge) các khóa không sử dụng trong từ điển
|
|
278
|
+
*/
|
|
279
|
+
purge: true;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Cho biết liệu quá trình build có nên kiểm tra các kiểu TypeScript hay không
|
|
283
|
+
*/
|
|
284
|
+
checkTypes: false;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export default config;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
> Giữ tùy chọn mặc định cho `optimize` được khuyến nghị trong hầu hết các trường hợp.
|
|
292
|
+
|
|
293
|
+
> Xem cấu hình tài liệu để biết thêm chi tiết: [Cấu hình](https://github.com/aymericzip/intlayer/blob/main/docs/docs/vi/configuration.md)
|
|
294
|
+
|
|
295
|
+
### Tùy chọn Build
|
|
296
|
+
|
|
297
|
+
Các tùy chọn sau đây có sẵn trong đối tượng cấu hình `build`:
|
|
298
|
+
|
|
299
|
+
| Thuộc tính | Kiểu | Mặc định | Mô tả |
|
|
300
|
+
| :------------- | :-------- | :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| **`optimize`** | `boolean` | `undefined` | Kiểm soát xem có bật tối ưu hóa build hay không. Nếu là `true`, Intlayer thay thế các cuộc gọi từ điển bằng các lệnh chèn được tối ưu hóa. Nếu là `false`, tối ưu hóa bị tắt. Tốt nhất nên đặt thành `true` trong production. |
|
|
302
|
+
| **`minify`** | `boolean` | `false` | Liệu có nén các từ điển để giảm kích thước bundle hay không. |
|
|
303
|
+
| **`purge`** | `boolean` | `false` | Liệu có loại bỏ các khóa không sử dụng trong từ điển hay không. |
|
|
304
|
+
|
|
305
|
+
### Nén (Minification)
|
|
306
|
+
|
|
307
|
+
Nén từ điển giúp loại bỏ các khoảng trắng, nhận xét không cần thiết và giảm kích thước nội dung JSON. Điều này đặc biệt hữu ích cho các từ điển lớn.
|
|
308
|
+
|
|
309
|
+
```typescript fileName="intlayer.config.ts"
|
|
310
|
+
import type { IntlayerConfig } from "intlayer";
|
|
311
|
+
|
|
312
|
+
const config: IntlayerConfig = {
|
|
313
|
+
build: {
|
|
314
|
+
minify: true,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export default config;
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
> Lưu ý: Nén bị bỏ qua nếu `optimize` bị tắt hoặc nếu Trình chỉnh sửa trực quan (Visual Editor) được bật (vì trình chỉnh sửa cần nội dung đầy đủ để cho phép chỉnh sửa).
|
|
322
|
+
|
|
323
|
+
### Loại bỏ (Purging)
|
|
324
|
+
|
|
325
|
+
Loại bỏ đảm bảo rằng chỉ những khóa thực sự được sử dụng trong mã của bạn mới được đưa vào bundle từ điển cuối cùng. Điều này có thể giảm đáng kể kích thước bundle của bạn nếu bạn có các từ điển lớn với nhiều khóa không được sử dụng trong mọi phần của ứng dụng.
|
|
326
|
+
|
|
327
|
+
```typescript fileName="intlayer.config.ts"
|
|
328
|
+
import type { IntlayerConfig } from "intlayer";
|
|
329
|
+
|
|
330
|
+
const config: IntlayerConfig = {
|
|
331
|
+
build: {
|
|
332
|
+
purge: true,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export default config;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> Lưu ý: Loại bỏ bị bỏ qua nếu `optimize` bị tắt.
|
|
340
|
+
|
|
341
|
+
### Chế độ Nhập (Import Mode)
|
|
342
|
+
|
|
343
|
+
Đối với các ứng dụng lớn, bao gồm nhiều trang và ngôn ngữ, tệp JSON của bạn có thể chiếm một phần đáng kể trong kích thước bundle. Intlayer cho phép bạn kiểm soát cách tải từ điển.
|
|
344
|
+
|
|
345
|
+
Chế độ nhập có thể được xác định mặc định trên toàn cầu trong tệp `intlayer.config.ts` của bạn.
|
|
346
|
+
|
|
347
|
+
```typescript fileName="intlayer.config.ts"
|
|
348
|
+
import type { IntlayerConfig } from "intlayer";
|
|
349
|
+
|
|
350
|
+
const config: IntlayerConfig = {
|
|
351
|
+
build: {
|
|
352
|
+
minify: true,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export default config;
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Cũng như đối với mỗi từ điển trong các tệp `.content.{{ts|tsx|js|jsx|mjs|cjs|json|jsonc|json5}}` của bạn.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { type Dictionary, t } from "intlayer";
|
|
363
|
+
|
|
364
|
+
const appContent: Dictionary = {
|
|
365
|
+
key: "app",
|
|
366
|
+
importMode: "dynamic", // Ghi đè chế độ nhập mặc định
|
|
367
|
+
content: {
|
|
368
|
+
// ...
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export default appContent;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
| Thuộc tính | Kiểu | Mặc định | Mô tả |
|
|
376
|
+
| :--------------- | :--------------------------------- | :--------- | :------------------------------------------------------------------------------------------------------------ |
|
|
377
|
+
| **`importMode`** | `'static'`, `'dynamic'`, `'fetch'` | `'static'` | **Đã lỗi thời**: Sử dụng `dictionary.importMode` thay thế. Xác định cách tải từ điển (xem chi tiết bên dưới). |
|
|
378
|
+
|
|
379
|
+
Cài đặt `importMode` quy định cách nội dung từ điển được chèn vào thành phần của bạn.
|
|
380
|
+
Bạn có thể xác định nó trên toàn cầu trong tệp `intlayer.config.ts` dưới đối tượng `dictionary`, hoặc bạn có thể ghi đè nó cho một từ điển cụ thể trong tệp `.content.ts` của nó.
|
|
381
|
+
|
|
382
|
+
### 1. Chế độ Tĩnh (`default`)
|
|
383
|
+
|
|
384
|
+
Trong chế độ tĩnh, Intlayer thay thế `useIntlayer` bằng `useDictionary` và chèn từ điển trực tiếp vào bundle JavaScript.
|
|
385
|
+
|
|
386
|
+
- **Ưu điểm:** Hiển thị tức thì (đồng bộ), không có yêu cầu mạng bổ sung trong quá trình hydrat hóa.
|
|
387
|
+
- **Nhược điểm:** Bundle bao gồm các bản dịch cho **tất cả** các ngôn ngữ có sẵn cho thành phần cụ thể đó.
|
|
388
|
+
- **Tốt nhất cho:** Ứng dụng trang đơn (SPA).
|
|
389
|
+
|
|
390
|
+
**Ví dụ mã đã chuyển đổi:**
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
// Mã của bạn
|
|
394
|
+
const content = useIntlayer("my-key");
|
|
395
|
+
|
|
396
|
+
// Mã được tối ưu hóa (Tĩnh)
|
|
397
|
+
const content = useDictionary({
|
|
398
|
+
key: "my-key",
|
|
399
|
+
content: {
|
|
400
|
+
nodeType: "translation",
|
|
401
|
+
translation: {
|
|
402
|
+
en: "My title",
|
|
403
|
+
fr: "Mon titre",
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 2. Chế độ Động
|
|
410
|
+
|
|
411
|
+
Trong chế độ động, Intlayer thay thế `useIntlayer` bằng `useDictionaryAsync`. Điều này sử dụng `import()` (cơ chế giống như Suspense) để tải chậm (lazy-load) cụ thể JSON cho ngôn ngữ hiện tại.
|
|
412
|
+
|
|
413
|
+
- **Ưu điểm:** **Tree shaking cấp độ ngôn ngữ.** Người dùng xem phiên bản tiếng Anh sẽ _chỉ_ tải xuống từ điển tiếng Anh. Từ điển tiếng Pháp không bao giờ được tải.
|
|
414
|
+
- **Nhược điểm:** Kích hoạt một yêu cầu mạng (lấy tài nguyên) cho mỗi thành phần trong quá trình hydrat hóa.
|
|
415
|
+
- **Tốt nhất cho:** Các khối văn bản lớn, bài báo hoặc ứng dụng hỗ trợ nhiều ngôn ngữ nơi kích thước bundle là quan trọng.
|
|
416
|
+
|
|
417
|
+
**Ví dụ mã đã chuyển đổi:**
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
// Mã của bạn
|
|
421
|
+
const content = useIntlayer("my-key");
|
|
422
|
+
|
|
423
|
+
// Mã được tối ưu hóa (Động)
|
|
424
|
+
const content = useDictionaryAsync({
|
|
425
|
+
en: () =>
|
|
426
|
+
import(".intlayer/dynamic_dictionary/my-key/en.json").then(
|
|
427
|
+
(mod) => mod.default
|
|
428
|
+
),
|
|
429
|
+
fr: () =>
|
|
430
|
+
import(".intlayer/dynamic_dictionary/my-key/fr.json").then(
|
|
431
|
+
(mod) => mod.default
|
|
432
|
+
),
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
> Khi sử dụng `importMode: 'dynamic'`, nếu bạn có 100 thành phần sử dụng `useIntlayer` trên một trang duy nhất, trình duyệt sẽ thử 100 lần lấy riêng biệt. Để tránh tình trạng "thác nước" (waterfall) của các yêu cầu này, hãy nhóm nội dung vào ít tệp `.content` hơn (ví dụ: một từ điển cho mỗi phần trang) thay vì một từ điển cho mỗi thành phần nguyên tử.
|
|
437
|
+
|
|
438
|
+
### 3. Chế độ Fetch
|
|
439
|
+
|
|
440
|
+
Hoạt động tương tự như chế độ Động nhưng cố gắng lấy các từ điển từ Intlayer Live Sync API trước. Nếu cuộc gọi API thất bại hoặc nội dung không được đánh dấu để cập nhật trực tiếp, nó sẽ quay lại chế độ nhập động.
|
|
441
|
+
|
|
442
|
+
> Xem tài liệu CMS để biết thêm chi tiết: [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/vi/intlayer_CMS.md)
|
|
443
|
+
|
|
444
|
+
> Trong chế độ fetch, không thể sử dụng loại bỏ (purge) và nén (minification).
|
|
445
|
+
|
|
446
|
+
## Tóm tắt: Tĩnh vs Động
|
|
447
|
+
|
|
448
|
+
| Tính năng | Chế độ Tĩnh | Chế độ Động |
|
|
449
|
+
| :------------------------------ | :--------------------------------------------------- | :----------------------------------------- |
|
|
450
|
+
| **Kích thước Bundle JS** | Lớn hơn (bao gồm tất cả các ngôn ngữ cho thành phần) | Nhỏ nhất (chỉ mã, không có nội dung) |
|
|
451
|
+
| **Tải ban đầu** | Tức thì (Nội dung có trong bundle) | Độ trễ nhẹ (Lấy JSON) |
|
|
452
|
+
| **Yêu cầu mạng** | 0 yêu cầu bổ sung | 1 yêu cầu trên mỗi từ điển |
|
|
453
|
+
| **Tree Shaking** | Cấp độ thành phần | Cấp độ thành phần + Cấp độ ngôn ngữ |
|
|
454
|
+
| **Trường hợp sử dụng tốt nhất** | Các thành phần giao diện người dùng, Ứng dụng nhỏ | Các trang có nhiều văn bản, Nhiều ngôn ngữ |
|
|
@@ -257,7 +257,7 @@ export default function LocaleLayout({
|
|
|
257
257
|
params: { locale: string };
|
|
258
258
|
}) {
|
|
259
259
|
const locale: Locale = (locales as readonly string[]).includes(params.locale)
|
|
260
|
-
?
|
|
260
|
+
? params.locale
|
|
261
261
|
: defaultLocale;
|
|
262
262
|
|
|
263
263
|
const dir = isRtl(locale) ? "rtl" : "ltr";
|
|
@@ -103,7 +103,7 @@ async function loadMessages(locale: string) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export default getRequestConfig(async ({ locale }) => {
|
|
106
|
-
if (!locales.includes(locale
|
|
106
|
+
if (!locales.includes(locale)) notFound();
|
|
107
107
|
|
|
108
108
|
return {
|
|
109
109
|
messages: await loadMessages(locale),
|
|
@@ -193,9 +193,7 @@ const RootComponent: ParentComponent = (props) => {
|
|
|
193
193
|
</head>
|
|
194
194
|
<body>
|
|
195
195
|
<IntlayerProvider locale={locale}>
|
|
196
|
-
<Suspense>
|
|
197
|
-
{props.children}
|
|
198
|
-
</Suspense>
|
|
196
|
+
<Suspense>{props.children}</Suspense>
|
|
199
197
|
</IntlayerProvider>
|
|
200
198
|
<Scripts />
|
|
201
199
|
</body>
|
|
@@ -505,12 +503,33 @@ export const Route = createFileRoute("/{-$locale}/")({
|
|
|
505
503
|
component: RouteComponent,
|
|
506
504
|
head: ({ params }) => {
|
|
507
505
|
const { locale } = params;
|
|
508
|
-
const
|
|
506
|
+
const path = "/"; // The path for this route
|
|
507
|
+
|
|
508
|
+
const metaContent = getIntlayer("app", locale);
|
|
509
509
|
|
|
510
510
|
return {
|
|
511
|
+
links: [
|
|
512
|
+
// Canonical link: Points to the current localized page
|
|
513
|
+
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
514
|
+
|
|
515
|
+
// Hreflang: Tell Google about all localized versions
|
|
516
|
+
...localeMap(({ locale: mapLocale }) => ({
|
|
517
|
+
rel: "alternate",
|
|
518
|
+
hrefLang: mapLocale,
|
|
519
|
+
href: getLocalizedUrl(path, mapLocale),
|
|
520
|
+
})),
|
|
521
|
+
|
|
522
|
+
// x-default: For users in unmatched languages
|
|
523
|
+
// Define the default fallback locale (usually your primary language)
|
|
524
|
+
{
|
|
525
|
+
rel: "alternate",
|
|
526
|
+
hrefLang: "x-default",
|
|
527
|
+
href: getLocalizedUrl(path, defaultLocale),
|
|
528
|
+
},
|
|
529
|
+
],
|
|
511
530
|
meta: [
|
|
512
531
|
{ title: metaContent.title },
|
|
513
|
-
{
|
|
532
|
+
{ name: "description", content: metaContent.meta.description },
|
|
514
533
|
],
|
|
515
534
|
};
|
|
516
535
|
},
|
|
@@ -224,9 +224,7 @@ function RootDocument({ children }: { children: ReactNode }) {
|
|
|
224
224
|
<HeadContent />
|
|
225
225
|
</head>
|
|
226
226
|
<body>
|
|
227
|
-
<IntlayerProvider locale={locale}>
|
|
228
|
-
{children}
|
|
229
|
-
</IntlayerProvider>
|
|
227
|
+
<IntlayerProvider locale={locale}>{children}</IntlayerProvider>
|
|
230
228
|
<Scripts />
|
|
231
229
|
</body>
|
|
232
230
|
</html>
|
|
@@ -325,30 +323,20 @@ import { getPrefix } from "intlayer";
|
|
|
325
323
|
|
|
326
324
|
export const LOCALE_ROUTE = "{-$locale}" as const;
|
|
327
325
|
|
|
328
|
-
|
|
329
|
-
export type RemoveLocaleParam<T> = T extends string
|
|
330
|
-
? RemoveLocaleFromString<T>
|
|
331
|
-
: T;
|
|
326
|
+
export type To = StripLocalePrefix<LinkComponentProps["to"]>;
|
|
332
327
|
|
|
333
|
-
export type
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
328
|
+
export type StripLocalePrefix<T extends string | undefined> = T extends
|
|
329
|
+
| `/${typeof LOCALE_ROUTE}/`
|
|
330
|
+
| `/${typeof LOCALE_ROUTE}`
|
|
331
|
+
? "/"
|
|
332
|
+
: T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
|
|
333
|
+
? `/${Rest}`
|
|
334
|
+
: T;
|
|
337
335
|
|
|
338
336
|
type LocalizedLinkProps = {
|
|
339
337
|
to?: To;
|
|
340
338
|
} & Omit<LinkComponentProps, "to">;
|
|
341
339
|
|
|
342
|
-
// Các hàm trợ giúp
|
|
343
|
-
type RemoveAll<
|
|
344
|
-
S extends string,
|
|
345
|
-
Sub extends string,
|
|
346
|
-
> = S extends `${infer H}${Sub}${infer T}` ? RemoveAll<`${H}${T}`, Sub> : S;
|
|
347
|
-
|
|
348
|
-
type RemoveLocaleFromString<S extends string> = CollapseDoubleSlashes<
|
|
349
|
-
RemoveAll<S, typeof LOCALE_ROUTE>
|
|
350
|
-
>;
|
|
351
|
-
|
|
352
340
|
export const LocalizedLink: FC<LocalizedLinkProps> = (props) => {
|
|
353
341
|
const { locale } = useLocale();
|
|
354
342
|
const { localePrefix } = getPrefix(locale);
|
|
@@ -377,26 +365,26 @@ Sau đó, chúng ta có thể tạo một hook `useLocalizedNavigate` để đi
|
|
|
377
365
|
import { useNavigate } from "@tanstack/react-router";
|
|
378
366
|
import { getPrefix } from "intlayer";
|
|
379
367
|
import { useLocale } from "react-intlayer";
|
|
380
|
-
import {
|
|
368
|
+
import type { StripLocalePrefix } from "@/components/localized-link";
|
|
381
369
|
import type { FileRouteTypes } from "@/routeTree.gen";
|
|
382
370
|
|
|
383
|
-
type
|
|
384
|
-
|
|
385
|
-
| `/${typeof LOCALE_ROUTE}/`
|
|
386
|
-
? "/"
|
|
387
|
-
: T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
|
|
388
|
-
? `/${Rest}`
|
|
389
|
-
: never;
|
|
371
|
+
type NavigateFn = ReturnType<typeof useNavigate>;
|
|
372
|
+
type BaseNavigateOptions = Parameters<NavigateFn>[0];
|
|
390
373
|
|
|
391
374
|
type LocalizedTo = StripLocalePrefix<FileRouteTypes["to"]>;
|
|
392
375
|
|
|
393
|
-
type
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
376
|
+
export type LocalizedNavigateOptions = Omit<
|
|
377
|
+
BaseNavigateOptions,
|
|
378
|
+
"to" | "params"
|
|
379
|
+
> & {
|
|
380
|
+
to: LocalizedTo;
|
|
381
|
+
params?: Omit<NonNullable<BaseNavigateOptions["params"]>, "locale">;
|
|
398
382
|
};
|
|
399
383
|
|
|
384
|
+
type LocalizedNavigate = (
|
|
385
|
+
options: LocalizedNavigateOptions
|
|
386
|
+
) => ReturnType<NavigateFn>;
|
|
387
|
+
|
|
400
388
|
export const useLocalizedNavigate = () => {
|
|
401
389
|
const navigate = useNavigate();
|
|
402
390
|
|
|
@@ -443,38 +431,6 @@ import { useLocalizedNavigate } from "@/hooks/useLocalizedNavigate";
|
|
|
443
431
|
|
|
444
432
|
export const Route = createFileRoute("/{-$locale}/")({
|
|
445
433
|
component: RouteComponent,
|
|
446
|
-
head: ({ params }) => {
|
|
447
|
-
const { locale } = params;
|
|
448
|
-
const path = "/"; // The path for this route
|
|
449
|
-
|
|
450
|
-
const metaContent = getIntlayer("app", locale);
|
|
451
|
-
|
|
452
|
-
return {
|
|
453
|
-
links: [
|
|
454
|
-
// Canonical link: Points to the current localized page
|
|
455
|
-
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
456
|
-
|
|
457
|
-
// Hreflang: Tell Google about all localized versions
|
|
458
|
-
...localeMap(({ locale: mapLocale }) => ({
|
|
459
|
-
rel: "alternate",
|
|
460
|
-
hrefLang: mapLocale,
|
|
461
|
-
href: getLocalizedUrl(path, mapLocale),
|
|
462
|
-
})),
|
|
463
|
-
|
|
464
|
-
// x-default: For users in unmatched languages
|
|
465
|
-
// Define the default fallback locale (usually your primary language)
|
|
466
|
-
{
|
|
467
|
-
rel: "alternate",
|
|
468
|
-
hrefLang: "x-default",
|
|
469
|
-
href: getLocalizedUrl(path, defaultLocale),
|
|
470
|
-
},
|
|
471
|
-
],
|
|
472
|
-
meta: [
|
|
473
|
-
{ title: metaContent.title },
|
|
474
|
-
{ name: "description", content: metaContent.meta.description },
|
|
475
|
-
],
|
|
476
|
-
};
|
|
477
|
-
},
|
|
478
434
|
});
|
|
479
435
|
|
|
480
436
|
function RouteComponent() {
|
|
@@ -629,12 +585,33 @@ export const Route = createFileRoute("/{-$locale}/")({
|
|
|
629
585
|
component: RouteComponent,
|
|
630
586
|
head: ({ params }) => {
|
|
631
587
|
const { locale } = params;
|
|
632
|
-
const
|
|
588
|
+
const path = "/"; // The path for this route
|
|
589
|
+
|
|
590
|
+
const metaContent = getIntlayer("app", locale);
|
|
633
591
|
|
|
634
592
|
return {
|
|
593
|
+
links: [
|
|
594
|
+
// Canonical link: Points to the current localized page
|
|
595
|
+
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
596
|
+
|
|
597
|
+
// Hreflang: Tell Google about all localized versions
|
|
598
|
+
...localeMap(({ locale: mapLocale }) => ({
|
|
599
|
+
rel: "alternate",
|
|
600
|
+
hrefLang: mapLocale,
|
|
601
|
+
href: getLocalizedUrl(path, mapLocale),
|
|
602
|
+
})),
|
|
603
|
+
|
|
604
|
+
// x-default: For users in unmatched languages
|
|
605
|
+
// Define the default fallback locale (usually your primary language)
|
|
606
|
+
{
|
|
607
|
+
rel: "alternate",
|
|
608
|
+
hrefLang: "x-default",
|
|
609
|
+
href: getLocalizedUrl(path, defaultLocale),
|
|
610
|
+
},
|
|
611
|
+
],
|
|
635
612
|
meta: [
|
|
636
613
|
{ title: metaContent.title },
|
|
637
|
-
{
|
|
614
|
+
{ name: "description", content: metaContent.meta.description },
|
|
638
615
|
],
|
|
639
616
|
};
|
|
640
617
|
},
|