From 954e0e8a59f4231d57e57923d2af172b8753065d Mon Sep 17 00:00:00 2001
From: Jonas Kvinge
Date: Fri, 22 Feb 2019 20:24:38 +0100
Subject: [PATCH] Add translations support (#82)
* Add translations support
* Update .gitignore
---
.gitignore | 2 +
CMakeLists.txt | 11 ++++
cmake/Translations.cmake | 77 +++++++++++++++++++++++++
ext/libstrawberry-common/core/lazy.h | 8 +--
src/CMakeLists.txt | 35 +++++++++++
src/config.h.in | 3 +-
src/context/contextview.cpp | 6 +-
src/core/thread.cpp | 8 +--
src/core/thread.h | 14 ++---
src/core/utilities.cpp | 30 ++++++++++
src/core/utilities.h | 5 ++
src/deezer/deezerservice.cpp | 7 ++-
src/device/udisks2lister.cpp | 35 ++++++-----
src/device/udisks2lister.h | 35 ++++++-----
src/dialogs/about.cpp | 57 +++++++++---------
src/dialogs/edittagdialog.cpp | 2 +-
src/engine/enginebase.cpp | 2 +-
src/engine/enginebase.h | 2 +-
src/internet/internetsearchview.cpp | 4 +-
src/main.cpp | 29 ++++++++++
src/queue/queueview.ui | 4 +-
src/scrobbler/listenbrainzscrobbler.cpp | 2 +-
src/scrobbler/scrobblingapi20.cpp | 8 +--
src/settings/behavioursettingspage.cpp | 58 +++++++++++++++++++
src/settings/behavioursettingspage.h | 2 +
src/settings/behavioursettingspage.ui | 34 ++++++++++-
src/settings/shortcutssettingspage.cpp | 2 +-
src/tidal/tidalservice.cpp | 14 +++--
src/translations/header | 8 +++
29 files changed, 401 insertions(+), 103 deletions(-)
create mode 100644 cmake/Translations.cmake
create mode 100644 src/translations/header
diff --git a/.gitignore b/.gitignore
index 310b9b53..6b77415b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,6 +106,8 @@ PKGBUILD
# Translations
translations.pot
+zanata.xml
+.zanata-cache/
# Snap
parts/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70e7c88a..471d4a27 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -114,6 +114,7 @@ pkg_check_modules(LIBUSBMUXD libusbmuxd)
pkg_check_modules(LIBPLIST libplist)
pkg_check_modules(LIBDEEZER libdeezer)
pkg_check_modules(LIBDZMEDIA libdzmedia)
+find_package(Gettext)
if(WIN32)
find_package(ZLIB REQUIRED)
@@ -151,6 +152,11 @@ if(WIN32)
set(QT_LIBRARIES ${QT_LIBRARIES} Qt5::WinExtras)
endif()
+find_package(Qt5LinguistTools CONFIG)
+if (Qt5LinguistTools_FOUND)
+ set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
+endif()
+
if(X11_FOUND)
find_path(KEYSYMDEF_H NAMES "keysymdef.h" PATHS "${X11_INCLUDE_DIR}" PATH_SUFFIXES "X11")
find_path(XF86KEYSYM_H NAMES "XF86keysym.h" PATHS "${XCB_INCLUDEDIR}" PATH_SUFFIXES "X11")
@@ -346,6 +352,11 @@ optional_component(SPARKLE ON "Sparkle integration"
DEPENDS "Sparkle" SPARKLE
)
+optional_component(TRANSLATIONS OFF "Translations (No languages included yet)"
+ DEPENDS "gettext" GETTEXT_FOUND
+ DEPENDS "Qt5LinguistTools" Qt5LinguistTools_FOUND
+)
+
optional_component(STREAM_TIDAL ON "Streaming: Tidal support")
if (LIBDZMEDIA_FOUND OR LIBDEEZER_FOUND)
diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake
new file mode 100644
index 00000000..a3ae0ab7
--- /dev/null
+++ b/cmake/Translations.cmake
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8.11)
+
+find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
+if(NOT GETTEXT_XGETTEXT_EXECUTABLE)
+ message(FATAL_ERROR "Could not find xgettext executable")
+endif(NOT GETTEXT_XGETTEXT_EXECUTABLE)
+
+set (XGETTEXT_OPTIONS
+ --qt
+ --keyword=tr:1,2c
+ --keyword=tr --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format
+ --keyword=trUtf8 --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format
+ --keyword=translate:2,3c
+ --keyword=translate:2 --flag=translate:2:pass-c-format --flag=translate:2:pass-qt-format
+ --keyword=QT_TR_NOOP --flag=QT_TR_NOOP:1:pass-c-format --flag=QT_TR_NOOP:1:pass-qt-format
+ --keyword=QT_TRANSLATE_NOOP:2 --flag=QT_TRANSLATE_NOOP:2:pass-c-format --flag=QT_TRANSLATE_NOOP:2:pass-qt-format
+ --keyword=_ --flag=_:1:pass-c-format --flag=_:1:pass-qt-format
+ --keyword=N_ --flag=N_:1:pass-c-format --flag=N_:1:pass-qt-format
+ --from-code=utf-8
+ )
+
+macro(add_pot outfiles header pot)
+ # Make relative filenames for all source files
+ set(add_pot_sources)
+ foreach(_filename ${ARGN})
+ get_filename_component(_absolute_filename ${_filename} ABSOLUTE)
+ file(RELATIVE_PATH _relative_filename ${CMAKE_CURRENT_SOURCE_DIR} ${_absolute_filename})
+ list(APPEND add_pot_sources ${_relative_filename})
+ endforeach(_filename)
+
+ # Generate the .pot
+ add_custom_command(
+ OUTPUT ${pot}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTIONS} -s -C --omit-header --output=${CMAKE_CURRENT_BINARY_DIR}/pot.temp ${add_pot_sources}
+ COMMAND cat ${header} ${CMAKE_CURRENT_BINARY_DIR}/pot.temp > ${pot}
+ DEPENDS ${add_pot_sources} ${header}
+ )
+
+ list(APPEND ${outfiles} ${pot})
+endmacro(add_pot)
+
+# Syntax is:
+# add_po(sources_var po_prefix LANGUAGES language1 language2 ... DIRECTORY dir)
+
+macro(add_po outfiles po_prefix)
+ parse_arguments(ADD_PO
+ "LANGUAGES;DIRECTORY"
+ ""
+ ${ARGN}
+ )
+
+ foreach (_lang ${ADD_PO_LANGUAGES})
+ set(_po_filename "${_lang}.po")
+ set(_po_filepath "${CMAKE_CURRENT_SOURCE_DIR}/${ADD_PO_DIRECTORY}/${_po_filename}")
+ set(_qm_filename "strawberry_${_lang}.qm")
+ set(_qm_filepath "${CMAKE_CURRENT_BINARY_DIR}/${ADD_PO_DIRECTORY}/${_qm_filename}")
+
+ # Convert the .po files to .qm files
+ add_custom_command(
+ OUTPUT ${_qm_filepath}
+ COMMAND ${QT_LCONVERT_EXECUTABLE} ARGS ${_po_filepath} -o ${_qm_filepath} -of qm
+ DEPENDS ${_po_filepath} ${_po_filepath}
+ )
+
+ list(APPEND ${outfiles} ${_qm_filepath})
+ endforeach (_lang)
+
+ # Generate a qrc file for the translations
+ set(_qrc ${CMAKE_CURRENT_BINARY_DIR}/${ADD_PO_DIRECTORY}/translations.qrc)
+ file(WRITE ${_qrc} "")
+ foreach(_lang ${ADD_PO_LANGUAGES})
+ file(APPEND ${_qrc} "${po_prefix}${_lang}.qm")
+ endforeach(_lang)
+ file(APPEND ${_qrc} "")
+ qt5_add_resources(${outfiles} ${_qrc})
+endmacro(add_po)
diff --git a/ext/libstrawberry-common/core/lazy.h b/ext/libstrawberry-common/core/lazy.h
index 228e51e5..53ee4b1e 100644
--- a/ext/libstrawberry-common/core/lazy.h
+++ b/ext/libstrawberry-common/core/lazy.h
@@ -1,18 +1,18 @@
-/* This file is part of Clementine.
+/* This file is part of Strawberry.
Copyright 2016, John Maguire
- Clementine is free software: you can redistribute it and/or modify
+ Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
- Clementine is distributed in the hope that it will be useful,
+ Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with Clementine. If not, see .
+ along with Strawberry. If not, see .
*/
#ifndef LAZY_H
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cab682bb..bf427b75 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,10 @@ if(BUILD_WERROR)
endif (LINUX)
endif(BUILD_WERROR)
+if(HAVE_TRANSLATIONS)
+ include(../cmake/Translations.cmake)
+endif(HAVE_TRANSLATIONS)
+
# Set up definitions and paths
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
@@ -904,6 +908,37 @@ qt5_wrap_cpp(MOC ${HEADERS})
qt5_wrap_ui(UIC ${UI})
qt5_add_resources(QRC ${RESOURCES})
+if(HAVE_TRANSLATIONS)
+
+ set(LINGUAS "All" CACHE STRING "A space-seperated list of translations to compile in to Strawberry, or \"None\".")
+ if (LINGUAS STREQUAL "All")
+ # build LANGUAGES from all existing .po files
+ file(GLOB pofiles translations/*.po)
+ foreach(pofile ${pofiles})
+ get_filename_component(lang ${pofile} NAME_WE)
+ list(APPEND LANGUAGES ${lang})
+ endforeach(pofile)
+ else (LINGUAS STREQUAL "All")
+ if (NOT LINGUAS OR LINGUAS STREQUAL "None")
+ set (LANGUAGES "")
+ else (NOT LINGUAS OR LINGUAS STREQUAL "None")
+ string(REGEX MATCHALL [a-zA-Z_@]+ LANGUAGES ${LINGUAS})
+ endif (NOT LINGUAS OR LINGUAS STREQUAL "None")
+ endif (LINGUAS STREQUAL "All")
+
+ add_pot(POT
+ ${CMAKE_CURRENT_SOURCE_DIR}/translations/header
+ ${CMAKE_CURRENT_SOURCE_DIR}/translations/translations.pot
+ ${SOURCES}
+ ${MOC}
+ ${UIC}
+ ${OTHER_SOURCES}
+ ../data/html/oauthsuccess.html
+ )
+ add_po(PO strawberry_ LANGUAGES ${LANGUAGES} DIRECTORY translations)
+
+endif(HAVE_TRANSLATIONS)
+
add_library(strawberry_lib STATIC
${SOURCES}
${MOC}
diff --git a/src/config.h.in b/src/config.h.in
index 5e12cf9b..eb7c40e8 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -60,5 +60,6 @@
#define USE_BUNDLE_DIR "${USE_BUNDLE_DIR}"
-#endif // CONFIG_H_IN
+#cmakedefine HAVE_TRANSLATIONS
+#endif // CONFIG_H_IN
diff --git a/src/context/contextview.cpp b/src/context/contextview.cpp
index 7a536703..e5f429cd 100644
--- a/src/context/contextview.cpp
+++ b/src/context/contextview.cpp
@@ -232,9 +232,9 @@ void ContextView::NoSong() {
"font-weight: Regular;"
);
- ui_->label_stop_top->setText("No song playing");
+ ui_->label_stop_top->setText(tr("No song playing"));
- QString html = QString(
+ QString html = tr(
"%1 songs
\n"
"%2 artists
\n"
"%3 albums
\n"
@@ -404,7 +404,7 @@ void ContextView::SetSong(const Song &song) {
if (albumlist.count() > 1) {
ui_->label_play_albums->setVisible(true);
ui_->label_play_albums->setMinimumSize(0, 20);
- ui_->label_play_albums->setText(QString("Albums by %1").arg( song.artist().toHtmlEscaped()));
+ ui_->label_play_albums->setText(tr("Albums by %1").arg( song.artist().toHtmlEscaped()));
ui_->label_play_albums->setStyleSheet("background-color: #3DADE8; color: rgb(255, 255, 255); font: 11pt;");
for (CollectionBackend::Album album : albumlist) {
SongList songs = app_->collection_backend()->GetSongs(song.artist(), album.album_name, opt);
diff --git a/src/core/thread.cpp b/src/core/thread.cpp
index ac209e6f..118ab974 100644
--- a/src/core/thread.cpp
+++ b/src/core/thread.cpp
@@ -1,18 +1,18 @@
-/* This file is part of Clementine.
+/* This file is part of Strawberry.
Copyright 2015, David Sansome
- Clementine is free software: you can redistribute it and/or modify
+ Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
- Clementine is distributed in the hope that it will be useful,
+ Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with Clementine. If not, see .
+ along with Strawberry. If not, see .
*/
#include "config.h"
diff --git a/src/core/thread.h b/src/core/thread.h
index 94dab505..35d19338 100644
--- a/src/core/thread.h
+++ b/src/core/thread.h
@@ -1,22 +1,22 @@
-/* This file is part of Clementine.
+/* This file is part of Strawberry.
Copyright 2015, David Sansome
- Clementine is free software: you can redistribute it and/or modify
+ Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
- Clementine is distributed in the hope that it will be useful,
+ Strawberry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with Clementine. If not, see .
+ along with Strawberry. If not, see .
*/
-#ifndef CORE_THREAD_H_
-#define CORE_THREAD_H_
+#ifndef THREAD_H
+#define THREAD_H
#include "config.h"
@@ -40,4 +40,4 @@ class Thread : public QThread {
Utilities::IoPriority io_priority_;
};
-#endif // CORE_THREAD_H_
+#endif // THREAD_H
diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp
index 8accd62e..063233ee 100644
--- a/src/core/utilities.cpp
+++ b/src/core/utilities.cpp
@@ -58,6 +58,9 @@
#include
#include
#include
+#ifdef HAVE_TRANSLATIONS
+# include
+#endif
#ifdef Q_OS_LINUX
# include
@@ -96,6 +99,10 @@
# include "scoped_cftyperef.h"
#endif
+#ifdef HAVE_TRANSLATIONS
+# include "potranslator.h"
+#endif
+
namespace Utilities {
static QString tr(const char *str) {
@@ -764,6 +771,29 @@ QString DesktopEnvironment() {
}
+#ifdef HAVE_TRANSLATIONS
+
+QString SystemLanguageName() {
+
+ QString system_language = QLocale::system().uiLanguages().empty() ? QLocale::system().name() : QLocale::system().uiLanguages().first();
+ // uiLanguages returns strings with "-" as separators for language/region; however QTranslator needs "_" separators
+ system_language.replace("-", "_");
+
+ return system_language;
+
+}
+
+void LoadTranslation(const QString &prefix, const QString &path, const QString &language) {
+
+ QTranslator *t = new PoTranslator;
+ if (t->load(prefix + "_" + language, path))
+ QCoreApplication::installTranslator(t);
+ else
+ delete t;
+
+}
+#endif
+
} // namespace Utilities
ScopedWCharArray::ScopedWCharArray(const QString &str)
diff --git a/src/core/utilities.h b/src/core/utilities.h
index 51129bfe..794edb1f 100644
--- a/src/core/utilities.h
+++ b/src/core/utilities.h
@@ -154,6 +154,11 @@ QString GetRandomString(const int len, const QString &UseCharacters);
QString DesktopEnvironment();
+#ifdef HAVE_TRANSLATIONS
+QString SystemLanguageName();
+void LoadTranslation(const QString &prefix, const QString &path, const QString &language);
+#endif
+
} // namespace
class ScopedWCharArray {
diff --git a/src/deezer/deezerservice.cpp b/src/deezer/deezerservice.cpp
index 3642a91d..e6c949ed 100644
--- a/src/deezer/deezerservice.cpp
+++ b/src/deezer/deezerservice.cpp
@@ -460,7 +460,7 @@ void DeezerService::ClearSearch() {
void DeezerService::SendSearch() {
- emit UpdateStatus("Searching...");
+ emit UpdateStatus(tr("Searching..."));
QList parameters;
parameters << Param("q", search_text_);
@@ -503,7 +503,7 @@ void DeezerService::SearchFinished(QNetworkReply *reply, int id) {
QJsonArray json_data = json_value.toArray();
if (json_data.isEmpty()) {
- Error("No match.");
+ Error(tr("No match."));
CheckFinish();
return;
}
@@ -588,7 +588,8 @@ void DeezerService::SearchFinished(QNetworkReply *reply, int id) {
}
if (albums_requested_ > 0) {
- emit UpdateStatus(QString("Retrieving %1 album%2...").arg(albums_requested_).arg(albums_requested_ == 1 ? "" : "s"));
+ if (albums_requested_ == 1) emit UpdateStatus(tr("Retrieving %1 album...").arg(albums_requested_));
+ else emit UpdateStatus(tr("Retrieving %1 albums...").arg(albums_requested_));
emit ProgressSetMaximum(albums_requested_);
emit UpdateProgress(0);
}
diff --git a/src/device/udisks2lister.cpp b/src/device/udisks2lister.cpp
index fe92dc43..dd925665 100644
--- a/src/device/udisks2lister.cpp
+++ b/src/device/udisks2lister.cpp
@@ -1,19 +1,22 @@
-/* This file is part of Clementine.
- Copyright 2016, Valeriy Malov
-
- Clementine is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Clementine is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Clementine. If not, see .
-*/
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2016, Valeriy Malov
+ *
+ * Strawberry is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Strawberry is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Strawberry. If not, see .
+ *
+ */
#include "config.h"
diff --git a/src/device/udisks2lister.h b/src/device/udisks2lister.h
index e3ca3fd4..4c4d5799 100644
--- a/src/device/udisks2lister.h
+++ b/src/device/udisks2lister.h
@@ -1,19 +1,22 @@
-/* This file is part of Clementine.
- Copyright 2016, Valeriy Malov
-
- Clementine is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Clementine is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Clementine. If not, see .
-*/
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2016, Valeriy Malov
+ *
+ * Strawberry is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Strawberry is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Strawberry. If not, see .
+ *
+ */
#ifndef UDISKS2LISTER_H
#define UDISKS2LISTER_H
diff --git a/src/dialogs/about.cpp b/src/dialogs/about.cpp
index 0e3a3b14..24ee9872 100644
--- a/src/dialogs/about.cpp
+++ b/src/dialogs/about.cpp
@@ -75,7 +75,7 @@ About::About(QWidget *parent):QDialog(parent) {
<< Person("Valeriy Malov", "jazzvoid@gmail.com")
<< Person("Nick Lanham", "nick@afternight.org");
- QString Title("About Strawberry");
+ QString Title(tr("About Strawberry"));
QFont title_font;
title_font.setBold(true);
@@ -99,28 +99,27 @@ QString About::MainHtml() const {
QString ret;
- ret = QString("Version %1
").arg(QCoreApplication::applicationVersion());
+ ret = tr("Version %1
").arg(QCoreApplication::applicationVersion());
- ret += QString("");
- ret += QString("Strawberry is a audio player and music collection organizer.
");
- ret += QString("It is a fork of Clementine released in 2018 aimed at music collectors, audio enthusiasts and audiophiles.
");
- ret += QString("The name is inspired by the band Strawbs. It's based on a heavily modified version of Clementine created in 2012-2013. It's written in C++ and Qt 5.");
- ret += QString("
");
- //ret += QString("Website: http://www.strawbs.org/
");
- ret += QString("");
- ret += QString("Strawberry is free software: you can redistribute it and/or modify
");
- ret += QString("it under the terms of the GNU General Public License as published by
");
- ret += QString("the Free Software Foundation, either version 3 of the License, or
");
- ret += QString("(at your option) any later version.
");
- ret += QString("
");
- ret += QString("Strawberry is distributed in the hope that it will be useful,
");
- ret += QString("but WITHOUT ANY WARRANTY; without even the implied warranty of
");
- ret += QString("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
");
- ret += QString("GNU General Public License for more details.
");
- ret += QString("
");
- ret += QString("You should have received a copy of the GNU General Public License
");
- ret += QString("along with Strawberry. If not, see http://www.gnu.org/licenses/.");
- ret += QString("
");
+ ret += "";
+ ret += tr("Strawberry is a audio player and music collection organizer.
");
+ ret += tr("It is a fork of Clementine released in 2018 aimed at music collectors, audio enthusiasts and audiophiles.
");
+ ret += tr("The name is inspired by the band Strawbs. It's based on a heavily modified version of Clementine created in 2012-2013. It's written in C++ and Qt 5.");
+ ret += "
";
+ ret += "";
+ ret += tr("Strawberry is free software: you can redistribute it and/or modify
");
+ ret += tr("it under the terms of the GNU General Public License as published by
");
+ ret += tr("the Free Software Foundation, either version 3 of the License, or
");
+ ret += tr("(at your option) any later version.
");
+ ret += "
";
+ ret += tr("Strawberry is distributed in the hope that it will be useful,
");
+ ret += tr("but WITHOUT ANY WARRANTY; without even the implied warranty of
");
+ ret += tr("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
");
+ ret += tr("GNU General Public License for more details.
");
+ ret += "
";
+ ret += tr("You should have received a copy of the GNU General Public License
");
+ ret += tr("along with Strawberry. If not, see http://www.gnu.org/licenses/.");
+ ret += "
";
return ret;
@@ -130,25 +129,25 @@ QString About::ContributorsHtml() const {
QString ret;
- ret += QString("Strawberry Authors");
+ ret += tr("
Strawberry Authors");
for (const Person &person : strawberry_authors_) {
ret += "
" + PersonToHtml(person);
}
- ret += QString("
");
+ ret += "
";
- ret += QString("Clementine Authors");
+ ret += tr("
Clementine Authors");
for (const Person &person : clementine_authors_) {
ret += "
" + PersonToHtml(person);
}
- ret += QString("
");
+ ret += "";
- ret += QString("Clementine Contributors");
+ ret += tr("
Clementine Contributors");
for (const Person &person : constributors_) {
ret += "
" + PersonToHtml(person);
}
- ret += QString("
");
+ ret += "";
- ret += QString("Thanks to all the Amarok and Clementine contributors.
");
+ ret += tr("Thanks to all the Amarok and Clementine contributors.
");
return ret;
}
diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp
index caf52438..ab5a3972 100644
--- a/src/dialogs/edittagdialog.cpp
+++ b/src/dialogs/edittagdialog.cpp
@@ -899,7 +899,7 @@ void EditTagDialog::SongSaveComplete(TagReaderReply *reply, const QString filena
pending_--;
if (!reply->message().save_file_response().success()) {
- QString message = QString("An error occurred writing metadata to '%1'").arg(filename);
+ QString message = tr("An error occurred writing metadata to '%1'").arg(filename);
emit Error(message);
}
else if (song.directory_id() != -1) {
diff --git a/src/engine/enginebase.cpp b/src/engine/enginebase.cpp
index 1ce16043..bfcb4f51 100644
--- a/src/engine/enginebase.cpp
+++ b/src/engine/enginebase.cpp
@@ -17,7 +17,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with Clementine. If not, see .
+ * along with Strawberry. If not, see .
*
*/
diff --git a/src/engine/enginebase.h b/src/engine/enginebase.h
index 7f016a36..f610c438 100644
--- a/src/engine/enginebase.h
+++ b/src/engine/enginebase.h
@@ -17,7 +17,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with Clementine. If not, see .
+ * along with Strawberry. If not, see .
*
*/
diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp
index 5b74fffc..2486cc6b 100644
--- a/src/internet/internetsearchview.cpp
+++ b/src/internet/internetsearchview.cpp
@@ -141,7 +141,7 @@ InternetSearchView::InternetSearchView(Application *app, InternetSearch *engine,
QMenu *settings_menu = new QMenu(this);
settings_menu->addActions(group_by_actions_->actions());
settings_menu->addSeparator();
- settings_menu->addAction(IconLoader::Load("configure"), QString("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog()));
+ settings_menu->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(engine->source())), this, SLOT(OpenSettingsDialog()));
ui_->settings->setMenu(settings_menu);
connect(ui_->radiobutton_search_artists, SIGNAL(clicked(bool)), SLOT(SearchArtistsClicked(bool)));
@@ -430,7 +430,7 @@ bool InternetSearchView::ResultsContextMenuEvent(QContextMenuEvent *event) {
context_menu_->addSeparator();
context_menu_->addMenu(tr("Group by"))->addActions(group_by_actions_->actions());
- context_menu_->addAction(IconLoader::Load("configure"), QString("Configure %1...").arg(Song::TextForSource(engine_->source())), this, SLOT(OpenSettingsDialog()));
+ context_menu_->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(engine_->source())), this, SLOT(OpenSettingsDialog()));
const bool enable_context_actions = ui_->results->selectionModel() && ui_->results->selectionModel()->hasSelection();
diff --git a/src/main.cpp b/src/main.cpp
index 207d2418..f8044f82 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -51,11 +51,13 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -64,6 +66,9 @@
#ifdef HAVE_DBUS
# include
#endif
+#ifdef HAVE_TRANSLATIONS
+# include
+#endif
#include "main.h"
@@ -84,6 +89,10 @@
#include "core/application.h"
#include "core/networkproxyfactory.h"
#include "core/scangiomodulepath.h"
+#ifdef HAVE_TRANSLATIONS
+# include "core/potranslator.h"
+#endif
+#include "settings/behavioursettingspage.h"
#include "widgets/osd.h"
@@ -206,11 +215,31 @@ int main(int argc, char* argv[]) {
// Resources
Q_INIT_RESOURCE(data);
Q_INIT_RESOURCE(icons);
+#ifdef HAVE_TRANSLATIONS
+ Q_INIT_RESOURCE(translations);
+#endif
#ifdef DEBUG
QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, true);
#endif
+#ifdef HAVE_TRANSLATIONS
+ QString override_language = options.language();
+ if (override_language.isEmpty()) {
+ QSettings s;
+ s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
+ override_language = s.value("language").toString();
+ s.endGroup();
+ }
+
+ const QString language = override_language.isEmpty() ? Utilities::SystemLanguageName() : override_language;
+
+ Utilities::LoadTranslation("qt", QLibraryInfo::location(QLibraryInfo::TranslationsPath), language);
+ Utilities::LoadTranslation("strawberry", ":/translations", language);
+ Utilities::LoadTranslation("strawberry", a.applicationDirPath(), language);
+ Utilities::LoadTranslation("strawberry", QDir::currentPath(), language);
+#endif
+
Application app;
// Network proxy
diff --git a/src/queue/queueview.ui b/src/queue/queueview.ui
index 547dee15..95affed1 100644
--- a/src/queue/queueview.ui
+++ b/src/queue/queueview.ui
@@ -55,7 +55,7 @@
- Ctrl+↑
+ Ctrl+Up
@@ -74,7 +74,7 @@
- Ctrl+↓
+ Ctrl+Down
diff --git a/src/scrobbler/listenbrainzscrobbler.cpp b/src/scrobbler/listenbrainzscrobbler.cpp
index fa034377..bd062bcd 100644
--- a/src/scrobbler/listenbrainzscrobbler.cpp
+++ b/src/scrobbler/listenbrainzscrobbler.cpp
@@ -141,7 +141,7 @@ void ListenBrainzScrobbler::Authenticate() {
bool result = QDesktopServices::openUrl(url);
if (!result) {
- QMessageBox messagebox(QMessageBox::Information, "ListenBrainz Authentication", QString("Please open this URL in your browser:
%1").arg(url.toString()), QMessageBox::Ok);
+ QMessageBox messagebox(QMessageBox::Information, tr("ListenBrainz Authentication"), tr("Please open this URL in your browser:
%1").arg(url.toString()), QMessageBox::Ok);
messagebox.setTextFormat(Qt::RichText);
messagebox.exec();
}
diff --git a/src/scrobbler/scrobblingapi20.cpp b/src/scrobbler/scrobblingapi20.cpp
index e5628bd4..d91bda43 100644
--- a/src/scrobbler/scrobblingapi20.cpp
+++ b/src/scrobbler/scrobblingapi20.cpp
@@ -132,7 +132,7 @@ void ScrobblingAPI20::Authenticate() {
url_query.addQueryItem("cb", redirect_url.toString());
url.setQuery(url_query);
- QMessageBox messagebox(QMessageBox::Information, QString("%1 Scrobbler Authentication").arg(name_), QString("Open URL in web browser?
%1
Press \"Save\" to copy the URL to clipboard and manually open it in a web browser.").arg(url.toString()), QMessageBox::Open|QMessageBox::Save|QMessageBox::Cancel);
+ QMessageBox messagebox(QMessageBox::Information, tr("%1 Scrobbler Authentication").arg(name_), tr("Open URL in web browser?
%1
Press \"Save\" to copy the URL to clipboard and manually open it in a web browser.").arg(url.toString()), QMessageBox::Open|QMessageBox::Save|QMessageBox::Cancel);
messagebox.setTextFormat(Qt::RichText);
int result = messagebox.exec();
switch (result) {
@@ -141,7 +141,7 @@ void ScrobblingAPI20::Authenticate() {
if (openurl_result) {
break;
}
- QMessageBox messagebox_error(QMessageBox::Warning, QString("%1 Scrobbler Authentication").arg(name_), QString("Could not open URL. Please open this URL in your browser:
%1").arg(url.toString()), QMessageBox::Ok);
+ QMessageBox messagebox_error(QMessageBox::Warning, tr("%1 Scrobbler Authentication").arg(name_), tr("Could not open URL. Please open this URL in your browser:
%1").arg(url.toString()), QMessageBox::Ok);
messagebox_error.setTextFormat(Qt::RichText);
messagebox_error.exec();
}
@@ -149,7 +149,7 @@ void ScrobblingAPI20::Authenticate() {
QApplication::clipboard()->setText(url.toString());
break;
case QMessageBox::Cancel:
- AuthError("Authentication was cancelled.");
+ AuthError(tr("Authentication was cancelled."));
break;
default:
break;
@@ -436,7 +436,7 @@ void ScrobblingAPI20::Scrobble(const Song &song) {
if (app_->scrobbler()->IsOffline()) return;
if (!IsAuthenticated()) {
- emit ErrorMessage(QString("Scrobbler %1 is not authenticated!").arg(name_));
+ emit ErrorMessage(tr("Scrobbler %1 is not authenticated!").arg(name_));
return;
}
diff --git a/src/settings/behavioursettingspage.cpp b/src/settings/behavioursettingspage.cpp
index 884c60d8..a7f93c62 100644
--- a/src/settings/behavioursettingspage.cpp
+++ b/src/settings/behavioursettingspage.cpp
@@ -21,6 +21,10 @@
#include "config.h"
#include
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -37,6 +41,14 @@ class SettingsDialog;
const char *BehaviourSettingsPage::kSettingsGroup = "Behaviour";
+#ifdef HAVE_TRANSLATIONS
+namespace {
+bool LocaleAwareCompare(const QString &a, const QString &b) {
+ return a.localeAwareCompare(b) < 0;
+}
+} // namespace
+#endif
+
BehaviourSettingsPage::BehaviourSettingsPage(SettingsDialog *dialog) : SettingsPage(dialog), ui_(new Ui_BehaviourSettingsPage) {
ui_->setupUi(this);
@@ -71,6 +83,43 @@ BehaviourSettingsPage::BehaviourSettingsPage(SettingsDialog *dialog) : SettingsP
ui_->combobox_menuplaymode->setItemData(1, MainWindow::PlayBehaviour_IfStopped);
ui_->combobox_menuplaymode->setItemData(2, MainWindow::PlayBehaviour_Always);
+#ifdef HAVE_TRANSLATIONS
+ // Populate the language combo box. We do this by looking at all the compiled in translations.
+ QDir dir(":/translations/");
+ QStringList codes(dir.entryList(QStringList() << "*.qm"));
+ QRegExp lang_re("^strawberry_(.*).qm$");
+ for (const QString &filename : codes) {
+
+ // The regex captures the "ru" from "strawberry_ru.qm"
+ if (!lang_re.exactMatch(filename)) continue;
+
+ QString code = lang_re.cap(1);
+ QString lookup_code = QString(code)
+ .replace("@latin", "_Latn")
+ .replace("_CN", "_Hans_CN")
+ .replace("_TW", "_Hant_TW");
+
+ QString language_name = QLocale::languageToString(QLocale(lookup_code).language());
+ QString native_name = QLocale(lookup_code).nativeLanguageName();
+ if (!native_name.isEmpty()) {
+ language_name = native_name;
+ }
+ QString name = QString("%1 (%2)").arg(language_name, code);
+
+ language_map_[name] = code;
+ }
+
+ language_map_["English (en)"] = "en";
+
+ // Sort the names and show them in the UI
+ QStringList names = language_map_.keys();
+ std::stable_sort(names.begin(), names.end(), LocaleAwareCompare);
+ ui_->combobox_language->addItems(names);
+#else
+ ui_->groupbox_language->setEnabled(false);
+ ui_->groupbox_language->setVisible(false);
+#endif
+
}
BehaviourSettingsPage::~BehaviourSettingsPage() {
@@ -114,6 +163,12 @@ void BehaviourSettingsPage::Load() {
ui_->spinbox_seekstepsec->setValue(s.value("seek_step_sec", 10).toInt());
+ QString name = language_map_.key(s.value("language").toString());
+ if (name.isEmpty())
+ ui_->combobox_language->setCurrentIndex(0);
+ else
+ ui_->combobox_language->setCurrentIndex(ui_->combobox_language->findText(name));
+
s.endGroup();
}
@@ -141,6 +196,9 @@ void BehaviourSettingsPage::Save() {
s.setValue("doubleclick_playmode", doubleclick_playmode);
s.setValue("menu_playmode", menu_playmode);
s.setValue("seek_step_sec", ui_->spinbox_seekstepsec->value());
+
+ s.setValue("language", language_map_.contains(ui_->combobox_language->currentText()) ? language_map_[ui_->combobox_language->currentText()] : QString());
+
s.endGroup();
}
diff --git a/src/settings/behavioursettingspage.h b/src/settings/behavioursettingspage.h
index a059a470..4d84e19f 100644
--- a/src/settings/behavioursettingspage.h
+++ b/src/settings/behavioursettingspage.h
@@ -26,6 +26,7 @@
#include
#include
+#include
#include
#include "settingspage.h"
@@ -49,6 +50,7 @@ private slots:
private:
Ui_BehaviourSettingsPage *ui_;
+ QMap language_map_;
};
diff --git a/src/settings/behavioursettingspage.ui b/src/settings/behavioursettingspage.ui
index 4776a0c2..dc2dcbfe 100644
--- a/src/settings/behavioursettingspage.ui
+++ b/src/settings/behavioursettingspage.ui
@@ -101,6 +101,38 @@
+ -
+
+
+ Language
+
+
+
-
+
+
-
+
+ Use the system default
+
+
+
+
+ -
+
+
+ You will need to restart Strawberry if you change the language.
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
-
@@ -243,7 +275,7 @@
-
-
+
Qt::Horizontal
diff --git a/src/settings/shortcutssettingspage.cpp b/src/settings/shortcutssettingspage.cpp
index 3d97e0be..5a71d27c 100644
--- a/src/settings/shortcutssettingspage.cpp
+++ b/src/settings/shortcutssettingspage.cpp
@@ -237,7 +237,7 @@ void GlobalShortcutsSettingsPage::OpenGnomeKeybindingProperties() {
if (!QProcess::startDetached("gnome-keybinding-properties")) {
if (!QProcess::startDetached("gnome-control-center", QStringList() << "keyboard")) {
- QMessageBox::warning(this, "Error", QString("The \"%1\" command could not be started.").arg("gnome-keybinding-properties"));
+ QMessageBox::warning(this, "Error", tr("The \"%1\" command could not be started.").arg("gnome-keybinding-properties"));
}
}
diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp
index f3791e91..b10fd058 100644
--- a/src/tidal/tidalservice.cpp
+++ b/src/tidal/tidalservice.cpp
@@ -148,7 +148,7 @@ void TidalService::SendLogin() {
void TidalService::SendLogin(const QString &username, const QString &password) {
- if (search_id_ != 0) emit UpdateStatus("Authenticating...");
+ if (search_id_ != 0) emit UpdateStatus(tr("Authenticating..."));
login_sent_ = true;
login_attempts_++;
@@ -471,7 +471,7 @@ int TidalService::Search(const QString &text, InternetSearch::SearchType type) {
void TidalService::StartSearch() {
if (username_.isEmpty() || password_.isEmpty()) {
- emit SearchError(pending_search_id_, "Missing username and/or password.");
+ emit SearchError(pending_search_id_, tr("Missing username and/or password."));
next_pending_search_id_ = 1;
ShowConfig();
return;
@@ -509,7 +509,7 @@ void TidalService::ClearSearch() {
void TidalService::SendSearch() {
- emit UpdateStatus("Searching...");
+ emit UpdateStatus(tr("Searching..."));
switch (pending_search_type_) {
case InternetSearch::SearchType_Artists:
@@ -582,7 +582,7 @@ void TidalService::ArtistsReceived(QNetworkReply *reply, int search_id) {
QJsonArray json_items = json_value.toArray();
if (json_items.isEmpty()) {
artist_search_ = false;
- Error("No match.");
+ Error(tr("No match."));
return;
}
@@ -610,7 +610,8 @@ void TidalService::ArtistsReceived(QNetworkReply *reply, int search_id) {
}
if (artist_albums_requested_ > 0) {
- emit UpdateStatus(QString("Retrieving albums for %1 artist%2...").arg(artist_albums_requested_).arg(artist_albums_requested_ == 1 ? "" : "s"));
+ if (artist_albums_requested_ == 1) emit UpdateStatus(tr("Retrieving albums for %1 artist...").arg(artist_albums_requested_));
+ else emit UpdateStatus(tr("Retrieving albums for %1 artists...").arg(artist_albums_requested_));
emit ProgressSetMaximum(artist_albums_requested_);
emit UpdateProgress(0);
}
@@ -802,7 +803,8 @@ void TidalService::AlbumsFinished(const int artist_id, const int offset_requeste
}
if (album_songs_requested_ > 0) {
- emit UpdateStatus(QString("Retrieving songs for %1 album%2...").arg(album_songs_requested_).arg(album_songs_requested_ == 1 ? "" : "s"));
+ if (album_songs_requested_ == 1) emit UpdateStatus(tr("Retrieving songs for %1 album...").arg(album_songs_requested_));
+ else emit UpdateStatus(tr("Retrieving songs for %1 albums...").arg(album_songs_requested_));
emit ProgressSetMaximum(album_songs_requested_);
emit UpdateProgress(0);
}
diff --git a/src/translations/header b/src/translations/header
new file mode 100644
index 00000000..6a423af2
--- /dev/null
+++ b/src/translations/header
@@ -0,0 +1,8 @@
+# Strawberry Music Player
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+