diff --git a/docs/settings.rst b/docs/settings.rst index 2a29f3a8..fcafbd6c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -18,7 +18,7 @@ Rosetta can be configured via the following parameters, to be defined in your pr * ``ROSETTA_CACHE_NAME``: When using ``rosetta.storage.CacheRosettaStorage``, you can store the Rosetta data in a specific cache. This is particularly useful when your ``default`` cache is a ``django.core.cache.backends.dummy.DummyCache`` (which happens on pre-production environments). If unset, it will default to ``rosetta`` if a cache with this name exists, or ``default`` if not. * ``ROSETTA_POFILENAMES``: Defines which po file names are exposed in the web interface. Defaults to ``('django.po', 'djangojs.po')`` * ``ROSETTA_EXCLUDED_PATHS``: Exclude paths defined in this list from being searched (usually ends with "locale"). Defaults to ``()`` - +* ``ROSETTA_AUTO_COMPILE``: Determines whether the MO file is automatically compiled when the PO file is saved. Defaults to ``True``. Storages -------- diff --git a/rosetta/conf/settings.py b/rosetta/conf/settings.py index d24f8251..81310a8d 100644 --- a/rosetta/conf/settings.py +++ b/rosetta/conf/settings.py @@ -95,3 +95,6 @@ # 'translators` group, create individual per-language groups, e.g. # 'translators-de', 'translators-fr', ... ROSETTA_LANGUAGE_GROUPS = getattr(settings, 'ROSETTA_LANGUAGE_GROUPS', False) + +# Determines whether the MO file is automatically compiled when the PO file is saved. +AUTO_COMPILE = getattr(settings, 'ROSETTA_AUTO_COMPILE', True) diff --git a/rosetta/tests/tests.py b/rosetta/tests/tests.py index 2defbc6c..513ea098 100644 --- a/rosetta/tests/tests.py +++ b/rosetta/tests/tests.py @@ -14,6 +14,7 @@ import six import django import vcr +import hashlib class RosettaTestCase(TestCase): @@ -58,6 +59,7 @@ def setUp(self): self.__require_auth = rosetta_settings.ROSETTA_REQUIRES_AUTH self.__google_translate = rosetta_settings.GOOGLE_TRANSLATE self.__enable_translation = rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS + self.__auto_compile = rosetta_settings.AUTO_COMPILE shutil.copy(self.dest_file, self.dest_file + '.orig') @@ -68,6 +70,7 @@ def tearDown(self): rosetta_settings.ROSETTA_REQUIRES_AUTH = self.__require_auth rosetta_settings.GOOGLE_TRANSLATE = self.__google_translate rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS = self.__enable_translation + rosetta_settings.AUTO_COMPILE = self.__auto_compile shutil.move(self.dest_file + '.orig', self.dest_file) def test_1_ListLoading(self): @@ -726,6 +729,83 @@ def test_38_issue_161_more_weird_locales(self): r = self.client.get(reverse('rosetta-pick-file')) self.assertTrue(os.path.normpath('locale/zh_Hans/LC_MESSAGES/django.po') in str(r.content)) + def test_40_issue_155_auto_compile(self): + + def file_hash(file_string): + if six.PY3: + with open(file_string, encoding="latin-1") as file: + file_content = file.read().encode('utf-8') + else: + with open(file_string) as file: + file_content = file.read() + return hashlib.md5(file_content).hexdigest() + + def message_hashes(): + r = self.client.get(reverse('rosetta-home')) + return dict([(m.msgid, 'm_' + m.md5hash) for m in r.context['rosetta_messages']]) + + po_file = self.dest_file + mo_file = self.dest_file[:-3] + '.mo' + + # Load the template file + self.client.get(reverse('rosetta-pick-file') + '?filter=third-party') + self.client.get(reverse('rosetta-language-selection', args=('xx', 0), kwargs=dict())) + + # MO file will be compiled by default. + # Get PO and MO files into an initial reference state (MO will be created or updated) + msg_hashes = message_hashes() + self.client.post(reverse('rosetta-home'), { + msg_hashes['String 1']: "Translation 1", '_next': '_next'}) + po_file_hash_before, mo_file_hash_before = file_hash(po_file), file_hash(mo_file) + + # Make a change to the translations + msg_hashes = message_hashes() + self.client.post(reverse('rosetta-home'), { + msg_hashes['String 1']: "Translation 2", '_next': '_next'}) + + # Get the new hashes of the PO and MO file contents + po_file_hash_after, mo_file_hash_after = file_hash(po_file), file_hash(mo_file) + + # Both the PO and MO should have changed + self.assertNotEqual(po_file_hash_before, po_file_hash_after) + self.assertNotEqual(mo_file_hash_before, mo_file_hash_after) + + # Disable auto-compilation of the MO when the PO is saved + rosetta_settings.AUTO_COMPILE = False + + # Make a change to the translations + po_file_hash_before, mo_file_hash_before = po_file_hash_after, mo_file_hash_after + msg_hashes = message_hashes() + self.client.post(reverse('rosetta-home'), { + msg_hashes['String 1']: "Translation 3", '_next': '_next'}) + po_file_hash_after, mo_file_hash_after = file_hash(po_file), file_hash(mo_file) + + # Only the PO should have changed, the MO should be unchanged + self.assertNotEqual(po_file_hash_before, po_file_hash_after) + self.assertEqual(mo_file_hash_before, mo_file_hash_after) + + # Verify that translating another string also leaves the MO unchanged + po_file_hash_before, mo_file_hash_before = po_file_hash_after, mo_file_hash_after + msg_hashes = message_hashes() + self.client.post(reverse('rosetta-home'), { + msg_hashes['String 2']: "Translation 4", '_next': '_next'}) + po_file_hash_after, mo_file_hash_after = file_hash(po_file), file_hash(mo_file) + + self.assertNotEqual(po_file_hash_before, po_file_hash_after) + self.assertEqual(mo_file_hash_before, mo_file_hash_after) + + # Double check that switching back to auto compilation updates the MO + rosetta_settings.AUTO_COMPILE = True + + po_file_hash_before, mo_file_hash_before = po_file_hash_after, mo_file_hash_after + msg_hashes = message_hashes() + self.client.post(reverse('rosetta-home'), { + msg_hashes['String 2']: "Translation 5", '_next': '_next'}) + po_file_hash_after, mo_file_hash_after = file_hash(po_file), file_hash(mo_file) + + self.assertNotEqual(po_file_hash_before, po_file_hash_after) + self.assertNotEqual(mo_file_hash_before, mo_file_hash_after) + # Stubbed access control function def no_access(user): diff --git a/rosetta/views.py b/rosetta/views.py index 330e7057..29a5298c 100644 --- a/rosetta/views.py +++ b/rosetta/views.py @@ -161,8 +161,10 @@ def _request_request(key, default=None): try: rosetta_i18n_pofile.save() po_filepath, ext = os.path.splitext(rosetta_i18n_fn) - save_as_mo_filepath = po_filepath + '.mo' - rosetta_i18n_pofile.save_as_mofile(save_as_mo_filepath) + + if rosetta_settings.AUTO_COMPILE: + save_as_mo_filepath = po_filepath + '.mo' + rosetta_i18n_pofile.save_as_mofile(save_as_mo_filepath) post_save.send(sender=None, language_code=rosetta_i18n_lang_code, request=request) # Try auto-reloading via the WSGI daemon mode reload mechanism