diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f66ed8ab..13787ccea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,3 +9,4 @@ OpenLP 2.5.1 * Fix a problem with the new QMediaPlayer not controlling the playlist anymore * Added importing of author types to the OpenLP 2 song importer * Refactored the merge script and gave it some options +* Fix a problem with loading Qt's translation files, bug #1676163 diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 36c233dcd..aa44d1d1b 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -428,13 +428,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_translators(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/common/languagemanager.py b/openlp/core/common/languagemanager.py index 0e1787d2e..5023f7383 100644 --- a/openlp/core/common/languagemanager.py +++ b/openlp/core/common/languagemanager.py @@ -45,7 +45,7 @@ class LanguageManager(object): auto_language = False @staticmethod - def get_translator(language): + def get_translators(language): """ Set up a translator to use in this instance of OpenLP @@ -59,9 +59,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(): @@ -71,8 +74,8 @@ class LanguageManager(object): log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir))) 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_')] + # Remove qm files from the list which start with "qt". + file_names = [file_ for file_ in file_names if not file_.startswith('qt')] 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 5ee6f561a..a29c048e2 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_ @@ -184,7 +183,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): """ @@ -194,7 +193,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): """ @@ -205,9 +204,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 69881443a..09c12c136 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): """ @@ -290,3 +291,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