Skip to content

i18n: make the GUI translatable and add a Simplified Chinese interface - #8

Merged
wleeaf merged 3 commits into
wleeaf:mainfrom
h465855hgg:i18n-zh-cn
Nov 14, 2026
Merged

wleeaf merged 3 commits into
wleeaf:mainfrom
h465855hgg:i18n-zh-cn

Conversation

@h465855hgg

@h465855hgg h465855hgg commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The GUI is now fully translatable and ships with a Simplified Chinese (zh_CN)
interface. English remains the source language, so a build without a catalogue
behaves exactly as before; with nothing chosen in Settings the interface follows
the desktop locale.

  • Every user-visible string in gui/ goes through tr() in member functions, or
    QCoreApplication::translate() in free functions and classes without
    Q_OBJECT, with QT_TRANSLATE_NOOP for static display arrays. That covers the
    menu bar and menus, actions, windows and dialogs, labels, buttons, check
    boxes, table headers, tooltips, placeholders, file dialog filters, message
    boxes and the empty-state hints.
  • gui/i18n.cpp finds and installs the translators. The interface language comes
    from the new Settings > Language page (follow the system, English, 简体中文,
    stored as ui/language) and otherwise from the desktop locale (LANG). When
    the language changes, Settings offers to restart Cheat Engine right away; the
    main window closes through the normal path first (save prompt,
    releaseTargetUsers), and only then is the replacement process started with
    the same command line. Unsaved table state is the user's call.
  • translations/cheatengine_zh_CN.ts (1644 messages in 53 contexts) is compiled
    by lrelease and embedded in the binaries through qt_add_translations, so
    the .deb and the AppImage carry it. A .qm file dropped beside the
    executable or under translations/ overrides the built-in copy, which lets a
    translator try a catalogue without rebuilding; a privileged install (the
    .deb gives cap_sys_ptrace) is a secure-exec process and uses only the
    built-in catalogue, so those strings cannot be replaced from a writable path.
  • The desktop file and the AppStream metadata carry Chinese text.
  • docs/I18N.md documents the workflow for translators, how to add a language,
    the secure-exec caveat, and that CE_LANG is a hook of the screenshot mode
    only.
  • Building without Qt6 LinguistTools keeps working (English only, with a status
    line). CI installs qt6-tools-dev and qt6-l10n-tools, and tests the
    catalogues with the new tools/check-translations.py, which fails on an
    unfinished message or a dropped %1/%n placeholder. The same check runs
    from tools/ci-check.sh.
  • gui_lifecycle_smoke --screenshots can render another language with
    CE_LANG=zh_CN; the default English run is unchanged.

Deliberately not translated: cescan terminal output, messages produced by the
Qt-free cecore library, the tutorial games (their steps quote the exact code
and instructions in the game), Lua and agent tool names, and text stored in cheat
tables or scripts.

Verification

  • Rebased onto current main; the new scanWriteErrorText() warning, its two
    call sites and the "Scan results truncated" title are translatable as well.
  • Release build against Qt 6.4.2: cheatengine, gui_lifecycle_smoke and the
    GUI smoke targets compile.
  • build/gui_lifecycle_smoke passes every stage.
  • python3 tools/check-translations.py: all 1644 messages finished, placeholders
    preserved.
  • Startup checks: with no ui/language set the interface follows LANG
    (zh_CN shows Chinese, en_US English); ui/language=zh_CN shows the Chinese
    interface regardless of LANG; a binary copied away from the generated .qm
    still logs catalogue: built-in.
  • build/gui_lifecycle_smoke --screenshots DIR renders about 200 screenshots in
    both themes; the Chinese set was rendered with CE_LANG=zh_CN and inspected
    for clipped or overlapping text.
  • tools/ci-check.sh --config covers both the Qt and the no-GUI configuration.

@wleeaf wleeaf left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is careful and thorough work. I built it with Qt 6.4: the catalogue compiles, tools/check-translations.py passes, all 11 GUI smoke tests pass in English, and text that is read back as data (the string encoding combo, the comparisons by tr() text) is handled correctly. A few things before merging:

  1. Restart order (gui/settingsdialog.cpp, restartApplication). The new instance is started before this one closes. MainWindow::closeEvent can be cancelled (maybeSaveTable() offers Cancel) and it also runs releaseTargetUsers(), which removes breakpoints and injected code from the attached process. So if the user cancels the save prompt, two instances run; and the restart should not skip that release step either. Please close the main window first and start the new process only once the close was accepted (for example from QCoreApplication::aboutToQuit, or after mainWindow->close() returned true).

  2. Catalogue lookup in a privileged binary (gui/i18n.cpp). The .deb gives cheatengine cap_sys_ptrace, so it runs as a secure-exec process (see core/secure_exec.hpp). Loading a .qm from the current directory (and the translations/ paths relative to it) lets whoever starts it replace every interface string, including confirmation dialogs. This predates your PR, but please skip the working-directory and user-writable lookups when ce::secureExecution() is true and use the built-in catalogue then.

  3. Rebase. main moved: CHANGELOG.md, docs/PROGRESS.md and gui/mainwindow.cpp now conflict. In mainwindow.cpp the scan-failure warning became scanWriteErrorText(), which builds its text from parts; those strings will need tr() too.

Small note: gui_lifecycle_smoke with CE_LANG=zh_CN fails because it checks English status-bar text (First scan complete.) and opens windows by English action text. That is fine for screenshot runs, but please mention in docs/I18N.md that only the screenshot mode supports CE_LANG.

@h465855hgg

Copy link
Copy Markdown
Contributor Author

All three points and the small note are addressed in the new head (18b38f7, rebased onto current main):

  1. Restart order. SettingsDialog now only emits restartRequested(). MainWindow::restartAfterLanguageChange() closes the window through the normal path first, so closeEvent runs maybeSaveTable() and releaseTargetUsers(), and it calls the new gui/i18n.cpp helper launchRestart() only when close() returned true. A cancelled save prompt leaves one instance and no restart; the release step is never skipped.
  2. Catalogue lookup in a privileged binary. gui/i18n.cpp skips the executable-directory, translations/ and working-directory lookups when ce::secureExecution() is true, and uses the built-in catalogue then. The startup log line appends (secure-exec: built-in only).
  3. Rebase. Rebased onto current main. The CHANGELOG entry is folded into the existing Unreleased list, the PROGRESS entry keeps its date, and the new scanWriteErrorText() strings plus the "Scan results truncated" title go through tr()/QCoreApplication::translate("MainWindow", ...). Catalogue: 1644 messages, 0 unfinished.
  4. docs/I18N.md now states that CE_LANG is a screenshot-mode hook only, that the other smokes compare English text, and that a privileged install ignores .qm overrides.

Re-ran locally with Qt 6.4.2: Release build, gui_lifecycle_smoke OK, tools/check-translations.py OK, lupdate/lrelease 1644/1644 finished, tools/ci-check.sh --config passed, and startup checks as above (LANG=zh_CN follows the system, ui/language=zh_CN forces Chinese, copied binary loads the built-in catalogue).

@h465855hgg

Copy link
Copy Markdown
Contributor Author

The red run on 6f75f94 (run 43227316475) is from the pre-rebase head, so it does not contain main's latest fixes. Its two failures are not from the translation change:

  • arm64-compatibility / arm64-syscalls: build/arm64-vm/vmlinuz-virt: FAILED, the Alpine VM kernel fetch. Main fixed this in 0da4bb3 ("Fetch the ARM VM kernels from Alpine's versioned 3.23.6 netboot directory"), and the rebased head includes it.
  • wine-compatibility / wow64-wine: Failed to allocate memory for newmem: Cannot allocate memory during the injection check. Current main fails the same way (run 43527721621 on 9ed3553, job wow64-wine), so it is environmental and unrelated to this PR.

ubuntu-build and sanitizers passed on that run. The rebased head (18b38f7) is waiting for workflow approval; once approved, the ARM syscall job should pick up the kernel fix.

@wleeaf

wleeaf commented Nov 14, 2026

Copy link
Copy Markdown
Owner

CI note: the two failing jobs on this PR (arm64-syscalls and wow64-wine) are not caused by your change. The ARM job's kernel download changed upstream (fixed on main in 0da4bb3), and wow64-wine fails because Fedora moved to Wine 11.20 (tracked in #9). Rebasing onto main picks up both fixes.

@h465855hgg

Copy link
Copy Markdown
Contributor Author

Thanks for confirming. The branch is already rebased onto current main: head 18b38f7 is 0 commits behind upstream/main, so it includes 0da4bb3 (ARM kernels) and 9ed3553. wow64-wine stays red for the Wine 11.20 reason tracked in #9; nothing in this change touches the injection path.

The build run for this head (43535225690) is still action_required, waiting for a workflow approval. Once approved, arm64-syscalls should pass and only wow64-wine (#9) should remain red.

@h465855hgg

Copy link
Copy Markdown
Contributor Author

Let me state the current status plainly, since the last two notes were mostly CI bookkeeping:

1. The code side of the review is done. Head 18b38f7 already has the restart-order fix, the secure-exec catalogue fix, the rebase onto current main (0 commits behind, includes 0da4bb3 and 9ed3553), and the tr() work for the new scanWriteErrorText() strings and the "Scan results truncated" title. docs/I18N.md has the CE_LANG / privileged-install notes. Nothing else is pending on my side unless the re-review finds something.

2. The only thing blocking CI is the workflow approval. As a first-time contributor my runs are held at action_required; run 43535225690 is the one for this head. Could you approve it from the PR's Checks tab ("Approve and run") so the Ubuntu build and the GUI smokes run against the final code? I cannot approve it myself. After that I'd ask for the re-review.

3. A clarification about the Wine job: rebasing fixes arm64-syscalls, but wow64-wine is not a rebase issue, #9 is still open, so that gate will stay red after the rebase too (it is the Wine 11.20 VirtualAllocEx gap, unrelated to this change). If you would rather not merge with a red gate, tell me and I'll wait for #9; otherwise the PR is ready for re-review once the run has executed.

English remains the source language, so a build without a catalogue behaves as
before. Every user-visible GUI string now goes through tr() in member functions,
or QCoreApplication::translate() in free functions and classes without
Q_OBJECT, with QT_TRANSLATE_NOOP for static display arrays: the menu bar and
menus, dialogs, labels, buttons, table headers, tooltips, placeholders, file
dialog filters and status texts are covered.

gui/i18n.cpp loads the catalogue from the translations directory beside the
executable or from the built-in resource. The interface language comes from the
new Settings > Language page (ui/language: follow the system, English,
Simplified Chinese) and falls back to the desktop locale; a change applies on
the next start. translations/cheatengine_zh_CN.ts is compiled with lrelease and
embedded through qt_add_translations when the Qt6 Linguist tools are available,
so the .deb and the AppImage carry Chinese, and a .qm dropped next to the binary
still overrides the built-in copy. Building without the Linguist tools keeps
working, in English, with a status line.

The desktop file and the AppStream metadata carry Chinese text. CI installs
qt6-tools-dev and qt6-l10n-tools, and tests the catalogues with the new
tools/check-translations.py, which fails on an unfinished message or a dropped
%1/%n placeholder; tools/ci-check.sh runs the same check. docs/I18N.md documents
the workflow for translators and how to add a language. The screenshot review
renders another interface language with CE_LANG=zh_CN.

Not translated on purpose: cescan terminal output, messages from the Qt-free
cecore library, the tutorial games (their steps quote the code in the game),
Lua and agent tool names, and text stored in cheat tables.
Every window builds its texts in its constructor, so a new language needs a new
start. Instead of leaving that to the user, applying a different language in
Settings now asks to restart, and relaunches the same command line (the AppImage
file itself when running from a bundled build). Choosing Later keeps the
behaviour of applying the change on the next manual start.
- Close the main window before starting the replacement instance. The Settings
  dialog now only emits restartRequested; MainWindow closes itself through the
  normal closeEvent path (save prompt, releaseTargetUsers) and calls
  launchRestart() only when that close was accepted, so a cancelled save prompt
  cannot leave two instances behind and the release step is never skipped.
- A binary holding a file capability runs as a secure-exec process: gui/i18n.cpp
  now loads only the built-in catalogue then, so a .qm beside the executable or
  in the working directory can no longer replace the interface strings of its
  confirmation dialogs. The startup log line says when this applies.
- Rebase onto main. The new scanWriteErrorText() strings and the scan-warning
  title are translatable too, the CHANGELOG entry is folded into the existing
  Unreleased list, and the PROGRESS log entry keeps its date.
- Document that CE_LANG is a screenshot-mode hook and that a privileged install
  ignores .qm overrides.
@h465855hgg

Copy link
Copy Markdown
Contributor Author

Rebased onto 1b73cb9. Only documentation conflicted:

  • CHANGELOG.md: the i18n bullet now sits after the Wine 11.20 known-limitation bullet, both in the existing Unreleased list.
  • docs/PROGRESS.md: kept newest first, so Wine 11.20, Next scans, Simplified Chinese, GUI polish.
  • No code conflicts. The new Auto Assembler strings from 1b73cb9 are in the Qt-free core and stay out of the GUI catalogue, as documented.

Head is 337d099 (3 commits, 0 behind main, 56 files, mergeable again). Re-ran the Release build and gui_lifecycle_smoke on it: OK. The build run for this head will again wait for workflow approval.

@h465855hgg
h465855hgg requested a review from wleeaf November 14, 2026 02:38

@wleeaf wleeaf left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, all three points are addressed: the restart now goes through the normal close (save prompt and target release) and starts the new instance only when the close was accepted, a secure-exec binary loads only the built-in catalogue, and the new scan-error strings are translated in the matching context. Verified on the rebased head: the translation check, a fresh Release build with translations enabled, the full regression suite, all 11 GUI smoke tests and the CI configuration check pass.

@wleeaf
wleeaf merged commit aa14047 into wleeaf:main Nov 14, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants