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

4.7 KiB

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:

./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:

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:
    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:
    <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.