diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index f4a6ad59..a998212c 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -722,6 +722,60 @@ Flickable { anchors.fill: parent spacing: 5 + Label { + width: parent.width + id: languageTitle + text: qsTr("Language") + font.pointSize: 12 + wrapMode: Text.Wrap + } + + AutoResizingComboBox { + // ignore setting the index at first, and actually set it when the component is loaded + Component.onCompleted: { + var saved_language = StreamingPreferences.language + currentIndex = 0 + for (var i = 0; i < languageListModel.count; i++) { + var el_language = languageListModel.get(i).val; + if (saved_language === el_language) { + currentIndex = i + break + } + } + + activated(currentIndex) + } + + id: languageComboBox + textRole: "text" + model: ListModel { + id: languageListModel + ListElement { + text: qsTr("Automatic") + val: StreamingPreferences.LANG_AUTO + } + ListElement { + text: qsTr("English") + val: StreamingPreferences.LANG_EN + } + ListElement { + text: qsTr("French") + val: StreamingPreferences.LANG_FR + } + ListElement { + text: qsTr("Simplified Chinese") + val: StreamingPreferences.LANG_ZH_CN + } + } + // ::onActivated must be used, as it only listens for when the index is changed by a human + onActivated : { + StreamingPreferences.language = languageListModel.get(currentIndex).val + if (!StreamingPreferences.retranslate()) { + ToolTip.show(qsTr("You must restart Moonlight for this change to take effect"), 5000) + } + } + } + Label { width: parent.width id: uiDisplayModeTitle diff --git a/app/main.cpp b/app/main.cpp index 2b0a0214..b9174132 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -451,9 +451,9 @@ int main(int argc, char *argv[]) QGuiApplication app(argc, argv); - QTranslator translator; - qDebug() << "Translation loaded:" << translator.load(QString(":/languages/qml_") + QLocale::system().name()); - app.installTranslator(&translator); + // Apply the initial translation based on user preference + StreamingPreferences prefs; + prefs.retranslate(); // After the QGuiApplication is created, the platform stuff will be initialized // and we can set the SDL video driver to match Qt. @@ -525,8 +525,8 @@ int main(int argc, char *argv[]) }); qmlRegisterSingletonType("StreamingPreferences", 1, 0, "StreamingPreferences", - [](QQmlEngine*, QJSEngine*) -> QObject* { - return new StreamingPreferences(); + [](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* { + return new StreamingPreferences(qmlEngine); }); // Create the identity manager on the main thread diff --git a/app/settings/streamingpreferences.cpp b/app/settings/streamingpreferences.cpp index 38ef46ab..3eafe93f 100644 --- a/app/settings/streamingpreferences.cpp +++ b/app/settings/streamingpreferences.cpp @@ -1,6 +1,8 @@ #include "streamingpreferences.h" #include +#include +#include #define SER_STREAMSETTINGS "streamsettings" #define SER_WIDTH "width" @@ -36,11 +38,20 @@ #define SER_REVERSESCROLL "reversescroll" #define SER_SWAPFACEBUTTONS "swapfacebuttons" #define SER_CAPTURESYSKEYS "capturesyskeys" +#define SER_LANGUAGE "language" #define CURRENT_DEFAULT_VER 1 StreamingPreferences::StreamingPreferences(QObject *parent) - : QObject(parent) + : QObject(parent), + m_QmlEngine(nullptr) +{ + reload(); +} + +StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent) + : QObject(parent), + m_QmlEngine(qmlEngine) { reload(); } @@ -95,6 +106,8 @@ void StreamingPreferences::reload() uiDisplayMode = static_cast(settings.value(SER_UIDISPLAYMODE, static_cast(settings.value(SER_STARTWINDOWED, true).toBool() ? UIDisplayMode::UI_WINDOWED : UIDisplayMode::UI_MAXIMIZED)).toInt()); + language = static_cast(settings.value(SER_LANGUAGE, + static_cast(Language::LANG_AUTO)).toInt()); // Perform default settings updates as required based on last default version @@ -108,6 +121,74 @@ void StreamingPreferences::reload() } } +bool StreamingPreferences::retranslate() +{ + static QTranslator* translator = nullptr; + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + if (m_QmlEngine != nullptr) { + // Dynamic retranslation is not supported until Qt 5.10 + return false; + } +#endif + + QTranslator* newTranslator = new QTranslator(); + QString languageSuffix = getSuffixFromLanguage(language); + + // Remove the old translator, even if we can't load a new one. + // Otherwise we'll be stuck with the old translated values instead + // of defaulting to English. + if (translator != nullptr) { + QCoreApplication::removeTranslator(translator); + delete translator; + translator = nullptr; + } + + if (newTranslator->load(QString(":/languages/qml_") + languageSuffix)) { + qInfo() << "Successfully loaded translation for " << languageSuffix; + + translator = newTranslator; + QCoreApplication::installTranslator(translator); + } + else { + qInfo() << "No translation available for " << languageSuffix; + delete newTranslator; + } + + if (m_QmlEngine != nullptr) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + // This is a dynamic retranslation from the settings page. + // We have to kick the QML engine into reloading our text. + m_QmlEngine->retranslate(); +#else + // Unreachable below Qt 5.10 due to the check above + Q_ASSERT(FALSE); +#endif + } + else { + // This is a translation from a non-QML context, which means + // it is probably app startup. There's nothing to refresh. + } + + return true; +} + +QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Language lang) +{ + switch (lang) + { + case LANG_EN: + return "en"; + case LANG_FR: + return "fr"; + case LANG_ZH_CN: + return "zh_cn"; + case LANG_AUTO: + default: + return QLocale::system().name(); + } +} + void StreamingPreferences::save() { QSettings settings; @@ -136,6 +217,7 @@ void StreamingPreferences::save() settings.setValue(SER_VIDEODEC, static_cast(videoDecoderSelection)); settings.setValue(SER_WINDOWMODE, static_cast(windowMode)); settings.setValue(SER_UIDISPLAYMODE, static_cast(uiDisplayMode)); + settings.setValue(SER_LANGUAGE, static_cast(language)); settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER); settings.setValue(SER_SWAPMOUSEBUTTONS, swapMouseButtons); settings.setValue(SER_MUTEONFOCUSLOSS, muteOnFocusLoss); diff --git a/app/settings/streamingpreferences.h b/app/settings/streamingpreferences.h index 229227b6..80b04905 100644 --- a/app/settings/streamingpreferences.h +++ b/app/settings/streamingpreferences.h @@ -2,6 +2,7 @@ #include #include +#include class StreamingPreferences : public QObject { @@ -9,6 +10,7 @@ class StreamingPreferences : public QObject public: StreamingPreferences(QObject *parent = nullptr); + StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr); Q_INVOKABLE static int getDefaultBitrate(int width, int height, int fps); @@ -58,6 +60,15 @@ public: }; Q_ENUM(UIDisplayMode) + enum Language + { + LANG_AUTO, + LANG_EN, + LANG_FR, + LANG_ZH_CN + }; + Q_ENUM(Language); + Q_PROPERTY(int width MEMBER width NOTIFY displayModeChanged) Q_PROPERTY(int height MEMBER height NOTIFY displayModeChanged) Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged) @@ -88,6 +99,9 @@ public: Q_PROPERTY(bool reverseScrollDirection MEMBER reverseScrollDirection NOTIFY reverseScrollDirectionChanged) Q_PROPERTY(bool swapFaceButtons MEMBER swapFaceButtons NOTIFY swapFaceButtonsChanged) Q_PROPERTY(bool captureSysKeys MEMBER captureSysKeys NOTIFY captureSysKeysChanged) + Q_PROPERTY(Language language MEMBER language NOTIFY languageChanged); + + Q_INVOKABLE bool retranslate(); // Directly accessible members for preferences int width; @@ -121,6 +135,7 @@ public: WindowMode windowMode; WindowMode recommendedFullScreenMode; UIDisplayMode uiDisplayMode; + Language language; signals: void displayModeChanged(); @@ -150,5 +165,11 @@ signals: void reverseScrollDirectionChanged(); void swapFaceButtonsChanged(); void captureSysKeysChanged(); + void languageChanged(); + +private: + QString getSuffixFromLanguage(Language lang); + + QQmlEngine* m_QmlEngine; };