Đặt câu hỏi và nhận tóm tắt tài liệu bằng cách tham chiếu trang này và nhà cung cấp AI bạn chọn
Nội dung của trang này đã được dịch bằng AI.
Xem phiên bản mới nhất của nội dung gốc bằng tiếng AnhNếu bạn có ý tưởng để cải thiện tài liệu này, vui lòng đóng góp bằng cách gửi pull request trên GitHub.
Liên kết GitHub tới tài liệuSao chép Markdown của tài liệu vào bộ nhớ tạm
Cách chọn đúng thư viện Vue i18n
"Vue i18n" vừa là một thuật ngữ chung vừa là tên của thư viện mà hầu như ai cũng cài đặt. Điều đó vừa tiện lợi vừa dễ gây hiểu lầm: vue-i18n là một lựa chọn mặc định tốt, nhưng không phải là lựa chọn duy nhất, và những câu hỏi định hình quyết định (có dùng SSR hay không, bao nhiêu trang, ai là người viết bản dịch) hiếm khi được đặt ra trước khi chạy npm install.
Hướng dẫn này sẽ đặt ra những câu hỏi đó trước, sau đó ánh xạ các câu trả lời tới những thư viện phù hợp, cho cả Vite + Vue thuần lẫn Nuxt.

Mục lục
Sáu câu hỏi cần trả lời trước khi so sánh các thư viện
- Vite SPA hay Nuxt? Trong một SPA, chi phí catalog là vấn đề về JS bundle. Trong Nuxt, đó còn là vấn đề về HTML payload, bởi vì các message được serialize vào SSR state và hydrate. Hầu hết các báo cáo "vue-i18n chạy chậm" xuất phát từ các ứng dụng Nuxt chính vì lý do này.
- Ai là người viết bản dịch? Developer, một TMS, một agency bàn giao chuỗi ICU, hay một AI pipeline.
vue-i18nsử dụng cú pháp số nhiều riêng biệt phân cách bằng dấu gạch đứng (pipe-separated), không phải ICU. Điều này rất quan trọng nếu các chuỗi văn bản đến từ bên ngoài. - Có bao nhiêu locale và trang? Hai locale và năm trang có thể chuyển giao (ship) tất cả mọi thứ cùng lúc. Mười locale và bốn mươi route thì không thể, và chiến lược tải dữ liệu (loading strategy) sẽ trở thành chi phí chính.
- Bạn có cần type trên các key không?
t("cart.totl")vẫn biên dịch bình thường trongvue-i18ntrừ khi bạn truyền một message schema generic, và schema đó lại xung đột với các catalog được lazy load. - Nội dung bao gồm những gì? Chỉ các nhãn UI, hay cả markdown, liên kết bên trong câu, và các block riêng theo từng locale. Rich content là nơi mà việc
t()trả về một chuỗi string trở nên bất tiện. - CSP có phải là một ràng buộc không? Bản build mặc định của
vue-i18nbiên dịch message trong trình duyệt bằngnew Function. Các bản build runtime-only cần@intlify/unplugin-vue-i18nđể precompile tại thời điểm build time.
Hãy ghi lại các câu trả lời. Mọi phân tích bên dưới đều sẽ đối chiếu lại với chúng.
Bức tranh toàn cảnh qua một hình ảnh
Hệ sinh thái Vue có ít thư viện i18n hơn React, và chúng đến từ các làn sóng kiến trúc khác nhau.

vue-i18n xuất hiện vào năm 2015 và luôn là lựa chọn mặc định kể từ đó. @nuxt/i18n bọc lấy nó với tính năng định tuyến locale (locale routing), thẻ SEO và lazy loading theo từng locale. Các message được biên dịch thành các render function, tại thời điểm build time nếu bạn thêm unplugin, hoặc trực tiếp trong trình duyệt nếu không cấu hình thêm.
Các file .ftl của Mozilla Fluent mang lại cú pháp message thân thiện hơn cùng các biến thể nhận biết ngữ pháp (grammar-aware). Không có key type, và Vite plugin sẽ nạp mọi locale vào từng trang.
Paraglide sinh ra một hàm cho mỗi message và để bundler tự động tree-shake phần còn lại. Intlayer khai báo nội dung theo từng component trong các file .content.ts, sinh type tự động, và chỉ chuyển giao những gì một route thực sự render.
Bài viết lịch sử của JavaScript i18n đi sâu chi tiết vào từng làn sóng.
Quyết định quan trọng nhất: nội dung nằm ở đâu và được tải khi nào
Hai lựa chọn mang tính cấu trúc giải thích cho hầu hết sự khác biệt về bundle size giữa các thiết lập:
- Nội dung tập trung hay phân tán theo phạm vi (scoped). Một file
locales/en.jsoncho toàn bộ ứng dụng, hay một khai báo riêng cho từng component. - Static hay dynamic import. Nạp tất cả mọi thứ khi khởi động, hay chỉ tải locale đang hoạt động (và lý tưởng nhất là route đang hoạt động) theo nhu cầu (on demand).
Biểu đồ ước tính payload cho một ứng dụng trên lý thuyết từ 1 đến 10 trang, được dịch sang 1 đến 10 locale, với khoảng 30 KB văn bản trên mỗi trang.

vue-i18n hỗ trợ trục dynamic: gọi setLocaleMessage sau một lệnh import() đồng nghĩa với việc bạn không còn phải chuyển giao chín locale mà không ai đọc. Điều mà nó không mang lại cho bạn là trục theo trang. Một catalog locale là một đối tượng duy nhất, và việc nạp nó sẽ nạp toàn bộ nội dung của mọi trang. Trong một SPA, không ai nhận ra điều đó. Nhưng trong Nuxt, với @nuxtjs/i18n và nhiều hơn mười trang, mỗi route đều phải gánh các chuỗi của mọi route khác hai lần: trong JS chunk và trong SSR payload.
Bài benchmark Vue đo lường điều này dưới dạng "rò rỉ từ các route khác" (leakage from other routes) và "rò rỉ từ các locale khác" (leakage from other locales). Nếu câu trả lời của bạn cho câu hỏi 3 là "nhiều trang", phần này sẽ quan trọng hơn bất kỳ sở thích API nào. Bài viết i18n theo từng component so với tập trung đề cập đến khía cạnh bảo trì của cùng sự đánh đổi này.
Các ứng cử viên
Kích thước thư viện được lấy từ bài benchmark Vue: plugin cộng với composable trong một component rỗng, sau khi bundling, tree-shaking và minification, trên một ứng dụng 10 trang, 10 locale. Nội dung được đo lường riêng biệt.
Mở bảng trong một cửa sổ bật lên để xem toàn bộ nội dung dữ liệu một cách rõ ràng
| Thư viện | Mô hình nội dung | Type trên key | Định dạng message | Phân tách theo route | Kích thước thư viện |
|---|---|---|---|---|---|
vue-i18n | Catalog tập trung theo locale, tùy chọn SFC <i18n> block | Opt-in qua schema generic | Riêng (pipe plural) | Không | ~24.3 kB |
@nuxtjs/i18n | Tương tự vue-i18n, cộng thêm routing và thẻ SEO | Tương tự | Tương tự | Không, chỉ theo locale | Thêm vào trên đó |
fluent-vue | File .ftl (Mozilla Fluent) | Không | Fluent | Không | ~29.7 kB |
| Paraglide | Project inlang, các hàm được sinh ra | Được sinh ra | Riêng | Qua tree-shaking | Gần như bằng 0 |
| Intlayer | Một file .content.ts cho mỗi component | Được sinh ra, bật mặc định | Helper (plural) | Có, theo component | Baseline |
Các con số là ảnh chụp nhanh tại các phiên bản của bài benchmark. Hãy chạy thử nghiệm trên chính ứng dụng của bạn trước khi đưa ra quyết định chỉ dựa vào kích thước.
Kích thước thư viện gần như bằng 0 của Paraglide đạt được nhờ thiết kế: runtime được sinh trực tiếp vào repository của bạn, điều này đồng nghĩa với việc cần một bước sinh lại mã (regeneration) trước mỗi lần push và nguy cơ merge conflict trên các file được sinh ra. Intlayer cần vite-intlayer (hoặc Nuxt module), vì vậy nó không thể chạy nếu không có một build step.
Đối chiếu câu trả lời với thư viện phù hợp
vue-i18n ở chế độ Composition (legacy: false), kết hợp với @intlify/unplugin-vue-i18n để bạn chuyển giao bản build runtime-only. Lazy-load các locale bằng import(). Cấu hình này đáp ứng hầu hết các ứng dụng nhỏ và câu trả lời từ cộng đồng có ở khắp mọi nơi. Các block SFC <i18n> đặt message đồng vị trí với component, điều này rất hữu ích, nhưng công cụ trích xuất và hỗ trợ TMS xung quanh chúng còn hạn chế hơn so với catalog JSON, vì vậy hãy sớm thống nhất lựa chọn mà team sẽ sử dụng.
@nuxtjs/i18n cung cấp chiến lược định tuyến, các thẻ hreflang và tự động phát hiện locale mà không cần viết thêm code, và chỉ riêng điều đó đã đủ để chọn nó cho các trang nội dung có vài trang. Giới hạn của nó nằm ở catalog theo từng locale: khi vượt quá khoảng mười trang, SSR payload sẽ phải gánh toàn bộ nội dung của mọi route. Nếu đó là trường hợp của bạn, hãy tự cấu hình thủ công vue-i18n với message theo từng route, hoặc chuyển sang mô hình nội dung scoped. Bài viết Nuxt i18n sẽ hướng dẫn bạn lựa chọn chiến lược định tuyến trước tiên.
Cú pháp số nhiều của vue-i18n ("no item | one item | {count} items") không phải là ICU và không mang tính tương thích cao (portable). Các biên dịch viên cần được thông báo trước về điều này, và bản xuất từ TMS thông thường sẽ không tạo ra định dạng đó. Hãy thống nhất định dạng trước khi tạo catalog đầu tiên, hoặc chọn một thư viện có định dạng phù hợp với vendor của bạn. Khả năng hỗ trợ ICU của Intlayer mới ở mức một phần, vì vậy nếu hiện tại bạn nhận chuỗi ICU, hãy cân nhắc đó cũng là một yếu tố cản trở.
Nên ưu tiên nội dung scoped được biên dịch tại build time. Paraglide đạt được điều này thông qua tree-shaking, hoạt động rất hiệu quả trên Vite. Intlayer đạt được điều này thông qua các khai báo theo từng component và chỉ chuyển giao những gì route thực sự render. Với vue-i18n, bạn có thể chia tách message theo route một cách thủ công, nhưng không có gì đảm bảo tính thực thi và một component dùng chung import namespace toàn cục có thể âm thầm phá vỡ cấu trúc đó.
vue-i18n có thể được gán type bằng cách truyền một schema generic vào createI18n. Cách này hoạt động được, nhưng sẽ bị phá vỡ ngay khi các catalog được lazy load, bởi vì schema mô tả các message có thể chưa được tải về. Nếu không muốn phải tự bảo trì điều đó, hãy chọn thư viện có type được sinh trực tiếp từ nội dung: Paraglide hoặc Intlayer. Bài viết phát hiện bản dịch còn thiếu so sánh những gì mỗi công cụ phát hiện được tại build time.
Các trang markdown, câu có chứa <RouterLink> ở giữa, hoặc các component riêng biệt theo locale. vue-i18n có <i18n-t> cho phép nội suy component (component interpolation), hoạt động ổn nhưng khá dài dòng. Content node của Intlayer chấp nhận markdown, HTML và các object lồng nhau trực tiếp, phù hợp hơn nhiều khi ứng dụng chứa nhiều nội dung phong phú.
Khi đó, file JSON tập trung không còn đối tượng sử dụng nào để duy trì sự tồn tại của nó. Nội dung đồng vị trí kết hợp với một CLI tự động điền các locale còn thiếu là con đường ngắn hơn nhiều. Lệnh fill của Intlayer chạy trực tiếp với API key của chính bạn (OpenAI, Anthropic, Mistral, Gemini) và chỉ dịch lại những gì đã thay đổi.
Hạn chế của từng thư viện
vue-i18n: nặng nhất trong nhóm, định dạng số nhiều riêng, type là tùy chọn (opt-in) và dễ vỡ khi lazy loading, không hỗ trợ phân tách phạm vi theo route, các key không dùng tích tụ một cách âm thầm. Việc để lạilegacy: truetrong ứng dụng Vue 3 sẽ giữ lại layer tương thích Vue 2 và làm mất khả năng gõ type củauseI18n().@nuxtjs/i18n: thừa hưởng tất cả các nhược điểm trên, và SSR payload sẽ gánh toàn bộ chuỗi ký tự của mọi trang khi ứng dụng vượt quá khoảng một tá route.fluent-vue: cú pháp message tốt, không có key type, và Vite plugin nạp tất cả nội dung ở mọi ngôn ngữ vào từng trang. Nặng nhất trong bài benchmark.- Paraglide: các file được sinh ra phải commit vào repo, cần regenerate trước mỗi lần push, và locale được đọc từ cookie hoặc storage trên mỗi lệnh gọi message thay vì từ một reactive store, làm tốn thêm tài nguyên xử lý khi chuyển đổi locale.
- Intlayer: bắt buộc phải có build plugin, hệ sinh thái nhỏ hơn, hỗ trợ ICU một phần, và nội dung phân tán khắp codebase theo thiết kế, vì vậy việc xuất ra một file JSON duy nhất cho biên dịch viên sẽ cần đến công cụ hỗ trợ.
Mã nguồn thực tế của từng lựa chọn
Cùng một component, phần tóm tắt giỏ hàng với tiêu đề và định dạng số nhiều, được viết bằng từng ứng cử viên. Điểm thú vị không nằm ở template, mà ở vị trí lưu trữ nội dung và những gì vue-tsc nhận biết được về nó.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Số nhiều phân cách bằng dấu gạch đứng (pipe) là định dạng riêng của vue-i18n, không phải ICU. t chấp nhận bất kỳ chuỗi nào trừ khi bạn truyền một message schema generic vào createI18n.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Cú pháp của Fluent xử lý số nhiều và các biến thể ngữ pháp rất tốt. Message id là các chuỗi untyped, và Vite plugin đóng gói mọi locale vào từng trang.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Mỗi message là một hàm được sinh ra và có đầy đủ type, vì vậy một key bị thiếu sẽ dẫn đến lỗi import. Thư mục paraglide/ được sinh trực tiếp vào repo của bạn và được regenerate lại sau mỗi lần thay đổi.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Tất cả các locale nằm trong một file duy nhất cạnh component. Type được sinh ra tại thời điểm build, vì vậy title được tự động gợi ý (autocomplete) và lỗi chính tả sẽ khiến vue-tsc báo lỗi ngay. <title /> render một node mà visual editor có thể nhắm tới; {{ items(props.count) }} trả về chuỗi văn bản thuần.
Bạn đang sử dụng vue-i18n? Compat adapter @intlayer/vue-i18n alias package ở cấp độ bundler, nhờ đó useI18n(), $t, pipe plural và v-t tiếp tục hoạt động trong khi Intlayer đảm nhận cung cấp nội dung. Hướng dẫn di chuyển sẽ hướng dẫn bạn cách gỡ bỏ adapter sau đó, và cũng có hướng dẫn dành riêng cho Nuxt.
Những điều cần cân nhắc trước khi quyết định
Bảng tính năng chỉ cho bạn biết một thư viện có thể làm được gì hôm nay. Những điểm dưới đây cho bạn biết trải nghiệm thực tế khi đồng hành cùng nó sẽ như thế nào.
Kiểm tra mức độ hoạt động của repository.
Các commit, thời gian phản hồi issue, và liệu bản phát hành minor gần nhất có diễn ra trong năm nay hay không. Một thiết kế hoàn hảo nhưng không có người duy trì sẽ sớm trở thành một cuộc di chuyển (migration) bắt buộc trong tương lai.
Đừng chọn thư viện chỉ dựa vào lượt tải trên npm.
Thư viện được cài đặt nhiều nhất là thư viện xuất hiện đầu tiên, không phải thư viện phù hợp nhất cho một codebase Vue năm 2026. Lượt tải phản ánh lịch sử, không phản ánh sự phù hợp.

Tìm hiểu xem ai tài trợ cho người duy trì, và họ bán sản phẩm gì.
vue-i18n được bảo trợ bởi Crowdin, tương tự như next-intl và svelte-i18n. i18next được bảo trợ bởi Locize. Tolgee, Paraglide (inlang) và Intlayer tự vận hành nền tảng riêng của mình. Một vendor có nguồn thu đến từ dịch vụ lưu trữ bản dịch (hosted translation) ít có lý do để cung cấp bản dịch miễn phí ngay bên trong chuỗi công cụ của bạn. Intlayer là thư viện duy nhất trong nhóm cung cấp tính năng dịch AI qua CLI bằng chính API key của bạn, cùng một CMS mà bạn có thể tự host.
Thư viện đã sẵn sàng cho AI-agent chưa?
Các agent vẫn còn gặp khó khăn với i18n: chúng quên locale, tự tạo key bừa bãi, và trộn lẫn các cú pháp message. Thư viện có cung cấp Agent Skills hoặc một MCP server để agent có thể liệt kê, điền và kiểm thử nội dung hay không? Và việc nạp nội dung có được tối ưu hóa theo mặc định không, hay ai đó phải xem xét lại các namespace và lazy import mỗi quý một lần?
Type safety ngay từ đầu.
Không phải "có thể gõ type nếu cấu hình thêm" mà là "sai key sẽ khiến tsc báo lỗi ngay trên một bản cài đặt mới". Hãy kiểm tra xem điều gì sẽ xảy ra khi một key không tồn tại, và khi một locale bị thiếu một bản dịch.
Phát hiện nội dung không sử dụng.
Các catalog chỉ có xu hướng phình to. Quá trình build của Intlayer sẽ loại bỏ các trường không sử dụng và ghi log chúng (build.purge). Paraglide đạt được điều này nhờ kiến trúc, vì một hàm message không được gọi sẽ bị tree-shake. Tất cả các giải pháp khác đều để lại công việc dọn dẹp cho bạn.
Trải nghiệm lập trình viên (Developer experience).
Thời gian thiết lập cho đến chuỗi dịch đầu tiên, một LSP hoặc tiện ích mở rộng VS Code hiển thị bản dịch khi hover và nhảy thẳng tới định nghĩa, một CLI để fill, test và push, cùng phương thức để người không phải developer có thể chỉnh sửa nội dung (visual editor hoặc CMS) mà không cần tạo pull request.
Câu hỏi thường gặp
Đối với hầu hết các ứng dụng Vue, câu trả lời là có. Hệ sinh thái lớn nhất, tài liệu kỹ lưỡng, và các chi phí đều có thể dự đoán được: runtime nặng, định dạng số nhiều tùy chỉnh, và việc phân tách phạm vi theo route đòi hỏi bạn phải tự xây dựng và kiểm soát.
Hãy sử dụng module trừ khi việc định tuyến của bạn có yêu cầu đặc biệt hoặc ứng dụng chỉ có rất ít trang. Cấu hình thủ công đồng nghĩa với việc bạn phải tự xây dựng lại locale route, middleware, hreflang và sitemap, và những thứ đó phức tạp hơn vẻ bề ngoài rất nhiều.
Chỉ khi bundle size, SSR payload, type được sinh tự động hoặc kiểm tra thiếu key tại build time là các yêu cầu thực tế của dự án. Bài viết so sánh i18n compiler và declarative giải thích những gì compiler mang lại và những điểm chúng có thể xử lý chưa tối ưu.
Ảnh hưởng gián tiếp. Các công cụ tìm kiếm quan tâm đến định tuyến, hreflang, <html lang> và việc văn bản có nằm trong HTML được render từ server hay không. Xem thêm hướng dẫn hreflang.
Tìm hiểu thêm
- Benchmark Vue i18n: bundle size, độ rò rỉ và thời gian chuyển đổi locale
- Vue i18n: cách vue-i18n hoạt động và những điểm bất cập và bài viết về Nuxt i18n
- vue-i18n so với Intlayer, từng tính năng chi tiết và benchmark vue-i18n so với Intlayer
- vue-i18n có lỗi thời không?
- Lịch sử của JavaScript i18n
- So sánh i18n compiler và declarative
- i18n theo từng component so với tập trung
- Cài đặt i18n trong ứng dụng Vite + Vue và trong ứng dụng Nuxt
- Cùng hướng dẫn cho React, Svelte và Solid
Bình luận
Chưa có bình luận nào. Hãy là người đầu tiên chia sẻ suy nghĩ của bạn.
