* 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
127 lines
4.7 KiB
Markdown
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.
|