Đặ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 Solid i18n
Mô hình phản ứng (reactivity) của Solid làm thay đổi hoàn toàn những gì một thư viện i18n cần phải làm. Các component chỉ chạy một lần duy nhất, vì vậy bản dịch được lưu trong một const lúc setup sẽ trở thành một chuỗi bị đóng băng (frozen string), và một thư viện trả về chuỗi thay vì accessors sẽ tạo ra một trang web chuyển đổi ngôn ngữ ở khắp mọi nơi ngoại trừ ba component mà ai đó đã viết như vậy. Việc chọn thư viện cho Solid một phần là về API, và một phần là về việc thư viện nào giúp bạn tránh mắc phải sai lầm đó nhất.
Hướng dẫn này liệt kê các câu hỏi cần trả lời trước, sau đó đối chiếu chúng với @solid-primitives/i18n, solid-i18next, Paraglide, @lingui/solid và Intlayer, dành cho Vite + Solid và cho SolidStart.

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 SolidStart? Trong một SPA, locale có thể nằm hoàn toàn trong một signal. Trên SolidStart, locale phải được resolve trên server từ URL, và bất kỳ nội dung nào crawler cần thấy mà không cần JavaScript (
<html lang>,hreflang) đều thuộc vềentry-server.tsx. - Khả năng phản ứng (reactivity) khi thay đổi locale cần mức độ nào? Việc tải lại toàn bộ trang khi chuyển đổi ngôn ngữ có thể chấp nhận được với một số ứng dụng. Nếu không, các giá trị của thư viện phải là signals hoặc accessors, và việc đọc chúng phải được track (theo dõi), chứ không phải copy.
- Ai là người viết bản dịch? Developer, một TMS, một agency bàn giao các chuỗi ICU, hay một AI pipeline.
solid-i18nextsử dụng định dạng của i18next.@solid-primitives/i18nnhận bất kỳ dictionary object nào của bạn. Hãy chọn thư viện khớp với bên cung cấp bản dịch. - Có bao nhiêu locale và trang? Hai locale và năm trang có thể đóng gói toàn bộ nội dung. Mười locale và bốn mươi route thì không thể, và lazy catalog kèm scoping sẽ trở thành chi phí chính.
- Bạn có cần type cho các key không?
@solid-primitives/i18ntự suy luận type từ source dictionary.solid-i18nextyêu cầu khai báo thủ công. Các thư viện dạng compile-time sẽ tự động generate ra chúng. - Bạn cần phạm vi tính năng (feature surface) nhiều đến mức nào? Quản lý cookie, routing có tiền tố locale, redirects, formatters. Lựa chọn nhẹ nhất không có tính năng nào trong số này, và điều đó hoàn toàn ổn cho đến khi bạn thực sự cần chúng.
Hãy ghi lại các câu trả lời. Mọi nội dung 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
Solid là hệ sinh thái trẻ nhất ở đây và có ít lựa chọn nhất, trải qua ba làn sóng.

i18next được bọc lại cho Solid. Namespaces, backends, detectors, cùng một thập kỷ phát triển plugin. Là lựa chọn nặng nhất trong nhóm, mang chi phí tra cứu t("a.b") tương tự như trong React.
Một dictionary phẳng do bạn làm chủ, một hàm translator() trả về các accessors, type được suy luận trực tiếp từ source object. Rất nhỏ gọn, không có scoping, không có routing, không có formatters. Đây là mặc định của cộng đồng.
Paraglide tạo ra một hàm cho mỗi message. Intlayer khai báo nội dung theo từng component trong các file .content.ts và trả về các node được hỗ trợ bởi signal. Binding cho Solid của Lingui ra mắt năm 2026, mang lại khả năng trích xuất dựa trên macro.
Bài viết lịch sử JavaScript i18n phân tích chi tiết từng làn sóng.
Quyết định quan trọng nhất: nơi lưu trữ nội dung và thời điểm tải
Hai lựa chọn mang tính cấu trúc giải thích phần lớn sự khác biệt về kích thước bundle giữa các thiết lập:
- Nội dung tập trung (centralized) hay phân phạm vi (scoped). Một dictionary duy nhất cho toàn ứng dụng, hoặc một khai báo riêng cho từng component.
- Static import hay dynamic import. Toàn bộ nội dung khi khởi động, hoặc chỉ tải locale đang hoạt động (và lý tưởng nhất là route đang hoạt động) theo yêu cầu.
Biểu đồ dưới đây ước tính payload cho một ứng dụng giả định 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.

@solid-primitives/i18n không can thiệp vào cả hai trục: bạn dùng createResource cho dictionary của từng locale để tải động (dynamic loading), phần còn lại bạn tự xử lý. solid-i18next hỗ trợ namespaces và lazy backends, nhưng không có gì đảm bảo sự phân tách này, vì vậy một component dùng chung import common sẽ biến nó thành dependency của mọi route. Paraglide xử lý trục trang thông qua tree-shaking, mặc dù điều này chưa phát huy tác dụng trong triển khai Solid benchmark. Intlayer đạt được điều đó thông qua việc khai báo nội dung theo từng component.
Nếu câu trả lời của bạn cho câu hỏi 4 là "nhiều trang", hãy cân nhắc phần này 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 phân tích 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ừ Solid benchmark: provider kèm accessor trong một component trống, sau khi bundling, tree-shaking và minification, trên ứng dụng 10 trang và 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 | Tính phản ứng khi đổi locale | Type cho key | Scoping và lazy loading | Kích thước thư viện |
|---|---|---|---|---|---|
@solid-primitives/i18n | Dictionary phẳng do bạn làm chủ | Signal, accessors trả về từ translator | Suy luận từ source dictionary | Không có sẵn | Rất nhỏ |
solid-i18next | Catalog và namespace của i18next | Store, re-render qua provider | Khai báo thủ công | Namespaces, lazy backends | ~14.9 kB |
| Paraglide | inlang project, các hàm được generate | Đọc mỗi lần gọi từ cookie hoặc storage | Được generate | Tree-shaking (chưa có trong bench) | Gần như bằng 0 |
@lingui/solid | Source text trong code, catalog đã compile | Dựa trên signal | Từ trình biên dịch | Theo từng catalog | Nhỏ |
| Intlayer | Một file .content.ts cho mỗi component | Node hỗ trợ signal, không re-run component | Được generate, bật mặc định | Có, theo từng component | Chuẩn tham chiếu (Baseline) |
Các con số là ảnh chụp nhanh tại các phiên bản của bài benchmark. @lingui/solid không có trong benchmark. Hãy chạy thử nghiệm trên ứng dụng của riêng 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 là do bản chất thiết kế: runtime được generate trực tiếp vào repository của bạn. Intlayer cần vite-intlayer, vì vậy nó không thể chạy nếu không có bước build.
Đối chiếu câu trả lời của bạn với thư viện phù hợp
@solid-primitives/i18n. Một dictionary phẳng, một translator() trả về các accessors, type được suy luận mà không cần cấu hình phức tạp. Đây là câu trả lời chuẩn xác cho một ứng dụng nhỏ, và việc đọc source code chỉ mất mười phút. Những gì bạn sẽ phải tự viết: lưu trữ locale (locale persistence), routing, formatters và tách code theo route (per-route splitting). Nếu danh sách đó dài ra, đó là tín hiệu để chuyển đổi giải pháp.
solid-i18next cho phép bạn tái sử dụng catalogs, namespaces, backends và detectors nguyên trạng. Đây là lựa chọn nặng nhất và mang lại các chi phí tương tự như react-i18next: khai báo type thủ công, tối ưu hóa khả thi nhưng tốn thời gian, và hàm t() trả về chuỗi, khiến lỗi đóng băng bản dịch (frozen translation) rất dễ xảy ra. Hãy bọc các lần đọc trong JSX hoặc một memo và không bao giờ lưu trữ chúng lúc setup.
Locale phải được lấy từ URL phía server để cả hai bên đồng bộ; việc phát hiện locale ở client là quá muộn. @solid-primitives/i18n và solid-i18next để bạn tự xử lý route [[locale]], matchFilters, redirect và các thẻ trong entry-server.tsx. Paraglide có một plugin Vite giúp xử lý routing. Intlayer cung cấp sẵn middleware và các helper cho route. Dù chọn giải pháp nào, hãy đặt <html lang> và hreflang trong entry-server.tsx; @solidjs/meta chỉ áp dụng trên client sau khi hydration trong SolidStart v2. Bài viết Solid i18n hướng dẫn chi tiết cách thiết lập này.
Hãy chọn một thư viện có giá trị là signals hoặc accessors và việc đọc giá trị được track đầy đủ. Accessors của @solid-primitives/i18n và các node của Intlayer đều chỉ cập nhật đúng các DOM node đọc chúng mà không làm component chạy lại (re-run). solid-i18next re-render thông qua provider. Paraglide đọc locale từ cookie hoặc storage trên mỗi lần gọi message thay vì từ một signal, cách này hoạt động được nhưng xử lý nhiều công việc trên mỗi node hơn mức cần thiết.
Nội dung phân phạm vi (scoped) được biên dịch tại thời điểm build. Intlayer chỉ tải những gì một route thực sự render. Paraglide trên lý thuyết đạt được điều này qua tree-shaking; hãy kiểm tra lại trong thiết lập của bạn vì nó chưa phát huy tác dụng trong bài benchmark. Với solid-i18next, hãy lên kế hoạch cho chiến lược namespace và lazy-loading ngay từ ngày đầu và kiểm soát chặt chẽ khi code review.
@solid-primitives/i18n cung cấp type được suy luận miễn phí, điều mà hầu hết các thư viện React không có. Đối với type được generate mà vẫn giữ được tính toàn vẹn qua lazy loading và chia tách route, Paraglide, @lingui/solid và Intlayer đều tạo ra type từ chính nội dung. Bài viết phát hiện thiếu bản dịch so sánh những gì mỗi thư viện có thể bắt lỗi được tại thời điểm build.
Khi đó, một dictionary tập trung không còn đối tượng sử dụng nào để biện minh cho sự tồn tại của nó. Nội dung đặt cùng component (colocated) 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. Lệnh fill của Intlayer chạy với API key của riêng bạn (OpenAI, Anthropic, Mistral, Gemini) và chỉ dịch lại những gì đã thay đổi.
Nhược điểm của từng thư viện
@solid-primitives/i18n: không có lazy loading hay scoping ngoài những gì bạn tự xây dựng, không có routing, không xử lý cookie, không có formatters. Tuyệt vời cho các ứng dụng nhỏ, nhanh chóng bộc lộ hạn chế đối với các ứng dụng chuyên nghiệp.solid-i18next: nặng nhất trong nhóm, type thủ công, định dạng số nhiều riêng, vàt()trả về chuỗi nên bản dịch bị đóng băng nếu lưu tại bước setup.- Paraglide: các file được generate phải commit vào repo và phải regenerate trước mỗi lần push, tree-shaking chưa hoạt động trong bài Solid benchmark, và locale được đọc từ storage trong mỗi lần gọi thay vì từ signal.
@lingui/solid: mới ra mắt năm 2026, nên chưa có nhiều phản hồi từ môi trường production thực tế. Thừa hưởng bước buildextract/compilecủa Lingui cùng nhiều cú pháp chồng chéo nhau.- 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 nằm rải rác trong codebase theo thiết kế, vì vậy việc xuất một file JSON duy nhất cho biên dịch viên cần công cụ hỗ trợ.
Cú pháp code của từng lựa chọn
Cùng một component, bản tóm tắt giỏ hàng gồm tiêu đề và dạng số nhiều, được viết bằng từng ứng cử viên. Hãy chú ý vị trí đọc bản dịch: trong JSX nó được track, trong phần thân của setup nó là một chuỗi đóng băng.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Key được định kiểu type từ object tiếng Anh mà không cần codegen. Không có quy tắc số nhiều, không có lazy loading và không có routing; bạn sẽ cần tự bổ sung từng phần.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Sử dụng catalogs, namespaces và plugins của i18next nguyên trạng. t trả về chuỗi, vì vậy const title = t("cart:title") lúc setup sẽ làm nó bị đóng băng; hãy giữ lệnh gọi bên trong JSX.
Sao chép mã vào clipboard
Sao chép mã vào clipboard
Mỗi message là một hàm có type được generate sẵn. Locale được đọc từ cookie hoặc storage trên mỗi lần gọi thay vì từ một signal, vì vậy khả năng phản ứng khi đổi ngôn ngữ bạn sẽ phải tự cấu hình.
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. useIntlayer trả về các node hỗ trợ signal, vì vậy việc thay đổi locale chỉ cập nhật đúng các DOM node đọc chúng. {content.title} trong JSX được track; content.title.value trong thân của setup thì không.
Trên một codebase i18next hiện có, i18next compat adapter tạo alias cho package ở cấp độ bundler để các catalog và t() tiếp tục hoạt động trong khi Intlayer phân phối nội dung, và hướng dẫn chuyển đổi sẽ phụ trách phần còn lại.
Trước khi bạn đưa ra quyết định
Một bảng tính năng chỉ cho bạn biết một thư viện làm được gì hôm nay. Những điểm dưới đây cho bạn biết trải nghiệm sử dụng lâu dài sẽ ra sao.
Kiểm tra mức độ hoạt động của repository.
Số lượng commit, thời gian phản hồi issue, và liệu bản minor release gần nhất có diễn ra trong năm nay không. Một thiết kế tốt nhưng không có người duy trì sẽ là một cuộc di cư (migration) đang chờ đón bạn trong tương lai.
Đừng chọ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, chứ không phải thư viện phù hợp nhất với một codebase Solid năm 2026. Lượt tải đo lường lịch sử, không đo lường độ tương thích.

Tìm hiểu xem ai tài trợ cho người bảo trì, và họ bán sản phẩm gì.
i18next (đứng sau solid-i18next) được hậu thuẫn bởi Locize. next-intl, vue-i18n, svelte-i18n và Lingui được tài trợ bởi Crowdin. Tolgee, Paraglide (inlang) và Intlayer đều vận hành nền tảng riêng của họ. Một nhà cung cấp có doanh thu đến từ dịch vụ lưu trữ bản dịch (hosted translation) sẽ có rất ít lý do để cung cấp bản dịch miễn phí ngay bên trong chuỗi công cụ (toolchain) của bạn. Intlayer là thư viện duy nhất trong nhóm cung cấp tính năng dịch thuật bằng AI qua CLI với API key của chính bạn, cùng một CMS mà bạn có thể tự host (self-host).
Thư viện đã sẵn sàng cho AI agent chưa?
Các agent vẫn gặp khó khăn với i18n: chúng quên locale, tự bịa ra key và trộn lẫn các cú pháp message. Thư viện có cung cấp Agent Skills hoặc MCP server để agent có thể liệt kê, điền và kiểm thử nội dung không? Và việc nạp nội dung có được tối ưu hóa theo mặc định hay ai đó phải định kỳ rà soát namespaces và lazy imports hàng quý?
Type safety ngay từ đầu.
Không phải là "có thể định kiểu với cấu hình bổ sung" mà là "một key sai 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 điều gì xảy ra với một key không tồn tại, và với 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 ra. Quá trình build của Intlayer sẽ loại bỏ các trường không dùng và ghi log (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ị loại bỏ qua tree-shaking. Tất cả các thư viện khác đều để lại công việc dọn dẹp cho bạn.
Developer experience.
Thời gian setup cho đến chuỗi dịch đầu tiên, một LSP hoặc VS Code extension hiển thị bản dịch khi hover và nhảy tới khai báo, một CLI để fill, test và push, cùng cách để người không phải lập trình viên 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
Với ứng dụng nhỏ, câu trả lời là có, và đây là lựa chọn nhẹ nhất hiện có. Nó sẽ không còn đủ khi bạn cần lazy catalog theo từng route, routing theo locale trên SolidStart, lưu trữ cookie hoặc formatters, bởi vì toàn bộ những phần đó bạn sẽ phải tự xây dựng.
Bởi vì các component trong Solid chỉ chạy một lần. Một bản dịch được đọc vào một const lúc setup là một chuỗi thông thường, không phải một subscription. Hãy đọc nó bên trong JSX, một effect hoặc một memo, hoặc chọn một thư viện có các giá trị là accessors để khó viết sai hơn.
Chỉ khi bundle size, generated types hoặc việc kiểm tra thiếu key tại thời điểm build là các yêu cầu thực tế. Bài viết i18n dạng compiler so với declarative giải thích những gì trình biên dịch mang lại và những trường hợp chúng có thể xử lý không đúng.
Có, một cách gián tiếp. Các công cụ tìm kiếm quan tâm đến routing, hreflang, <html lang> và liệu văn bản có nằm trong HTML được render trên server hay không, điều mà trên SolidStart gắn liền với entry-server.tsx. Xem thêm hướng dẫn hreflang.
Tìm hiểu thêm
- Solid i18n benchmark: kích thước bundle, độ rò rỉ và thời gian chuyển đổi locale
- Solid i18n: tại sao bản dịch bị đóng băng khi đổi locale
- Drop-in i18next compat adapter và hướng dẫn chuyển đổi i18next
- Lịch sử JavaScript i18n
- i18n dạng compiler so với declarative
- i18n theo từng component so với tập trung
- Cách tối ưu hóa bundle hoạt động tại thời điểm build
- Thiết lập i18n trong ứng dụng Vite + Solid và trong ứng dụng SolidStart
- Hướng dẫn tương tự cho React, Vue và Svelte
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.
