diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9c95fdd7..d6ff32b4b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,3 +8,4 @@ OpenLP 2.4.6 * Fix opening the data folder (KDE thought the old way was an SMB share) * Fix a problem with the new QMediaPlayer not controlling the playlist anymore * Added importing of author types to the OpenLP 2 song importer +* Fix a problem with loading Qt's translation files, bug #1676163 diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 862345e68..a1dd2f8fc 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -426,13 +426,12 @@ def main(args=None): sys.exit() # i18n Set Language language = LanguageManager.get_language() - application_translator, default_translator = LanguageManager.get_translator(language) - if not application_translator.isEmpty(): - application.installTranslator(application_translator) - if not default_translator.isEmpty(): - application.installTranslator(default_translator) - else: - log.debug('Could not find default_translator.') + translators = LanguageManager.get_translator(language) + for translator in translators: + if not translator.isEmpty(): + application.installTranslator(translator) + if not translators: + log.debug('Could not find translators.') if args and not args.no_error_form: sys.excepthook = application.hook_exception sys.exit(application.run(qt_args)) diff --git a/openlp/core/utils/languagemanager.py b/openlp/core/utils/languagemanager.py index df0c6d7ca..e623c1d3c 100644 --- a/openlp/core/utils/languagemanager.py +++ b/openlp/core/utils/languagemanager.py @@ -24,7 +24,6 @@ The :mod:`languagemanager` module provides all the translation settings and lang """ import logging import re -import sys from PyQt5 import QtCore, QtWidgets @@ -56,9 +55,12 @@ class LanguageManager(object): # A translator for buttons and other default strings provided by Qt. if not is_win() and not is_macosx(): lang_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath) + # As of Qt5, the core translations come in 2 files per language default_translator = QtCore.QTranslator() default_translator.load('qt_%s' % language, lang_path) - return app_translator, default_translator + base_translator = QtCore.QTranslator() + base_translator.load('qtbase_%s' % language, lang_path) + return app_translator, default_translator, base_translator @staticmethod def find_qm_files(): @@ -69,7 +71,7 @@ class LanguageManager(object): trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir)) file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name) # Remove qm files from the list which start with "qt_". - file_names = [file_ for file_ in file_names if not file_.startswith('qt_')] + file_names = [file_ for file_ in file_names if not file_.startswith('qt_') or file_.startswith('qtbase_')] return list(map(trans_dir.filePath, file_names)) @staticmethod diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 113bf2b1b..871feb31d 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -20,7 +20,6 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### import logging -import os from PyQt5 import QtCore, QtWidgets from sqlalchemy.sql import and_ @@ -167,7 +166,7 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP Author.display_name == new_author.display_name ) ) - return self.__check_object_exists(authors, new_author, edit) + return self._check_object_exists(authors, new_author, edit) def check_topic_exists(self, new_topic, edit=False): """ @@ -177,7 +176,7 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP :param edit: Are we editing the song? """ topics = self.manager.get_all_objects(Topic, Topic.name == new_topic.name) - return self.__check_object_exists(topics, new_topic, edit) + return self._check_object_exists(topics, new_topic, edit) def check_song_book_exists(self, new_book, edit=False): """ @@ -188,9 +187,9 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP """ books = self.manager.get_all_objects( Book, and_(Book.name == new_book.name, Book.publisher == new_book.publisher)) - return self.__check_object_exists(books, new_book, edit) + return self._check_object_exists(books, new_book, edit) - def __check_object_exists(self, existing_objects, new_object, edit): + def _check_object_exists(self, existing_objects, new_object, edit): """ Utility method to check for an existing object. diff --git a/tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py b/tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py index 549303296..de336bd88 100644 --- a/tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py +++ b/tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py @@ -25,7 +25,7 @@ Package to test the openlp.plugins.songs.forms.songmaintenanceform package. from unittest import TestCase from unittest.mock import MagicMock, patch, call -from PyQt5 import QtCore, QtTest, QtWidgets +from PyQt5 import QtCore, QtWidgets from openlp.core.common import Registry, UiStrings from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm @@ -89,6 +89,7 @@ class TestSongMaintenanceForm(TestCase, TestMixin): mocked_reset_song_books.assert_called_once_with() mocked_type_list_widget.setFocus.assert_called_once_with() mocked_exec.assert_called_once_with(self.form) + assert result is True def test_get_current_item_id_no_item(self): """ @@ -291,3 +292,131 @@ class TestSongMaintenanceForm(TestCase, TestMixin): MockedQListWidgetItem.assert_called_once_with('Hymnal (Hymns and Psalms, Inc.)') mocked_song_book_item.setData.assert_called_once_with(QtCore.Qt.UserRole, 1) mocked_song_book_list_widget.addItem.assert_called_once_with(mocked_song_book_item) + + @patch('openlp.plugins.songs.forms.songmaintenanceform.and_') + @patch('openlp.plugins.songs.forms.songmaintenanceform.Author') + def test_check_author_exists(self, MockedAuthor, mocked_and): + """ + Test the check_author_exists() method + """ + # GIVEN: A bunch of mocked out stuff + MockedAuthor.first_name = 'John' + MockedAuthor.last_name = 'Newton' + MockedAuthor.display_name = 'John Newton' + mocked_new_author = MagicMock() + mocked_new_author.first_name = 'John' + mocked_new_author.last_name = 'Newton' + mocked_new_author.display_name = 'John Newton' + mocked_and.return_value = True + mocked_authors = [MagicMock(), MagicMock()] + self.mocked_manager.get_all_objects.return_value = mocked_authors + + # WHEN: check_author_exists() is called + with patch.object(self.form, '_check_object_exists') as mocked_check_object_exists: + mocked_check_object_exists.return_value = True + result = self.form.check_author_exists(mocked_new_author, edit=True) + + # THEN: The correct result is returned + mocked_and.assert_called_once_with(True, True, True) + self.mocked_manager.get_all_objects.assert_called_once_with(MockedAuthor, True) + mocked_check_object_exists.assert_called_once_with(mocked_authors, mocked_new_author, True) + assert result is True + + @patch('openlp.plugins.songs.forms.songmaintenanceform.Topic') + def test_check_topic_exists(self, MockedTopic): + """ + Test the check_topic_exists() method + """ + # GIVEN: Some mocked stuff + MockedTopic.name = 'Grace' + mocked_new_topic = MagicMock() + mocked_new_topic.name = 'Grace' + mocked_topics = [MagicMock(), MagicMock()] + self.mocked_manager.get_all_objects.return_value = mocked_topics + + # WHEN: check_topic_exists() is run + with patch.object(self.form, '_check_object_exists') as mocked_check_object_exists: + mocked_check_object_exists.return_value = True + result = self.form.check_topic_exists(mocked_new_topic, True) + + # THEN: The correct things should have been called + self.mocked_manager.get_all_objects.assert_called_once_with(MockedTopic, True) + mocked_check_object_exists.assert_called_once_with(mocked_topics, mocked_new_topic, True) + assert result is True + + @patch('openlp.plugins.songs.forms.songmaintenanceform.and_') + @patch('openlp.plugins.songs.forms.songmaintenanceform.Book') + def test_check_song_book_exists(self, MockedBook, mocked_and): + """ + Test the check_song_book_exists() method + """ + # GIVEN: Some mocked stuff + MockedBook.name = 'Hymns' + MockedBook.publisher = 'Christian Songs' + mocked_new_book = MagicMock() + mocked_new_book.name = 'Hymns' + mocked_new_book.publisher = 'Christian Songs' + mocked_and.return_value = True + mocked_books = [MagicMock(), MagicMock()] + self.mocked_manager.get_all_objects.return_value = mocked_books + + # WHEN: check_book_exists() is run + with patch.object(self.form, '_check_object_exists') as mocked_check_object_exists: + mocked_check_object_exists.return_value = True + result = self.form.check_song_book_exists(mocked_new_book, True) + + # THEN: The correct things should have been called + mocked_and.assert_called_once_with(True, True) + self.mocked_manager.get_all_objects.assert_called_once_with(MockedBook, True) + mocked_check_object_exists.assert_called_once_with(mocked_books, mocked_new_book, True) + assert result is True + + def test_check_object_exists_no_existing_objects(self): + """ + Test the _check_object_exists() method when there are no existing objects + """ + # GIVEN: A SongMaintenanceForm instance + # WHEN: _check_object_exists() is called without existing objects + result = self.form._check_object_exists([], None, False) + + # THEN: The result should be True + assert result is True + + def test_check_object_exists_without_edit(self): + """ + Test the _check_object_exists() method when edit is false + """ + # GIVEN: A SongMaintenanceForm instance + # WHEN: _check_object_exists() is called with edit set to false + result = self.form._check_object_exists([MagicMock()], None, False) + + # THEN: The result should be False + assert result is False + + def test_check_object_exists_not_found(self): + """ + Test the _check_object_exists() method when the object is not found + """ + # GIVEN: A SongMaintenanceForm instance and some mocked objects + mocked_existing_objects = [MagicMock(id=1)] + mocked_new_object = MagicMock(id=2) + + # WHEN: _check_object_exists() is called with edit set to false + result = self.form._check_object_exists(mocked_existing_objects, mocked_new_object, True) + + # THEN: The result should be False + assert result is False + + def test_check_object_exists(self): + """ + Test the _check_object_exists() method + """ + # GIVEN: A SongMaintenanceForm instance and some mocked objects + mocked_existing_objects = [MagicMock(id=1)] + mocked_new_object = MagicMock(id=1) + + # WHEN: _check_object_exists() is called with edit set to false + result = self.form._check_object_exists(mocked_existing_objects, mocked_new_object, True) + + # THEN: The result should be False + assert result is True