Files
kcc/TRANSLATION.md
T
Ryuga6 a053b3ea9f Add multi-language i18n support (Simplified Chinese, Japanese, Korean) (#1453)
* Add multi-language i18n support (English, Chinese, Japanese, Korean)

- Add i18n.py module for translation management via QTranslator
- Add Simplified Chinese (zh_CN), Japanese (ja_JP), and Korean (ko_KR) translations
- Integrate translation files into Qt resource system (KCC.qrc / KCC_rc.py)
- Add language switcher button in the top button bar of main window
- Language setting persists via QSettings
- Dynamic text updates work correctly after language switch
- Support %1, %2, %3 placeholders via tr_arg() helper function
- KCCGUI and KCCGUI_MetaEditor now inherit QObject for tr() support

* Complete Japanese/Korean/Chinese translations + fix language switch issues

- Fill 61 missing Japanese translations (tooltips, 3-state checkboxes, etc.)
- Fill 61 missing Korean translations (tooltips, 3-state checkboxes, etc.)
- Fill 6 missing Chinese translations
- Fix language menu: use QActionGroup for exclusive (single-select) checkmarks
- Fix jobList initial tips: refresh translations on language switch by
  updating label text directly (preserves message order and user-added entries)
- Add PROJECT_I18N_README.md developer documentation

* Replace emoji language icon with PNG globe icon

Fixes PR review feedback from axu2: the emoji character doesn't render on Windows 7 (no emoji font support). Replaced with a PNG globe icon from Wikimedia Commons (CC license), loaded via Qt resource system for consistent cross-platform rendering.

* Fix RuntimeError when switching language: re-fetch labels from jobList

Previously, QLabel references were saved directly, but the underlying

C++ objects could be deleted by Qt, causing RuntimeError on access.

Now we save only source strings and re-fetch labels from jobList

items when updating, with try/except as a safety net.

* Use text label for language button instead of PNG icon

Replace the globe PNG icon with a plain text button (' 文 A ')

so its color automatically follows the system light/dark theme,

per review feedback. Removes icons/language.png and its QRC entry.

* Finalize i18n: retranslate jobList messages and translate CLI output

- Add source= to 16 GUI addMessage calls so already-shown messages retranslate on language switch
- Translate comic2ebook.py conversion/CLI messages via QCoreApplication.translate('KCCGUI', ...)
- Clear 29 unfinished WorkerThread translations (context unified to KCCGUI)
- Guard apply_language when QApplication is not yet created
- Trim dead imports and redundant code

* Avoid duplicated source strings: translate inside addMessage

Replace the self.tr/translate + source= pattern (same string written twice) with
QT_TRANSLATE_NOOP('KCCGUI', ...) at call sites; addMessage now translates internally
and stores the source text for retranslation on language switch.

- Drop the source parameter from addMessage; simplify signal to 3 args
- Include comic2ebook.py in lupdate (20 CLI/conversion messages)

* Add translation maintenance workflow

Add gen_translation_files.sh/.bat, TRANSLATION.md, and a README pointer; remove the superseded PROJECT_I18N_README.md.

* chmod +x
2026-10-13 11:10:33 -07:00

127 lines
4.7 KiB
Markdown

# Translating KCC
KCC ships its UI and CLI strings in English by default, with optional
translations loaded at runtime. This file documents how to keep those
translations up to date when strings are added or changed.
## Why translation files need maintenance
Translatable strings are wrapped in `self.tr(...)` or
`QT_TRANSLATE_NOOP('KCCGUI', ...)` in the source. The English text lives in
the code, but the actual translations live in `translations/*.ts`. If a new
string is added to the code and the `.ts` files are not regenerated, the new
string has no translation and silently falls back to English.
The `.ts` files are the human-editable source of truth. The `.qm` files are
the compiled, runtime-format translations. Both are committed to the
repository.
## Prerequisites
- Python 3 with PySide6 installed in the active environment, which provides
the `pyside6-lupdate`, `pyside6-lrelease`, and `pyside6-rcc` tools.
- Run all commands from the repository root.
## Updating translations after adding a string
The whole flow is wrapped in one script:
```sh
./gen_translation_files.sh # macOS / Linux
gen_translation_files.bat # Windows
```
It runs three steps in order:
1. `pyside6-lupdate` scans the source files and updates every
`translations/*.ts` in place.
2. `pyside6-lrelease` compiles each `.ts` into its `.qm` runtime file.
3. `pyside6-rcc` re-bundles the `.qm` files into `KCC_rc.py`, because the
`.qm` files are loaded from the Qt resource system, not from disk.
After running the script, open the `.ts` files and fill in the newly added
`<translation>` entries. `lupdate` extracts strings but does not translate
them.
### Manual equivalent
If you prefer to run the steps yourself, the script is equivalent to:
```sh
for ts in translations/*.ts; do
pyside6-lupdate \
kindlecomicconverter/KCC_gui.py \
kindlecomicconverter/comic2ebook.py \
gui/KCC.ui \
gui/MetaEditor.ui \
-ts "$ts"
done
for ts in translations/*.ts; do
pyside6-lrelease "$ts" -qm "${ts%.ts}.qm"
done
pyside6-rcc gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
```
The source file list is significant. `comic2ebook.py` contains the CLI-side
messages; omitting it drops 20 strings (250 -> 230 extracted). The `.ui`
files contribute the `mainWindow` and `editorDialog` contexts.
`pyside6-project` is intentionally not used. Its `lupdate` scans the whole
project, which would pick up both `gui/KCC.ui` and the generated
`kindlecomicconverter/KCC_ui.py` and extract the `mainWindow` context twice.
## Generated files and what to commit
| File | Generated by | Commit? |
|------|--------------|---------|
| `translations/*.ts` | edited by hand (updated by `lupdate`) | yes |
| `translations/*.qm` | `pyside6-lrelease` | yes |
| `kindlecomicconverter/KCC_rc.py` | `pyside6-rcc` | yes |
The `.qm` files are also embedded into `KCC_rc.py` via `gui/KCC.qrc`; both
the standalone `.qm` and the resource module are kept in sync by the script.
## Adding a new language
1. Generate the new source file:
```sh
pyside6-lupdate \
kindlecomicconverter/KCC_gui.py \
kindlecomicconverter/comic2ebook.py \
gui/KCC.ui \
gui/MetaEditor.ui \
-ts translations/kcc_xx_XX.ts
```
2. Fill in the `<translation>` entries.
3. Register the language in `kindlecomicconverter/i18n.py` in the
`LANGUAGES` dict.
4. Add the `.qm` to `gui/KCC.qrc` under the `/i18n` prefix:
```xml
<file alias="kcc_xx_XX.qm">../translations/kcc_xx_XX.qm</file>
```
5. Run `gen_translation_files` to compile and re-bundle the new language.
## Verifying the result
- Run the script twice: the second run should produce no further changes
(`git diff` stays empty), confirming the flow is idempotent.
- Launch KCC and switch languages via the language button; verify the UI,
the metadata editor, and the conversion progress messages all switch.
## Common problems
- **`pyside6-lupdate: command not found`** — PySide6 is not installed or the
virtual environment is not active. Install with `pip install pyside6`.
- **Hundreds of `<location>` lines changed after running `lupdate`** — the
`-ts` target was written outside `translations/`. Location paths are
relative to the `.ts` file, so always update the files in place.
- **`dropping duplicate messages` warnings from `lrelease`** — a `.ts` file
has the same `(context, source)` entry twice. Regenerate it with `lupdate`.
- **Some strings stay English after switching languages** — the `.qm` was not
re-bundled into `KCC_rc.py`. Run `gen_translation_files` (step 3).
- **`type="vanished"` entries accumulate** — this is normal: `lupdate` marks
entries whose source string is no longer found instead of deleting them.
`lrelease` skips them, so they do not affect the runtime translations.