Skip to content

Commit ef24bc7

Browse files
authored
Merge pull request #2604 from SunderB/patch-localeOption
GUI: Add UI language option
2 parents a7612c8 + 767e3aa commit ef24bc7

20 files changed

+1075
-48
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ app/gui/qt/moc_mainwindow.cpp
6161
app/gui/qt/moc_sonicpiudpserver.cpp
6262
app/gui/qt/qrc_SonicPi.cpp
6363
app/gui/qt/utils/ruby_help.h
64+
app/gui/qt/utils/lang_list.h
6465
app/gui/qt/help/*.html
6566
app/gui/qt/help_files.qrc
6667
app/gui/qt/lang/*.qm

app/gui/qt/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,15 @@ set(QT_SOURCES
7474
set(SOURCES
7575
${QTAPP_ROOT}/main.cpp
7676
${QTAPP_ROOT}/utils/scintilla_api.cpp
77+
${QTAPP_ROOT}/utils/sonicpi_i18n.cpp
7778
${QTAPP_ROOT}/widgets/sonicpilog.cpp
7879
${QTAPP_ROOT}/widgets/sonicpilog.h
7980
${QTAPP_ROOT}/widgets/sonicpicontext.cpp
8081
${QTAPP_ROOT}/widgets/sonicpicontext.h
8182
${QTAPP_ROOT}/utils/scintilla_api.h
83+
${QTAPP_ROOT}/utils/sonicpi_i18n.h
8284
${QTAPP_ROOT}/utils/ruby_help.h
85+
${QTAPP_ROOT}/utils/lang_list.h
8386
${QTAPP_ROOT}/model/settings.h
8487
)
8588

app/gui/qt/main.cpp

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
// notice is included.
1212
//++
1313

14+
#include <iostream>
15+
1416
#include <QApplication>
1517
#include <QSplashScreen>
1618
#include <QPixmap>
1719
#include <QBitmap>
1820
#include <QLabel>
19-
#include <QTranslator>
2021
#include <QLibraryInfo>
2122

2223
#include "mainwindow.h"
@@ -35,7 +36,7 @@
3536

3637
int main(int argc, char *argv[])
3738
{
38-
39+
std::cout << "Starting Sonic Pi..." << std::endl;
3940
#ifndef Q_OS_MAC
4041
Q_INIT_RESOURCE(SonicPi);
4142
#endif
@@ -49,16 +50,6 @@ int main(int argc, char *argv[])
4950

5051
qRegisterMetaType<SonicPiLog::MultiMessage>("SonicPiLog::MultiMessage");
5152

52-
QString systemLocale = QLocale::system().uiLanguages()[0].replace("-", "_");
53-
54-
QTranslator qtTranslator;
55-
qtTranslator.load("qt_" + systemLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
56-
app.installTranslator(&qtTranslator);
57-
58-
QTranslator translator;
59-
bool i18n = translator.load(QLatin1String("sonic-pi_") + systemLocale, QLatin1String(":/lang")) || systemLocale.startsWith("en") || systemLocale == "C";
60-
app.installTranslator(&translator);
61-
6253
app.setApplicationName(QObject::tr("Sonic Pi"));
6354
app.setStyle("gtk");
6455

@@ -75,7 +66,7 @@ int main(int argc, char *argv[])
7566
splash->show();
7667
splash->repaint();
7768
app.processEvents();
78-
MainWindow mainWin(app, i18n, splash);
69+
MainWindow mainWin(app, splash);
7970

8071
return app.exec();
8172
#elif _WIN32
@@ -109,7 +100,7 @@ int main(int argc, char *argv[])
109100
splash->show();
110101
splash->repaint();
111102
app.processEvents();
112-
MainWindow mainWin(app, i18n, splash);
103+
MainWindow mainWin(app, splash);
113104

114105
// Fix for full screen mode. See: https://doc.qt.io/qt-5/windows-issues.html#fullscreen-opengl-based-windows
115106
QWindowsWindowFunctions::setHasBorderInFullScreen(mainWin.windowHandle(), true);
@@ -139,9 +130,8 @@ int main(int argc, char *argv[])
139130
splashWindow->show();
140131
app.processEvents();
141132

142-
MainWindow mainWin(app, i18n, splashWindow);
133+
MainWindow mainWin(app, splashWindow);
143134
return app.exec();
144135

145136
#endif
146-
147137
}

app/gui/qt/mainwindow.cpp

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
#include "widgets/sonicpilexer.h"
5353
#include "widgets/sonicpiscintilla.h"
5454

55+
#include "utils/sonicpi_i18n.h"
56+
5557
#include "utils/borderlesslinksproxystyle.h"
5658
#include "visualizer/scope_window.h"
5759

@@ -89,9 +91,9 @@ using namespace std::chrono;
8991
using namespace SonicPi;
9092

9193
#ifdef Q_OS_MAC
92-
MainWindow::MainWindow(QApplication& app, bool i18n, QMainWindow* splash)
94+
MainWindow::MainWindow(QApplication& app, QMainWindow* splash)
9395
#else
94-
MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash)
96+
MainWindow::MainWindow(QApplication& app, QSplashScreen* splash)
9597
#endif
9698
{
9799
app.installEventFilter(this);
@@ -103,7 +105,6 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash)
103105
this->piSettings = new SonicPiSettings();
104106

105107
this->splash = splash;
106-
this->i18n = i18n;
107108

108109
// API and Client
109110
m_spClient = std::make_shared<QtAPIClient>(this);
@@ -126,7 +127,6 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash)
126127
version_num = 0;
127128
latest_version_num = 0;
128129
this->splash = splash;
129-
this->i18n = i18n;
130130
QString settings_path = sonicPiConfigPath() + QDir::separator() + "gui-settings.ini";
131131

132132
gui_settings = new QSettings(settings_path, QSettings::IniFormat);
@@ -139,6 +139,19 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash)
139139
m_spAPI->Init(rootPath().toStdString());
140140
guiID = QString::fromStdString(m_spAPI->GetGuid());
141141

142+
this->sonicPii18n = new SonicPii18n(rootPath());
143+
std::cout << "[GUI] - Language setting: " << piSettings->language.toUtf8().constData() << std::endl;
144+
std::cout << "[GUI] - System language: " << QLocale::system().name().toStdString() << std::endl;
145+
this->ui_language = sonicPii18n->determineUILanguage(piSettings->language);
146+
std::cout << "[GUI] - Using language: " << ui_language.toUtf8().constData() << std::endl;
147+
this->i18n = sonicPii18n->loadTranslations(ui_language);
148+
149+
if(i18n) {
150+
std::cout << "[GUI] - translations available " << std::endl;
151+
} else {
152+
std::cout << "[GUI] - translations unavailable (using EN)" << std::endl;
153+
}
154+
142155
std::cout << "[GUI] - hiding main window" << std::endl;
143156
hide();
144157

@@ -212,6 +225,11 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash)
212225
toggleOSCServer(1);
213226

214227
app.setActiveWindow(tabs->currentWidget());
228+
229+
if (!i18n) {
230+
showLanguageLoadingError();
231+
}
232+
215233
showWelcomeScreen();
216234
}
217235

@@ -348,7 +366,8 @@ void MainWindow::setupWindowStructure()
348366
prefsWidget->setAllowedAreas(Qt::RightDockWidgetArea);
349367
prefsWidget->setFeatures(QDockWidget::DockWidgetClosable);
350368

351-
settingsWidget = new SettingsWidget(m_spAPI->GetPort(SonicPiPortId::server_osc_cues), piSettings, this);
369+
settingsWidget = new SettingsWidget(m_spAPI->GetPort(SonicPiPortId::server_osc_cues), i18n, piSettings, sonicPii18n, this);
370+
connect(settingsWidget, SIGNAL(restartApp()), this, SLOT(restartApp()));
352371
connect(settingsWidget, SIGNAL(volumeChanged(int)), this, SLOT(changeSystemPreAmp(int)));
353372
connect(settingsWidget, SIGNAL(mixerSettingsChanged()), this, SLOT(mixerSettingsChanged()));
354373
connect(settingsWidget, SIGNAL(midiSettingsChanged()), this, SLOT(toggleMidi()));
@@ -1216,6 +1235,18 @@ void MainWindow::startupError(QString msg)
12161235
// TODO: Add format error to API
12171236
}
12181237

1238+
void MainWindow::showLanguageLoadingError() {
1239+
QMessageBox msgBox(this);
1240+
msgBox.setIcon(QMessageBox::Warning);
1241+
msgBox.setText(QString(tr("Failed to load translations for language: %1")).arg(sonicPii18n->getNativeLanguageName(this->ui_language)));
1242+
msgBox.setInformativeText(tr("Falling back to English. Sorry about this.") + "\n" + tr("Please consider reporting a bug at") + "\nhttp://github.com/sonic-pi-net/sonic-pi/issues");
1243+
1244+
QPushButton *okButton = msgBox.addButton(tr("OK"), QMessageBox::AcceptRole);
1245+
msgBox.setDefaultButton(okButton);
1246+
1247+
msgBox.exec();
1248+
}
1249+
12191250
void MainWindow::replaceBuffer(QString id, QString content, int line, int index, int first_line)
12201251
{
12211252
SonicPiScintilla* ws = filenameToWorkspace(id.toStdString());
@@ -2880,6 +2911,39 @@ void MainWindow::createToolBar()
28802911
viewMenu->addAction(focusHelpListingAct);
28812912
viewMenu->addAction(focusHelpDetailsAct);
28822913
viewMenu->addAction(focusErrorsAct);
2914+
2915+
languageMenu = menuBar()->addMenu(tr("Language"));
2916+
QStringList available_languages = sonicPii18n->getAvailableLanguages();
2917+
2918+
langActionGroup = new QActionGroup(this);
2919+
langActionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive);
2920+
2921+
QSignalMapper *signalMapper = new QSignalMapper(this);
2922+
2923+
for (size_t i = 0; i < available_languages.length(); i += 1) {
2924+
bool is_current_lang = (available_languages[i] == piSettings->language);
2925+
2926+
QAction *langAct = new QAction(sonicPii18n->getNativeLanguageName(available_languages[i]), this);
2927+
langAct->setCheckable(true);
2928+
langAct->setChecked(is_current_lang);
2929+
2930+
connect(langAct, SIGNAL(triggered()), signalMapper, SLOT(map()));
2931+
signalMapper->setMapping(langAct, i);
2932+
2933+
langActionGroup->addAction(langAct);
2934+
languageMenu->addAction(langAct);
2935+
2936+
if (i == 0) { // add separator after System language
2937+
languageMenu->addSeparator();
2938+
}
2939+
}
2940+
2941+
connect(signalMapper, SIGNAL(mappedInt(int)), settingsWidget, SLOT(updateUILanguage(int)));
2942+
connect(settingsWidget, SIGNAL(uiLanguageChanged(QString)), this, SLOT(updateSelectedUILanguageAction(QString)));
2943+
}
2944+
2945+
void MainWindow::updateSelectedUILanguageAction(QString lang) {
2946+
langActionGroup->actions()[sonicPii18n->getAvailableLanguages().indexOf(lang)]->setChecked(true);
28832947
}
28842948

28852949
QString MainWindow::readFile(QString name)
@@ -3074,6 +3138,7 @@ void MainWindow::readSettings()
30743138
std::cout << "[GUI] - reading settings" << std::endl;
30753139

30763140
// Read in preferences from previous session
3141+
piSettings->language = gui_settings->value("prefs/language", "system_language").toString();
30773142
piSettings->show_buttons = gui_settings->value("prefs/show-buttons", true).toBool();
30783143
piSettings->show_tabs = gui_settings->value("prefs/show-tabs", true).toBool();
30793144
piSettings->show_log = gui_settings->value("prefs/show-log", true).toBool();
@@ -3100,6 +3165,7 @@ void MainWindow::readSettings()
31003165
piSettings->show_scope_labels = gui_settings->value("prefs/scope/show-labels", false).toBool();
31013166
piSettings->show_cues = gui_settings->value("prefs/show_cues", true).toBool();
31023167
QString styleName = gui_settings->value("prefs/theme", "").toString();
3168+
31033169
piSettings->themeStyle = theme->themeNameToStyle(styleName);
31043170
piSettings->show_autocompletion = gui_settings->value("prefs/show-autocompletion", true).toBool();
31053171
piSettings->show_context = gui_settings->value("prefs/show-context", true).toBool();
@@ -3121,10 +3187,13 @@ void MainWindow::restoreScopeState(std::vector<QString> names)
31213187
void MainWindow::writeSettings()
31223188
{
31233189
std::cout << "[GUI] - writing settings" << std::endl;
3190+
31243191
gui_settings->setValue("pos", pos());
31253192
gui_settings->setValue("size", size());
31263193
gui_settings->setValue("first_time", 0);
31273194

3195+
gui_settings->setValue("prefs/language", piSettings->language);
3196+
31283197
gui_settings->setValue("prefs/midi-default-channel", piSettings->midi_default_channel);
31293198
gui_settings->setValue("prefs/midi-enable", piSettings->midi_enabled);
31303199
gui_settings->setValue("prefs/osc-public", piSettings->osc_public);
@@ -3172,6 +3241,9 @@ void MainWindow::writeSettings()
31723241
gui_settings->setValue("docsplitState", docsplit->saveState());
31733242
gui_settings->setValue("windowState", saveState());
31743243
gui_settings->setValue("windowGeom", saveGeometry());
3244+
3245+
// Force Qt to write the settings to the ini file
3246+
gui_settings->sync();
31753247
}
31763248

31773249
void MainWindow::loadFile(const QString& fileName, SonicPiScintilla*& text)
@@ -3261,6 +3333,31 @@ void MainWindow::onExitCleanup()
32613333
}
32623334
}
32633335

3336+
void MainWindow::restartApp() {
3337+
QApplication* app = dynamic_cast<QApplication*>(parent());
3338+
statusBar()->showMessage(tr("Restarting Sonic Pi..."), 10000);
3339+
3340+
// Save settings and perform some cleanup
3341+
writeSettings();
3342+
onExitCleanup();
3343+
std::cout << "Performing application restart..." << std::endl;
3344+
3345+
// Create new process
3346+
QStringList args = qApp->arguments();
3347+
args.removeFirst();
3348+
QProcess process;
3349+
bool restart_success = process.startDetached(qApp->arguments()[0], args);
3350+
if (restart_success) {
3351+
std::cout << "Successfully restarted sonic-pi" << std::endl;
3352+
} else {
3353+
std::cout << "Failed to restart sonic-pi" << std::endl;
3354+
}
3355+
3356+
// Quit
3357+
app->exit(0);
3358+
exit(0);
3359+
}
3360+
32643361
void MainWindow::heartbeatOSC()
32653362
{
32663363
// Message msg("/gui-heartbeat");

app/gui/qt/mainwindow.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <vector>
1818
#include <memory>
1919

20+
2021
#include <QDate>
2122
#include <QMainWindow>
2223
#include <QFuture>
@@ -34,6 +35,7 @@
3435
#include "config.h"
3536

3637
class QAction;
38+
class QActionGroup;
3739
class QMenu;
3840
class QToolBar;
3941
class QLineEdit;
@@ -67,6 +69,7 @@ class InfoWidget;
6769
class SettingsWidget;
6870
class Scope;
6971
class ScintillaAPI;
72+
class SonicPii18n;
7073
class SonicPiLog;
7174
class SonicPiScintilla;
7275
class SonicPiTheme;
@@ -91,9 +94,9 @@ class MainWindow : public QMainWindow
9194

9295
public:
9396
#if defined(Q_OS_MAC)
94-
MainWindow(QApplication &ref, bool i18n, QMainWindow* splash);
97+
MainWindow(QApplication &ref, QMainWindow* splash);
9598
#else
96-
MainWindow(QApplication &ref, bool i18n, QSplashScreen* splash);
99+
MainWindow(QApplication &ref, QSplashScreen* splash);
97100
#endif
98101

99102
SonicPiLog* GetOutputPane() const;
@@ -118,6 +121,8 @@ class MainWindow : public QMainWindow
118121

119122
bool loaded_workspaces;
120123
QString hash_salt;
124+
QString ui_language;
125+
121126

122127
protected:
123128
void closeEvent(QCloseEvent *event);
@@ -132,6 +137,7 @@ class MainWindow : public QMainWindow
132137

133138
private slots:
134139

140+
void updateSelectedUILanguageAction(QString lang);
135141
void updateContext(int line, int index);
136142
void updateContextWithCurrentWs();
137143
void docLinkClicked(const QUrl &url);
@@ -178,6 +184,7 @@ class MainWindow : public QMainWindow
178184
void help();
179185
void toggleHelpIcon();
180186
void onExitCleanup();
187+
void restartApp();
181188
void toggleRecording();
182189
void toggleRecordingOnIcon();
183190
void changeSystemPreAmp(int val, int silent=0);
@@ -229,6 +236,7 @@ class MainWindow : public QMainWindow
229236
void splashClose();
230237
void setMessageBoxStyle();
231238
void startupError(QString msg);
239+
void showLanguageLoadingError();
232240
void tabNext();
233241
void tabPrev();
234242
void tabGoto(int index);
@@ -327,11 +335,13 @@ class MainWindow : public QMainWindow
327335
QString rootPath();
328336

329337
void addUniversalCopyShortcuts(QTextEdit *te);
338+
void updateTranslatedUIText();
330339

331-
QMenu *liveMenu, *codeMenu, *audioMenu, *displayMenu, *viewMenu, *ioMenu, *ioMidiInMenu, *ioMidiOutMenu, *ioMidiOutChannelMenu, *localIpAddressesMenu, *themeMenu, *scopeKindVisibilityMenu;
340+
QMenu *liveMenu, *codeMenu, *audioMenu, *displayMenu, *viewMenu, *ioMenu, *ioMidiInMenu, *ioMidiOutMenu, *ioMidiOutChannelMenu, *localIpAddressesMenu, *themeMenu, *scopeKindVisibilityMenu, *languageMenu;
332341

333342
QSettings *gui_settings;
334343
SonicPiSettings *piSettings;
344+
SonicPii18n *sonicPii18n;
335345

336346
bool focusMode;
337347
QCheckBox *startup_error_reported;
@@ -380,6 +390,7 @@ class MainWindow : public QMainWindow
380390

381391
QAction *exitAct, *runAct, *stopAct, *saveAsAct, *loadFileAct, *recAct, *textAlignAct, *textIncAct, *textDecAct, *scopeAct, *infoAct, *helpAct, *prefsAct, *focusEditorAct, *focusLogsAct, *focusContextAct, *focusCuesAct, *focusPreferencesAct, *focusHelpListingAct, *focusHelpDetailsAct, *focusErrorsAct, *showLineNumbersAct, *showAutoCompletionAct, *showContextAct, *audioSafeAct, *audioTimingGuaranteesAct, *enableExternalSynthsAct, *mixerInvertStereoAct, *mixerForceMonoAct, *midiEnabledAct, *enableOSCServerAct, *allowRemoteOSCAct, *showLogAct, *showCuesAct, *logAutoScrollAct, *logCuesAct, *logSynthsAct, *clearOutputOnRunAct, *autoIndentOnRunAct, *showButtonsAct, *showTabsAct, *fullScreenAct, *lightThemeAct, *darkThemeAct, *proLightThemeAct, *proDarkThemeAct, *highContrastThemeAct, *showScopeLabelsAct;
382392
QShortcut *runSc, *stopSc, *saveAsSc, *loadFileSc, *recSc, *textAlignSc, *textIncSc, *textDecSc, *scopeSc, *infoSc, *helpSc, *prefsSc, *focusEditorSc, *focusLogsSc, *focusContextSc, *focusCuesSc, *focusPreferencesSc, *focusHelpListingSc, *focusHelpDetailsSc, *focusErrorsSc;
393+
QActionGroup *langActionGroup;
383394

384395
SettingsWidget *settingsWidget;
385396

0 commit comments

Comments
 (0)