Quy trình i18n cho Vue 3 với hơn 500 khóa — tránh sự cố ở production
Quy trình i18n thực tế cho Vue 3 ở ứng dụng lớn: quy tắc đặt tên khóa, số nhiều, kiểm tra QA và bước phát hành để tránh thiếu bản dịch trên production.

Những gì thường hỏng khi có hơn 500 khóa i18n
Khi ứng dụng có vài trăm chuỗi, điều đầu tiên hỏng thường không phải là Vue I18n mà là tính nhất quán. Mọi người thêm khóa theo nhiều kiểu khác nhau, trùng ý tưởng với tên khác nhau, và chẳng ai chắc thông điệp nào có thể xóa.
Các bản dịch thiếu cũng không còn hiếm nữa. Chúng xuất hiện trên các luồng người dùng bình thường, đặc biệt ở màn hình ít dùng như cài đặt, trạng thái lỗi, trạng thái rỗng và thông báo.
Khi bản dịch thiếu, người dùng thường gặp một trong ba lỗi: giao diện trống (nút không có nhãn), thấy khóa thô (như checkout.pay_now), hoặc fallback kỳ lạ khiến một phần trang đổi ngôn ngữ. Không thứ nào trong số đó trông như một lỗi nhỏ — chúng khiến app có vẻ hỏng.
Đó là lý do vì sao một quy trình i18n cho Vue 3 quan trọng hơn thư viện cụ thể. Thư viện sẽ làm theo bạn yêu cầu. Ở quy mô lớn, các team thường không đồng ý về việc nào là "hoàn thành".
Một ví dụ thường gặp: một dev ra mắt flow "Invite teammate" với 40 chuỗi mới. File tiếng Anh được cập nhật, nhưng file tiếng Pháp thì không. Ở staging mọi thứ ổn vì tester dùng tiếng Anh. Lên production, người dùng Pháp thấy giao diện lẫn lộn giữa đã dịch và chưa dịch, và support nhận được ảnh chụp màn hình chứa khóa thô.
Cách khắc phục là định nghĩa rõ "hoàn thành" cho giao diện đã dịch. Không thể chỉ là "đã thêm chuỗi". Một định nghĩa hoàn thành thực tế thường bao gồm: khóa tuân theo quy tắc đặt tên, các locale build mà không có cảnh báo missing-key, số nhiều và biến hiển thị đúng với dữ liệu thực, ít nhất một locale không mặc định được kiểm tra, và thay đổi nội dung được theo dõi để khóa cũ không còn tồn đọng.
Khi có hơn 500 khóa, bạn thắng nếu coi bản địa hóa như một quy trình phát hành, không phải chỉnh file phút chót.
Đặt vài quy tắc trước khi thêm chuỗi
Sau vài trăm chuỗi, việc dịch không còn lộn xộn nhất nữa—mà là tính nhất quán. Một tập quy tắc nhỏ khiến quy trình i18n Vue 3 của bạn dự đoán được, dù nhiều người cùng chỉnh nội dung mỗi tuần.
Bắt đầu bằng cách quyết định thế nào là một “khái niệm” và giữ một nguồn chân lý cho nó. Nếu cùng một ý giao diện xuất hiện ở năm nơi (ví dụ "Lưu thay đổi"), bạn muốn một khóa duy nhất, không phải năm biến thể như save, saveChanges, save_update, và saveBtn. Khóa trùng lặp thay đổi nghĩa theo thời gian, và người dùng cảm nhận được sự không nhất quán đó.
Tiếp theo, quyết định nơi xử lý định dạng. Các team thường vô tình chia việc này: một số thông điệp bao gồm dấu câu và kiểu chữ, số khác dựa vào code để thêm vào. Chọn một cách và giữ nó.
Một mặc định thực tế:
- Đặt ngữ pháp, dấu câu và định dạng hướng người dùng (như “(tùy chọn)”) trong thông điệp.
- Giữ việc định dạng dữ liệu thuần trong code (ngày, tiền tệ, đơn vị), rồi truyền kết quả vào i18n.
- Dùng placeholder cho tên và số lượng, không nối chuỗi thủ công.
- Xử lý HTML trong thông điệp như trường hợp đặc biệt với quy tắc rõ ràng (cho phép hay không).
Rồi định nghĩa quyền sở hữu. Quyết ai được thêm khóa mới, ai review bản gốc (base-language), và ai phê duyệt các locale khác. Nếu không, chuỗi được thêm vội và không bao giờ được review.
Cuối cùng, chọn và ghi lại chiến lược fallback. Nếu một khóa thiếu, người dùng nên thấy gì: tên khóa, văn bản locale mặc định, hay một thông báo chung an toàn? Trong production, nhiều team thích fallback về locale mặc định kèm logging, để người dùng không bị chặn trong khi bạn vẫn có tín hiệu rằng có chuyện sai.
Nếu bạn xây app Vue 3 với generator như AppMaster (Vue3 web UI plus real backend code), những quy tắc này vẫn áp dụng. Hãy coi bản dịch như nội dung sản phẩm, không phải "chỉ text dev", và bạn sẽ tránh hầu hết bất ngờ phút chót.
Quy tắc đặt tên khóa để dễ đọc
Khi vượt vài trăm chuỗi, tính nhất quán là nhân tố lớn nhất. Chọn một kiểu khóa (đa phần teams dùng dot paths như billing.invoice.title) và biến nó thành quy tắc. Trộn lẫn dấu chấm, slash, snake_case và kiểu chữ ngẫu nhiên làm chậm việc tìm và review.
Dùng khóa ổn định chịu được thay đổi copy. Một khóa như "Please enter your email" sẽ vỡ ngay khi marketing sửa câu. Ưu tiên tên theo ý nghĩa như auth.email.required hoặc auth.email.invalid.
Nhóm khóa theo khu vực sản phẩm hoặc bề mặt UI trước, rồi theo mục đích. Nghĩ theo cùng các mục mà app đã có: auth, billing, settings, support, dashboard. Điều này giúp file locale dễ quét và giảm trùng lặp khi hai màn hình cùng cần ý tưởng giống nhau.
Trong mỗi khu vực, giữ vài mẫu nhỏ cho các phần UI phổ biến:
- Buttons:
*.actions.save,*.actions.cancel - Labels:
*.fields.email.label,*.fields.password.label - Hints/help text:
*.fields.email.hint - Errors/validation:
*.errors.required,*.errors.invalidFormat - Notifications/toasts:
*.notices.saved,*.notices.failed
Các thông điệp động nên mô tả phần thay đổi, không mô tả cách ghép. Đặt tên theo mục tiêu và dùng tham số cho phần biến. Ví dụ, billing.invoice.dueInDays với {days} rõ ràng hơn billing.invoice.dueIn3Days.
Ví dụ (phù hợp với workflow Vue 3 i18n): orders.summary.itemsCount với {count} cho số lượng, và orders.summary.total với {amount} cho tiền. Khi ai đó đọc khóa trong code, họ nên biết nó thuộc phần nào và vì sao tồn tại, ngay cả khi copy cuối cùng thay đổi sau này.
Quy tắc số nhiều và định dạng thông điệp để không bất ngờ
Văn bản số nhiều (plural) vỡ một cách lặng lẽ khi bạn coi mọi ngôn ngữ giống tiếng Anh. Quyết sớm khi nào dùng cú pháp ICU và khi nào chỉ dùng placeholder đơn giản.
Dùng thay thế đơn giản cho nhãn và văn bản ngắn không đổi theo số (ví dụ, "Welcome, {name}"). Chuyển sang ICU cho mọi thứ dựa trên số, vì nó giữ tất cả dạng ở một chỗ và làm rõ quy tắc.
{
"notifications.count": "{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
}
Viết thông điệp đếm sao cho dễ dịch. Ưu tiên một câu đầy đủ và giữ placeholder số (#) gần danh từ. Tránh mẹo dùng cùng một khóa cho "1 item" và "2 items" trong code. Người dịch cần thấy toàn bộ câu, không đoán cách ghép.
Lên kế hoạch cho =0, one, và other ít nhất, và ghi rõ bạn mong đợi gì cho 0. Một số sản phẩm muốn “0 items”, số khác muốn “No items”. Chọn một kiểu và giữ nó để giao diện cảm thấy nhất quán.
Cũng để ý những ngôn ngữ có nhiều dạng số nhiều hơn bạn nghĩ. Nhiều ngôn ngữ không theo mẫu “one vs many”. Nếu bạn thêm locale mới sau này, thông điệp chỉ có one và other có thể sai ngữ pháp dù vẫn hiển thị.
Trước khi phát hành, test số nhiều với các số thực trong UI, không chỉ nhìn JSON. Một kiểm tra nhanh bắt được hầu hết vấn đề là 0, 1, 2, 5, và 21.
Nếu bạn xây app Vue3 web (ví dụ, trong AppMaster), làm kiểm tra này trên màn hình thực tế nơi chữ xuất hiện. Vấn đề bố cục, chữ bị cắt, và diễn đạt lúng túng hiện ra ở đó trước.
Tổ chức file locale để dễ mở rộng
Sau vài trăm chuỗi, một en.json khổng lồ trở thành cổ chai. Mọi người cùng chỉnh file, xung đột merge tăng, và bạn mất dấu nơi copy nằm. Cấu trúc tốt giữ workflow i18n Vue 3 ổn định dù sản phẩm thay đổi.
Cấu trúc đề xuất
Với 2–5 locale, tách theo feature thường đủ. Giữ cùng layout file ở mọi locale để việc thêm khóa là một chỉnh sửa có thể dự đoán.
locales/en/common.json,locales/en/auth.json,locales/en/billing.jsonlocales/es/common.json,locales/es/auth.json,locales/es/billing.jsonlocales/index.ts(tải và merge messages)
Với 20+ locale, mở rộng cùng ý tưởng nhưng làm cho việc sai lệch khó xảy ra. Xem English là nguồn chân lý và bắt buộc mọi locale phản chiếu cùng thư mục và tên file. Nếu một miền mới xuất hiện (ví dụ, notifications), nó nên tồn tại cho mọi locale ngay cả khi text tạm thời.
Tách theo domain giảm xung đột merge vì hai người có thể thêm chuỗi vào file khác nhau mà không đụng nhau. Domain nên khớp với cấu trúc app của bạn: common, navigation, errors, settings, reports, cùng các thư mục feature cho khu vực lớn hơn.
Giữ khóa nhất quán
Trong mỗi file, giữ dạng khóa giống nhau across locales: cùng nesting, cùng tên khóa, khác text. Tránh đặt khóa “sáng tạo” theo ngôn ngữ, ngay cả khi một cụm khó dịch. Nếu English có billing.invoice.status.paid, mọi locale phải có chính xác khóa đó.
Tập trung hóa chỉ những thứ thực sự lặp lại ở khắp nơi: nhãn nút, lỗi xác thực chung, và navigation toàn cục. Giữ copy đặc thù feature gần domain feature, ngay cả khi có vẻ tái sử dụng. “Save” thuộc common. “Save payment method” thuộc billing.
Nội dung dài
Văn bản trợ giúp dài, bước onboarding và mẫu email nhanh trở nên lộn xộn. Một vài quy tắc giúp:
- Đặt chuỗi dài vào domain riêng (ví dụ
helphoặconboarding) và tránh nested quá sâu. - Ưu tiên đoạn ngắn thay vì một chuỗi khổng lồ, để người dịch làm việc an toàn.
- Nếu marketing hoặc support chỉnh nội dung thường xuyên, giữ những thông điệp đó trong file riêng để giảm churn tại nơi khác.
- Với email, lưu subject và body riêng và giữ placeholder đồng nhất (tên, ngày, số tiền).
Cài đặt này giúp dễ review thay đổi, dịch dần, và tránh thiếu hụt bất ngờ ngay trước release.
Quy trình từng bước để thêm và phát hành chuỗi
Một workflow i18n Vue 3 ổn định ít liên quan đến công cụ hơn là lặp lại cùng vài bước nhỏ mỗi lần. Văn bản UI mới không nên tới production mà không có khóa, văn bản mặc định và trạng thái dịch rõ ràng.
Bắt đầu bằng cách thêm khóa vào locale gốc (thường là en). Viết văn bản mặc định như copy thực, không phải placeholder. Điều này cho product và QA thứ để review, và tránh "chuỗi bí ẩn" sau này.
Khi dùng khóa trong component, include params và logic plural từ ngày đầu để người dịch thấy đầy đủ hình dạng thông điệp.
// simple param
$t('billing.invoiceDue', { date: formattedDate })
// plural
$t('files.selected', count, { count })
Nếu bản dịch chưa sẵn, đừng để khóa thiếu. Thêm bản dịch placeholder cho các locale khác hoặc đánh dấu là pending theo cách nhất quán (ví dụ, tiền tố TODO:). Quan trọng là app render có dự đoán và reviewer dễ thấy copy chưa hoàn thành.
Trước khi merge, chạy các kiểm tra tự động nhanh. Giữ chúng khô khan và nghiêm ngặt: missing keys across locales, unused keys, mismatch placeholder (thiếu {count}, {date} hoặc dấu ngoặc sai), plural forms không hợp lệ cho ngôn ngữ hỗ trợ, và override vô tình.
Cuối cùng, làm một lượt UI ngắn ở ít nhất một locale không phải mặc định. Chọn ngôn ngữ có chuỗi dài hơn (thường là tiếng Đức hoặc tiếng Pháp) để bắt overflow, nút bị cắt, và xuống dòng lúng túng. Nếu UI Vue 3 của bạn được sinh hoặc duy trì cùng phần khác của sản phẩm (ví dụ Vue3 web app do AppMaster sinh), bước này vẫn quan trọng vì layout thay đổi theo tính năng.
Coi những bước này là định nghĩa "done" cho bất kỳ feature nào thêm văn bản.
Những lỗi phổ biến các team hay lặp lại
Cách nhanh nhất để làm bản địa hóa trở nên khổ sở là coi khóa i18n là "chỉ chuỗi". Sau vài trăm khóa, thói quen nhỏ lớn dần thành bug production.
Khóa trôi, trùng hoặc sai ý
Lỗi chính tả và khác kiểu chữ là nguyên nhân cổ điển của text bị mất: checkout.title ở chỗ này, Checkout.title ở chỗ kia. Trông ổn ở code review, rồi locale fallback âm thầm được deploy.
Vấn đề khác là dùng lại một khóa cho nhiều nghĩa. “Open” có thể là “Open ticket” trong màn support và “Open now” trong trang cửa hàng. Nếu dùng chung một khóa, một trong hai màn sẽ sai ở ngôn ngữ khác, và người dịch sẽ phải đoán.
Một quy tắc đơn giản: một khóa = một nghĩa. Nếu nghĩa thay đổi, tạo khóa mới dù tiếng Anh giống nhau.
Lỗi bố cục do giả thiết "hình chữ"
Teams thường hardcode dấu câu, khoảng cách, hoặc mảnh HTML vào bản dịch. Cách đó ổn cho tới khi một ngôn ngữ cần dấu câu khác, hoặc UI escape/render markup khác. Giữ quyết định markup trong template, và giữ thông điệp chỉ là text.
Mobile là nơi các vấn đề ẩn. Nhãn vừa với tiếng Anh có thể xuống ba dòng trong tiếng Đức, hoặc tràn trong tiếng Thái. Nếu bạn chỉ test một kích thước màn hình, bạn sẽ bỏ sót.
Chú ý các thói quen hay gây lỗi: giả sử trật tự từ tiếng Anh khi chèn biến (tên, số lượng, ngày), ghép thông điệp bằng các đoạn riêng thay vì một thông điệp duy nhất, quên test giá trị dài (tên sản phẩm, địa chỉ, chi tiết lỗi), ship với fallback im lặng khiến khóa thiếu trông "ổn" bằng tiếng Anh, và copy-paste khóa trong khi chỉ chỉnh tiếng Anh.
Nếu bạn muốn workflow i18n Vue 3 ổn định ở 500+ khóa, hãy coi khóa như một phần API: ổn định, cụ thể và được test như phần khác.
Bước QA bắt lỗi thiếu bản dịch sớm
Bản dịch thiếu dễ bị bỏ sót vì app vẫn "hoạt động". Nó chỉ fallback về khóa, locale sai, hoặc chuỗi rỗng. Hãy coi coverage bản dịch như test: bạn muốn phản hồi nhanh trước khi lên production.
Kiểm tra tự động (chạy trên mỗi PR)
Bắt đầu với các kiểm tra khiến build fail, không phải chỉ in cảnh báo mà không ai đọc.
- Scan codebase tìm
$t('...')vàt('...'), rồi xác minh mọi khóa được dùng tồn tại trong locale gốc. - So sánh tập khóa giữa các locale và fail nếu bất kỳ locale nào thiếu khóa (trừ khi khóa thuộc danh sách ngoại lệ nhỏ đã review như “en-only legal notes”).
- Đánh dấu orphan keys tồn trong file locale nhưng không bao giờ được dùng.
- Validate cú pháp thông điệp (placeholders, ICU/plural blocks). Một thông điệp hỏng có thể crash trang tại runtime.
- Xử lý duplicate keys hoặc khác kiểu chữ như lỗi.
Giữ danh sách ngoại lệ ngắn và có người chịu trách nhiệm, không để là “bất cứ thứ gì pass CI”.
Kiểm tra runtime và trực quan (staging)
Ngay cả với CI, bạn vẫn cần một lưới an toàn ở staging vì các luồng người dùng thực kích hoạt chuỗi bạn quên.
Bật logging khi thiếu bản dịch ở staging và kèm đủ ngữ cảnh để sửa nhanh: locale, route, tên component (nếu có) và khóa thiếu. Làm cho nó ồn ào. Nếu dễ bị bỏ qua, nó sẽ bị bỏ qua.
Thêm pseudo-locale và dùng cho một lượt kiểm tra UI nhanh. Một cách đơn giản là biến mỗi chuỗi (kéo dài và thêm dấu) để lỗi bố cục hiện ra, ví dụ: [!!! 𝗧𝗲𝘅𝘁 𝗲𝘅𝗽𝗮𝗻𝗱𝗲𝗱 !!!]. Điều này bắt được nút bị cắt, header bảng bị hỏng và khoảng cách thiếu trước khi ship.
Cuối cùng, làm kiểm tra ngắn trước phát hành trên các luồng giá trị nhất: sign-in, checkout/payment, cài đặt chính và feature mới. Đây là nơi bạn bắt được lỗi “đã dịch nhưng placeholder sai”.
Xử lý ngôn ngữ mới và thay đổi nội dung liên tục
Thêm ngôn ngữ mới sẽ lộn xộn nếu bạn coi đó là “việc copy” thay vì một bản phát hành nhỏ. Cách dễ nhất để giữ workflow i18n Vue 3 ổn định là làm cho app build ngay cả khi locale chưa hoàn chỉnh, nhưng vẫn khiến khe hở rõ ràng trước khi người dùng thấy chúng.
Khi thêm locale mới, bắt đầu bằng việc sinh cùng cấu trúc file như locale nguồn (thường là en). Đừng dịch trước, cấu trúc trước.
- Tạo thư mục/file locale mới với đầy đủ khóa copy từ nguồn.
- Đánh dấu giá trị là placeholder TODO để chuỗi thiếu dễ thấy trong QA.
- Thêm locale vào language switcher chỉ sau khi phần cơ bản đã xong.
- Chạy một lượt kiểm tra màn hình để bắt vấn đề bố cục (từ dài hơn, xuống dòng).
Các bản dịch thường tới muộn, nên quyết trước thế nào là “partial” cho sản phẩm bạn. Nếu feature hướng tới khách hàng, cân nhắc feature flag để feature và chuỗi của nó cùng được ship. Nếu phải ship mà chưa đủ bản dịch, ưu tiên fallback rõ ràng (như tiếng Anh) hơn là nhãn rỗng, nhưng làm cho chuyện đó to lên ở staging.
Thay đổi copy là nơi teams làm vỡ khóa. Nếu bạn đổi cách diễn đạt, giữ khóa ổn định. Khóa nên mô tả ý định, không phải câu chính xác. Chỉ đổi tên khóa khi nghĩa thay đổi, và dù vậy hãy làm migration có kiểm soát.
Để tránh “chuỗi zombie”, deprecate khóa có chủ ý: đánh dấu khóa là deprecated kèm ngày và khóa thay thế, giữ trong một cycle release, rồi xóa sau khi chắc không còn tham chiếu.
Giữ chú thích dịch gần khóa. Nếu JSON không chứa comment, lưu ghi chú trong một tài liệu kèm nhỏ hoặc file metadata bên cạnh (ví dụ, “dùng ở màn checkout success”). Điều này đặc biệt hữu ích khi Vue 3 web app của bạn được sinh từ công cụ như AppMaster và nhiều người cùng chỉnh copy và UI.
Ví dụ: phát hành feature với 60 chuỗi mới
Một sprint, team bạn phát hành ba thứ cùng lúc: trang Settings mới, màn Billing, và ba template email (receipt, payment failed, trial ending). Khoảng 60 chuỗi mới — nơi i18n thường bắt đầu lộn xộn.
Team thống nhất nhóm khóa theo feature rồi theo bề mặt. Tạo file mới cho mỗi feature, và khóa theo cùng mẫu: feature.area.element.
// settings
"settings.profile.title": "Profile",
"settings.security.mfa.enable": "Enable two-factor authentication",
// billing
"billing.plan.current": "Current plan",
"billing.invoice.count": "{count} invoice | {count} invoices",
// email
"email.receipt.subject": "Your receipt",
"email.payment_failed.cta": "Update payment method"
Trước khi làm dịch, các chuỗi plural được test với giá trị thực, không đoán. Với billing.invoice.count, QA kiểm tra 0, 1, 2 và 5 bằng dữ liệu seed (hoặc toggle dev đơn giản). Điều này bắt các trường hợp lúng túng sớm, như "0 invoice".
QA sau đó chạy flow tập trung hay tiết lộ thiếu khóa: mở Settings và Billing, click từng tab một lần, kích hoạt mỗi template email trong staging với tài khoản test, chạy app với locale không mặc định đang active, và fail build nếu có bất kỳ cảnh báo missing-translation nào trong log.
Trong sprint này, QA tìm hai khóa thiếu: một nhãn trên Billing và một subject email. Team sửa trước khi phát hành.
Khi quyết có block release hay cho fallback, họ dùng một quy tắc đơn giản: màn hình hướng khách hàng và email giao dịch block release; text admin rủi ro thấp có thể tạm fallback về ngôn ngữ mặc định, nhưng chỉ với ticket và deadline rõ ràng.
Bước tiếp theo và checklist phát hành đơn giản
Workflow i18n Vue 3 ổn định khi bạn đối xử với bản dịch như mã: thêm theo cùng cách, test, và chấm điểm.
Checklist phát hành (5 phút trước merge)
- Keys: tuân mẫu đặt tên, và giữ scope rõ (page, feature, component).
- Plurals: xác nhận các form số nhiều hiển thị đúng ít nhất ở một ngôn ngữ có nhiều dạng số.
- Placeholders: kiểm tra biến tồn tại, đặt tên giống nhau ở mọi nơi, và hiển thị đúng với dữ liệu thực.
- Fallbacks: xác nhận hành vi khi thiếu khóa khớp với chính sách hiện tại.
- Màn hình: spot-check vài màn hình dễ hỏng nhất (bảng, toasts, modal, empty states).
Quyết định đo lường (để vấn đề hiện sớm)
Chọn vài con số đơn giản và review định kỳ: tỷ lệ thiếu-key trong chạy test, số chuỗi chưa dịch ở các locale không mặc định, và số khóa không dùng (strings không còn xuất hiện). Theo dõi theo release nếu có thể, để thấy xu hướng thay vì lỗi lẻ.
Ghi lại các bước chính xác để thêm chuỗi, cập nhật bản dịch và verify kết quả. Giữ ngắn, và kèm ví dụ tên khóa và cách dùng plural. Người đóng góp mới nên theo được mà không cần hỏi.
Nếu bạn xây công cụ nội bộ, AppMaster (appmaster.io) có thể là cách thực tế để giữ copy UI và khóa dịch nhất quán giữa Vue3 web app và app di động kèm theo, vì mọi thứ được quản lý cùng một nơi.
Lên lịch một nhiệm vụ sức khỏe i18n nhỏ mỗi sprint: xóa khóa chết, sửa placeholder không nhất quán, và review các lần miss gần đây. Các dọn dẹp nhỏ thắng các cuộc săn bản dịch khẩn cấp ở production.


