diff --git a/wagtail_localize/tests/test_update_translations.py b/wagtail_localize/tests/test_update_translations.py index c828aae0..98a2c266 100644 --- a/wagtail_localize/tests/test_update_translations.py +++ b/wagtail_localize/tests/test_update_translations.py @@ -5,12 +5,18 @@ from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError +from django.forms.widgets import CheckboxInput, HiddenInput from django.test import TestCase, override_settings from django.urls import reverse from wagtail.models import Locale, Page, PageViewRestriction from wagtail.test.utils import WagtailTestUtils -from wagtail_localize.models import StringSegment, Translation, TranslationSource +from wagtail_localize.models import ( + StringSegment, + StringTranslation, + Translation, + TranslationSource, +) from wagtail_localize.test.models import NonTranslatableSnippet, TestSnippet from .utils import assert_permission_denied, make_test_page @@ -612,3 +618,99 @@ def test_post_update_page_translation_will_run_update_target_view_restrictions( ) # one call from the first post, and one from above self.assertEqual(update_target_view_restrictions.call_count, 2) + + @mock.patch( + "wagtail_localize.views.update_translations.HAS_MACHINE_TRANSLATOR", False + ) + def test_update_translations_form_without_machine_translator(self): + response = self.client.get( + reverse( + "wagtail_localize:update_translations", + args=[self.snippet_source.id], + ) + ) + + self.assertEqual(response.status_code, 200) + + self.assertIsInstance( + response.context["form"].fields["use_machine_translation"].widget, + HiddenInput, + ) + + def test_update_translations_form_with_machine_translator(self): + response = self.client.get( + reverse( + "wagtail_localize:update_translations", + args=[self.snippet_source.id], + ) + ) + + self.assertEqual(response.status_code, 200) + + self.assertIsInstance( + response.context["form"].fields["use_machine_translation"].widget, + CheckboxInput, + ) + + def test_post_update_page_translation_with_publish_translations_and_use_machine_translation( + self, + ): + self.en_blog_post.test_charfield = "Edited blog post" + self.en_blog_post.save_revision().publish() + + response = self.client.post( + reverse( + "wagtail_localize:update_translations", + args=[self.page_source.id], + ), + { + "publish_translations": "on", + "use_machine_translation": "on", + }, + ) + + self.assertRedirects( + response, reverse("wagtailadmin_explore", args=[self.en_blog_index.id]) + ) + + # The FR version should be translated (Dummy translator will reverse) and updated + self.fr_blog_post.refresh_from_db() + self.assertEqual(self.fr_blog_post.test_charfield, "post blog Edited") + + def test_post_update_page_translation_with_use_machine_translation(self): + self.en_blog_post.test_charfield = "Edited blog post" + self.en_blog_post.save_revision().publish() + + response = self.client.post( + reverse( + "wagtail_localize:update_translations", + args=[self.page_source.id], + ), + { + "use_machine_translation": "on", + }, + ) + + self.assertRedirects( + response, reverse("wagtailadmin_explore", args=[self.en_blog_index.id]) + ) + + self.fr_blog_post.refresh_from_db() + self.assertEqual(self.fr_blog_post.test_charfield, "Test content") + + # Check that the translation is done, awaiting publication + fr = Locale.objects.get(language_code="fr") + translation_source = TranslationSource.objects.get_for_instance_or_none( + self.en_blog_post + ) + translation = translation_source.translations.get(target_locale=fr) + + string_segment = translation.source.stringsegment_set.get( + string__data="Edited blog post" + ) + string_translation = StringTranslation.objects.get( + translation_of_id=string_segment.string_id, + locale=fr, + context_id=string_segment.context_id, + ) + self.assertEqual(string_translation.data, "post blog Edited") diff --git a/wagtail_localize/views/edit_translation.py b/wagtail_localize/views/edit_translation.py index 4f2ff023..b2a13076 100644 --- a/wagtail_localize/views/edit_translation.py +++ b/wagtail_localize/views/edit_translation.py @@ -1370,19 +1370,14 @@ def upload_pofile(request, translation_id): return redirect(next_url) -@require_POST -def machine_translate(request, translation_id): +def apply_machine_translation(translation_id, user, machine_translator): translation = get_object_or_404(Translation, id=translation_id) instance = translation.get_target_instance() - if not user_can_edit_instance(request.user, instance): + if not user_can_edit_instance(user, instance): raise PermissionDenied - translator = get_machine_translator() - if translator is None: - raise Http404 - - if not translator.can_translate( + if not machine_translator.can_translate( translation.source.locale, translation.target_locale ): raise Http404 @@ -1411,7 +1406,7 @@ def machine_translate(request, translation_id): ) if segments: - translations = translator.translate( + translations = machine_translator.translate( translation.source.locale, translation.target_locale, segments.keys() ) @@ -1425,16 +1420,28 @@ def machine_translate(request, translation_id): defaults={ "data": translations[string].data, "translation_type": StringTranslation.TRANSLATION_TYPE_MACHINE, - "tool_name": translator.display_name, - "last_translated_by": request.user, + "tool_name": machine_translator.display_name, + "last_translated_by": user, "has_error": False, "field_error": "", }, ) + return True + return False + +@require_POST +def machine_translate(request, translation_id): + machine_translator = get_machine_translator() + if machine_translator is None: + raise Http404 + + if apply_machine_translation(translation_id, request.user, machine_translator): messages.success( request, - _("Successfully translated with {}.").format(translator.display_name), + _("Successfully translated with {}.").format( + machine_translator.display_name + ), ) else: diff --git a/wagtail_localize/views/update_translations.py b/wagtail_localize/views/update_translations.py index 09093da6..b427187c 100644 --- a/wagtail_localize/views/update_translations.py +++ b/wagtail_localize/views/update_translations.py @@ -17,18 +17,50 @@ from wagtail.snippets.models import get_snippet_models from wagtail.utils.version import get_main_version +from wagtail_localize.machine_translators import get_machine_translator from wagtail_localize.models import TranslationSource +from wagtail_localize.views.edit_translation import apply_machine_translation from wagtail_localize.views.submit_translations import TranslationComponentManager +HAS_MACHINE_TRANSLATOR = get_machine_translator() is not None + +if HAS_MACHINE_TRANSLATOR: + PUBLISH_TRANSLATIONS_HELP = ( + "Apply the updates and publish immediately. The changes will use " + "the original language until translated unless you also select " + '"Use machine translation".' + ) +else: + PUBLISH_TRANSLATIONS_HELP = ( + "Apply the updates and publish immediately. The changes will use " + "the original language until translated." + ) + +USE_MACHINE_TRANSLATION_HELP = "Apply machine translations to the incoming changes." + + class UpdateTranslationsForm(forms.Form): publish_translations = forms.BooleanField( label=gettext_lazy("Publish immediately"), - help_text=gettext_lazy( - "This will apply the updates and publish immediately, before any new translations happen." - ), + help_text=gettext_lazy(PUBLISH_TRANSLATIONS_HELP), required=False, ) + use_machine_translation = forms.BooleanField( + label=gettext_lazy("Use machine translation"), + help_text=gettext_lazy(USE_MACHINE_TRANSLATION_HELP), + required=False, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not HAS_MACHINE_TRANSLATOR: + self.fields["use_machine_translation"].widget = forms.HiddenInput() + + def clean(self): + cleaned_data = super().clean() + if cleaned_data.get("use_machine_translation") and not HAS_MACHINE_TRANSLATOR: + raise ValidationError(_("A machine translator could not be found.")) class UpdateTranslationsView(SingleObjectMixin, TemplateView): @@ -133,6 +165,12 @@ def form_valid(self, form): self.object.update_from_db() enabled_translations = self.object.translations.filter(enabled=True) + if form.cleaned_data["use_machine_translation"]: + machine_translator = get_machine_translator() + for translation in enabled_translations.select_related("target_locale"): + apply_machine_translation( + translation.id, self.request.user, machine_translator + ) if form.cleaned_data["publish_translations"]: for translation in enabled_translations.select_related("target_locale"):