Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là QA của một ứng dụng gọi xe đang chuẩn bị mở rộng từ Việt Nam sang Lào và Campuchia. App chạy ngon lành ở phiên bản tiếng Việt: nút bấm vừa vặn, giá tiền hiển thị đúng, ngày giờ chuẩn. Nhưng ngay ngày đầu bật tiếng Anh cho khách du lịch, hàng loạt bug ập đến: nút "Confirmation" dài gấp đôi tiếng Việt nên tràn ra ngoài màn hình, giá "1.000.000₫" bỗng hiển thị thành "1,000,000₫" khiến kế toán bối rối, và màn hình chọn ngày lại theo định dạng MM/DD/YYYY khiến khách đặt nhầm chuyến.
Đây chính là địa hạt của Localization (l10n) và Internationalization (i18n) — hai khái niệm mà rất nhiều đội QA Việt Nam bỏ qua cho đến khi sản phẩm "vấp ngã" ở thị trường mới. Trong bài này, tôi sẽ giúp bạn hiểu bản chất, biết cách test một cách có hệ thống, và tránh những cái bẫy kinh điển mà tôi từng chứng kiến nhiều đội mắc phải. Với thị trường Việt Nam — nơi tiếng Việt có dấu, tiền tệ VND với số 0 dài, và nhu cầu phục vụ cả người nước ngoài lẫn cross-border — đây là kỹ năng bạn sẽ dùng gần như mọi dự án.
Khái niệm cốt lõi
i18n và l10n khác nhau thế nào
Hai từ này hay bị dùng lẫn lộn, nhưng phân biệt được chúng sẽ giúp bạn báo bug đúng người, đúng tầng.
Internationalization (i18n) — viết tắt vì giữa chữ "i" và "n" có 18 ký tự — là công việc của lập trình viên: thiết kế app sao cho có khả năng thích ứng với nhiều ngôn ngữ và vùng miền mà không phải viết lại code. Ví dụ: tách toàn bộ chuỗi text ra file resource (strings.xml bên Android, Localizable.strings bên iOS) thay vì hardcode trong code; dùng API định dạng ngày/giờ/số theo Locale thay vì tự nối chuỗi; thiết kế layout co giãn được để chứa text dài ngắn khác nhau.
Localization (l10n) — 10 ký tự giữa "l" và "n" — là công việc áp dụng i18n cho một thị trường cụ thể: dịch chuỗi sang tiếng Lào, chỉnh định dạng tiền tệ sang Kip, thuê người bản địa review văn phong. Nói ngắn gọn: i18n là làm cho app "dịch được", l10n là "dịch cụ thể" cho từng nơi.
Với QA, ranh giới này rất quan trọng. Nếu app hardcode chuỗi "Đăng nhập" thẳng trong code, đó là bug i18n (lỗi kiến trúc) — báo cho dev sửa cách viết code. Nếu file dịch tiếng Anh dịch "Đăng xuất" thành "Log ount" sai chính tả, đó là bug l10n (lỗi nội dung) — báo cho translator hoặc content team.
Locale — trái tim của mọi thứ
Locale không chỉ là ngôn ngữ, mà là bộ ba: ngôn ngữ + vùng lãnh thổ + tùy chọn định dạng. Ví dụ vi-VN (tiếng Việt, Việt Nam) khác en-US (tiếng Anh, Mỹ) và cũng khác en-GB (tiếng Anh, Anh). Cùng là tiếng Anh nhưng en-US dùng MM/DD/YYYY còn en-GB dùng DD/MM/YYYY; dấu phân cách hàng nghìn cũng khác.
Locale chi phối rất nhiều thứ bạn phải kiểm: định dạng số (1.000,50 ở nhiều nước châu Âu vs 1,000.50 ở Mỹ), tiền tệ, ngày giờ, thứ tự sắp xếp chữ cái (collation — tiếng Việt sắp "Ă" sau "A" chứ không phải cuối bảng), múi giờ, và cả hướng chữ.
Thị trường ngôn ngữ mục tiêu cho app Việt
Với các app Việt Nam, danh sách ngôn ngữ mục tiêu thường là:
- Tiếng Việt — ngôn ngữ chính. Thách thức lớn nhất: dấu thanh và dấu phụ (diacritics). Tiếng Việt có tổ hợp ký tự Unicode phức tạp, dễ bị lỗi hiển thị hoặc lỗi khi search/sort.
- Tiếng Anh — phục vụ người nước ngoài, expat, khách du lịch, và cả người Việt trẻ thích dùng tiếng Anh. Đây là ngôn ngữ "test đối chiếu" phổ biến nhất vì text tiếng Anh thường dài hơn tiếng Việt, làm lộ ra lỗi tràn layout.
- Lào / Khmer / Trung — khi app mở rộng cross-border sang Đông Nam Á hoặc phục vụ cộng đồng người Hoa. Tiếng Khmer và Lào có hệ chữ viết riêng, chiều cao dòng khác, dễ bị cắt chữ (clipping). Tiếng Trung dùng ký tự CJK, cần font hỗ trợ và có thể ngắn hơn nhiều so với tiếng Việt.
Pseudo-localization — vũ khí bí mật của QA
Pseudo-localization là kỹ thuật thay text thật bằng text "giả lập" phóng đại: thêm dấu phụ vào ký tự (Đăng nhập → [!!! Đăng nhập ~~~]), kéo dài chuỗi thêm 30-40%, bọc bằng ký tự đánh dấu đầu-cuối. Mục đích: phát hiện ngay các bug i18n trước cả khi có bản dịch thật — chuỗi bị hardcode sẽ hiện nguyên tiếng gốc không bị biến đổi, còn layout tràn sẽ lộ ra vì text bị kéo dài. Đây là cách test cực kỳ hiệu quả mà nhiều đội chưa biết tận dụng.
Tình huống thực tế
Tình huống 1 — Cái nút "tràn ly" của app giao đồ ăn
Một startup giao đồ ăn ở TP.HCM (gọi là "FoodNow") ra mắt bản tiếng Anh để phục vụ khu vực quận 1 nhiều người nước ngoài. Ở tiếng Việt, nút thanh toán ghi "Đặt hàng" — 8 ký tự, vừa khít. Khi chuyển sang tiếng Anh, chuỗi thành "Place Order Now" — dài gấp đôi. Nút được thiết kế width cố định 120dp nên chữ bị cắt thành "Place Ord..." trên các máy màn hình nhỏ như iPhone SE.
Tệ hơn, ở màn hình xác nhận, dòng "Tổng cộng: 250.000₫" sang tiếng Anh thành "Total amount payable: 250,000₫" — bị xuống 2 dòng và đè lên nút bên dưới. QA phát hiện muộn vì họ chỉ test tiếng Việt, còn bản tiếng Anh "để dev tự kiểm".
Bài học: Không bao giờ giả định layout đúng ở ngôn ngữ này thì đúng ở ngôn ngữ khác. Text tiếng Anh, tiếng Đức thường dài hơn tiếng Việt; ngược lại tiếng Trung ngắn hơn. Luôn test layout ở mọi locale trên mọi kích thước màn hình nhỏ nhất trong ma trận thiết bị. Nếu FoodNow dùng pseudo-localization ngay từ sprint đầu, họ đã bắt được lỗi này trước khi có bản dịch.
Tình huống 2 — Dấu phẩy triệu đô của ví điện tử
Một ví điện tử (gọi là "PayViet") mở tính năng chuyển tiền quốc tế, hỗ trợ giao diện tiếng Anh cho người dùng nhận kiều hối. Ở locale vi-VN, số tiền "1.500.000" dùng dấu chấm làm phân cách hàng nghìn. Nhưng khi app chuyển sang en-US, thư viện định dạng tự đổi dấu chấm thành dấu phẩy: "1,500,000". Vấn đề là màn hình nhập tiền lại không đổi theo locale — nó vẫn parse dấu chấm là phân cách. Người dùng ở giao diện tiếng Anh gõ "1.5" (ý là một phẩy năm, kiểu Mỹ) nhưng app hiểu thành "một nghìn năm trăm". Kết quả: có giao dịch lệch giá trị.
QA của PayViet ban đầu bỏ sót vì họ test input và output tách rời nhau, và luôn test bằng máy đặt locale vi-VN. Chỉ khi một tester đổi cả hệ điều hành sang en-US mới lộ ra sự bất nhất giữa format hiển thị và format nhập liệu.
Bài học: Định dạng số và tiền tệ phải nhất quán giữa input và output theo đúng locale đang chọn. QA phải test cả hai chiều: app hiển thị đúng chưa, và app hiểu đúng thứ người dùng nhập chưa. Đặc biệt với tiền — sai một dấu phân cách là sai tiền thật.
Tình huống 3 — Chữ Khmer bị "chặt đầu" khi mở sang Campuchia
Một app học tiếng Anh của Việt Nam mở rộng sang Phnom Penh, bổ sung giao diện tiếng Khmer. Chữ Khmer có các dấu phụ xếp chồng phía trên và dưới ký tự gốc, nên chiều cao một dòng cao hơn tiếng Latin đáng kể. App dùng TextView với lineHeight cố định tính theo tiếng Việt, nên phần dấu phụ phía trên của chữ Khmer bị cắt cụt — người Campuchia đọc thành nghĩa khác. Ngoài ra, một số máy Android đời cũ không có sẵn font Khmer nên hiển thị toàn ô vuông "tofu" (□□□).
Bài học: Mỗi hệ chữ viết có đặc điểm chiều cao dòng, font và render riêng. Khi mở sang ngôn ngữ có hệ chữ khác (Khmer, Lào, Trung, Ả Rập), phải test riêng về clipping, font fallback (máy thiếu font thì hiển thị gì), và cả kiểm tra trên thiết bị đời cũ phổ biến ở thị trường đó — chứ không chỉ trên máy flagship của đội dev.
Hướng dẫn từng bước
Đây là quy trình test l10n/i18n có hệ thống bạn có thể áp dụng ngay:
Bước 1 — Lập ma trận locale × thiết bị. Liệt kê tất cả locale app hỗ trợ (vi-VN, en-US, lo-LA, km-KH, zh-CN…) nhân với các kích thước màn hình đại diện (nhỏ nhất như iPhone SE / Android 5", trung bình, lớn) và các phiên bản OS phổ biến ở thị trường đích. Đây là bản đồ test của bạn.
Bước 2 — Kiểm tra i18n bằng pseudo-localization. Trước khi có bản dịch thật, bật chế độ pseudo-locale (Android có sẵn en-XA và ar-XB trong Developer Options → "Force screen layout / pseudolocales"; iOS bật qua scheme Xcode với "Show non-localized strings"). Đi qua toàn bộ màn hình, tìm: chuỗi không bị biến đổi (= hardcode), chuỗi bị cắt (= layout không co giãn), và text bị nối chuỗi sai ngữ pháp.
Bước 3 — Test chuyển đổi ngôn ngữ runtime. Đổi ngôn ngữ trong lúc app đang chạy (nếu app hỗ trợ), rồi kiểm tra: màn hình có cập nhật ngay không, hay phải khởi động lại; dữ liệu đang nhập dở có bị mất không; và các màn đã mở sẵn có được refresh không.
Bước 4 — Kiểm tra định dạng theo locale. Với mỗi locale, xác nhận: số (dấu phân cách hàng nghìn, dấu thập phân), tiền tệ (ký hiệu, vị trí trước/sau số), ngày/giờ (thứ tự ngày-tháng-năm, định dạng 12h/24h), và thứ tự sắp xếp danh sách. Test cả input lẫn output như tình huống PayViet.
Bước 5 — Kiểm tra plural và giới tính. Nhiều ngôn ngữ có quy tắc số nhiều khác nhau. Kiểm tra chuỗi kiểu "1 item" vs "2 items" xem có dùng đúng cơ chế plural resource không, thử với 0, 1, 2, nhiều để bắt lỗi ghép chuỗi kiểu "1 mục(s)".
Bước 6 — Kiểm tra font fallback và ký tự đặc biệt. Test trên máy thiếu font (nhất là Khmer, Lào, CJK) xem có hiện "tofu" không; kiểm tra dấu tiếng Việt hiển thị đủ dấu thanh, dấu mũ.
Bước 7 — Kiểm tra nội dung l10n (dịch thuật). Sau khi có bản dịch thật, đối chiếu văn phong, tránh dịch máy vô nghĩa, kiểm tra các placeholder (%s, {name}) không bị dịch nhầm hoặc mất.
Lỗi thường gặp & mẹo
Lỗi ghép chuỗi (string concatenation). Dev hay viết "Xin chào " + name + ", bạn có " + count + " tin nhắn". Khi dịch, trật tự từ ở ngôn ngữ khác có thể đảo ngược, và việc ghép này khiến translator không thể sắp lại. Đúng ra phải dùng placeholder: "Xin chào %1$s, bạn có %2$d tin nhắn". Khi thấy chuỗi ghép, hãy báo bug i18n.
Hardcode text trong code. Chuỗi viết thẳng trong Java/Kotlin/Swift thay vì file resource sẽ không bao giờ được dịch. Pseudo-localization là cách nhanh nhất phát hiện: chuỗi nào không biến đổi là chuỗi bị hardcode.
Chỉ test một locale. Cái bẫy phổ biến nhất của QA Việt Nam là chỉ chạy máy ở vi-VN. Hãy chủ động đổi locale hệ điều hành sang từng ngôn ngữ mục tiêu, đừng tin rằng "đổi trong app là đủ".
Bỏ qua giá trị biên. Text dài nhất (tên sản phẩm dài, địa chỉ dài), số lớn nhất (999.999.999₫), số 0, số âm — đây là nơi layout và định dạng dễ vỡ nhất.
Quên RTL nếu có ngôn ngữ Ả Rập. Nếu app từng có kế hoạch hỗ trợ tiếng Ả Rập, toàn bộ layout phải lật ngược (right-to-left). Kể cả chưa hỗ trợ, mẹo hay là bật pseudo-locale RTL để kiểm tra sớm.
Mẹo vàng: Luôn giữ một "checklist đối chiếu tiếng Anh". Vì text tiếng Anh thường dài hơn tiếng Việt, chỉ cần app đẹp ở tiếng Anh thì gần như chắc chắn đẹp ở tiếng Việt về mặt không gian layout. Ngược lại, nếu chỉ test tiếng Việt (text ngắn) thì bạn sẽ mù trước lỗi tràn.
Bài tập thực hành
- Lập ma trận test: Chọn một app bất kỳ trên máy bạn (ví dụ app ngân hàng), giả định nó hỗ trợ 4 locale:
vi-VN,en-US,lo-LA,zh-CN. Vẽ bảng ma trận locale × 3 kích thước màn hình × 2 phiên bản OS. Đây là scope test tối thiểu.
- Săn bug hardcode: Đổi ngôn ngữ hệ điều hành trên điện thoại của bạn sang tiếng Anh, mở một app Việt bất kỳ, và ghi lại tất cả chuỗi vẫn còn tiếng Việt — đó là các chuỗi bị hardcode hoặc thiếu bản dịch. Chụp màn hình làm bằng chứng.
- Test định dạng số: Tìm một màn hình nhập số tiền trong một app ví điện tử. Đổi locale sang
en-USrồi thử nhập "1.5" và "1,500". Quan sát app hiểu con số đó là bao nhiêu. Viết một bug report giả định nếu phát hiện bất nhất giữa input và output.
- Viết test case pseudo-localization: Soạn 5 test case cụ thể để phát hiện lỗi i18n bằng pseudo-locale, mỗi test case gồm: bước thực hiện, kết quả mong đợi, và loại bug nó nhằm bắt.
Tóm tắt
Localization (l10n) và Internationalization (i18n) là hai mặt của việc đưa app tới nhiều thị trường: i18n là làm cho app "dịch được" (việc của dev, kiến trúc), l10n là "dịch cụ thể" cho từng nơi (nội dung, văn phong). Locale — bộ ba ngôn ngữ + vùng + định dạng — chi phối số, tiền tệ, ngày giờ, sắp xếp và font.
Với app Việt Nam, ngôn ngữ mục tiêu điển hình là tiếng Việt (cẩn thận dấu thanh), tiếng Anh (text dài hơn, dễ tràn layout, là ngôn ngữ đối chiếu tốt), và đôi khi Lào/Khmer/Trung khi mở rộng cross-border (hệ chữ khác, cần test clipping và font fallback). Ba tình huống — nút tràn ly, dấu phẩy triệu đô, và chữ Khmer bị chặt đầu — cho thấy hậu quả thật khi bỏ qua l10n.
Quy trình test có hệ thống: lập ma trận locale × thiết bị, dùng pseudo-localization để bắt lỗi i18n sớm, test chuyển ngôn ngữ runtime, kiểm định dạng cả input lẫn output, xử lý plural, font fallback và cuối cùng là chất lượng dịch thuật. Nhớ hai cạm bẫy chết người của QA Việt: chỉ test một locale, và chỉ test tiếng Việt (text ngắn nên không lộ lỗi tràn). Nắm chắc bài này, bạn đã có một kỹ năng mà rất nhiều QA còn thiếu — và là chìa khóa để sản phẩm Việt vươn ra khu vực.