# HANDOFF REPORT — i18n Architecture & Cross-Platform Survey

**Agent**: `explorer_survey_i18n_1`  
**Milestone**: M0 — Survey & Architecture Mapping  
**Recipient**: `orchestrator_13`  
**Date**: 2026-10-02T01:50:00Z  
**Reference Report**: [survey_report.md](file:///c:/Projects/FreeExile/.agents/teamwork/explorer_survey_i18n_1/survey_report.md)

---

## 1. OBSERVATION

1. **Vị trí và triển khai i18n hiện tại**:
   - `client/webapp/js/data/i18n.js` (306 dòng): Chứa lớp `FreeExileI18nEngine` (dòng 118-255).
     - Lưu trữ ngôn ngữ chọn trong `localStorage.getItem('freeexile_locale')` / `localStorage.setItem('freeexile_locale', locale)` (dòng 126, 141).
     - Khi đổi locale, phát sự kiện DOM:
       ```javascript
       window.dispatchEvent(new CustomEvent('freeexile:localeChanged', {
         detail: { locale, dict: this.catalog[locale] || {} }
       }));
       ```
     - Chứa listener mảng callback nội bộ: `this.listeners = []`, `onLocaleChanged(callback)` (dòng 122, 159).
     - Nhồi nhét 116 dòng preload ảnh tài nguyên đồ họa (`ASSETS`, `assetList`) ngay đầu file (dòng 4-115).
   - `client/webapp/js/data/i18n_catalog.js` (634 dòng): Chứa từ điển `I18N_CATALOG` gồm đúng 143 key chuẩn hóa trên 9 ngôn ngữ (`vi`, `en`, `zh`, `ja`, `ko`, `th`, `de`, `ru`, `es`).
   - `client/src/i18n/LocalizationManager.ts` (102 dòng): Triển khai i18n bằng TypeScript cho iOS Metal client với fallback `Target -> EN -> VI -> Key` và `LocaleFontProfile` cho Metal text shaders, nhưng chưa có subscriber bus.

2. **Cách Settings Modal xử lý ngôn ngữ**:
   - Không tồn tại file độc lập `settings_modal.js`. Logic mở/đóng modal được viết trong `client/webapp/js/ui/feedback.js` (dòng 15-33) và template trong `client/webapp/templates/settings_modal.html` / `client/webapp/js/ui/templates/template_catalog_system.js`.
   - Trong `modal-settings` **hoàn toàn không có phần tử chọn ngôn ngữ**.
   - Phần tử chọn ngôn ngữ `#lang-select` nằm ở thanh header HUD chính (`client/webapp/index.html:85`) cạnh nút `#btn-open-settings`.

3. **Nguyên nhân cốt lõi Chat UI không phản hồi ngôn ngữ**:
   - Trong `client/webapp/js/ui/chat_ui.js` dòng 310:
     ```javascript
     document.getElementById('lang-select')?.addEventListener('change', (e) => updateLanguage(e.target.value));
     ```
   - Listener này chỉ phản hồi khi người dùng thao tác vật lý trên thẻ `<select id="lang-select">`. Khi ngôn ngữ được thay đổi qua code (`FreeExileI18n.setLocale('en')`), qua phím tắt hoặc từ native host, sự kiện DOM `change` không được kích hoạt, khiến `chat_ui.js` không hề biết ngôn ngữ đã đổi.
   - `chat_ui.js` không đăng ký `window.addEventListener('freeexile:localeChanged')` hay `FreeExileI18n.onLocaleChanged()`.
   - Trong `initChatUI()` (dòng 293-316), không hề gọi `updateLanguage(FreeExileI18n.getLocale())`. Do đó, khi tải trang với locale đã lưu trong `localStorage` là `'en'`, tab kênh và giao diện chat vẫn giữ nguyên tiếng Việt mặc định từ HTML.
   - Hàm `updateLanguage(lang)` (dòng 318-331) chỉ cập nhật tên 8 tab kênh (`.chat-channel-tab`) và ticker channel; hoàn toàn bỏ qua placeholder input (dòng 147: `Nhập tin nhắn...`), thông báo quyền hạn (dòng 124-129: `Chưa gia nhập Bang Hội...`), badge cooldown (dòng 156: `Khóa`, `Chờ ${sec}s`), nút gửi (HTML: `Gửi`), thông báo hệ thống (dòng 135: `Hệ Thống`), và toàn bộ tooltip vật phẩm (phẩm cấp, hệ ngũ hành, HMAC badge).
   - `chat_ui.js` duy trì riêng từ điển độc lập `CHANNEL_I18N` (dòng 17-29) tách biệt khỏi `I18N_CATALOG`.

4. **Trạng thái i18n trên các module UI khác**:
   - Chỉ có 5 module lắng nghe `freeexile:localeChanged`: `dialogue_npc.js`, `hideout_gates.js`, `story_quest_board.js`, `seasonal_reset.js`, `shop_megashop.js`.
   - Các module còn lại (`character_equipment.js`, `inventory_stash.js`, `agent_orb.js`, `minimap_hud.js`, `feedback.js`) đều hardcode tiếng Việt trong data structures và template rendering.
   - `template_manager.js` phát sự kiện `freeexile:templateMounted` và `freeexile:templatesMounted`, nhưng `i18n.js` không lắng nghe sự kiện này mà dùng listener `click` với `setTimeout(..., 15)` không tin cậy.

5. **Kết quả test hiện tại**:
   - `pytest tests/unit/test_webapp_localization_engine.py` -> 16 passed trong 0.29s.
   - `pytest tests/unit/test_webapp_chat_ui.py` -> 18 passed trong 1.40s.

---

## 2. LOGIC CHAIN

1. **Từ Quan sát 1 & 2**: Người dùng chọn ngôn ngữ qua `#lang-select` trên thanh HUD, gọi `applyLanguage(lang)` -> `FreeExileI18n.setLocale(lang)`. Lớp này lưu locale vào `localStorage['freeexile_locale']` và phát sự kiện `freeexile:localeChanged` trên `window`.
2. **Từ Quan sát 3**: `chat_ui.js` lại đi gắn sự kiện trực tiếp vào `select#lang-select` DOM `change` event thay vì lắng nghe sự kiện `freeexile:localeChanged` từ `window` hay gọi `FreeExileI18n.onLocaleChanged()`.
3. **Suy luận trực tiếp**: Do đó, mọi luồng đổi ngôn ngữ qua script, qua phím tắt, hoặc khi khởi tạo ứng dụng từ `localStorage` đều không kích hoạt DOM `change` event trên `<select>`, làm `chat_ui.js` hoàn toàn mất đồng bộ.
4. **Từ Quan sát 3 & 4**: Hàm `updateLanguage()` trong `chat_ui.js` chỉ đổi text của 8 thẻ tab và ticker, trong khi toàn bộ 10+ vị trí hiển thị khác (placeholder, permissions, system warning, badges, item tooltips) bị hardcoded tiếng Việt trong code, dẫn đến hiện tượng giao diện lai tạp nửa Anh nửa Việt.
5. **Từ Quan sát 1 & 4**: `i18n.js` thiếu cơ chế Pub/Sub subscriber chuẩn (không có `subscribe()` trả về unregister callback) và không lắng nghe `templateMounted` từ `template_manager.js`, làm các modal mount sau thời điểm load trang không tự động dịch các thẻ có thuộc tính `data-i18n`.

---

## 3. CAVEATS

1. **Giới hạn số dòng theo chuẩn FreeExile GEMINI.md**:
   - `chat_ui.js` hiện dài **342 dòng** (Soft Cap là 350 dòng). Bất kỳ việc mở rộng logic i18n nào vào file này phải tối ưu cực kỳ gọn gàng, loại bỏ từ điển thừa `CHANNEL_I18N` để tránh chạm ngưỡng Hard Cap (500 dòng).
   - `i18n_catalog.js` hiện dài **634 dòng** (Soft Cap là 700 dòng). Việc thêm key mới cho Chat UI (8 channel keys, placeholder, permission, cooldown) phải giữ cho file dưới 700 dòng.
2. **Khả năng tương thích ngược của Unit Test**:
   - `tests/unit/test_webapp_localization_engine.py` hiện assert số lượng key chính xác `len(master_keys) == 143`. Khi thêm key mới cho Chat UI vào `I18N_CATALOG`, test case này cần được cập nhật đồng bộ để kiểm tra parity trên con số key mới mà không gây fail test suite.
   - `tests/unit/test_webapp_chat_ui.py` hiện assert nhãn tiếng Việt trực tiếp khi khởi tạo test (`test_16`, `test_17` assert `"⚠ Chưa Xác Thực"`, `"✓ HMAC Xác Thực"`). Đảm bảo khi ngôn ngữ mặc định là `'vi'`, các giá trị này không đổi.

---

## 4. CONCLUSION

Hệ thống i18n của FreeExile đã có nền tảng từ điển 9 ngôn ngữ vững chắc, nhưng bị nghẽn ở **tầng phân phối sự kiện (Event Bus Layer)** và **tầng tiêu thụ sự kiện (UI Consumer Layer)**:
1. `chat_ui.js` bị cô lập do dùng DOM `change` listener thay vì Central Event Bus, thiếu khởi tạo ban đầu từ `localStorage`, và nhồi nhét chuỗi hardcoded tiếng Việt.
2. Cần triển khai kiến trúc **Reactive i18n Event Bus** hoàn chỉnh:
   - Nâng cấp `FreeExileI18n` với `subscribe(callback)` chuẩn Pub/Sub, lắng nghe `templateMounted` và đồng bộ `storage` event.
   - Di chuyển các key của Chat UI vào `i18n_catalog.js` (đủ 9 ngôn ngữ, tuân thủ microcopy <= 2 từ).
   - Refactor `chat_ui.js` loại bỏ `CHANNEL_I18N` cục bộ, đăng ký lắng nghe Event Bus, dịch 100% placeholder, cooldown, permission, tooltips.
   - Xây dựng công cụ kiểm tra tĩnh `check_i18n_hygiene.py` và test case chống hồi quy.

---

## 5. VERIFICATION METHOD

1. **Kiểm tra trạng thái unit test hiện tại**:
   ```bash
   pytest tests/unit/test_webapp_localization_engine.py
   pytest tests/unit/test_webapp_chat_ui.py
   ```
2. **Kiểm tra giới hạn số dòng code và chuẩn vệ sinh**:
   ```bash
   python tools/lint/check_code_and_doc_hygiene.py --strict
   ```
3. **Kiểm tra vị trí phần tử và event trong codebase**:
   - `client/webapp/index.html`: Dòng 85 (`#lang-select`), dòng 151-164 (`#chat-dock`).
   - `client/webapp/js/ui/chat_ui.js`: Dòng 310 (`addEventListener('change', ...)`), dòng 318-331 (`updateLanguage`).
   - `client/webapp/js/data/i18n.js`: Dòng 118-255 (`FreeExileI18nEngine`).
   - `client/webapp/templates/settings_modal.html`: Kiểm chứng không có thẻ chọn ngôn ngữ trong settings.
