From d340e8760150b28f62f3c51be6cbc043a96768d2 Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Thu, 7 Nov 2024 13:12:36 +0100 Subject: [PATCH] Revert translations for now. Increase build size to much, Heroku has a 500 MB max (#4199) --- hypha/apply/funds/fields.py | 39 ---- hypha/apply/funds/forms.py | 28 --- hypha/apply/funds/services.py | 62 +----- .../applicationsubmission_admin_detail.html | 5 +- .../funds/applicationsubmission_detail.html | 13 +- .../funds/includes/admin_primary_actions.html | 9 +- .../funds/includes/rendered_answers.html | 45 +--- .../includes/translate_application_form.html | 125 ----------- .../partials/submission-title.html | 1 - .../funds/templatetags/translate_tags.py | 18 -- hypha/apply/funds/tests/test_services.py | 121 ----------- hypha/apply/funds/tests/test_tags.py | 42 +--- hypha/apply/funds/tests/test_utils.py | 195 +----------------- hypha/apply/funds/tests/test_views.py | 34 --- hypha/apply/funds/urls.py | 12 -- hypha/apply/funds/utils.py | 82 -------- hypha/apply/funds/views.py | 92 --------- hypha/apply/funds/views_partials.py | 92 +-------- hypha/apply/translate/__init__.py | 0 hypha/apply/translate/management/__init__.py | 0 .../translate/management/commands/__init__.py | 0 .../management/commands/install_languages.py | 142 ------------- .../commands/uninstall_languages.py | 109 ---------- hypha/apply/translate/tests/__init__.py | 0 hypha/apply/translate/tests/test_translate.py | 72 ------- hypha/apply/translate/tests/test_utils.py | 116 ----------- hypha/apply/translate/translate.py | 35 ---- hypha/apply/translate/utils.py | 64 ------ hypha/settings/base.py | 3 - hypha/settings/django.py | 2 - .../javascript/translate-application.js | 13 -- requirements.txt | 1 - 32 files changed, 11 insertions(+), 1561 deletions(-) delete mode 100644 hypha/apply/funds/fields.py delete mode 100644 hypha/apply/funds/templates/funds/includes/translate_application_form.html delete mode 100644 hypha/apply/funds/templates/submissions/partials/submission-title.html delete mode 100644 hypha/apply/funds/templatetags/translate_tags.py delete mode 100644 hypha/apply/funds/tests/test_services.py delete mode 100644 hypha/apply/translate/__init__.py delete mode 100644 hypha/apply/translate/management/__init__.py delete mode 100644 hypha/apply/translate/management/commands/__init__.py delete mode 100644 hypha/apply/translate/management/commands/install_languages.py delete mode 100644 hypha/apply/translate/management/commands/uninstall_languages.py delete mode 100644 hypha/apply/translate/tests/__init__.py delete mode 100644 hypha/apply/translate/tests/test_translate.py delete mode 100644 hypha/apply/translate/tests/test_utils.py delete mode 100644 hypha/apply/translate/translate.py delete mode 100644 hypha/apply/translate/utils.py delete mode 100644 hypha/static_src/javascript/translate-application.js diff --git a/hypha/apply/funds/fields.py b/hypha/apply/funds/fields.py deleted file mode 100644 index 629c99deff..0000000000 --- a/hypha/apply/funds/fields.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Iterable, Literal, Optional - -from argostranslate.package import Package -from django import forms - - -class LanguageChoiceField(forms.ChoiceField): - def __init__( - self, - role: Literal["to", "from"], - available_packages: Iterable[Package], - choices: Optional[Iterable[str]] = set(), - **kwargs, - ) -> None: - self.available_packages = available_packages - - # Ensure the given language is either "to" or "from" - if role not in ["to", "from"]: - raise ValueError(f'Invalid role "{role}", must be "to" or "from"') - - self.role = role - - super().__init__(choices=choices, **kwargs) - self.widget.attrs.update({"data-placeholder": f"{role.capitalize()}..."}) - - def validate(self, value: Any) -> None: - """Basic validation to ensure the language (depending on role) is available in the installed packages - - Only checks the language exists as from/to code, doesn't validate based on from -> to. - """ - if self.role == "from": - valid_list = [package.from_code for package in self.available_packages] - else: - valid_list = [package.to_code for package in self.available_packages] - - if value not in valid_list: - raise forms.ValidationError( - "The specified language is either invalid or not installed" - ) diff --git a/hypha/apply/funds/forms.py b/hypha/apply/funds/forms.py index ac9d3f1a09..5ddeeb000e 100644 --- a/hypha/apply/funds/forms.py +++ b/hypha/apply/funds/forms.py @@ -11,10 +11,8 @@ from wagtail.signal_handlers import disable_reference_index_auto_update from hypha.apply.categories.models import MetaTerm -from hypha.apply.translate.utils import get_available_translations from hypha.apply.users.models import User -from .fields import LanguageChoiceField from .models import ( ApplicationSubmission, AssignedReviewers, @@ -379,32 +377,6 @@ def submissions_cant_have_external_reviewers(self, submissions): return False -class TranslateSubmissionForm(forms.Form): - available_packages = get_available_translations() - - from_lang = LanguageChoiceField("from", available_packages) - to_lang = LanguageChoiceField("to", available_packages) - - def clean(self): - form_data = self.cleaned_data - try: - from_code = form_data["from_lang"] - to_code = form_data["to_lang"] - - to_packages = get_available_translations([from_code]) - - if to_code not in [package.to_code for package in to_packages]: - self.add_error( - "to_lang", - "The specified language is either invalid or not installed", - ) - - return form_data - except KeyError as err: - # If one of the fields could not be parsed, there is likely bad input being given - raise forms.ValidationError("Invalid input selected") from err - - def make_role_reviewer_fields(): role_fields = [] staff_reviewers = User.objects.staff().only("full_name", "pk") diff --git a/hypha/apply/funds/services.py b/hypha/apply/funds/services.py index 0a971cee4d..ce82e05540 100644 --- a/hypha/apply/funds/services.py +++ b/hypha/apply/funds/services.py @@ -1,6 +1,4 @@ -import re - -from bs4 import BeautifulSoup, element +from bs4 import element from django.apps import apps from django.conf import settings from django.core.exceptions import PermissionDenied @@ -23,7 +21,6 @@ from hypha.apply.funds.models.assigned_reviewers import AssignedReviewers from hypha.apply.funds.workflow import INITIAL_STATE from hypha.apply.review.options import DISAGREE, MAYBE -from hypha.apply.translate import translate def bulk_archive_submissions( @@ -266,63 +263,6 @@ def annotate_review_recommendation_and_count(submissions: QuerySet) -> QuerySet: return submissions -def translate_application_form_data(application, from_code: str, to_code: str) -> dict: - """Translate the content of an application's live revision `form_data`. - Will parse fields that contain both plaintext & HTML, extracting & replacing strings. - - NOTE: Mixed formatting like `

Hey from Hypha

` will result in a - string that is stripped of text formatting (untranslated: `

Hey from Hypha

`). On - the other hand, unmixed strings like `

Hey from Hypha

` will be - replaced within formatting tags. - - Args: - application: the application to translate - from_code: the ISO 639 code of the original language - to_code: the ISO 639 code of the language to translate to - - Returns: - The `form_data` with values translated (including nested HTML strings) - - Raises: - ValueError if an invalid `from_code` or `to_code` is requested - """ - form_data: dict = application.live_revision.form_data - - translated_form_data = form_data.copy() - - # Only translate content fields or the title - don't with name, email, etc. - translated_form_data["title"] = translate.translate( - form_data["title"], from_code, to_code - ) - - # RegEx to match wagtail's generated field UIDs - ie. "97c51cea-ab47-4a64-a64a-15d893788ef2" - uid_regex = re.compile(r"([a-z]|\d){8}(-([a-z]|\d){4}){3}-([a-z]|\d){12}") - fields_to_translate = [ - key - for key in form_data - if uid_regex.match(key) and isinstance(form_data[key], str) - ] - - for key in fields_to_translate: - field_html = BeautifulSoup(form_data[key], "html.parser") - if field_html.find(): # Check if BS detected any HTML - for field in field_html.find_all(has_valid_str): - # Removes formatting if mixed into the tag to prioritize context in translation - # ie. `

Hey y'all

` -> `

Hey y'all

` (but translated) - to_translate = field.string if field.string else field.text - field.clear() - field.string = translate.translate(to_translate, from_code, to_code) - - translated_form_data[key] = str(field_html) - # Ensure the field value isn't empty & translate as is - elif form_data[key].strip(): - translated_form_data[key] = translate.translate( - form_data[key], from_code, to_code - ) - - return translated_form_data - - def has_valid_str(tag: element.Tag) -> bool: """Checks that an Tag contains a valid text element and/or string. diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_admin_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_admin_detail.html index 5bf69f9c5f..cadddf3ffe 100644 --- a/hypha/apply/funds/templates/funds/applicationsubmission_admin_detail.html +++ b/hypha/apply/funds/templates/funds/applicationsubmission_admin_detail.html @@ -1,5 +1,5 @@ {% extends "funds/applicationsubmission_detail.html" %} -{% load i18n static workflow_tags review_tags determination_tags translate_tags heroicons %} +{% load i18n static workflow_tags review_tags determination_tags heroicons %} {% block extra_css %} @@ -98,7 +98,4 @@
{% trans "Reminders" %}
- {% if request.user|can_translate_submission %} - - {% endif %} {% endblock %} diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html index 503dd242fe..5424511c15 100644 --- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html +++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html @@ -1,5 +1,5 @@ {% extends "base-apply.html" %} -{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags translate_tags %} +{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags %} {% load heroicons %} {% block title %}{{ object.title_text_display }}{% endblock %} @@ -146,15 +146,8 @@
{% blocktrans with stage=object.previous.stage %}Your {{ stage }} applicatio {% endif %} - {% if request.user|can_translate_submission %} -
- {% include "funds/includes/rendered_answers.html" %} -
- {% else %} -
- {% include "funds/includes/rendered_answers.html" %} -
- {% endif %} + + {% include "funds/includes/rendered_answers.html" %} {% endif %} diff --git a/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html b/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html index 6e7ccc47ab..89892ff9b3 100644 --- a/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html +++ b/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html @@ -1,5 +1,5 @@ {% load i18n %} -{% load heroicons primaryactions_tags translate_tags %} +{% load heroicons primaryactions_tags %}
{% trans "Actions to take" %}
@@ -84,13 +84,6 @@
{% trans "Actions to take" %}
{% trans "More actions" %} {% trans "Revisions" %} - {% if request.user|can_translate_submission %} - - {% endif %} - - - - {% heroicon_outline "information-circle" aria_hidden="true" size=15 stroke_width=2 class="inline align-baseline me-1" %} - - - - \ No newline at end of file diff --git a/hypha/apply/funds/templates/submissions/partials/submission-title.html b/hypha/apply/funds/templates/submissions/partials/submission-title.html deleted file mode 100644 index e3038ceb93..0000000000 --- a/hypha/apply/funds/templates/submissions/partials/submission-title.html +++ /dev/null @@ -1 +0,0 @@ -

{{ object.title }} #{{ object.public_id|default:object.id }}

\ No newline at end of file diff --git a/hypha/apply/funds/templatetags/translate_tags.py b/hypha/apply/funds/templatetags/translate_tags.py deleted file mode 100644 index 110c48dd70..0000000000 --- a/hypha/apply/funds/templatetags/translate_tags.py +++ /dev/null @@ -1,18 +0,0 @@ -from django import template -from django.conf import settings - -register = template.Library() - - -@register.filter -def can_translate_submission(user) -> bool: - """Verify that system settings & user role allows for submission translations. - - Args: - user: the user to check the role of. - - Returns: - bool: true if submission can be translated, false if not. - - """ - return bool(settings.SUBMISSION_TRANSLATIONS_ENABLED and user.is_org_faculty) diff --git a/hypha/apply/funds/tests/test_services.py b/hypha/apply/funds/tests/test_services.py deleted file mode 100644 index 2b7a9836d9..0000000000 --- a/hypha/apply/funds/tests/test_services.py +++ /dev/null @@ -1,121 +0,0 @@ -from unittest.mock import patch - -from django.test import TestCase - -from hypha.apply.funds.services import translate_application_form_data -from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory -from hypha.apply.users.tests.factories import ApplicantFactory - - -class TestTranslateSubmissionFormData(TestCase): - @staticmethod - def mocked_translate(string: str, from_code, to_code): - """Use pig latin for all test translations - ie. 'hypha is cool' -> 'yphahay isway oolcay' - https://en.wikipedia.org/wiki/Pig_Latin - """ - valid_codes = ["en", "fr", "zh", "es"] - if from_code == to_code or not ( - from_code in valid_codes and to_code in valid_codes - ): - raise ValueError() - - vowels = {"a", "e", "i", "o", "u"} - string = string.lower() - pl = [ - f"{word}way" if word[0] in vowels else f"{word[1:]}{word[0]}ay" - for word in string.split() - ] - return " ".join(pl) - - @classmethod - def setUpClass(cls): - """Used to patch & mock all the methods called from hypha.apply.translate""" - cls.patcher = patch( - "hypha.apply.translate.translate.translate", - side_effect=cls.mocked_translate, - ) - cls.patcher.start() - - @classmethod - def tearDownClass(cls): - cls.patcher.stop() - - def setUp(self): - self.applicant = ApplicantFactory( - email="test@hyphaiscool.com", full_name="Johnny Doe" - ) - self.application = ApplicationSubmissionFactory(user=self.applicant) - - @property - def form_data(self): - return self.application.live_revision.form_data - - def test_translate_application_form_data_plaintext_fields(self): - uuid = "97c51cea-ab47-4a64-a64a-15d893788ef2" # random uuid - self.application.form_data[uuid] = "Just a plain text field" - - translated_form_data = translate_application_form_data( - self.application, "en", "fr" - ) - - self.assertEqual( - translated_form_data[uuid], "ustjay away lainpay exttay ieldfay" - ) - - def test_translate_application_form_data_html_fields(self): - uuid_mixed_format = "ed89378g-3b54-4444-abcd-37821f58ed89" # random uuid - self.application.form_data[uuid_mixed_format] = ( - "

Hello from a Hyper Text Markup Language field

" - ) - - uuid_same_format = "9191fc65-02c6-46e0-9fc8-3b778113d19f" # random uuid - self.application.form_data[uuid_same_format] = ( - "

Hypha rocks

yeah

" - ) - - translated_form_data = translate_application_form_data( - self.application, "en", "fr" - ) - - self.assertEqual( - translated_form_data[uuid_mixed_format], - "

ellohay romfay away yperhay exttay arkupmay anguagelay ieldfay

", - ) - self.assertEqual( - translated_form_data[uuid_same_format], - "

yphahay ocksray

eahyay

", - ) - - def test_translate_application_form_data_skip_info_fields(self): - self.application.form_data["random"] = "don't translate me pls" - - name = self.form_data["full_name"] - email = self.form_data["email"] - random = self.form_data["random"] - - translated_form_data = translate_application_form_data( - self.application, "en", "fr" - ) - self.assertEqual(translated_form_data["full_name"], name) - self.assertEqual(translated_form_data["email"], email) - self.assertEqual(translated_form_data["random"], random) - - def test_translate_application_form_data_skip_non_str_fields(self): - uuid = "4716ddd4-ce87-4964-b82d-bf2db75bdbc3" # random uuid - self.application.form_data[uuid] = {"test": "dict field"} - - translated_form_data = translate_application_form_data( - self.application, "en", "fr" - ) - self.assertEqual(translated_form_data[uuid], {"test": "dict field"}) - - def test_translate_application_form_data_error_bubble_up(self): - """Ensure errors bubble up from underlying translate func""" - application = ApplicationSubmissionFactory() - with self.assertRaises(ValueError): - # duplicate language code - translate_application_form_data(application, "en", "en") - - with self.assertRaises(ValueError): - # language code not in `mocked_translate` - translate_application_form_data(application, "de", "en") diff --git a/hypha/apply/funds/tests/test_tags.py b/hypha/apply/funds/tests/test_tags.py index 2f857c1d92..c85a5408a4 100644 --- a/hypha/apply/funds/tests/test_tags.py +++ b/hypha/apply/funds/tests/test_tags.py @@ -1,8 +1,7 @@ from django.template import Context, Template -from django.test import RequestFactory, TestCase, override_settings +from django.test import TestCase from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory -from hypha.apply.users.tests.factories import ApplicantFactory, StaffFactory class TestTemplateTags(TestCase): @@ -27,42 +26,3 @@ def test_submission_tags(self): output == f'Lorem ipsum dolor {submission.title} #{submission.public_id or submission.id} sit amet.' ) - - @override_settings(SUBMISSION_TRANSLATIONS_ENABLED=True) - def test_translate_tags_as_applicant(self): - submission = ApplicationSubmissionFactory() - request = RequestFactory().get(submission.get_absolute_url()) - request.user = ApplicantFactory() - template = Template( - "{% load translate_tags %}{% if request.user|can_translate_submission %}

some translation stuff

{% endif %}" - ) - context = Context({"request": request}) - output = template.render(context) - - self.assertEqual(output, "") - - @override_settings(SUBMISSION_TRANSLATIONS_ENABLED=True) - def test_translate_tags_as_staff(self): - submission = ApplicationSubmissionFactory() - request = RequestFactory().get(submission.get_absolute_url()) - request.user = StaffFactory() - template = Template( - "{% load translate_tags %}{% if request.user|can_translate_submission %}

some translation stuff

{% endif %}" - ) - context = Context({"request": request}) - output = template.render(context) - - self.assertEqual(output, "

some translation stuff

") - - @override_settings(SUBMISSION_TRANSLATIONS_ENABLED=False) - def test_translate_tags_disabled(self): - submission = ApplicationSubmissionFactory() - request = RequestFactory().get(submission.get_absolute_url()) - request.user = StaffFactory() - template = Template( - "{% load translate_tags %}{% if request.user|can_translate_submission %}

some translation stuff

{% endif %}" - ) - context = Context({"request": request}) - output = template.render(context) - - self.assertEqual(output, "") diff --git a/hypha/apply/funds/tests/test_utils.py b/hypha/apply/funds/tests/test_utils.py index f2ee07b579..03d73c0857 100644 --- a/hypha/apply/funds/tests/test_utils.py +++ b/hypha/apply/funds/tests/test_utils.py @@ -1,12 +1,7 @@ -import json -from unittest.mock import Mock, patch -from urllib.parse import parse_qs, urlparse - import pytest -from django.test import RequestFactory, SimpleTestCase, override_settings from freezegun import freeze_time -from hypha.apply.funds.utils import get_copied_form_name, get_language_choices_json +from hypha.apply.funds.utils import get_copied_form_name date = "2024-10-16 15:05:13.721861" @@ -27,191 +22,3 @@ @pytest.mark.parametrize("original_name,expected", form_name_dataset) def test_get_copied_form_name(original_name, expected): assert get_copied_form_name(original_name) == expected - - -# Setup for testing `get_language_choices_json` - - -def equal_ignore_order(a: list, b: list) -> bool: - """Used to compare two lists that are unsortable & unhashable - - Primarily used when comparing the result of json.loads - - Args: - a: the first unhashable & unsortable list - b: the second unhashable & unsortable list - - Returns: - bool: true when lists are equal in length & content - """ - if len(a) != len(b): - return False - unmatched = list(b) - for element in a: - try: - unmatched.remove(element) - except ValueError: - return False - return not unmatched - - -class TestGetLanguageChoices(SimpleTestCase): - @staticmethod - def mocked_get_lang_name(code): - # Added "!" to ensure the mock is being called rather than the actual get_lang - codes_to_lang = { - "en": "English!", - "ar": "Arabic!", - "zh": "Chinese!", - "fr": "French!", - } - return codes_to_lang[code] - - @staticmethod - def mocked_get_translation_params(url): - query_dict = {k: v[0] for (k, v) in parse_qs(urlparse(url).query).items()} - if (to_lang := query_dict.get("tl")) and (from_lang := query_dict.get("fl")): - return (from_lang, to_lang) - - @classmethod - def setUpClass(cls): - """Used to patch & mock all the methods called from hypha.apply.translate.utils""" - available_packages = [ - Mock(from_code="ar", to_code="en"), - Mock(from_code="fr", to_code="en"), - Mock(from_code="en", to_code="ar"), - Mock(from_code="zh", to_code="en"), - Mock(from_code="en", to_code="fr"), - ] - - cls.patcher = [ - patch( - "hypha.apply.translate.utils.get_lang_name", - side_effect=cls.mocked_get_lang_name, - ), - patch( - "hypha.apply.translate.utils.get_available_translations", - return_value=available_packages, - ), - patch( - "hypha.apply.translate.utils.get_translation_params", - side_effect=cls.mocked_get_translation_params, - ), - ] - - for patched in cls.patcher: - patched.start() - - @classmethod - def tearDownClass(cls): - for patched in cls.patcher: - patched.stop() - - def test_get_language_choices_json(self): - expected_json = [ - { - "label": "English!", - "selected": False, - "to": [ - {"label": "Arabic!", "selected": True, "value": "ar"}, - {"label": "French!", "selected": False, "value": "fr"}, - ], - "value": "en", - }, - { - "label": "Arabic!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "ar", - }, - { - "label": "Chinese!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "zh", - }, - { - "label": "French!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "fr", - }, - ] - request = RequestFactory().get("/test/") - - json_out = get_language_choices_json(request) - self.assertTrue(equal_ignore_order(json.loads(json_out), expected_json)) - - def test_get_language_choices_json_with_current_url(self): - expected_json = [ - { - "label": "English!", - "selected": False, - "to": [ - {"label": "Arabic!", "selected": True, "value": "ar"}, - {"label": "French!", "selected": False, "value": "fr"}, - ], - "value": "en", - }, - { - "label": "Arabic!", - "selected": True, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "ar", - }, - { - "label": "Chinese!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "zh", - }, - { - "label": "French!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "fr", - }, - ] - current_url = "https://hyphaiscool.org/apply/submissions/6/?fl=ar&tl=en" - request = RequestFactory().get( - "/test/", headers={"Hx-Current-Url": current_url} - ) - - json_out = get_language_choices_json(request) - self.assertTrue(equal_ignore_order(json.loads(json_out), expected_json)) - - @override_settings(LANGUAGE_CODE="fr") - def test_get_language_choices_json_with_language_code(self): - expected_json = [ - { - "label": "English!", - "selected": False, - "to": [ - {"label": "Arabic!", "selected": False, "value": "ar"}, - {"label": "French!", "selected": True, "value": "fr"}, - ], - "value": "en", - }, - { - "label": "Arabic!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "ar", - }, - { - "label": "Chinese!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "zh", - }, - { - "label": "French!", - "selected": False, - "to": [{"label": "English!", "selected": True, "value": "en"}], - "value": "fr", - }, - ] - request = RequestFactory().get("/test/") - - json_out = get_language_choices_json(request) - self.assertTrue(equal_ignore_order(json.loads(json_out), expected_json)) diff --git a/hypha/apply/funds/tests/test_views.py b/hypha/apply/funds/tests/test_views.py index b1729349c5..8e06f78ece 100644 --- a/hypha/apply/funds/tests/test_views.py +++ b/hypha/apply/funds/tests/test_views.py @@ -673,40 +673,6 @@ def test_applicant_can_see_application_draft_status(self): response = SubmissionDetailView.as_view()(request, pk=submission.pk) self.assertEqual(response.status_code, 200) - @override_settings(SUBMISSION_TRANSLATIONS_ENABLED=True) - def test_staff_can_see_translate_primary_action(self): - def assert_view_translate_displayed(submission): - response = self.get_page(submission) - buttons = ( - BeautifulSoup(response.content, "html5lib") - .find("div", attrs={"data-testid": "sidebar-primary-actions"}) - .find_all("button") - ) - - self.assertEqual( - len([button.text for button in buttons if "Translate" in button.text]), - 1, - ) - - assert_view_translate_displayed(self.submission) - - @override_settings(SUBMISSION_TRANSLATIONS_ENABLED=False) - def test_staff_cant_see_translate_primary_action(self): - def assert_view_translate_displayed(submission): - response = self.get_page(submission) - buttons = ( - BeautifulSoup(response.content, "html5lib") - .find("div", attrs={"data-testid": "sidebar-primary-actions"}) - .find_all("button") - ) - - self.assertEqual( - len([button.text for button in buttons if "Translate" in button.text]), - 0, - ) - - assert_view_translate_displayed(self.submission) - class TestReviewersUpdateView(BaseSubmissionViewTestCase): user_factory = StaffFactory diff --git a/hypha/apply/funds/urls.py b/hypha/apply/funds/urls.py index a6cf4f4d05..55f7d89210 100644 --- a/hypha/apply/funds/urls.py +++ b/hypha/apply/funds/urls.py @@ -27,7 +27,6 @@ SubmissionResultView, SubmissionsByStatus, SubmissionSealedView, - TranslateSubmissionView, UpdateLeadView, UpdateMetaTermsView, UpdatePartnersView, @@ -51,7 +50,6 @@ partial_submission_activities, partial_submission_answers, partial_submission_lead, - partial_translate_answers, sub_menu_bulk_update_lead, sub_menu_bulk_update_reviewers, sub_menu_category_options, @@ -200,11 +198,6 @@ partial_meta_terms_card, name="partial-meta-terms-card", ), - path( - "partial/translate/answers", - partial_translate_answers, - name="partial-translate-answers", - ), path( "project/create/", CreateProjectView.as_view(), @@ -220,11 +213,6 @@ ReminderCreateView.as_view(), name="create_reminder", ), - path( - "translate/", - TranslateSubmissionView.as_view(), - name="translate", - ), path( "progress/", ProgressSubmissionView.as_view(), name="progress" ), diff --git a/hypha/apply/funds/utils.py b/hypha/apply/funds/utils.py index 389aa70be3..089e0cbe50 100644 --- a/hypha/apply/funds/utils.py +++ b/hypha/apply/funds/utils.py @@ -1,5 +1,4 @@ import csv -import json import re from datetime import datetime from functools import reduce @@ -8,12 +7,9 @@ from operator import iconcat import django_filters as filters -from django.conf import settings -from django.http import HttpRequest from django.utils.html import strip_tags from django.utils.translation import gettext as _ -from hypha.apply.translate import utils as translate_utils from hypha.apply.utils.image import generate_image_tag from .models.screening import ScreeningStatus @@ -195,81 +191,3 @@ def get_copied_form_name(original_form_name: str) -> str: # If a copied timestamp already exists, remove it new_name = re.sub(name_reg, "", original_form_name) return f"{new_name} ({copy_str.format(copy_time=copy_time)})" - - -def get_language_choices_json(request: HttpRequest) -> str: - """Generate a JSON output of available translation options - - Utilized for populating the reactive form fields on the client side - - Args: - request: an `HttpRequest` containing an "Hx-Current-Url" header to extract current translation params from - - Returns: - A JSON string in the format of: - - ``` - [ - { - "value": "", - "label": "", - "to": [ - { - "value": "", - "label": "" - "selected": - } - ], - "selected": - }, - ... - ] - ``` - """ - available_translations = translate_utils.get_available_translations() - from_langs = {package.from_code for package in available_translations} - default_to_lang = settings.LANGUAGE_CODE if settings.LANGUAGE_CODE else None - default_from_lang = None - - # If there's existing lang params, use those as the default in the form - # ie. the user has an active translation for ar -> en, those should be selected in the form - if (current_url := request.headers.get("Hx-Current-Url")) and ( - params := translate_utils.get_translation_params(current_url) - ): - default_from_lang, default_to_lang = params - - choices = [] - for lang in from_langs: - to_langs = [ - package.to_code - for package in available_translations - if package.from_code == lang - ] - - # Set the default selection to be the default_to_lang if it exists in the to_langs list, - # otherwise use the first value in the list. - selected_to = ( - default_to_lang - if default_to_lang and default_to_lang in to_langs - else to_langs[0] - ) - - to_choices = [ - { - "value": to_lang, - "label": translate_utils.get_lang_name(to_lang), - "selected": to_lang == selected_to, - } - for to_lang in to_langs - ] - - choices.append( - { - "value": lang, - "label": translate_utils.get_lang_name(lang), - "to": to_choices, - "selected": lang == default_from_lang, - } - ) - - return json.dumps(choices) diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index cd7c5daaee..d41c72f0d4 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -65,7 +65,6 @@ from hypha.apply.stream_forms.blocks import GroupToggleBlock from hypha.apply.todo.options import PROJECT_WAITING_PAF from hypha.apply.todo.views import add_task_to_user -from hypha.apply.translate.utils import get_lang_name, get_translation_params from hypha.apply.users.decorators import ( is_apply_staff, staff_or_finance_required, @@ -91,7 +90,6 @@ BatchUpdateSubmissionLeadForm, CreateReminderForm, ProgressSubmissionForm, - TranslateSubmissionForm, UpdateMetaTermsForm, UpdatePartnersForm, UpdateReviewersForm, @@ -130,7 +128,6 @@ from .utils import ( export_submissions_to_csv, format_submission_sum_value, - get_language_choices_json, is_filter_empty, ) from .workflow import ( @@ -987,70 +984,6 @@ def post(self, *args, **kwargs): ) -@method_decorator(staff_required, name="dispatch") -class TranslateSubmissionView(View): - template = "funds/includes/translate_application_form.html" - - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - if not request.user.is_org_faculty: - messages.warning( - self.request, - "User attempted to translate submission but is not org faculty", - ) - return HttpResponseRedirect(self.submission.get_absolute_url()) - return super(TranslateSubmissionView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - translate_form = TranslateSubmissionForm() - return render( - self.request, - self.template, - context={ - "form": translate_form, - "value": _("Update"), - "object": self.submission, - "json_choices": get_language_choices_json(self.request), - }, - ) - - def post(self, request, *args, **kwargs): - form = TranslateSubmissionForm(self.request.POST) - - if form.is_valid(): - FROM_LANG_KEY = "from_lang" - TO_LANG_KEY = "to_lang" - - from_lang = form.cleaned_data[FROM_LANG_KEY] - to_lang = form.cleaned_data[TO_LANG_KEY] - - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps( - { - "translateSubmission": { - FROM_LANG_KEY: from_lang, - TO_LANG_KEY: to_lang, - } - } - ), - }, - ) - - return render( - self.request, - self.template, - context={ - "form": form, - "value": _("Update"), - "object": self.submission, - "json_choices": get_language_choices_json(self.request), - }, - status=400, - ) - - @login_required @user_passes_test(is_apply_staff) @require_http_methods(["GET"]) @@ -1159,31 +1092,6 @@ def dispatch(self, request, *args, **kwargs): redirect = SubmissionSealedView.should_redirect(request, submission) return redirect or super().dispatch(request, *args, **kwargs) - def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: - self.object = self.get_object() - - extra_context = {} - - # Check for language params - if they exist and are valid then update the context - if lang_params := get_translation_params(request=request): - from_lang, to_lang = lang_params - try: - self.object.form_data = services.translate_application_form_data( - self.object, from_lang, to_lang - ) - extra_context.update( - { - "from_lang_name": get_lang_name(from_lang), - "to_lang_name": get_lang_name(to_lang), - } - ) - except ValueError: - # Language package isn't valid or installed, redirect to the submission w/o params - return redirect(self.object.get_absolute_url()) - - context = self.get_context_data(object=self.object, **extra_context) - return self.render_to_response(context) - def get_context_data(self, **kwargs): other_submissions = ( self.model.objects.filter(user=self.object.user) diff --git a/hypha/apply/funds/views_partials.py b/hypha/apply/funds/views_partials.py index 2b78e5dee0..88e9097a57 100644 --- a/hypha/apply/funds/views_partials.py +++ b/hypha/apply/funds/views_partials.py @@ -1,15 +1,13 @@ import functools -import json from urllib.parse import parse_qs, urlparse from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required from django.db.models import Count, Q -from django.http import HttpRequest, HttpResponse, QueryDict +from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, render from django.urls import reverse_lazy from django.utils.text import slugify -from django.utils.translation import gettext as _ from django.views.decorators.http import require_GET, require_http_methods from django_htmx.http import ( HttpResponseClientRefresh, @@ -28,7 +26,6 @@ from hypha.apply.funds.reviewers.services import get_all_reviewers from hypha.apply.funds.services import annotate_review_recommendation_and_count from hypha.apply.review.options import REVIEWER -from hypha.apply.translate.utils import get_lang_name, get_translation_params from hypha.apply.users.groups import REVIEWER_GROUP_NAME from . import services @@ -517,90 +514,3 @@ def partial_screening_card(request, pk): "no_screening_options": no_screening_statuses, } return render(request, "funds/includes/screening_status_block.html", ctx) - - -@login_required -def partial_translate_answers(request: HttpRequest, pk: int) -> HttpResponse: - """Partial to translate submissions's answers - - Args: - request: HttpRequest object - pk: pk of the submission to translate - - """ - submission = get_object_or_404(ApplicationSubmission, pk=pk) - - if not request.user.is_org_faculty or request.method != "GET": - return HttpResponse(status=204) - - ctx = {"object": submission} - - # The existing params that were in the URL when the request was made - prev_params = get_translation_params(request.headers.get("Hx-Current-Url", "")) - # The requested params provided in the GET request - params = get_translation_params(request=request) - - updated_url = submission.get_absolute_url() - - message = None - - if params and not params[0] == params[1] and not params == prev_params: - from_lang, to_lang = params - try: - submission.form_data = services.translate_application_form_data( - submission, from_lang, to_lang - ) - - if current_url := request.headers.get("Hx-Current-Url"): - updated_params = QueryDict(urlparse(current_url).query, mutable=True) - updated_params["fl"] = from_lang - updated_params["tl"] = to_lang - updated_url = f"{updated_url}?{updated_params.urlencode()}" - - to_lang_name = get_lang_name(to_lang) - from_lang_name = get_lang_name(from_lang) - - message = _("Submission translated from {fl} to {tl}.").format( - fl=from_lang_name, tl=to_lang_name - ) - - ctx.update( - { - "object": submission, - "from_lang_name": from_lang_name, - "to_lang_name": to_lang_name, - } - ) - except ValueError: - # TODO: WA Error/failed message type rather than success - message = _("Submission translation failed. Contact your Administrator.") - return HttpResponse( - status=400, - headers={"HX-Trigger": json.dumps({"showMessage": {message}})}, - ) - - elif params == prev_params: - message = _("Translation cleared.") - - response = render(request, "funds/includes/rendered_answers.html", ctx) - - trigger_dict = {} - if title := submission.form_data.get("title"): - trigger_dict.update( - { - "translatedSubmission": { - "appTitle": title, - "docTitle": submission.title_text_display, - } - } - ) - - if message: - trigger_dict.update({"showMessage": message}) - - if trigger_dict: - response["HX-Trigger"] = json.dumps(trigger_dict) - - response["HX-Replace-Url"] = updated_url - - return response diff --git a/hypha/apply/translate/__init__.py b/hypha/apply/translate/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hypha/apply/translate/management/__init__.py b/hypha/apply/translate/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hypha/apply/translate/management/commands/__init__.py b/hypha/apply/translate/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hypha/apply/translate/management/commands/install_languages.py b/hypha/apply/translate/management/commands/install_languages.py deleted file mode 100644 index b927f15fc2..0000000000 --- a/hypha/apply/translate/management/commands/install_languages.py +++ /dev/null @@ -1,142 +0,0 @@ -import argparse -from typing import List - -import argostranslate.package -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = ( - "Delete all drafts that haven't been modified in the specified time (in days)" - ) - - available_packages = argostranslate.package.get_available_packages() - installed_packages = argostranslate.package.get_installed_packages() - - def __validate_language(self, value): - """Used to validate `from_to_language` argument""" - try: - from_code, to_code = value.split("_") - except ValueError: - raise argparse.ArgumentTypeError( - f'Invalid language package "{value}", expected "_" in ISO 639 format' - ) from None - - package = next( - filter( - lambda x: x.from_code == from_code and x.to_code == to_code, - self.available_packages, - ), - None, - ) - - if not package: - raise argparse.ArgumentTypeError( - f'Package "{value}" is not available for install' - ) - - return package - - def add_arguments(self, parser): - parser.add_argument( - "languages", - action="store", - nargs="*", - type=self.__validate_language, - help='Language packages to install in the format of "_" in ISO 639 format', - ) - - parser.add_argument( - "--noinput", - "--no-input", - action="store_false", - dest="interactive", - help="Do not prompt the user for confirmation", - required=False, - ) - - parser.add_argument( - "--all", - action="store_true", - help="Install all available language packages", - required=False, - ) - - def __print_package_list(self, packages: List[argostranslate.package.Package]): - for package in packages: - self.stdout.write(f"{package.from_name} ➜ {package.to_name}") - - def handle(self, *args, **options): - interactive = options["interactive"] - packages = options["languages"] - verbosity = options["verbosity"] - all = options["all"] - - # Require either languages or "--all" to be specified - if not bool(packages) ^ bool(all): - raise argparse.ArgumentTypeError("A language selection must be specified") - - if all: - packages = self.available_packages - - existing_packages = [ - lang for lang in packages if lang in self.installed_packages - ] - pending_packages = [ - lang for lang in packages if lang not in self.installed_packages - ] - - if existing_packages: - if verbosity > 1: - self.stdout.write( - f"The following package{'s are' if len(existing_packages) > 1 else ' is'} already installed:" - ) - self.__print_package_list(existing_packages) - elif ( - not pending_packages - ): # Only notify the user if no packages will be installed - self.stdout.write( - f"The specified package{'s are' if len(existing_packages) > 1 else ' is'} already installed." - ) - - if not pending_packages: - return - - if pending_packages: - if verbosity > 1: - self.stdout.write( - f"The following package{'s' if len(pending_packages) > 1 else ''} will be installed:" - ) - self.__print_package_list(pending_packages) - elif ( - interactive - ): # Only log what will be installed if prompting the user to confirm - self.stdout.write( - f"{len(pending_packages)} package{'s' if len(pending_packages) > 1 else ''} will be installed." - ) - - if interactive: - confirm = input( - "Are you sure you want to do this?\n\nType 'yes' to continue, or 'no' to cancel: " - ) - else: - confirm = "yes" - - if confirm == "yes": - for package in pending_packages: - argostranslate.package.install_from_path(package.download()) - - successful_installs = len( - argostranslate.package.get_installed_packages() - ) - len(self.installed_packages) - - success_msg = f"{successful_installs} new package{'s' if successful_installs > 1 else ''} installed" - - if existing_packages: - success_msg = f"{success_msg}, while {len(existing_packages)} package{'s were' if len(existing_packages) > 1 else ' was'} already installed." - else: - success_msg = f"{success_msg}." - - self.stdout.write(success_msg) - else: - self.stdout.write("Installation cancelled.") diff --git a/hypha/apply/translate/management/commands/uninstall_languages.py b/hypha/apply/translate/management/commands/uninstall_languages.py deleted file mode 100644 index 585e882ca9..0000000000 --- a/hypha/apply/translate/management/commands/uninstall_languages.py +++ /dev/null @@ -1,109 +0,0 @@ -import argparse -from typing import List - -import argostranslate.package -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = ( - "Delete all drafts that haven't been modified in the specified time (in days)" - ) - - installed_packages = argostranslate.package.get_installed_packages() - - def __validate_language(self, value): - """Used to validate `from_to_language` argument""" - try: - from_code, to_code = value.split("_") - except ValueError: - raise argparse.ArgumentTypeError( - f'Invalid language package "{value}", expected "_" in ISO 639 format' - ) from None - - package = next( - filter( - lambda x: x.from_code == from_code and x.to_code == to_code, - self.installed_packages, - ), - None, - ) - - if not package: - raise argparse.ArgumentTypeError(f'Package "{value}" is not installed') - - return package - - def add_arguments(self, parser): - parser.add_argument( - "languages", - action="store", - nargs="*", - type=self.__validate_language, - help='Language packages to uninstall in the format of "_" in ISO 639 format', - ) - - parser.add_argument( - "--noinput", - "--no-input", - action="store_false", - dest="interactive", - help="Do not prompt the user for confirmation", - required=False, - ) - - parser.add_argument( - "--all", - action="store_true", - help="Uninstall all installed language packages", - required=False, - ) - - def __print_package_list(self, packages: List[argostranslate.package.Package]): - for package in packages: - self.stdout.write(f"{package.from_name} ➜ {package.to_name}") - - def handle(self, *args, **options): - interactive = options["interactive"] - packages = options["languages"] - verbosity = options["verbosity"] - - # Require either languages or "--all" to be specified - if not bool(packages) ^ bool(all): - raise argparse.ArgumentTypeError("A language selection must be specified") - - if all: - packages = self.installed_packages - - if verbosity > 1: - self.stdout.write( - f"The following package{'s' if len(packages) > 1 else ''} will be uninstalled:" - ) - self.__print_package_list(packages) - elif ( - interactive - ): # Only log what will be uninstalled if prompting the user to confirm - self.stdout.write( - f"{len(packages)} package{'s' if len(packages) > 1 else ''} will be uninstalled." - ) - - if interactive: - confirm = input( - "Are you sure you want to do this?\n\nType 'yes' to continue, or 'no' to cancel: " - ) - else: - confirm = "yes" - - if confirm == "yes": - for package in packages: - argostranslate.package.uninstall(package) - - successful_uninstalls = len(self.installed_packages) - len( - argostranslate.package.get_installed_packages() - ) - - self.stdout.write( - f"{successful_uninstalls} package{'s' if successful_uninstalls > 1 else ''} uninstalled." - ) - else: - self.stdout.write("Removal cancelled.") diff --git a/hypha/apply/translate/tests/__init__.py b/hypha/apply/translate/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hypha/apply/translate/tests/test_translate.py b/hypha/apply/translate/tests/test_translate.py deleted file mode 100644 index f8cb6de422..0000000000 --- a/hypha/apply/translate/tests/test_translate.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import List -from unittest.mock import Mock, patch - -from django.test import SimpleTestCase - -from hypha.apply.translate.translate import translate - - -class TestTranslate(SimpleTestCase): - @staticmethod - def mocked_translate(string: str, from_code, to_code): - """Use pig latin for all test translations - ie. 'hypha is cool' -> 'yphahay isway oolcay' - https://en.wikipedia.org/wiki/Pig_Latin - """ - vowels = {"a", "e", "i", "o", "u"} - string = string.lower() - pl = [ - f"{word}way" if word[0] in vowels else f"{word[1:]}{word[0]}ay" - for word in string.split() - ] - return " ".join(pl) - - @staticmethod - def mocked_get_available_translations(codes: List[str]): - mocked_packages = [ - Mock(from_code="ar", to_code="en"), - Mock(from_code="fr", to_code="en"), - Mock(from_code="en", to_code="ar"), - Mock(from_code="zh", to_code="en"), - Mock(from_code="en", to_code="fr"), - ] - - return list(filter(lambda x: x.from_code in codes, mocked_packages)) - - @classmethod - def setUpClass(cls): - """Used to patch & mock all the methods called from argostranslate & hypha.apply.translate.utils""" - - cls.patcher = [ - patch( - "hypha.apply.translate.utils.get_available_translations", - side_effect=cls.mocked_get_available_translations, - ), - patch( - "argostranslate.translate.translate", side_effect=cls.mocked_translate - ), - ] - - for patched in cls.patcher: - patched.start() - - @classmethod - def tearDownClass(cls): - for patched in cls.patcher: - patched.stop() - - def test_valid_translate(self): - self.assertEqual(translate("hey there", "fr", "en"), "eyhay heretay") - - def test_duplicate_code_translate(self): - with self.assertRaises(ValueError) as context: - translate("hey there", "fr", "fr") - - self.assertEqual( - "Translation from_code cannot match to_code", str(context.exception) - ) - - def test_invalid_code_translate(self): - with self.assertRaises(ValueError) as context: - translate("hey there", "test", "test2") - - self.assertIn("is not installed", str(context.exception)) diff --git a/hypha/apply/translate/tests/test_utils.py b/hypha/apply/translate/tests/test_utils.py deleted file mode 100644 index 194971983c..0000000000 --- a/hypha/apply/translate/tests/test_utils.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Optional -from unittest.mock import Mock, patch - -from django.http import QueryDict -from django.test import RequestFactory, SimpleTestCase - -from hypha.apply.translate.utils import ( - get_available_translations, - get_lang_name, - get_translation_params, -) - - -class TesGetAvailableTranslations(SimpleTestCase): - @classmethod - def setUpClass(cls): - mock_packages = [ - Mock(from_code="ar", to_code="en"), - Mock(from_code="fr", to_code="en"), - Mock(from_code="en", to_code="ar"), - Mock(from_code="zh", to_code="en"), - Mock(from_code="fr", to_code="zh"), - ] - - cls.patcher = patch( - "argostranslate.package.get_installed_packages", return_value=mock_packages - ) - cls.patcher.start() - - @classmethod - def tearDownClass(cls): - cls.patcher.stop() - - def test_get_available_translations(self): - codes = {(p.from_code, p.to_code) for p in get_available_translations()} - self.assertEqual( - codes, - {("ar", "en"), ("fr", "en"), ("en", "ar"), ("zh", "en"), ("fr", "zh")}, - ) - - def test_get_available_translations_with_codes(self): - codes = {(p.from_code, p.to_code) for p in get_available_translations(["fr"])} - self.assertEqual(codes, {("fr", "en"), ("fr", "zh")}) - - codes = { - (p.from_code, p.to_code) for p in get_available_translations(["en", "zh"]) - } - self.assertEqual(codes, {("en", "ar"), ("zh", "en")}) - - -class TestGetTranslationParams(SimpleTestCase): - def get_test_get_request(self, extra_params: Optional[str] = None) -> Mock: - extra_params = f"{extra_params}&" if extra_params else "" - return RequestFactory().get( - "/test/", data=QueryDict(f"{extra_params}fl=ar&tl=en") - ) - - def get_test_url(self, extra_params: Optional[str] = None) -> str: - extra_params = f"{extra_params}&" if extra_params else "" - return f"https://hyphaiscool.org/apply/submissions/6/?{extra_params}fl=ar&tl=en" - - def test_get_translation_params_with_request(self): - self.assertEqual( - get_translation_params(request=self.get_test_get_request()), ("ar", "en") - ) - - # Ensure param extraction works even when unrelated params are present - mock_request = self.get_test_get_request(extra_params="ref=table-view") - self.assertEqual(get_translation_params(request=mock_request), ("ar", "en")) - - def test_get_translation_params_with_url(self): - self.assertEqual(get_translation_params(url=self.get_test_url()), ("ar", "en")) - - # Ensure param extraction works even when unrelated params are present - url = self.get_test_url("ref=table-view") - self.assertEqual(get_translation_params(url=url), ("ar", "en")) - - def test_get_translation_params_with_invalid_args(self): - # Should fail with no args given... - with self.assertRaises(ValueError): - get_translation_params() - - # ...and with both args given - with self.assertRaises(ValueError): - get_translation_params(self.get_test_url(), self.get_test_get_request()) - - def test_get_translation_params_with_invalid_params(self): - # Testing using params that hypha can give but are unrelated to translations - mock_request = RequestFactory().get("/test/", data=QueryDict("ref=table-view")) - self.assertIsNone(get_translation_params(request=mock_request)) - - url = "https://hyphaiscool.org/apply/submissions/6/?ref=table-view" - self.assertIsNone(get_translation_params(url=url)) - - -class TestGetLangName(SimpleTestCase): - def test_get_lang_name(self): - # "!" added to ensure mock is working rather than actually calling argos - language_mock = Mock() - language_mock.name = ( - "Arabic!" # Done this way as `name` is an attribute of Mock() objects - ) - with patch( - "argostranslate.translate.get_language_from_code", - return_value=language_mock, - ) as from_code_mock: - self.assertEqual(get_lang_name("ar"), "Arabic!") - from_code_mock.assert_called_once_with("ar") - - def test_get_lang_name_invalid_code(self): - with patch( - "argostranslate.translate.get_language_from_code", - side_effect=AttributeError(), - ) as from_code_mock: - self.assertIsNone(get_lang_name("nope")) - from_code_mock.assert_called_once_with("nope") diff --git a/hypha/apply/translate/translate.py b/hypha/apply/translate/translate.py deleted file mode 100644 index 74b448348f..0000000000 --- a/hypha/apply/translate/translate.py +++ /dev/null @@ -1,35 +0,0 @@ -import argostranslate.translate - -from . import utils - - -def translate(string: str, from_code: str, to_code: str) -> str: - """Translate a string from one language to another - - Requires the request language's argostranslate package to be installed first - - Args: - string: the string to translate - from_code: the ISO 639 code of the original language - to_code: the ISO 639 code of the language to translate to - - Returns: - str: the translated string - - Raises: - ValueError: if the requested language translation package is not installed or request is invalid - """ - - if from_code == to_code: - raise ValueError("Translation from_code cannot match to_code") - - available_translations = utils.get_available_translations([from_code]) - - if not available_translations or to_code not in [ - package.to_code for package in available_translations - ]: - raise ValueError(f"Package {from_code} -> {to_code} is not installed") - - translated_text = argostranslate.translate.translate(string, from_code, to_code) - - return translated_text diff --git a/hypha/apply/translate/utils.py b/hypha/apply/translate/utils.py deleted file mode 100644 index d322432d07..0000000000 --- a/hypha/apply/translate/utils.py +++ /dev/null @@ -1,64 +0,0 @@ -from typing import List, Optional, Tuple -from urllib.parse import parse_qs, urlparse - -from argostranslate import package, translate -from django.http import HttpRequest - - -def get_available_translations( - from_codes: Optional[List[str]] = None, -) -> List[package.Package]: - """Get languages available for translation - - Args: - from_codes: optionally specify a list of languages to view available translations to - - Returns: - A list of argostranslate package objects that are installed and available. - """ - - available_packages = package.get_installed_packages() - - if not from_codes: - return available_packages - - return list(filter(lambda x: x.from_code in from_codes, available_packages)) - - -def get_translation_params( - url: str = None, request: HttpRequest = None -) -> Tuple[str, str] | None: - r"""Attempts to extract the `fl` (from language) & `tl` (to language) params from the provided URL or request object - - Return values are *not* validated to ensure languages are valid & packages exist. - - Args: - url: the URL to extract the params from - - Returns: - tuple: in the format of (\, \) - - Raises: - ValueError: If `url`/`request` are not provided OR if both are provided - """ - - # Ensure either url or request is provided but not both. - if not (bool(url) ^ bool(request)): - raise ValueError("Either a URL or HttpRequest must be provided.") - - if url: - query_dict = {k: v[0] for (k, v) in parse_qs(urlparse(url).query).items()} - else: - query_dict = request.GET - - if (to_lang := query_dict.get("tl")) and (from_lang := query_dict.get("fl")): - return (from_lang, to_lang) - - return None - - -def get_lang_name(code: str) -> str | None: - try: - return translate.get_language_from_code(code).name - except AttributeError: - return None diff --git a/hypha/settings/base.py b/hypha/settings/base.py index 6f39637d3a..f62b2ccdff 100644 --- a/hypha/settings/base.py +++ b/hypha/settings/base.py @@ -185,9 +185,6 @@ # The corrosponding locale dir is named: en, en_GB, en_US LANGUAGE_CODE = env.str("LANGUAGE_CODE", "en") -# Translation settings -SUBMISSION_TRANSLATIONS_ENABLED = env.bool("SUBMISSION_TRANSLATIONS_ENABLED", False) - # Number of seconds that password reset and account activation links are valid (default 259200, 3 days). PASSWORD_RESET_TIMEOUT = env.int("PASSWORD_RESET_TIMEOUT", 259200) diff --git a/hypha/settings/django.py b/hypha/settings/django.py index d100fd22dd..7f97e38f4a 100644 --- a/hypha/settings/django.py +++ b/hypha/settings/django.py @@ -21,7 +21,6 @@ "hypha.apply.determinations", "hypha.apply.stream_forms", "hypha.apply.todo", - "hypha.apply.translate", "hypha.apply.utils.apps.UtilsConfig", "hypha.apply.projects.apps.ProjectsConfig", "hypha.public.funds", @@ -88,7 +87,6 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", - "django.middleware.common.CommonMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "elevate.middleware.ElevateMiddleware", diff --git a/hypha/static_src/javascript/translate-application.js b/hypha/static_src/javascript/translate-application.js deleted file mode 100644 index 27e3daaf47..0000000000 --- a/hypha/static_src/javascript/translate-application.js +++ /dev/null @@ -1,13 +0,0 @@ -(function () { - // eslint-disable-next-line no-undef - htmx.on("translatedSubmission", (event) => { - if (event.detail?.appTitle) { - document.getElementById("app-title").textContent = - event.detail.appTitle; - } - - if (event.detail?.docTitle) { - document.title = event.detail.docTitle; - } - }); -})(); diff --git a/requirements.txt b/requirements.txt index 7e2145297d..36fd37fbd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ scout-apm==3.1.0 sentry-sdk==2.8.0 # Production dependencies -argostranslate==1.9.6 Babel==2.14.0 boto3==1.34.66 celery==5.3.6