Skip to content

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 check

Homework

  • [ ] 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 →

Powered by CodyMaster × VitePress