Kiến trúc quốc tế hóa cho ứng dụng xây dựng từ chat
Kiến trúc quốc tế hóa cho ứng dụng xây dựng từ chat: xác định khóa chuỗi ổn định, quy tắc số nhiều và một quy trình dịch duy nhất áp dụng nhất quán trên web và mobile.

Điều gì hỏng đầu tiên khi bạn thêm nhiều ngôn ngữ
Điều hỏng đầu tiên không phải là code. Là từ ngữ.
Ứng dụng được xây dựng qua chat thường bắt đầu như một nguyên mẫu nhanh: bạn gõ “Thêm nút có chữ Lưu”, giao diện xuất hiện, và bạn tiếp tục. Vài tuần sau, bạn cần tiếng Tây Ban Nha và tiếng Đức, và phát hiện ra những nhãn “tạm thời” đó rải rác khắp màn hình, component, email và thông báo lỗi.
Việc thay đổi nội dung cũng thường xuyên hơn thay đổi code. Tên sản phẩm được đổi, văn bản pháp lý thay đổi, phần onboarding viết lại, và support yêu cầu thông báo lỗi rõ hơn. Nếu văn bản nằm trực tiếp trong code UI, mỗi thay đổi nhỏ sẽ thành một phát hành rủi ro, và bạn sẽ bỏ sót những chỗ cùng ý nhưng diễn đạt khác nhau.
Dưới đây là những dấu hiệu sớm báo hiệu bạn đang xây dựng "nợ dịch":
- Lẫn lộn ngôn ngữ trên cùng một màn hình (một số chuỗi đã dịch, số khác vẫn bằng tiếng Anh).
- Cùng một nhãn bị trùng với khác biệt nhỏ ("Sign up", "Sign Up", "Create account").
- Giao diện bị hỏng khi văn bản dài hơn (nút tràn, tiêu đề xuống dòng xấu).
- Web và mobile lệch nhau (từ khác nhau cho cùng một hành động).
- Nội dung support và tin hệ thống chạy sau UI.
Ví dụ thực tế: bạn xây CRM đơn giản trên Koder.ai. Web app hiển thị “Deal stage”, app mobile hiển thị “Pipeline step”, và một toast lỗi nói “Invalid status”. Dù cả ba được dịch, người dùng vẫn thấy không nhất quán vì khái niệm không trùng.
"Nhất quán" không có nghĩa là "cùng kí tự ở mọi nơi". Nó có nghĩa:
- Cùng một khái niệm dùng cùng một khóa và cùng ý nghĩa trên các màn hình.
- Web và mobile dùng cùng nguồn dữ liệu cho bản dịch.
- Giọng điệu và thuật ngữ ổn định (trang trọng hay thân mật, “customer” hay “client”).
- Bố cục UI được thiết kế để xử lý câu dài hoặc ngắn.
Khi bạn coi văn bản là dữ liệu sản phẩm, không phải trang trí, việc thêm ngôn ngữ sẽ không còn là cuộc tranh cựu mà trở thành phần thường xuyên của phát triển.
Khái niệm cơ bản và mục tiêu đơn giản để hướng tới
Internationalization (i18n) là công việc để một ứng dụng hỗ trợ nhiều ngôn ngữ mà không phải viết lại. Localization (l10n) là nội dung thực tế cho một ngôn ngữ và vùng, ví dụ tiếng Pháp (Canada) với từ ngữ, định dạng ngày giờ và giọng điệu phù hợp.
Mục tiêu đơn giản: mọi đoạn văn bản tiếp xúc người dùng phải được chọn bằng một khóa ổn định, không gõ thẳng vào code UI. Nếu bạn có thể thay một câu mà không mở component React hay widget Flutter, bạn đang đi đúng hướng. Đây là lõi của kiến trúc quốc tế hóa cho ứng dụng xây dựng từ chat, nơi dễ vô tình deploy bản copy cứng được tạo trong phiên chat.
Văn bản hướng đến người dùng rộng hơn nhiều đội ngờ tới. Nó bao gồm nút, nhãn, lỗi xác thực, trạng thái rỗng, mẹo onboarding, thông báo push, email, xuất PDF và mọi thông điệp người dùng có thể thấy hoặc nghe. Thông thường không bao gồm log nội bộ, tên cột trong database, ID sự kiện analytics, feature flag, hay đầu ra debug chỉ dành cho admin.
Dịch nên nằm ở đâu? Thực tế thường là cả frontend và backend, với ranh giới rõ ràng.
- Frontend chịu phần chrome UI: điều hướng, form, menu và hầu hết văn bản trên màn hình.
- Backend chịu phần thông điệp nó tạo ra: email giao dịch, lỗi xác thực server-side và mọi thứ trả về như một phản hồi lỗi.
- Các miền chia sẻ (như nhãn trạng thái đơn hàng) nên đến từ một nguồn chân lý, để web và mobile không drift.
Sai lầm cần tránh là trộn trách nhiệm. Nếu backend trả câu tiếng Anh hoàn chỉnh cho lỗi UI, frontend sẽ không thể localize sạch. Mẫu tốt hơn: backend trả code lỗi (và có thể tham số an toàn), client ánh xạ code đó sang thông điệp đã được dịch.
Quyền sở hữu copy là quyết định sản phẩm, không phải chi tiết kỹ thuật. Quyết định sớm ai có thể thay từ và phê duyệt giọng điệu.
Nếu product quản lý copy, coi bản dịch như nội dung: version, review và cho product cách an toàn để yêu cầu thay đổi. Nếu engineering quản lý copy, đặt quy tắc rằng mọi chuỗi UI mới phải có khóa và bản dịch mặc định trước khi phát hành.
Ví dụ: nếu flow signup của bạn viết “Create account” ở ba màn hình khác nhau, hãy dùng một khóa duy nhất ở mọi nơi. Điều này giữ ý nghĩa nhất quán, làm cho dịch nhanh hơn và ngăn các sửa đổi nhỏ biến thành dọn dẹp nhiều màn hình sau này.
Cấu trúc khóa chuỗi sao cho ổn định
Khóa là hợp đồng giữa UI và bản dịch. Nếu hợp đồng này thay đổi, bạn sẽ có text thiếu, sửa vội và diễn đạt không nhất quán giữa web và mobile. Một kiến trúc i18n tốt cho ứng dụng xây dựng từ chat bắt đầu với một quy tắc: khóa nên mô tả ý nghĩa, không phải câu tiếng Anh hiện tại.
Dùng ID ổn định làm khóa (ví dụ billing.invoice.payNow) thay vì lấy nguyên văn copy (ví dụ "Pay now"). Khóa dạng câu vỡ ngay khi ai đó chỉnh từ, thêm dấu câu hoặc đổi chữ hoa.
Một mẫu thực tế, vẫn dễ đọc: màn hình (hoặc miền) + component + intent. Giữ nó nhàm và dự đoán được.
Ví dụ:
auth.login.titleauth.login.emailLabelbilling.checkout.payButtonnav.settingserrors.network.offline
Quyết định khi nào tái sử dụng khóa hay tạo mới bằng cách hỏi: “Ý nghĩa có hoàn toàn giống nhau ở mọi nơi không?” Tái sử dụng cho hành động thật sự chung, nhưng tách khóa khi bối cảnh khác. Ví dụ, “Save” trong màn hình profile có thể là hành động đơn giản, trong editor phức tạp có thể cần giọng điệu khác ở một số ngôn ngữ.
Giữ văn bản UI chia sẻ trong namespace riêng để không bị trùng across màn hình. Các nhóm chung hữu ích:
common.actions.*(save, cancel, delete)common.status.*(loading, success)common.fields.*(search, password)errors.*(validation, network)nav.*(tabs, menu items)
Khi wording thay đổi nhưng ý nghĩa giữ nguyên, giữ khóa và chỉ cập nhật giá trị dịch. Đó là mục đích của ID ổn định. Nếu ý nghĩa thay đổi (dù nhỏ), tạo khóa mới và giữ khóa cũ cho đến khi chắc chắn không dùng nữa. Điều này tránh mismatch âm thầm khi bản dịch cũ còn nhưng sai.
Một ví dụ nhỏ từ flow kiểu Koder.ai: chat sinh cả web React và mobile Flutter. Nếu cả hai dùng common.actions.save, bạn có dịch nhất quán. Nhưng nếu web dùng profile.save và mobile dùng account.saveButton, theo thời gian sẽ drift dù tiếng Anh hôm nay giống nhau.
Nơi lưu chuỗi và cách tổ chức file dịch
Coi ngôn ngữ nguồn (thường là tiếng Anh) như nguồn chân lý duy nhất. Giữ nó ở một nơi, review như code, và tránh để chuỗi xuất hiện lung tung trong component “tạm thời”. Đây là cách nhanh nhất để tránh copy cứng và việc làm lại sau này.
Một quy tắc đơn giản: app chỉ hiển thị văn bản từ hệ thống i18n. Nếu ai đó cần copy mới, họ thêm khóa và thông điệp mặc định trước, rồi dùng khóa đó trong UI. Điều này giữ kiến trúc i18n ổn định ngay cả khi feature di chuyển.
Cấu trúc thư mục dễ quản lý
Nếu phát hành cả web và mobile, bạn muốn một catalog khóa chung, cùng không gian để các feature team làm việc mà không đụng nhau. Một layout thực tế:
- /i18n
- /i18n/locales/en.json (nguồn)
- /i18n/locales/es.json, /i18n/locales/fr.json, ...
- /i18n/features/billing.json, /i18n/features/auth.json, ...
- /i18n/shared.json (nút, nhãn chung, lỗi)
Giữ khóa giống nhau trên mọi nền tảng, dù implement khác (React trên web, Flutter trên mobile). Nếu dùng nền tảng như Koder.ai để sinh cả hai app từ chat, xuất code sẽ dễ quản lý hơn khi cả hai project cùng trỏ tới tên khóa và định dạng thông điệp giống nhau.
Versioning và review để tránh nợ dịch
Bản dịch thay đổi theo thời gian. Đối xử thay đổi như thay đổi sản phẩm: nhỏ, được review và có thể truy vết. Review tốt tập trung vào ý nghĩa và tái sử dụng, không chỉ chính tả.
- Yêu cầu review PR cho mọi thay đổi ở locale nguồn
- Chặn xóa khóa mà không tìm kiếm và lên kế hoạch
- Thêm ghi chú cho dịch viên khi văn bản mơ hồ
- Dùng kiểm tra “missing translations” trong CI
Để khóa không drift giữa các team, gán khóa cho feature (billing., auth.), và không đổi tên khóa chỉ vì wording thay đổi. Cập nhật thông điệp, giữ khóa. Khóa là định danh, không phải copy.
Phân số nhiều và ngữ pháp không dùng mẹo vặt
Quy tắc số nhiều khác nhau theo ngôn ngữ, nên mẫu tiếng Anh đơn giản (1 vs tất cả còn lại) sẽ hỏng nhanh. Một số ngôn ngữ có dạng riêng cho 0, 1, 2-4, và nhiều dạng khác. Một số thay đổi cả câu, không chỉ danh từ. Nếu bạn nhét logic plural vào UI với if-else, bạn sẽ sao chép copy và bỏ sót trường hợp cạnh.
Cách an toàn hơn là giữ một thông điệp linh hoạt cho mỗi ý tưởng và để lớp i18n chọn dạng đúng. Thông điệp kiểu ICU được tạo cho việc này. Chúng để quyết định ngữ pháp ở phần dịch, không ở component.
Ví dụ nhỏ che được các trường hợp hay quên:
itemsCount = "{count, plural, =0 {No items} one {# item} other {# items}}"
Khóa duy nhất này bao phủ 0, 1 và các trường hợp còn lại. Dịch viên có thể thay bằng các dạng số nhiều phù hợp cho ngôn ngữ của họ mà không cần bạn đụng code.
Khi cần từ ngữ theo giới tính hoặc vai trò, tránh tạo khóa riêng như welcome_male và welcome_female trừ khi sản phẩm thực sự cần. Dùng select để câu vẫn là một đơn vị:
welcomeUser = "{gender, select, female {Welcome, Ms. {name}} male {Welcome, Mr. {name}} other {Welcome, {name}}}"
Để tránh bị kẹt với các trường hợp biến cách, giữ câu càng đầy đủ càng tốt. Đừng ghép các đoạn mảnh như "{count} " + t('items') vì nhiều ngôn ngữ không cho phép hoán vị từ như vậy. Ưu tiên một thông điệp bao gồm số, danh từ và từ xung quanh.
Quy tắc đơn giản hữu dụng cho ứng dụng xây dựng từ chat (kể cả dự án Koder.ai): nếu một câu chứa số, người hoặc trạng thái, hãy dùng ICU từ ngày đầu. Ban đầu tốn chút công nhưng tiết kiệm nhiều nợ dịch sau này.
Giữ dịch web và mobile nhất quán
Nếu web React và mobile Flutter mỗi bên giữ file dịch riêng, chúng sẽ drift. Cùng một nút có thể khác lời, một khóa có thể nghĩa khác trên web và mobile, và support ticket sẽ bắt đầu nói “app nói X nhưng website nói Y”.
Sửa đơn giản nhất cũng quan trọng nhất: chọn một nguồn chân lý và đối xử nó như code. Với hầu hết đội, điều đó là một bộ file locale chung (ví dụ JSON dùng thông điệp ICU) mà cả web và mobile tiêu thụ. Khi bạn xây nhanh bằng chat và generator, điều này càng quan trọng vì dễ vô tình tạo text mới ở hai chỗ.
Một nguồn chân lý duy nhất
Thiết lập thực tế là một package/ thư mục i18n nhỏ chứa:
- File locale cho mỗi ngôn ngữ (khóa giống nhau khắp nơi)
- Quy tắc định dạng thông điệp (ICU cho plural và placeholder)
- README ngắn giải thích cách thêm khóa
React và Flutter trở thành consumer. Chúng không tự sinh khóa mới cục bộ. Trong workflow kiểu Koder.ai (React web, Flutter mobile), bạn có thể sinh cả hai client từ cùng set khóa, và giữ thay đổi lọt qua review như bất kỳ thay đổi code nào.
Căn chỉnh backend là cùng câu chuyện. Lỗi, thông báo và email không nên là câu tiếng Anh viết tay trong Go. Thay vào đó, trả code lỗi ổn định (ví dụ auth.invalid_password) cùng tham số an toàn. Client ánh xạ code sang văn bản đã dịch. Với email render phía server, server có thể render template dùng cùng khóa và file locale.
Quy tắc giữ đồng bộ
Tạo một quyển luật nhỏ và áp dụng trong code review:
- Văn bản UI mới yêu cầu thêm khóa trong file locale chung trước
- Khóa phải có namespace và intent rõ ràng (không phải vị trí màn hình)
- Mỗi khóa phải có placeholder định nghĩa một lần và dùng chung
- Nếu hai câu khác nghĩa, không bao giờ chia sẻ cùng một khóa
- Nếu hai khóa cùng ý nghĩa, xóa một và chọn một khóa chiến thắng
Để tránh khóa trùng nhưng khác nghĩa, thêm trường “description” (hoặc file chú thích) cho dịch viên và tương lai. Ví dụ: billing.trial_days_left nên giải thích nó hiện trong banner, email hay cả hai. Một câu như vậy thường chặn việc tái sử dụng “gần đủ” gây nợ dịch.
Sự nhất quán này là xương sống của kiến trúc i18n cho ứng dụng xây dựng từ chat: một từ vựng chung, nhiều bề mặt, và không có bất ngờ khi bạn phát hành ngôn ngữ tiếp theo.
Các bước thiết lập theo dự án thực tế
Một kiến trúc i18n tốt cho ứng dụng xây dựng từ chat bắt đầu đơn giản: một bộ khóa, một nguồn chân lý cho copy, và cùng quy tắc trên web và mobile. Nếu bạn phát triển nhanh (ví dụ bằng Koder.ai), cấu trúc này giữ tốc độ mà không sinh nợ dịch.
Thiết lập thực tế (web + mobile)
Chọn locale sớm và quyết định chuyện gì xảy ra khi bản dịch thiếu. Lựa chọn phổ biến: hiển thị ngôn ngữ người dùng ưu tiên khi có, nếu không fallback sang tiếng Anh, và log key thiếu để sửa trước phát hành.
Rồi thực hiện:
- Định nghĩa locale và fallback: Quyết định ngôn ngữ hỗ trợ, locale mặc định và thứ tự fallback. Đồng ý cách phát hiện locale (cài đặt trình duyệt/app, profile người dùng).
- Tạo hàm dịch và quy ước khóa: Dùng khóa ổn định theo ý nghĩa (không phải câu đầy đủ). Ví dụ:
billing.plan_name.prohoặcauth.error.invalid_password. Giữ khóa giống nhau khắp nơi. - Kết nối vào React và Flutter: Trong React, wrap app bằng i18n provider và dùng
t("key")trong component. Trong Flutter, dùng wrapper localisation và gọi lookup theo khóa tương tự trong widget. Mục tiêu là cùng khóa, không nhất thiết cùng thư viện. - Hỗ trợ biến và quy tắc plural ngay từ đầu: Dùng thông điệp ICU cho plural và placeholder, như “{count, plural, one {# file} other {# files}}” và “Hello, {name}”. Tránh mẹo
if (count === 1)rải rác. - Thêm bước review copy nhẹ: Trước khi ship, review chuỗi mới/đổi: kiểm tra đặt tên khóa, loại bỏ copy cứng, xác nhận placeholder khớp, và đảm bảo web và mobile đều nhận thay đổi.
Cuối cùng, test một ngôn ngữ có từ dài hơn (tiếng Đức là kinh điển) và một ngôn ngữ có dấu câu khác. Điều này phơi bày nhanh các nút tràn, tiêu đề xuống dòng xấu và layout giả sử tiếng Anh.
Nếu bạn giữ bản dịch trong thư mục chung (hoặc package sinh ra) và đối xử thay đổi copy như thay đổi code, web và mobile vẫn nhất quán ngay cả khi feature được xây nhanh qua chat.
Nội dung động: ngày, số và văn bản do người dùng tạo
Chuỗi UI dịch chỉ là một nửa vấn đề. Hầu hết app còn hiển thị giá trị thay đổi như ngày, giá, số lượng và tên. Nếu bạn coi những giá trị đó là plain text, bạn sẽ gặp định dạng lạ, múi giờ sai và câu nghe "không tự nhiên" ở nhiều ngôn ngữ.
Bắt đầu bằng việc định dạng số, tiền tệ và ngày theo quy tắc locale, không phải code tay. Người dùng ở Pháp mong “1 234,50 €”, còn ở Mỹ mong “$1,234.50”. Tương tự với ngày: “03/04/2026” có thể gây mơ hồ, nhưng định dạng theo locale làm rõ.
Múi giờ là cái bẫy tiếp theo. Server nên lưu timestamp ở dạng trung lập (thường là UTC), nhưng người dùng mong thấy theo múi giờ của họ. Ví dụ: một đơn đặt lúc 23:30 UTC có thể là “ngày mai” với người ở Tokyo. Quyết một quy tắc cho từng màn hình: hiển thị thời gian theo local người dùng cho sự kiện cá nhân, và dùng múi giờ doanh nghiệp cố định cho các trường hợp như khoảng giờ lấy hàng (và gắn nhãn rõ).
Tránh ghép câu bằng cách nối các đoạn đã dịch. Nó phá ngữ pháp vì thứ tự từ thay đổi theo ngôn ngữ. Thay vì:
"{count} " + t("items") + " " + t("in_cart")
hãy dùng một thông điệp với placeholder: "{count} items in your cart". Dịch viên có thể đổi thứ tự từ an toàn.
Ngôn ngữ phải viết từ phải sang trái (RTL)
RTL không chỉ là hướng văn bản. Dòng layout đảo, một số icon cần lật (như mũi tên quay lại), và nội dung hỗn hợp (Ả Rập + mã sản phẩm tiếng Anh) có thể hiển thị theo thứ tự bất ngờ. Test màn hình thực tế, không chỉ một nhãn, và đảm bảo component UI hỗ trợ thay đổi hướng.
Nội dung do người dùng tạo
Đừng dịch nội dung người dùng viết (tên, địa chỉ, ticket support, tin nhắn chat). Bạn có thể dịch nhãn xung quanh và định dạng meta (ngày, số), nhưng nội dung phải giữ nguyên. Nếu thêm dịch tự động sau này, làm rõ là tính năng có thể bật/tắt kèm toggle “original/translated”.
Ví dụ thực tế: app dựng bởi Koder.ai có thể hiển thị "{name} renewed on {date} for {amount}". Giữ thành một thông điệp, định dạng {date} và {amount} theo locale, và hiển thị theo múi giờ người xem. Mẫu này tránh nhiều nợ dịch.
Những quy tắc nhanh ngăn lỗi:
- Lưu timestamp bằng UTC, định dạng theo locale và múi giờ người xem.
- Dùng placeholder trong câu hoàn chỉnh, không ghép mảnh.
- Định dạng tiền tệ theo locale và mã tiền tệ chính xác.
- Test ít nhất một locale RTL trên màn hình thực tế.
- Không dịch nội dung do người dùng tạo theo mặc định.
Những sai lầm phổ biến tạo nợ dịch
Nợ dịch thường bắt đầu bằng “chỉ một chuỗi nhanh” và biến thành tuần dọn dẹp sau đó. Trong dự án xây bằng chat, chuyện này xảy ra nhanh hơn vì text UI được tạo trong component, form và thậm chí thông báo backend.
Vấn đề gây tốn sau này
Những lỗi tốn kém nhất là những thứ lan rộng khắp app và khó tìm.
- Ghi cứng copy trong component UI, kể cả placeholder, trạng thái rỗng và nhãn nút. Bạn không audit hay tái sử dụng được, và sửa nhỏ cần sửa code.
- Để backend validation và lỗi API trả câu tiếng Anh. UI sẽ hiện ngôn ngữ lẫn lộn, và bạn không thể ánh xạ lỗi sang thông điệp đã dịch.
- Dùng câu tiếng Anh đầy đủ làm khóa dịch. Ban đầu tiện, nhưng khi copy thay đổi thì khóa thay đổi, khóa cũ tồn dư và bạn mất bộ nhớ dịch.
- Sao chép khóa giữa web và mobile rồi chỉnh riêng. Dần dần, màn hình “cùng” sẽ drift và người dùng nhận thấy khác lời.
- Hoãn quy tắc plural cho tới khi thêm ngôn ngữ khác. Rồi bạn thấy hàng loạt bug “1 items” và ngữ pháp vụng mà không thể fix bằng if đơn giản.
Ví dụ nhanh (thực tế trông như thế nào)
Tưởng tượng web React và mobile Flutter cả hai hiển thị banner billing: “You have 1 free credit left”. Ai đó tinh chỉnh web thành “You have one credit remaining” và giữ khóa là nguyên câu. Mobile vẫn dùng khóa cũ. Giờ có hai khóa cho một khái niệm, và dịch viên thấy cả hai.
Mẫu tốt hơn là khóa ổn định (ví dụ billing.creditsRemaining) và plural bằng ICU để ngữ pháp đúng giữa các ngôn ngữ. Nếu dùng công cụ vibe-coding như Koder.ai, thêm quy tắc sớm: mọi văn bản hướng người dùng sinh trong chat phải vào file dịch, không nằm trong component hoặc lỗi server. Thói quen nhỏ này bảo vệ kiến trúc i18n khi dự án lớn lên.
Checklist nhanh, một ví dụ thực tế và bước tiếp theo
Khi i18n có vẻ lộn xộn, thường là vì các điều cơ bản chưa được ghi lại. Một checklist nhỏ và một ví dụ cụ thể giúp đội bạn (và bạn tương lai) tránh nợ dịch.
Đây là checklist nhanh cho mỗi màn hình mới:
- Khóa ổn định: một khóa cho một ý nghĩa, không cho một cách diễn đạt (ví dụ:
billing.invoice.paidStatus, không phảibilling.greenLabel). - Fallback: định rõ ngôn ngữ mặc định, và quyết chuyện gì khi thiếu khóa (hiển thị fallback, log, hoặc chặn phát hành).
- Quy tắc số nhiều: dùng thông điệp ICU cho đếm (0, 1, nhiều) thay vì mẹo ghép chuỗi.
- Định dạng: định dạng ngày, tiền và số theo locale (và tách tiền tệ khỏi ngôn ngữ).
- Kiểm tra RTL: test ít nhất một ngôn ngữ RTL sớm để phát hiện lỗi layout trước khi có 200 màn hình.
Ví dụ đơn giản: bạn ra mắt màn hình billing ở tiếng Anh, Tây Ban Nha và Nhật. UI có: “Invoice”, “Paid”, “Due in 3 days”, “1 payment method” / “2 payment methods”, và tổng như “$1,234.50”. Nếu bạn xây theo kiến trúc i18n cho ứng dụng chat-built, bạn định nghĩa khóa một lần (dùng chung web và mobile), và mỗi ngôn ngữ chỉ điền giá trị. “Due in {days} days” thành thông điệp ICU, và định dạng tiền dùng bộ định dạng theo locale, không dấu phẩy cứng.
Triển khai hỗ trợ ngôn ngữ theo từng tính năng, không làm một lần lớn:
- Bắt đầu với một flow có lượng truy cập cao (billing, onboarding hoặc checkout).
- Chuyển copy cứng trong UI vào file dịch và thay bằng khóa.
- Thêm thông điệp plural và định dạng cho cùng flow.
- Mở rộng sang feature tiếp theo khi số key thiếu gần bằng 0.
Ghi hai thứ để feature mới giữ nhất quán: quy tắc đặt tên khóa (kèm ví dụ) và “định nghĩa hoàn thành” cho chuỗi (không copy cứng, ICU cho plural, định dạng ngày/số, thêm vào catalog chung).
Bước tiếp theo: nếu bạn xây trên Koder.ai, dùng Planning Mode để định nghĩa màn hình và khóa trước khi sinh UI. Sau đó dùng snapshot và rollback để lặp an toàn trên copy và bản dịch giữa web và mobile mà không lo phát hành lỗi.
Câu hỏi thường gặp
Tôi nên đặt tên khóa dịch như thế nào?
Dùng các khóa ổn định, dựa trên ý nghĩa như billing.invoice.payNow, thay vì dùng chính câu tiếng Anh. Sau đó, bạn có thể thay đổi câu chữ mà không phải đổi tham chiếu mã hoặc tạo bản dịch trùng lặp.
Những văn bản nào cần đưa vào tệp dịch?
Hãy đưa mọi chuỗi hiển thị cho người dùng vào hệ thống dịch, bao gồm lỗi, trạng thái trống, email, thông báo và mẹo hướng dẫn ban đầu. Nhật ký nội bộ, ID phân tích và các trường cơ sở dữ liệu thường không cần dịch.
Làm sao để câu chữ trên web và di động nhất quán?
Dùng chung một danh mục khóa và thông điệp theo ngôn ngữ cho cả hai ứng dụng. React và Flutter có thể dùng các thư viện khác nhau, nhưng nên tham chiếu cùng tên khóa và quy tắc thông điệp.
Backend có nên trả về thông báo lỗi đã dịch không?
Hãy để backend trả về một mã lỗi ổn định kèm các giá trị an toàn, rồi để ứng dụng khách hiển thị thông điệp đã được bản địa hóa. Ví dụ, trả về auth.invalid_password thay vì một câu tiếng Anh.
Tôi nên xử lý số nhiều trong các ngôn ngữ khác nhau như thế nào?
Dùng thông điệp số nhiều theo kiểu ICU để mỗi ngôn ngữ áp dụng quy tắc ngữ pháp riêng. Đặt cả câu vào một thông điệp thay vì ghép một con số với một danh từ được dịch riêng.
Tệp dịch nên được đặt ở đâu?
Giữ ngôn ngữ nguồn, thường là tiếng Anh, làm nguồn chuẩn và xem xét các thay đổi cùng với mã. Nhóm thông điệp theo tính năng và dành các không gian tên dùng chung cho thao tác phổ biến, trường dữ liệu, điều hướng và lỗi.
Mỗi lần câu chữ thay đổi, tôi có cần một khóa mới không?
Giữ nguyên khóa hiện có nếu ý nghĩa không đổi, kể cả khi câu chữ thay đổi. Chỉ tạo khóa mới khi ý nghĩa hoặc ngữ cảnh thay đổi, rồi xóa khóa cũ sau khi xác nhận không còn gì dùng đến nó.
Tôi nên bản địa hóa ngày tháng, tiền tệ và múi giờ như thế nào?
Lưu dấu thời gian theo UTC, rồi định dạng ngày, giờ, số và tiền tệ theo ngôn ngữ và múi giờ của người xem. Dùng trình định dạng nhận biết ngôn ngữ thay vì mã hóa cứng dấu phẩy, ký hiệu tiền tệ hoặc mẫu ngày tháng.
Tôi nên kiểm tra những gì đối với các ngôn ngữ viết từ phải sang trái?
Thiết kế toàn bộ màn hình trong ngôn ngữ RTL, không chỉ các nhãn. Đảo chiều bố cục khi cần, phản chiếu các biểu tượng có hướng như mũi tên quay lại và kiểm tra văn bản lẫn giữa tiếng Ả Rập và tiếng Latinh, chẳng hạn mã sản phẩm.
Cách đơn giản nhất để thêm i18n vào một ứng dụng hiện có là gì?
Bắt đầu với một luồng được dùng nhiều như hướng dẫn ban đầu, thanh toán hóa đơn hoặc quy trình thanh toán. Chuyển các câu chữ được mã hóa cứng của luồng đó vào thông điệp dùng chung, thêm hỗ trợ số nhiều và định dạng, kiểm tra các bản dịch dài hơn, rồi lặp lại cho tính năng tiếp theo.