Chương 15: i18n — Sản Phẩm Cho Thế Giới

"Minh: 'Sản phẩm cần hỗ trợ tiếng Anh để mở rộng. Nhưng đừng break tiếng Việt.' Hà chạy i18n sync test: '3 key bị thiếu ở file en.json!'"
🎯 Mục tiêu
- Implement i18n (Vietnamese + English)
- Safe i18n workflow (cm-safe-i18n)
- 🐛 Bug #9: i18n key mismatch
- Translation sync testing
Phần 1: i18n Architecture (15 phút)
public/static/i18n/
├── vi.json ← Primary language (Vietnamese)
└── en.json ← Secondary (English)json
// vi.json
{
"app.title": "TeamFlow",
"nav.projects": "Dự Án",
"nav.tasks": "Công Việc",
"nav.dashboard": "Bảng Điều Khiển",
"task.status.todo": "Cần Làm",
"task.status.in_progress": "Đang Làm",
"task.status.review": "Đang Review",
"task.status.done": "Hoàn Thành",
"task.priority.low": "Thấp",
"task.priority.medium": "Trung Bình",
"task.priority.high": "Cao",
"task.priority.urgent": "Khẩn Cấp",
"auth.login": "Đăng Nhập",
"auth.register": "Đăng Ký",
"auth.logout": "Đăng Xuất"
}json
// en.json — 🐛 BUG #9: Missing keys!
{
"app.title": "TeamFlow",
"nav.projects": "Projects",
"nav.tasks": "Tasks",
"task.status.todo": "To Do",
"task.status.in_progress": "In Progress",
"task.status.review": "Review",
"task.status.done": "Done",
"auth.login": "Login",
"auth.register": "Sign Up"
// MISSING: nav.dashboard, task.priority.*, auth.logout
}Phần 2: i18n Engine (20 phút)
javascript
// public/static/js/i18n.js
class I18n {
constructor() {
this.locale = localStorage.getItem('locale') || 'vi'
this.messages = {}
}
async load(locale) {
const res = await fetch(`/static/i18n/${locale}.json`)
this.messages = await res.json()
this.locale = locale
localStorage.setItem('locale', locale)
this.apply()
}
t(key, fallback = key) {
return this.messages[key] || fallback
}
apply() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.dataset.i18n
el.textContent = this.t(key)
})
}
}
const i18n = new I18n()html
<!-- Usage in HTML -->
<button data-i18n="auth.login">Đăng Nhập</button>
<select id="lang-switch">
<option value="vi">🇻🇳 Tiếng Việt</option>
<option value="en">🇬🇧 English</option>
</select>Phần 3: Bug #9 — Key Mismatch (20 phút)
Lab: i18n Sync Test
bash
antigravity "@cm-safe-i18n Audit i18n files for TeamFlow:
1. Compare vi.json and en.json keys
2. Find missing keys in each file
3. Find extra keys in each file
4. Verify all HTML data-i18n attributes have matching keys
5. Generate fix patches"Test Layer 4
typescript
// test/layer4-i18n.test.ts
import vi from '../public/static/i18n/vi.json'
import en from '../public/static/i18n/en.json'
describe('i18n sync', () => {
const viKeys = Object.keys(vi).sort()
const enKeys = Object.keys(en).sort()
it('should have matching keys', () => {
expect(viKeys).toEqual(enKeys)
})
it('should not have empty values', () => {
Object.entries(vi).forEach(([key, value]) => {
expect(value, `vi.${key} is empty`).not.toBe('')
})
Object.entries(en).forEach(([key, value]) => {
expect(value, `en.${key} is empty`).not.toBe('')
})
})
})Phần 4: Safe i18n Workflow (10 phút)
bash
# ALWAYS: Add key to ALL language files at same time
# NEVER: Add key to one file and forget the other
# cm-safe-i18n enforces:
# 1. Extract hardcoded strings → t() calls
# 2. Batch process per language
# 3. Audit gate: key sync check
# 4. HTML integrity checkHomework
- [ ] Both vi.json and en.json synced
- [ ] Language switcher working
- [ ] i18n test passing
- [ ] All UI text uses data-i18n
Chương tiếp: Security Audit — Trước Khi Mở Cửa →