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" +