From 4793b5952a1d67f01f00d9563957b98661e4b6e0 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 18 Oct 2023 15:28:33 +0200 Subject: [PATCH] Do translation setup before command-line option parsing Now the messages during early start-up can be localized. Fixes: #11142 --- src/gui/application.cpp | 115 ++------------------------------------ src/gui/application.h | 6 +- src/gui/main.cpp | 121 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 117 deletions(-) diff --git a/src/gui/application.cpp b/src/gui/application.cpp index fd9eb1179f2..a16007f3781 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -45,10 +45,8 @@ #include #include #include -#include #include #include -#include #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) #include @@ -70,13 +68,12 @@ ownCloudGui *Application::gui() const Application *Application::_instance = nullptr; -Application::Application(Platform *platform, bool debugMode) +Application::Application(Platform *platform, const QString &displayLanguage, bool debugMode) : _debugMode(debugMode) + , _displayLanguage(displayLanguage) { platform->migrate(); - setupTranslations(); - qCInfo(lcApplication) << "Plugin search paths:" << qApp->libraryPaths(); // Check vfs plugins @@ -246,110 +243,6 @@ bool Application::debugMode() return _debugMode; } -QString substLang(const QString &lang) -{ - // Map the more appropriate script codes - // to country codes as used by Qt and - // transifex translation conventions. - - // Simplified Chinese - if (lang == QLatin1String("zh_Hans")) - return QStringLiteral("zh_CN"); - // Traditional Chinese - if (lang == QLatin1String("zh_Hant")) - return QStringLiteral("zh_TW"); - return lang; -} - -void Application::setupTranslations() -{ - const auto trPath = Translations::translationsDirectoryPath(); - qCDebug(lcApplication) << "Translations directory path:" << trPath; - - QStringList uiLanguages = QLocale::system().uiLanguages(); - qCDebug(lcApplication) << "UI languages:" << uiLanguages; - - // the user can also set a locale in the settings, so we need to load the config file - const ConfigFile cfg; - - // we need to track the enforced language separately, since we need to distinguish between locale-provided - // and user-enforced one below - const QString enforcedLocale = cfg.uiLanguage(); - qCDebug(lcApplication) << "Enforced language:" << enforcedLocale; - - // note that user-enforced language are prioritized over the theme enforced one - // to make testing easier. - if (!enforcedLocale.isEmpty()) { - uiLanguages.prepend(enforcedLocale); - } - - QTranslator *translator = new QTranslator(this); - QTranslator *qtTranslator = new QTranslator(this); - QTranslator *qtkeychainTranslator = new QTranslator(this); - - for (QString lang : qAsConst(uiLanguages)) { - lang.replace(QLatin1Char('-'), QLatin1Char('_')); // work around QTBUG-25973 - lang = substLang(lang); - const QString trFile = Translations::translationsFilePrefix() + lang; - if (translator->load(trFile, trPath) || lang.startsWith(QLatin1String("en"))) { - // Permissive approach: Qt and keychain translations - // may be missing, but Qt translations must be there in order - // for us to accept the language. Otherwise, we try with the next. - // "en" is an exception as it is the default language and may not - // have a translation file provided. - qCInfo(lcApplication) << "Using" << lang << "translation"; - _displayLanguage = lang; - - const QString qtTrPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); - qCDebug(lcApplication) << "qtTrPath:" << qtTrPath; - const QString qtTrFile = QLatin1String("qt_") + lang; - qCDebug(lcApplication) << "qtTrFile:" << qtTrFile; - const QString qtBaseTrFile = QLatin1String("qtbase_") + lang; - qCDebug(lcApplication) << "qtBaseTrFile:" << qtBaseTrFile; - - if (!qtTranslator->load(qtTrFile, qtTrPath)) { - if (!qtTranslator->load(qtTrFile, trPath)) { - if (!qtTranslator->load(qtBaseTrFile, qtTrPath)) { - if (!qtTranslator->load(qtBaseTrFile, trPath)) { - qCCritical(lcApplication) << "Could not load Qt translations"; - } - } - } - } - - const QString qtkeychainTrFile = QLatin1String("qtkeychain_") + lang; - if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) { - if (!qtkeychainTranslator->load(qtkeychainTrFile, trPath)) { - qCCritical(lcApplication) << "Could not load qtkeychain translations"; - } - } - - if (!translator->isEmpty() && !qApp->installTranslator(translator)) { - qCCritical(lcApplication) << "Failed to install translator"; - } - if (!qtTranslator->isEmpty() && !qApp->installTranslator(qtTranslator)) { - qCCritical(lcApplication) << "Failed to install Qt translator"; - } - if (!qtkeychainTranslator->isEmpty() && !qApp->installTranslator(qtkeychainTranslator)) { - qCCritical(lcApplication) << "Failed to install qtkeychain translator"; - } - - // makes sure widgets with locale-dependent formatting, e.g., QDateEdit, display the correct formatting - // if the language is provided by the system locale anyway (i.e., coming from QLocale::system().uiLanguages()), we should - // not mess with the system locale, though - // if we did, we would enforce a locale for no apparent reason - // see https://github.com/owncloud/client/issues/8608 for more information - if (enforcedLocale == lang) { - QLocale newLocale(lang); - qCDebug(lcApplication) << "language" << lang << "was enforced, changing default locale to" << newLocale; - QLocale::setDefault(newLocale); - } - - break; - } - } -} - void Application::openVirtualFile(const QString &filename) { QString virtualFileExt = Theme::instance()->appDotVirtualFileSuffix(); @@ -396,10 +289,10 @@ bool Application::eventFilter(QObject *obj, QEvent *event) return QObject::eventFilter(obj, event); } -std::unique_ptr Application::createInstance(Platform *platform, bool debugMode) +std::unique_ptr Application::createInstance(Platform *platform, const QString &displayLanguage, bool debugMode) { Q_ASSERT(!_instance); - _instance = new Application(platform, debugMode); + _instance = new Application(platform, displayLanguage, debugMode); return std::unique_ptr(_instance); } diff --git a/src/gui/application.h b/src/gui/application.h index e2343d1abc8..8ae203d041a 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -47,7 +47,7 @@ class Application : public QObject { Q_OBJECT public: - static std::unique_ptr createInstance(Platform *platform, bool debugMode); + static std::unique_ptr createInstance(Platform *platform, const QString &displayLanguage, bool debugMode); ~Application(); bool debugMode(); @@ -72,8 +72,6 @@ public slots: void tryTrayAgain(); protected: - void setupTranslations(); - bool eventFilter(QObject *obj, QEvent *event) override; protected slots: @@ -83,7 +81,7 @@ protected slots: void slotAccountStateRemoved() const; private: - explicit Application(Platform *platform, bool debugMode); + explicit Application(Platform *platform, const QString &displayLanguage, bool debugMode); QPointer _gui = {}; diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 81a878a4f30..29a58f5e2ed 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -36,9 +36,11 @@ #include #include +#include #include #include #include +#include #ifdef Q_OS_WIN #include #endif @@ -242,7 +244,119 @@ void setupLogging(const CommandLineOptions &options) << "version:" << Theme::instance()->aboutVersions(Theme::VersionFormat::OneLiner); qCInfo(lcMain) << "Arguments:" << qApp->arguments(); } + +QString setupTranslations(QApplication *app) +{ + const auto trPath = Translations::translationsDirectoryPath(); + qCDebug(lcMain) << "Translations directory path:" << trPath; + + QStringList uiLanguages = QLocale::system().uiLanguages(); + qCDebug(lcMain) << "UI languages:" << uiLanguages; + + // the user can also set a locale in the settings, so we need to load the config file + const ConfigFile cfg; + + // we need to track the enforced language separately, since we need to distinguish between locale-provided + // and user-enforced one below + const QString enforcedLocale = cfg.uiLanguage(); + qCDebug(lcMain) << "Enforced language:" << enforcedLocale; + + // note that user-enforced language are prioritized over the theme enforced one + // to make testing easier. + if (!enforcedLocale.isEmpty()) { + uiLanguages.prepend(enforcedLocale); + } + + QString displayLanguage; + + auto substLang = [](const QString &lang) { + // Map the more appropriate script codes + // to country codes as used by Qt and + // transifex translation conventions. + + if (lang == QLatin1String("zh_Hans")) { + // Simplified Chinese + return QStringLiteral("zh_CN"); + } else if (lang == QLatin1String("zh_Hant")) { + // Traditional Chinese + return QStringLiteral("zh_TW"); + } + + return lang; + }; + + for (QString lang : qAsConst(uiLanguages)) { + lang.replace(QLatin1Char('-'), QLatin1Char('_')); // work around QTBUG-25973 + lang = substLang(lang); + const QString trFile = Translations::translationsFilePrefix() + lang; + QTranslator *translator = new QTranslator(app); + + if (translator->load(trFile, trPath) || lang.startsWith(QLatin1String("en"))) { + // Permissive approach: Qt and keychain translations + // may be missing, but Qt translations must be there in order + // for us to accept the language. Otherwise, we try with the next. + // "en" is an exception as it is the default language and may not + // have a translation file provided. + qCInfo(lcMain) << "Using" << lang << "translation"; + displayLanguage = lang; + + const QString qtTrPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); + qCDebug(lcMain) << "qtTrPath:" << qtTrPath; + const QString qtTrFile = QLatin1String("qt_") + lang; + qCDebug(lcMain) << "qtTrFile:" << qtTrFile; + const QString qtBaseTrFile = QLatin1String("qtbase_") + lang; + qCDebug(lcMain) << "qtBaseTrFile:" << qtBaseTrFile; + + QTranslator *qtTranslator = new QTranslator(app); + QTranslator *qtkeychainTranslator = new QTranslator(app); + + if (!qtTranslator->load(qtTrFile, qtTrPath)) { + if (!qtTranslator->load(qtTrFile, trPath)) { + if (!qtTranslator->load(qtBaseTrFile, qtTrPath)) { + if (!qtTranslator->load(qtBaseTrFile, trPath)) { + qCCritical(lcMain) << "Could not load Qt translations"; + } + } + } + } + + const QString qtkeychainTrFile = QLatin1String("qtkeychain_") + lang; + if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) { + if (!qtkeychainTranslator->load(qtkeychainTrFile, trPath)) { + qCCritical(lcMain) << "Could not load qtkeychain translations"; + } + } + + if (!translator->isEmpty() && !qApp->installTranslator(translator)) { + qCCritical(lcMain) << "Failed to install translator"; + } + if (!qtTranslator->isEmpty() && !qApp->installTranslator(qtTranslator)) { + qCCritical(lcMain) << "Failed to install Qt translator"; + } + if (!qtkeychainTranslator->isEmpty() && !qApp->installTranslator(qtkeychainTranslator)) { + qCCritical(lcMain) << "Failed to install qtkeychain translator"; + } + + // makes sure widgets with locale-dependent formatting, e.g., QDateEdit, display the correct formatting + // if the language is provided by the system locale anyway (i.e., coming from QLocale::system().uiLanguages()), we should + // not mess with the system locale, though + // if we did, we would enforce a locale for no apparent reason + // see https://github.com/owncloud/client/issues/8608 for more information + if (enforcedLocale == lang) { + QLocale newLocale(lang); + qCDebug(lcMain) << "language" << lang << "was enforced, changing default locale to" << newLocale; + QLocale::setDefault(newLocale); + } + + break; + } + + delete translator; + } + + return displayLanguage; } +} // Anonymous namespace int main(int argc, char **argv) { @@ -298,6 +412,9 @@ int main(int argc, char **argv) app.setWindowIcon(Theme::instance()->applicationIcon()); app.setApplicationVersion(Theme::instance()->versionSwitchOutput()); + // Load the translations before option parsing, so we can localize help text and error messages. + QString displayLanguage = setupTranslations(&app); + // parse the arguments before we handle singleApplication // errors and help/version need to be handled in this instance const auto options = parseOptions(app.arguments()); @@ -336,7 +453,7 @@ int main(int argc, char **argv) auto folderManager = FolderMan::createInstance(); if (!AccountManager::instance()->restore()) { - qCCritical(lcApplication) << "Could not read the account settings, quitting"; + qCCritical(lcMain) << "Could not read the account settings, quitting"; QMessageBox::critical(nullptr, QCoreApplication::translate("account loading", "Error accessing the configuration file"), QCoreApplication::translate("account loading", "There was an error while accessing the configuration file at %1.").arg(ConfigFile::configFile()), QMessageBox::Close); @@ -354,7 +471,7 @@ int main(int argc, char **argv) folderManager->setSyncEnabled(true); - auto ocApp = Application::createInstance(platform.get(), options.debugMode); + auto ocApp = Application::createInstance(platform.get(), displayLanguage, options.debugMode); QObject::connect(platform.get(), &Platform::requestAttention, ocApp->gui(), &ownCloudGui::slotShowSettings);