Unicode CJK Cho Lập Trình Viên
Chuyên mục: Lập trình × Ngôn ngữ
Xử lý ký tự CJK (Chinese-Japanese-Korean) trong lập trình đặt ra nhiều thách thức: từ encoding, surrogate pairs, đến normalization. Bài viết tổng hợp kiến thức cần thiết cho developer.
1. CJK Trong Unicode: Các Block Chính
Unicode phân bổ ký tự CJK vào nhiều block:
// Block chính
U+4E00–U+9FFF CJK Unified 20,992 chars
U+3400–U+4DBF Extension A 6,592 chars
U+20000–U+2A6DF Extension B 42,720 chars (surrogate pairs!)
U+2A700–U+2B73F Extension C 4,149 chars
U+2B740–U+2B81F Extension D 222 chars
U+2B820–U+2CEAF Extension E 5,762 chars
U+2CEB0–U+2EBEF Extension F 7,473 chars
U+30000–U+3134F Extension G 4,939 chars
U+31350–U+323AF Extension H 4,192 chars
2. Surrogate Pairs — Bẫy Cho JavaScript/Java
Ký tự Extension B trở đi (U+20000+) nằm ngoài BMP (Basic Multilingual Plane), cần 2 UTF-16 code units:
const char = '𠀀'; // U+20000 Extension B
console.log(char.length); // 2 (KHÔNG PHẢI 1!)
console.log([...char].length); // 1 (spread operator xử lý đúng)
console.log(char.codePointAt(0)); // 131072 = 0x20000
Luôn dùng codePointAt() thay vì charCodeAt(), và [...str] thay vì str.split('') khi xử lý CJK.
3. Nhận Dạng Ký Tự CJK
function isCJK(codePoint) {
return (
(codePoint >= 0x4E00 && codePoint <= 0x9FFF) || // CJK Unified
(codePoint >= 0x3400 && codePoint <= 0x4DBF) || // Ext A
(codePoint >= 0x20000 && codePoint <= 0x2A6DF) || // Ext B
(codePoint >= 0x2A700 && codePoint <= 0x2B73F) || // Ext C
(codePoint >= 0x2B740 && codePoint <= 0x2B81F) || // Ext D
(codePoint >= 0x2B820 && codePoint <= 0x2CEAF) || // Ext E
(codePoint >= 0x2CEB0 && codePoint <= 0x2EBEF) || // Ext F
(codePoint >= 0x30000 && codePoint <= 0x3134F) || // Ext G
(codePoint >= 0x31350 && codePoint <= 0x323AF) || // Ext H
(codePoint >= 0xF900 && codePoint <= 0xFAFF) // Compatibility
);
}
4. CJK Trong Database: Collation & Indexing
Vấn đề thường gặp khi lưu CJK trong PostgreSQL/MySQL:
- Collation: Dùng 'und-x-icu' hoặc 'zh-x-icu' thay vì 'C' để sắp xếp đúng thứ tự Hán tự
- Indexing: pg_trgm hoạt động tốt cho CJK search trong PostgreSQL
- Encoding: Luôn dùng UTF-8, kiểm tra connection encoding
- Full-text search: CJK cần tokenizer đặc biệt (jieba, mecab, ICU)
5. Giản Thể ↔ Phồn Thể Conversion
Chuyển đổi giản thể/phồn thể KHÔNG phải 1-1 mapping. Nhiều ký tự giản thể map đến nhiều ký tự phồn thể tùy context:
// 发 (giản) có thể là:
// 發 (phát - phát triển) hoặc 髮 (phát - tóc)
// Cần context để phân biệt!
// OpenCC library xử lý tốt vấn đề này