Repository navigation
The UI is English by accident, not by design: introduce translations with German as the second language #49
Description
Summary
MatteLoop has no internationalisation at all. There is not a single tr(),
QCoreApplication.translate() or QTranslator in src/ — every user-visible
string is a bare literal at its point of use. The interface is English only
because most of the literals happen to be English.
That is not a theoretical problem. #47 had to translate six German strings that
had been sitting in the execution-provider feature: a form label, an accessible
name, a state-invalidation reason shown to the user, a provider suffix, and a
CPU-fallback notice. None of them were caught by a test, a linter or a review.
They were found because a screenshot of the running window was rendered for the
README and the German was simply visible in the picture. Without a translation
layer there is no mechanism that can catch the next one.
Goal
English as the primary language, German as the second. A user should be able to
run the application in either, and a contributor should not be able to add a
user-visible string without it being obvious that it needs a translation.
Recommended approach: Qt's own translation system
Use tr() / QCoreApplication.translate() with .ts sources and compiled
.qm catalogues, loaded through QTranslator at startup.
It costs no new dependency. PySide6 already ships the whole toolchain — the
installed package contains lupdate, lrelease and Linguist.app. That
matters in this repository, where every third-party component has to be tracked
through THIRD_PARTY_NOTICES.md and the packaging pipeline.
It is also the system the framework expects: context-aware lookups, plural
forms, and disambiguation are built in, and .qm files load with no
application code beyond installing the translator.
Alternative considered: the standard library's gettext, with Babel for
extraction. Rejected — it duplicates machinery Qt already provides, needs a new
dependency for extraction, and integrates worse with Qt widgets.
Scope
- Wrap user-visible strings. Every literal that reaches a user: labels,
buttons, dialog text, tooltips, accessible names, status and error copy.
Strings that are not user-visible — log lines, error codes, telemetry
reasons — stay untranslated and it should be clear which is which. Note that
core/parameters.pycurrently passes a user-visible label as a plain
invalidation reason string, so the boundary is not currently drawn anywhere. - Toolchain. A documented command to regenerate
.tsfiles with
lupdate, and to compile.qmwithlrelease. Both checked into the
repository so a build does not depend on a translator being run. - Packaging. The
.qmcatalogues have to ship inside the native bundles.
The packaging tests assert on what lands in a build, so this needs to be
wired in deliberately rather than discovered later. - Language selection. A preference, defaulting to the system language when
a catalogue exists for it and to English otherwise. - A German catalogue that is actually complete, not a stub.
Number and date formatting needs a decision at the same time
Independently of the strings, the application formats numbers with QLocale's
system default while the interface is English. On a German system the inspector
reads 6,042 s, 0,000 MiB and 0,00 % inside otherwise English labels. The
README screenshots pin the locale to English to avoid showing this.
Decide the policy as part of this work: either formatting follows the selected
interface language, or it follows the system locale independently. Either is
defensible; the current state is neither, it is an accident.
CLAUDE.md has to change with it
A translation layer decays the moment a contributor adds an English string and
stops. CLAUDE.md is the authority document for how work is done here, and it
needs a rule stating that a change adding or altering user-visible copy also
updates the translation catalogues, and that a string reaching a user without
going through the translation function is an incomplete change — the same way a
fix: without a Trigger: line is.
This should land in the same pull request as the mechanism, not afterwards. A
rule pointing at machinery that does not exist yet is worse than no rule.
Acceptance criteria
- No user-visible string in
src/reaches a widget without going through the
translation function. - Running with the language set to German shows a fully German interface, with
no English fallback in normal use. - Running with the language set to English shows a fully English interface.
.tsand.qmfiles are checked in, and the command to regenerate them is
documented.- The
.qmcatalogues are present inside a native build. CLAUDE.mdcarries the rule described above.- The number-formatting policy is decided, documented and implemented
consistently. uv run ruff check . && uv run mypy src && QT_QPA_PLATFORM=offscreen uv run pytest -qstill green.
Not in scope
- Any language beyond English and German.
- Translating
docs/, the README, orTHIRD_PARTY_NOTICES.md. - Changing what any string says, beyond what a correct translation requires.
Activity
smb commentedon Sep 5, 2026
The language switch needs a surface, and the application currently has none —
no menu bar, no toolbar, no
QActionanywhere insrc/matteloop/ui/. Filed as#50, which introduces the menu bar and a Preferences dialog placed natively
through
PreferencesRole.This issue depends on #50 for the language selector in acceptance criterion 4.
The number-formatting policy decided here also belongs in that dialog if it
becomes a user-facing choice rather than a fixed rule.
Refs #50.