Bộ 7 role chuyên biệt (triage → specialist → implementer → reviewer) cùng lớp dùng chung: guardrail, tri thức về repo, checklist, và contract đầu ra. Vì sao có: bug UI/UX được báo bằng lời kể triệu chứng, và người sửa hay bỏ qua ba thứ mà repo này rất dễ vi phạm — luật "không file nào ngoài theme/ được đặt tên một màu", trần LOC theo bánh cóc, và việc ui/ với presentation/ cùng tồn tại nên sửa nhầm file là "đã fix mà vẫn thấy lỗi". knowledge/qt_pitfalls.md chép lại 20 nguyên nhân gốc hay gặp của bug PySide6; examples/bad_fix.md có hai ca CÓ THẬT, gồm ca chính bản vá trong nhánh này từng mắc (compare_digest trên str ngoài ASCII) và lọt qua vòng review đầu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.9 KiB
i18n — luật chuỗi hiển thị
Nguồn: docstring i18n/__init__.py.
1. Ba ngôn ngữ, mặc định tiếng Việt
LANGUAGES = {"en": "English", "ja": "日本語", "vi": "Tiếng Việt"}
LANGUAGE_SHORT = {"en": "EN", "ja": "JP", "vi": "VN"} # switcher gọn ở top bar
DEFAULT_LANGUAGE = "vi"
tr(key, **kwargs) trả chuỗi theo ngôn ngữ hiện tại, fallback lần lượt:
ngôn ngữ hiện tại → en → chính cái key. Nghĩa là thiếu entry thì UI hiện ra
workspace.tab_folder chứ không crash — nếu người dùng chụp màn hình có chuỗi dạng
a.b_c thì đó chính là triệu chứng thiếu key.
.format(**kwargs) được áp dụng khi có placeholder: tr("composer.attachments", n=3).
2. Widget nào phải đăng ký callback
| Loại widget | Cách xử lý |
|---|---|
| Sống lâu — chrome cửa sổ chính, tab, sidebar, composer | Đăng ký on_language_changed(cb); cb áp lại tr() cho chính widget đó. Callback chạy ngay một lần và mỗi lần đổi ngôn ngữ |
| Tạm thời — Settings, Skills, Flow, Permission dialog | Dựng lại từ đầu mỗi lần mở, nên chỉ cần gọi tr() lúc construct, không đăng ký |
Quy ước đặt tên hàm callback trong repo: _retranslate() / _apply_i18n() — xem
ui/workspace_tab.py:484 trở đi làm mẫu chuẩn.
Bug điển hình: "Đổi ngôn ngữ nhưng nhãn X không đổi" → widget sống lâu mà quên đăng ký,
hoặc có đăng ký nhưng callback bỏ sót đúng nhãn đó. Không sửa bằng cách gọi tr() lại ở
chỗ khác — sửa trong callback.
3. File từ điển
i18n/ chia theo màn hình, không phải một file khổng lồ:
i18n/login_dialog.py i18n/sidebar.py i18n/composer.py
i18n/cowork_tab.py i18n/settings_dialog.py i18n/skills_dialog.py
i18n/libreoffice_view.py i18n/agents_admin_tab.py i18n/monitoring_overview.py
i18n/hint.py
Mỗi file export dict key -> {"en":..., "ja":..., "vi":...}, được i18n/__init__.py
import và gộp lại. Thêm key mới:
- Chọn đúng file theo màn hình (không nhét đại vào
login_dialog.pychỉ vì nó lớn nhất). - Điền đủ 3 ngôn ngữ. Thiếu
jalà lỗi hay gặp nhất và chỉ lộ ra khi khách Nhật dùng. - Đặt key theo
<màn>.<thành_phần>—workspace.tab_folder,app.nav.recents.
4. Rủi ro riêng của tiếng Nhật và tiếng Việt
| Rủi ro | Triệu chứng | Cách xử lý |
|---|---|---|
| Tiếng Nhật ngắn hơn, tiếng Việt dài hơn tiếng Anh | Nút vừa với EN, tràn với VI; label bị ... với JA |
Không setFixedWidth theo chuỗi tiếng Anh. Dùng sizeHint + minimumWidth, hoặc cho phép wrap |
| Dấu tiếng Việt bị cắt phần trên/dưới | Ắ, ộ mất dấu ở nhãn cao cố định |
Không đặt setFixedHeight cho label theo pixel; để layout tự tính |
| Font mặc định thiếu glyph Nhật | Ô vuông tofu □□□ trên máy chưa cài font |
Kiểm tra _FONT trong theme/palettes.py, khai báo fallback |
| Sắp xếp / so sánh chuỗi | Danh sách project sắp sai với tên có dấu | Dùng locale-aware sort, không sorted() thô |
| Chiều dài chuỗi tính bằng ký tự ≠ chiều rộng hiển thị | Elide sai với chữ Nhật | Đo bằng QFontMetrics.horizontalAdvance, không len() |
5. Checklist sửa bug i18n
- Key mới có đủ
en/ja/vi? - Đã thử đổi qua cả 3 ngôn ngữ trong lúc app đang chạy (không phải restart)?
- Widget sống lâu đã đăng ký
on_language_changed? - Không còn chuỗi hardcode nào trong bản vá?
- Layout còn đúng với chuỗi dài nhất trong 3 ngôn ngữ?
- Không dùng
len()để đo bề rộng chữ?