Skip to content

Commit

Permalink
Merge pull request #1135 from digitalfabrik/feature/deepl
Browse files Browse the repository at this point in the history
Add automatic translations via DeepL API
  • Loading branch information
JoeyStk authored Mar 23, 2022
2 parents 1fee501 + 2547e42 commit 5c5937e
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ UNRELEASED
----------

* [ [#1319](https://github.com/digitalfabrik/integreat-cms/issues/1319) ] Fix error on Imprint API
* [ [#1103](https://github.com/digitalfabrik/integreat-cms/issues/1103) ] Add automatic translations via DeepL API


2022.3.6
Expand Down
8 changes: 8 additions & 0 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions integreat_cms/cms/urls/protected.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
"""
from django.urls import include, path

from ..forms import LanguageForm, OfferTemplateForm, OrganizationForm, RegionForm
from ..forms import (
LanguageForm,
OfferTemplateForm,
OrganizationForm,
RegionForm,
EventTranslationForm,
POITranslationForm,
)
from ..models import Event, Language, OfferTemplate, Organization, Page, POI, Role

from ..views import (
Expand Down Expand Up @@ -835,7 +842,7 @@
path(
"auto-translate/",
bulk_action_views.BulkAutoTranslateView.as_view(
model=Event
model=Event, form=EventTranslationForm
),
name="automatic_translation_events",
),
Expand Down Expand Up @@ -918,7 +925,7 @@
path(
"auto-translate/",
bulk_action_views.BulkAutoTranslateView.as_view(
model=POI
model=POI, form=POITranslationForm
),
name="automatic_translation_pois",
),
Expand Down
23 changes: 18 additions & 5 deletions integreat_cms/cms/views/bulk_action_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.views.generic.list import MultipleObjectMixin

from cacheops import invalidate_model
from ...deepl_api.utils import DeepLApi

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -100,6 +101,9 @@ class BulkAutoTranslateView(BulkActionView):
#: Whether the public translation objects should be prefetched
prefetch_translations = True

#: the form of this bulk action
form = None

def post(self, request, *args, **kwargs):
r"""
Translate multiple objects automatically
Expand All @@ -116,14 +120,23 @@ def post(self, request, *args, **kwargs):
:return: The redirect
:rtype: ~django.http.HttpResponseRedirect
"""
if not settings.DEEPL_ENABLED:
messages.error(request, _("Automatic translations are disabled"))
return super().post(request, *args, **kwargs)
# Collect the corresponding objects
logger.debug("Automatic translation for: %r", self.get_queryset())

if settings.DEEPL_ENABLED:
messages.warning(
request, _("Automatic translations are not fully implemented yet")
deepl = DeepLApi()
if deepl.check_availability(request, kwargs.get("language_slug")):
deepl.deepl_translation(
request, self.get_queryset(), kwargs.get("language_slug"), self.form
)
else:
messages.error(request, _("Automatic translations are disabled"))
messages.warning(
request,
_(
"This language is not supported by DeepL. Please try another language"
),
)

# Let the base view handle the redirect
return super().post(request, *args, **kwargs)
Expand Down
4 changes: 4 additions & 0 deletions integreat_cms/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,10 @@
"handlers": ["console", "logfile"],
"level": DEPS_LOG_LEVEL,
},
"deepl": {
"handlers": ["console", "logfile"],
"level": DEPS_LOG_LEVEL,
},
"django": {
"handlers": ["console", "logfile"],
"level": DEPS_LOG_LEVEL,
Expand Down
Empty file.
146 changes: 146 additions & 0 deletions integreat_cms/deepl_api/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import logging
import deepl

from django.conf import settings
from django.contrib import messages
from django.utils.translation import ugettext as _

logger = logging.getLogger(__name__)


class DeepLApi:
"""
DeepL API to auto translate selected posts.
"""

def __init__(self):
"""
Initialize the DeepL client
"""
self.translator = deepl.Translator(settings.DEEPL_AUTH_KEY)

def check_availability(self, request, language_slug):
"""
This function checks, if the selected language is supported by DeepL
:param request: request that was sent to the server
:type request: ~django.http.HttpRequest
:param language_slug: current language slug
:type language_slug: str
:return: true or false
:rtype: bool
"""
supported_source_languages = [
source_language.code.lower()
for source_language in self.translator.get_source_languages()
]
supported_target_languages = [
target_languages.code.lower()[:2]
for target_languages in self.translator.get_target_languages()
]
source_language = request.region.get_source_language(language_slug)
return (
source_language
and source_language.slug in supported_source_languages
and language_slug in supported_target_languages
)

def deepl_translation(self, request, content_objects, language_slug, form_class):
"""
This functions gets the translation from DeepL
:param request: passed request
:type request: ~django.http.HttpRequest
:param content_objects: passed content objects
:type content_objects: ~django.db.models.query.QuerySet [ ~integreat_cms.cms.models.abstract_content_model.AbstractContentModel ]
:param language_slug: current GUI language slug
:type language_slug: str
:param form_class: passed Form class of content type
:type form_class: ~integreat_cms.cms.forms.custom_content_model_form.CustomContentModelForm
"""
# Get target language
target_language = request.region.get_language_or_404(language_slug)
source_language = request.region.get_source_language(language_slug)
for content_object in content_objects:
source_translation = content_object.get_translation(source_language.slug)
if not source_translation:
messages.error(
request,
_('No source translation could be found for {} "{}".').format(
type(content_object)._meta.verbose_name.title(),
content_object.best_translation.title,
),
)
continue
existing_target_translation = content_object.get_translation(
target_language.slug
)
# For some languages, the DeepL client expects the BCP tag instead of the short language code
if target_language.slug in ("en", "pt"):
target_language_key = target_language.bcp47_tag
else:
target_language_key = target_language.slug
target_title = self.translator.translate_text(
source_translation.title,
source_lang=source_language.slug,
target_lang=target_language_key,
)
data = {
"title": target_title,
"status": existing_target_translation.status
if existing_target_translation
else source_translation.status,
}
if source_translation.content:
data["content"] = self.translator.translate_text(
source_translation.content,
source_lang=source_language.slug,
target_lang=target_language_key,
)
# for pois adds a short description
if hasattr(source_translation, "short_description"):
data["short_description"] = self.translator.translate_text(
source_translation.short_description,
source_lang=source_language.slug,
target_lang=target_language_key,
)
content_translation_form = form_class(
data=data,
instance=existing_target_translation,
additional_instance_attributes={
"creator": request.user,
"language": target_language,
source_translation.foreign_field(): content_object,
},
)
# Validate event translation
if content_translation_form.is_valid():
content_translation_form.save()
logger.debug(
"Successfully translated for: %r", content_translation_form.instance
)
messages.success(
request,
_('{} "{}" has been successfully translated.').format(
type(content_object)._meta.verbose_name.title(),
source_translation.title,
),
)
else:
logger.error(
"Automatic translation for %r could not be created because of %r",
content_object,
content_translation_form.errors,
)
messages.error(
request,
_('{} "{}" could not be automatically translated.').format(
type(content_object)._meta.verbose_name.title(),
source_translation.title,
),
)
45 changes: 30 additions & 15 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-03-20 18:58+0000\n"
"POT-Creation-Date: 2022-03-23 12:23+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Integreat <[email protected]>\n"
"Language-Team: Integreat <[email protected]>\n"
Expand Down Expand Up @@ -3121,7 +3121,7 @@ msgstr "Feedback"

#: cms/templates/_base.html:181
#: cms/templates/push_notifications/push_notification_list.html:9
#: core/settings.py:712
#: core/settings.py:716
msgid "News"
msgstr "Nachrichten"

Expand Down Expand Up @@ -5699,19 +5699,21 @@ msgstr ""
"Adresse eingegeben haben, mit der Sie sich registriert haben, und überprüfen "
"Sie Ihren Spam-Ordner."

#: cms/views/bulk_action_views.py:123
msgid "Automatic translations are not fully implemented yet"
msgstr "Automatische Übersetzungen sind noch nicht vollständig implementiert"

#: cms/views/bulk_action_views.py:126
#: cms/views/bulk_action_views.py:124
msgid "Automatic translations are disabled"
msgstr "Automatische Übersetzungen sind deaktiviert"

#: cms/views/bulk_action_views.py:164
#: cms/views/bulk_action_views.py:137
msgid "This language is not supported by DeepL. Please try another language"
msgstr ""
"Diese Sprache wird von DeepL nicht unterstützt. Bitte versuchen Sie eine "
"andere Sprache"

#: cms/views/bulk_action_views.py:177
msgid "The selected {} were successfully archived"
msgstr "Die ausgewählten {} wurden erfolgreich archiviert"

#: cms/views/bulk_action_views.py:203
#: cms/views/bulk_action_views.py:216
msgid "The selected {} were successfully restored"
msgstr "Die ausgewählten {} wurden erfolgreich wiederhergestellt"

Expand Down Expand Up @@ -6599,6 +6601,18 @@ msgid "Superuser permissions need to be set by another superuser."
msgstr ""
"Administratorrechte müssen von einem anderen Administrator vergeben werden."

#: deepl_api/utils.py:74
msgid "No source translation could be found for {} \"{}\"."
msgstr "Es konnte kein Quelltext für {} \"{}\" gefunden werden."

#: deepl_api/utils.py:129
msgid "{} \"{}\" has been successfully translated."
msgstr "{} \"{}\" wurde erfolgreich übersetzt"

#: deepl_api/utils.py:142
msgid "{} \"{}\" could not be automatically translated."
msgstr "{} \"{}\" konnte nicht automatisch übersetzt werden."

#: xliff/utils.py:152
msgid ""
"Page {} does not have a source translation in {} and therefore cannot be "
Expand Down Expand Up @@ -6650,6 +6664,10 @@ msgstr ""
"Diese Seite konnte nicht importiert werden, da sie zu einer anderen Region "
"gehört ({})."

#~ msgid "Automatic translations are not fully implemented yet"
#~ msgstr ""
#~ "Automatische Übersetzungen sind noch nicht vollständig implementiert"

#~ msgid "Filetype:"
#~ msgstr "Dateityp:"

Expand All @@ -6663,6 +6681,9 @@ msgstr ""
#~ msgid "Export XLIFF for translation to"
#~ msgstr "Exportiere XLIFF für Übersetzung nach"

#~ msgid "POI has been successfully translated"
#~ msgstr "POI wurde erfolgreich übersetzt."

#~ msgid "Access token to update the page content"
#~ msgstr "Zugangs-Token um Seiten-Inhalte zu aktualisieren"

Expand Down Expand Up @@ -6930,9 +6951,6 @@ msgstr ""
#~ msgid "Event was successfully created and published"
#~ msgstr "Veranstaltung wurde erfolgreich erstellt und veröffentlicht"

#~ msgid "Event was successfully created"
#~ msgstr "Veranstaltung wurde erfolgreich erstellt"

#~ msgid "Event translation was successfully created and published"
#~ msgstr ""
#~ "Veranstaltungsübersetzung wurde erfolgreich erstellt und veröffentlicht"
Expand Down Expand Up @@ -8000,9 +8018,6 @@ msgstr ""
#~ msgid "POI was successfully created and published."
#~ msgstr "POI wurde erfolgreich erstellt und veröffentlicht."

#~ msgid "POI was successfully created."
#~ msgstr "POI wurde erfolgreich erstellt."

#~ msgid "POI was successfully published."
#~ msgstr "POI wurde erfolgreich veröffentlicht."

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ install_requires =
argon2-cffi
bcrypt
cffi
deepl
Django>=3.2,<4.0
django-cacheops
django-cors-headers
Expand Down

0 comments on commit 5c5937e

Please sign in to comment.