Skip to content

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:

Translations/
  MyPlugin_en.ts
  MyPlugin_pl.ts
  MyPlugin_de.ts
  MyPlugin_fr.ts

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 wraps QCoreApplication::translate("<Context>", s) for strings outside a QObject.

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 a QFormLayout caption, which is otherwise unreachable once addRow() 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 a QPointer.

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 name and description from plugin.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.