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>
72 lines
3.9 KiB
Markdown
72 lines
3.9 KiB
Markdown
# 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
|
|
|
|
```python
|
|
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ồ:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. Chọn đúng file theo màn hình (không nhét đại vào `login_dialog.py` chỉ vì nó lớn nhất).
|
|
2. Điền **đủ 3 ngôn ngữ**. Thiếu `ja` là lỗi hay gặp nhất và chỉ lộ ra khi khách Nhật dùng.
|
|
3. Đặ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ữ?
|