Xử Lý Văn Bản Tiếng Nhật Trong Phát Triển Phần Mềm
Khi phát triển sản phẩm cho thị trường Nhật Bản, có một sự thật mà bất kỳ Developer hay QC nào cũng cần chấp nhận sớm: những giả định quen thuộc khi làm việc với text tiếng Anh không còn đúng nữa. Một ký tự không nhất thiết là một byte. Một input field không thể chỉ giới hạn bằng [A-Za-z0-9]. Một chuỗi "giống hệt nhau khi nhìn bằng mắt" có thể là hai giá trị hoàn toàn khác nhau trong database. Và một filter onKeyDown tưởng chừng vô hại có thể phá vỡ hoàn toàn trải nghiệm gõ tiếng Nhật của người dùng.
Bài viết này là 1 hướng dẫn dùng làm tài liệu tham khảo nội bộ cho Backend Developer, Frontend Developer, Fullstack Developer, QA/QC, Tech Lead/PL và Software Architect. Chúng ta sẽ đi xuyên suốt toàn bộ lifecycle của một chuỗi text tiếng Nhật, từ lúc người dùng gõ phím đến lúc dữ liệu nằm trong database, được search, được export ra Excel:
Keyboard / IME → User Input → Browser → Frontend → API / HTTP
↓
CSV / Excel / File ← Export / Import ← Search / Sort / Compare ← Database ← Backend
Sau khi đọc xong, bạn sẽ có khả năng: phân biệt các loại ký tự tiếng Nhật và biết khi nào nên cho phép từng loại trong từng input field; hiểu cơ chế IME để không viết code phá vỡ trải nghiệm gõ tiếng Nhật; thiết kế đúng chiến lược allow / reject / normalize / convert cho từng loại dữ liệu; nhận diện và debug được các lỗi encoding, 文字化け (mojibake), duplicate data do collation; và xây dựng được một bộ test case đủ rộng để QC không bỏ sót Japanese text edge case trước khi lên production.
Tại Sao Xử Lý Text Tiếng Nhật Lại Khó?
Phần lớn framework, thư viện validation, và thói quen coding phổ biến được thiết kế với giả định ngầm rằng text là ASCII, single-byte, không có khái niệm "chế độ nhập nhiều bước". Tiếng Nhật phá vỡ toàn bộ ba giả định đó:
- Không phải single-byte: một ký tự Hiragana/Katakana/Kanji chiếm 3 bytes trong UTF-8, khiến
strlen()(đếm byte) vàmb_strlen()(đếm ký tự) cho ra kết quả khác nhau hoàn toàn. - Không nhập một lần: người dùng gõ tiếng Nhật qua IME (Input Method Editor) — một chuỗi ký tự tạm thời (romaji hoặc kana thô) được "biên soạn" (compose) qua nhiều bước trước khi trở thành ký tự cuối cùng. Bắt sự kiện
keydownđể validate/filter input sẽ chặn nhầm quá trình này. - Không phải một representation duy nhất: cùng một khái niệm ("chữ A", "số 3", "katakana ア") có thể có nhiều dạng Unicode khác nhau — full-width, half-width, precomposed, decomposed — nhìn giống hệt nhau bằng mắt thường nhưng là các code point khác nhau, dẫn đến search/so sánh/database bị sai một cách âm thầm, không hề ném ra exception.
Chính vì lỗi loại này thường không crash, không log exception, mà chỉ âm thầm làm sai dữ liệu (hai category khác nhau bị gộp làm một, một khách hàng tìm không ra sản phẩm dù đã nhập đúng tên), nên nó nguy hiểm hơn nhiều so với một lỗi runtime thông thường — và đây chính là lý do bài viết này tồn tại.
Nền Tảng: Các Loại Ký Tự Tiếng Nhật
Hiragana
あ い う え お
か き く け こ
Hiragana là bộ chữ cái ngữ âm (phonetic) cơ bản nhất, dùng để viết từ thuần Nhật (native word), trợ từ ngữ pháp (particle: は, が, を, に), đuôi biến hình động từ/tính từ (okurigana), và toàn bộ văn bản khi người viết không nhớ Kanji. Đây là bộ ký tự bắt buộc phải hỗ trợ trong hầu như mọi input field liên quan đến tên riêng, địa chỉ, ghi chú tự do. Với các field như "furigana" (cách đọc của tên) — một trường cực kỳ phổ biến trong form Nhật Bản (ví dụ 名前: 田中太郎, フリガナ: たなか たろう) — Hiragana thường là loại ký tự duy nhất được cho phép, và validation pattern nên dùng Unicode script property thay vì liệt kê range thủ công:
/^\p{Script=Hiragana}+$/u
Katakana
ア イ ウ エ オ
カ キ ク ケ コ
Katakana dùng để viết từ mượn nước ngoài (パソコン = "personal computer"), tên nước ngoài, thuật ngữ khoa học, và — quan trọng với business system — trường "フリガナ" (furigana) trong rất nhiều hệ thống Nhật Bản yêu cầu Katakana chứ không phải Hiragana (ví dụ trường tên khách hàng trên hoá đơn ngân hàng, chứng từ hành chính).
Có một phân biệt quan trọng thường bị bỏ sót: Full-width Katakana và Half-width Katakana:
ア (full-width, U+30A2)
ア (half-width, U+FF71)
Hai ký tự này là hai Unicode code point hoàn toàn khác nhau, dù đọc giống nhau (đều là "a"). Half-width Katakana có nguồn gốc từ chuẩn cũ JIS X 0201 (dùng trong các hệ thống terminal/POS/máy fax đời cũ, và vẫn còn tồn tại trong dữ liệu hoá đơn convenience store hay legacy mainframe). Một hệ thống hiện đại hầu như luôn nên chuẩn hoá về Full-width Katakana khi lưu trữ, nhưng — như sẽ phân tích ở phần Full-width/Half-width — quyết định "convert" hay "giữ nguyên" phải dựa trên business requirement, không phải mặc định.
Kanji
東京
大阪
商品
会社
Kanji là chữ Hán được Nhật hoá, mang nghĩa (ideographic), không thể gõ trực tiếp bằng bàn phím mà phải qua IME chuyển đổi từ romaji/kana. Đây là lý do một input field tên công ty, tên sản phẩm, địa chỉ không bao giờ được validate bằng regex kiểu [A-Za-z]+ — điều này sẽ chặn hoàn toàn người dùng Nhật nhập đúng tên thật của họ. Một vấn đề nâng cao hơn là Kanji variant (異体字): cùng một chữ có thể có nhiều dạng Unicode hơi khác nhau về hình dạng (ví dụ 高 và 髙, 辺 và 邊/辺), thường gặp trong tên riêng của người lớn tuổi hoặc địa danh cổ. Với hệ thống thông thường, không cần xử lý đặc biệt; nhưng với hệ thống liên quan đến tên pháp lý (hộ tịch, hợp đồng), cần cân nhắc không tự động "sửa" Kanji variant vì có thể làm sai tên thật của khách hàng.
Romaji / Latin Characters
Tokyo
ABC
Product01
test@example.com
Người Nhật vẫn dùng Latin characters thường xuyên: mã sản phẩm (SKU), email, tên thương hiệu quốc tế, URL, mã nhân viên. Một input field như "Product Code" hay "Style No" thường chỉ nên cho phép Latin + số, không nên cho phép Kanji/Kana — ngược lại hoàn toàn so với field "Product Name".
Arabic Numbers Và Full-width Numbers
12345 (half-width, ASCII)
12345 (full-width)
Full-width number xuất hiện rất thường xuyên khi người dùng gõ tiếng Nhật, vì IME ở chế độ nhập toàn bộ full-width sẽ tự động biến số ASCII gõ vào thành full-width. Với các trường như số điện thoại, mã bưu điện, số lượng, giá tiền — hệ thống hầu như luôn cần convert về half-width number trước khi xử lý logic hoặc lưu database, vì các phép toán, so sánh số, và tích hợp API bên ngoài đều mong đợi ASCII digit.
Yêu cầu đăng nhập
Vui lòng đăng nhập để truy cập nội dung này
Additional Resources
Course Guide
Comprehensive PDF guide with examples
GitHub Repository
Example code for all lessons
Discussion
Have a question about this lesson? Post it here and get answers from instructors and peers.
