Translations¶
Every user-visible string a plugin shows must be translatable, with catalogs for
all fifteen supported languages — en, pl, de, fr, es, pt_PT,
pt_BR, nl, sv, nb, da, fi, it, cs, sk — before the plugin is
considered done. This is the same bar as tests.
Catalogs¶
Each plugin that has any string carries one .ts catalog per language under its
Translations/ folder:
Wire them into the build via mte_add_plugin:
mte_add_plugin(NAME MyPlugin
SOURCES Src/MyPlugin.cpp
TRANSLATIONS
Translations/MyPlugin_en.ts
Translations/MyPlugin_pl.ts
Translations/MyPlugin_de.ts
Translations/MyPlugin_fr.ts
)
The build runs lrelease to stage .qm files; TranslationManager loads them
by catalog name at runtime (plugin catalogs under
Plugins/Translations/<Plugin>/).
Marking strings for translation¶
- Prefer
QObject::tr(...)(context = the class) for normal UI strings. - The
tr_()helper wrapsQCoreApplication::translate("<Context>", s)for strings outside aQObject.
tr_() is invisible to lupdate
lupdate cannot resolve the context inside tr_(), so those catalogs are
maintained by hand. Keep the <source> text byte-identical to the
runtime string, or the translation silently won't apply.
Following a language change while a page is open¶
The user can change the interface language in Preferences ▸ General without
restarting. Qt then sends QEvent::LanguageChange to every widget — but a
widget tree whose strings were set once, in the code that built it, keeps the
old language until the dialog is closed and reopened.
PluginApi/Qt/Retranslator.h handles that without writing every string twice.
It records how each string was produced and replays the recording when the
language changes:
#include <PluginApi/Qt/Retranslator.h>
QWidget* MyPage::createWidget(QWidget* parent)
{
auto* page = new QWidget(parent);
auto& rt = MTE::plugin::Retranslator::installOn(page);
m_watch = new QCheckBox(page);
rt.add([this] { m_watch->setText(tr_("Watch open files")); });
m_size = new QSpinBox(page);
rt.row(form, m_size, [] { return tr_("Font size:"); }); // QFormLayout caption
return page;
}
add()runs its lambda immediately, so the call site reads exactly like the "set the text" line it replaces, and each string still appears once in your source.row()does the same for aQFormLayoutcaption, which is otherwise unreachable onceaddRow()has consumed it.installOn()is idempotent per widget; the recorder is a child of that widget and dies with it. If your page can outlive a widget you captured, hold it in aQPointer.
Like PluginApi/Qt/ISettingsPage.h, this is an opt-in Qt header: include it only
from code that already builds QWidgets (it needs Qt6::Widgets). The Qt-free
part of PluginApi is unaffected.
What is not translated¶
Two things stay in the language their author wrote them in, by design:
- Your plugin's
nameanddescriptionfromplugin.json, as shown on Preferences ▸ Plugins. The manifest is read before the plugin is loaded — that is what lets the page list plugins that are disabled or built against another API version — so there is no catalog to look them up in at that point. - The labels of a declarative (Python) settings page or dock panel. They are data your worker sends to the host, not strings the host can resolve against a catalog.
Everything a plugin registers at runtime — command titles, menu entries, dock titles, settings-page captions — is translated in the ordinary way described above.
Three of those strings are looked up by the host, not by your code, in the
shared PluginMenus context: every segment of a menuPath, every command
title, and the command category. Ship all three as <source> entries in
the PluginMenus context of your own catalogs. The category is what the user
reads in the Category column of Preferences ▸ Shortcuts; when it is
missing from the catalog, the column shows the raw English word next to the
translated core rows. A category that is also a menu title (Search,
Encoding) shares one entry: keep the & mnemonic in it for the menu, the
host strips it for the column.
Definition of done¶
A plugin's localisation is done only when all fifteen .ts files exist, are
wired, and lrelease reports 0 unfinished strings.