From 59968e7e68bd20c902acb801a281978a3623ba77 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Tue, 24 Feb 2026 16:02:42 +0100 Subject: [PATCH 1/8] Mark missing strings for translation --- hypha/addressfield/fields.py | 5 +- .../apply/activity/adapters/activity_feed.py | 4 +- hypha/apply/activity/adapters/base.py | 9 +- hypha/apply/activity/forms.py | 8 +- hypha/apply/activity/views.py | 9 +- hypha/apply/categories/admin.py | 3 +- hypha/apply/categories/admin_helpers.py | 10 +- hypha/apply/categories/admin_views.py | 11 +- hypha/apply/categories/models.py | 17 +- hypha/apply/determinations/blocks.py | 6 +- hypha/apply/determinations/forms.py | 128 ++++++++----- hypha/apply/determinations/models.py | 130 +++++++------ hypha/apply/flags/models.py | 5 +- hypha/apply/funds/admin.py | 13 +- hypha/apply/funds/admin_forms.py | 17 +- hypha/apply/funds/admin_helpers.py | 12 +- hypha/apply/funds/blocks.py | 61 ++---- hypha/apply/funds/forms.py | 8 +- hypha/apply/funds/models/applications.py | 33 ++-- hypha/apply/funds/models/forms.py | 5 +- hypha/apply/funds/models/reminders.py | 6 +- hypha/apply/funds/models/reviewer_role.py | 18 +- hypha/apply/funds/models/screening.py | 4 +- hypha/apply/funds/models/utils.py | 2 +- hypha/apply/funds/permissions.py | 62 +++--- hypha/apply/funds/services.py | 11 +- hypha/apply/funds/tables.py | 10 +- hypha/apply/funds/templatetags/table_tags.py | 3 +- hypha/apply/funds/utils.py | 7 +- hypha/apply/funds/views/submission_detail.py | 3 +- hypha/apply/funds/views/translate.py | 2 +- hypha/apply/funds/workflows/registry.py | 12 +- hypha/apply/projects/admin.py | 9 +- hypha/apply/projects/filters.py | 6 +- hypha/apply/projects/models/payment.py | 2 +- hypha/apply/projects/models/project.py | 45 +++-- hypha/apply/projects/permissions.py | 179 ++++++++++-------- hypha/apply/projects/reports/forms.py | 3 +- hypha/apply/projects/reports/tables.py | 11 +- hypha/apply/projects/tables.py | 8 +- hypha/apply/projects/views/payment.py | 4 +- hypha/apply/projects/views/project.py | 2 +- hypha/apply/review/admin_helpers.py | 5 +- hypha/apply/review/blocks.py | 8 +- hypha/apply/review/forms.py | 10 +- hypha/apply/review/views.py | 16 +- hypha/apply/stream_forms/blocks.py | 12 +- hypha/apply/stream_forms/forms.py | 32 ++-- hypha/apply/todo/options.py | 50 ++--- hypha/apply/todo/views.py | 3 +- hypha/apply/translate/forms.py | 5 +- hypha/apply/users/admin_views.py | 10 +- hypha/apply/users/forms.py | 2 +- hypha/apply/users/models.py | 8 +- hypha/apply/users/services.py | 13 +- hypha/apply/users/utils.py | 4 +- hypha/apply/users/views.py | 4 +- hypha/apply/users/wagtail_hooks.py | 3 +- hypha/apply/utils/blocks.py | 23 ++- hypha/apply/utils/models.py | 8 +- hypha/cookieconsent/models.py | 31 +-- hypha/core/models/system_settings.py | 40 ++-- hypha/core/navigation.py | 7 +- 63 files changed, 676 insertions(+), 521 deletions(-) diff --git a/hypha/addressfield/fields.py b/hypha/addressfield/fields.py index 5c872c90bf..c496da1edc 100644 --- a/hypha/addressfield/fields.py +++ b/hypha/addressfield/fields.py @@ -3,6 +3,7 @@ from django import forms from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ from .widgets import AddressWidget @@ -49,7 +50,7 @@ def clean(self, value, **kwargs): try: country_data = self.data[country] except KeyError: - raise ValidationError("Invalid country selected") from None + raise ValidationError(_("Invalid country selected")) from None fields = flatten_data(country_data["fields"]) @@ -59,7 +60,7 @@ def clean(self, value, **kwargs): if missing_fields: missing_field_name = [fields[field]["label"] for field in missing_fields] raise ValidationError( - "Please provide data for: {}".format(", ".join(missing_field_name)) + _("Please provide data for: {}").format(", ".join(missing_field_name)) ) return super().clean(value, **kwargs) diff --git a/hypha/apply/activity/adapters/activity_feed.py b/hypha/apply/activity/adapters/activity_feed.py index 570129dfa6..dd9cbef461 100644 --- a/hypha/apply/activity/adapters/activity_feed.py +++ b/hypha/apply/activity/adapters/activity_feed.py @@ -307,9 +307,9 @@ def handle_report_frequency(self, config, **kwargs): def handle_skipped_report(self, report, **kwargs): if report.skipped: - return "Skipped a Report" + return _("Skipped a Report") else: - return "Marked a Report as required" + return _("Marked a Report as required") def handle_update_invoice_status(self, invoice, **kwargs): base_message = _("Updated Invoice status to: {invoice_status}.") diff --git a/hypha/apply/activity/adapters/base.py b/hypha/apply/activity/adapters/base.py index c88813ec5d..1ff0e03c45 100644 --- a/hypha/apply/activity/adapters/base.py +++ b/hypha/apply/activity/adapters/base.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib import messages +from django.utils.translation import gettext as _ from hypha.apply.activity.options import MESSAGES @@ -192,11 +193,13 @@ def process_send( if not settings.SEND_MESSAGES: if recipient: - debug_message = "{} [to: {}]: {}".format( - self.adapter_type, recipient, message + debug_message = _("{adapter} [to: {recipient}]: {message}").format( + adapter=self.adapter_type, recipient=recipient, message=message ) else: - debug_message = "{}: {}".format(self.adapter_type, message) + debug_message = _("{adapter}: {message}").format( + adapter=self.adapter_type, message=message + ) messages.add_message(request, messages.DEBUG, debug_message) def create_logs(self, message, recipient, *events): diff --git a/hypha/apply/activity/forms.py b/hypha/apply/activity/forms.py index 096a1dd45e..6913c4d817 100644 --- a/hypha/apply/activity/forms.py +++ b/hypha/apply/activity/forms.py @@ -30,11 +30,13 @@ class Meta: "assign_to", ) labels = { - "visibility": "Visible to", - "message": "Message", + "visibility": _("Visible to"), + "message": _("Message"), } help_texts = { - "visibility": "Select a relevant user role. Staff can view every comment." + "visibility": _( + "Select a relevant user role. Staff can view every comment." + ) } widgets = { "visibility": forms.RadioSelect(), diff --git a/hypha/apply/activity/views.py b/hypha/apply/activity/views.py index 506700a119..207487fd99 100644 --- a/hypha/apply/activity/views.py +++ b/hypha/apply/activity/views.py @@ -3,6 +3,7 @@ from django.core.paginator import Paginator from django.shortcuts import get_object_or_404, render from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ from django.views.decorators.http import require_http_methods from django.views.generic import ListView from rolepermissions.checkers import has_object_permission @@ -57,10 +58,10 @@ def edit_comment(request, pk): activity = get_object_or_404(Activity, id=pk) if activity.type != COMMENT or activity.user != request.user: - raise PermissionError("You can only edit your own comments") + raise PermissionDenied(_("You can only edit your own comments")) if activity.deleted: - raise PermissionError("You can not edit a deleted comment") + raise PermissionDenied(_("You can not edit a deleted comment")) if request.GET.get("action") == "cancel": return render( @@ -88,10 +89,10 @@ def delete_comment(request, pk): activity = get_object_or_404(Activity, id=pk) if activity.type != COMMENT or activity.user != request.user: - raise PermissionError("You can only delete your own comments") + raise PermissionDenied(_("You can only delete your own comments")) if activity.deleted: - raise PermissionError("You can not delete a deleted comment") + raise PermissionDenied(_("You can not delete a deleted comment")) if request.method == "DELETE": activity = services.delete_comment(activity) diff --git a/hypha/apply/categories/admin.py b/hypha/apply/categories/admin.py index da1d2821f1..5226284c9f 100644 --- a/hypha/apply/categories/admin.py +++ b/hypha/apply/categories/admin.py @@ -1,4 +1,5 @@ from django.urls import re_path +from django.utils.translation import gettext_lazy as _ from wagtail_modeladmin.options import ModelAdmin from hypha.apply.utils.admin import AdminIcon @@ -9,7 +10,7 @@ class CategoryAdmin(ModelAdmin): - menu_label = "Category Questions" + menu_label = _("Category Questions") menu_icon = str(AdminIcon.CATEGORY) model = Category diff --git a/hypha/apply/categories/admin_helpers.py b/hypha/apply/categories/admin_helpers.py index 90310a4b30..711e4d64a8 100644 --- a/hypha/apply/categories/admin_helpers.py +++ b/hypha/apply/categories/admin_helpers.py @@ -1,4 +1,5 @@ from django.contrib.admin.utils import quote +from django.utils.translation import gettext as _ from wagtail_modeladmin.helpers import ButtonHelper @@ -33,9 +34,12 @@ def add_child_button(self, pk, child_verbose_name, **kwargs): ) return { "classname": classname, - "label": "Add %s %s" % (child_verbose_name, self.verbose_name), - "title": "Add %s %s under this one" - % (child_verbose_name, self.verbose_name), + "label": _("Add {child_verbose_name} {verbose_name}").format( + child_verbose_name=child_verbose_name, verbose_name=self.verbose_name + ), + "title": _("Add {child_verbose_name} {verbose_name} under this one").format( + child_verbose_name=child_verbose_name, verbose_name=self.verbose_name + ), "url": self.url_helper.get_action_url("add_child", quote(pk)), } diff --git a/hypha/apply/categories/admin_views.py b/hypha/apply/categories/admin_views.py index e0192e7a70..9cd424e68a 100644 --- a/hypha/apply/categories/admin_views.py +++ b/hypha/apply/categories/admin_views.py @@ -1,5 +1,6 @@ from django.contrib.admin.utils import unquote from django.shortcuts import get_object_or_404 +from django.utils.translation import gettext as _ from wagtail_modeladmin.views import CreateView @@ -18,11 +19,11 @@ def __init__(self, model_admin, parent_pk): def get_page_title(self): """Generate a title that explains you are adding a child.""" - title = super().get_page_title() - return title + " %s %s for %s" % ( - self.model.node_child_verbose_name, - self.opts.verbose_name, - self.parent_instance, + return _("{title} {child_verbose_name} {verbose_name} for {parent}").format( + title=super().get_page_title(), + child_verbose_name=self.model.node_child_verbose_name, + verbose_name=self.opts.verbose_name, + parent=self.parent_instance, ) def get_initial(self): diff --git a/hypha/apply/categories/models.py b/hypha/apply/categories/models.py index 53d0bda898..ad97bf4ae6 100644 --- a/hypha/apply/categories/models.py +++ b/hypha/apply/categories/models.py @@ -2,6 +2,7 @@ from django.core.exceptions import PermissionDenied from django.db import models from django.template.loader import render_to_string +from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from modelcluster.fields import ParentalKey from modelcluster.models import ClusterableModel @@ -45,7 +46,7 @@ def __str__(self): return self.name class Meta: - verbose_name_plural = "Categories" + verbose_name_plural = _("Categories") class MetaTerm(index.Indexed, MP_Node): @@ -106,13 +107,13 @@ def get_as_listing_header(self): ) return rendered - get_as_listing_header.short_description = "Name" + get_as_listing_header.short_description = _("Name") get_as_listing_header.admin_order_field = "name" def get_parent(self, *args, **kwargs): return super().get_parent(*args, **kwargs) - get_parent.short_description = "Parent" + get_parent.short_description = _("Parent") search_fields = [ index.SearchField("name"), @@ -120,7 +121,7 @@ def get_parent(self, *args, **kwargs): def delete(self): if self.is_root(): - raise PermissionDenied("Cannot delete root term.") + raise PermissionDenied(gettext("Cannot delete root term.")) else: super().delete() @@ -160,10 +161,12 @@ def __init__(self, *args, **kwargs): if instance.is_root() or MetaTerm.objects.count() == 0: self.fields["parent"].disabled = True self.fields["parent"].required = False - self.fields["parent"].empty_label = "N/A - Root Term" + self.fields["parent"].empty_label = gettext("N/A - Root Term") self.fields["parent"].widget = forms.HiddenInput() - self.fields["name"].label += " (Root - First term can be named root)" + self.fields["name"].label += gettext( + " (Root - First term can be named root)" + ) elif instance.id: self.fields["parent"].initial = instance.get_parent() @@ -172,7 +175,7 @@ def clean_parent(self): if parent and parent.is_archived: raise forms.ValidationError( - "The parent is archived therefore can not add child under it." + gettext("The parent is archived therefore can not add child under it.") ) return parent diff --git a/hypha/apply/determinations/blocks.py b/hypha/apply/determinations/blocks.py index 81e055a63f..d75bac29da 100644 --- a/hypha/apply/determinations/blocks.py +++ b/hypha/apply/determinations/blocks.py @@ -20,7 +20,7 @@ class DeterminationMustIncludeFieldBlock(MustIncludeFieldBlock): class DeterminationBlock(DeterminationMustIncludeFieldBlock): name = "determination" - description = "Overall determination" + description = _("Overall determination") field_class = forms.TypedChoiceField class Meta: @@ -40,7 +40,7 @@ def render(self, value, context=None): class DeterminationMessageBlock(DeterminationMustIncludeFieldBlock): name = "message" - description = "Determination message" + description = _("Determination message") widget = RICH_TEXT_WIDGET class Meta: @@ -55,7 +55,7 @@ def get_field_kwargs(self, struct_value): class SendNoticeBlock(DeterminationMustIncludeFieldBlock): name = "send_notice" - description = "Send Notice" + description = _("Send Notice") default_value = BooleanBlock(default=True, required=False) diff --git a/hypha/apply/determinations/forms.py b/hypha/apply/determinations/forms.py index b9c553f4d1..9ade2635ad 100644 --- a/hypha/apply/determinations/forms.py +++ b/hypha/apply/determinations/forms.py @@ -93,7 +93,9 @@ class Meta: error_messages = { NON_FIELD_ERRORS: { - "unique_together": "You have already created a determination for this submission", + "unique_together": _( + "You have already created a determination for this submission" + ), } } @@ -171,68 +173,78 @@ def save(self): class BaseConceptDeterminationForm(forms.Form): titles = { - 1: "Feedback", + 1: _("Feedback"), } outcome = forms.ChoiceField( choices=DETERMINATION_CHOICES, label=_("Determination"), - help_text="Do you recommend requesting a proposal based on this concept note?", + help_text=_( + "Do you recommend requesting a proposal based on this concept note?" + ), ) outcome.group = 1 message = RichTextField( label=_("Determination message"), - help_text="This text will be e-mailed to the applicant. " - "Ones when text is first added and then every time the text is changed.", + help_text=_( + "This text will be e-mailed to the applicant. " + "Ones when text is first added and then every time the text is changed." + ), ) message.group = 1 principles = RichTextField( label=_("Goals and principles"), - help_text="Does the project contribute and/or have relevance to OTF goals and principles?" - "Are the goals and objectives of the project clear? Is it a technology research, development, or deployment " - "project? Can project’s effort be explained to external audiences and non-technical people? What problem are " - "they trying to solve and is the solution strategical or tactical? Is the project strategically or tactically " - "important to OTF’s goals, principles and rationale and other OTF efforts? Is it clear how? What tools, if any, " - "currently exist to solve this problem? How is this project different? Does the effort have any overlap with " - "existing OTF and/or USG supported projects? Is the overlap complementary or duplicative? If complementary, " - "can it be explained clearly? I.e. geographic focus, technology, organization profile, etc. What are the " - "liabilities and risks of taking on this project? I.e. political personalities, financial concerns, technical " - "controversial, etc. Is the organization or its members known within any relevant communities? If yes, what is " - "their reputation and why? What is the entity’s motivation and principles? What are the entity member(s) " - "motivations and principles? Where is the organization physically and legally based? If the organization is " - "distributed, where is the main point of contact? Does the organization have any conflicts of interest with " - "RFA, OTF, the Advisory Council, or other RFA-OTF projects? Is the project team an organization, community " - "or an individual?", + help_text=_( + "Does the project contribute and/or have relevance to OTF goals and principles?" + "Are the goals and objectives of the project clear? Is it a technology research, development, or deployment " + "project? Can project’s effort be explained to external audiences and non-technical people? What problem are " + "they trying to solve and is the solution strategical or tactical? Is the project strategically or tactically " + "important to OTF’s goals, principles and rationale and other OTF efforts? Is it clear how? What tools, if any, " + "currently exist to solve this problem? How is this project different? Does the effort have any overlap with " + "existing OTF and/or USG supported projects? Is the overlap complementary or duplicative? If complementary, " + "can it be explained clearly? I.e. geographic focus, technology, organization profile, etc. What are the " + "liabilities and risks of taking on this project? I.e. political personalities, financial concerns, technical " + "controversial, etc. Is the organization or its members known within any relevant communities? If yes, what is " + "their reputation and why? What is the entity’s motivation and principles? What are the entity member(s) " + "motivations and principles? Where is the organization physically and legally based? If the organization is " + "distributed, where is the main point of contact? Does the organization have any conflicts of interest with " + "RFA, OTF, the Advisory Council, or other RFA-OTF projects? Is the project team an organization, community " + "or an individual?" + ), ) principles.group = 1 technical = RichTextField( label=_("Technical merit"), - help_text="Does the project clearly articulate the technical problem, solution, and approach? " - "Is the problem clearly justifiable? Does the project clearly articulate the technological objectives? " - "Is it an open or closed development project? I.e. Open source like Android or open source like Firefox OS " - "or closed like iOS. Does a similar technical solution already exist? If so, what are the differentiating " - "factors? Is the effort to sustain an existing technical approach? If so, are these considered successful? " - "Is the effort a new technical approach or improvement to an existing solution? If so, how? Is the effort " - "a completely new technical approach fostering new solutions in the field? Does the project’s technical " - "approach solve the problem? What are the limitations of the project’s technical approach and solution? " - "What are the unintended or illicit uses and consequences of this technology? Has the project identified " - "and/or developed any safeguards for these consequences?", + help_text=_( + "Does the project clearly articulate the technical problem, solution, and approach? " + "Is the problem clearly justifiable? Does the project clearly articulate the technological objectives? " + "Is it an open or closed development project? I.e. Open source like Android or open source like Firefox OS " + "or closed like iOS. Does a similar technical solution already exist? If so, what are the differentiating " + "factors? Is the effort to sustain an existing technical approach? If so, are these considered successful? " + "Is the effort a new technical approach or improvement to an existing solution? If so, how? Is the effort " + "a completely new technical approach fostering new solutions in the field? Does the project’s technical " + "approach solve the problem? What are the limitations of the project’s technical approach and solution? " + "What are the unintended or illicit uses and consequences of this technology? Has the project identified " + "and/or developed any safeguards for these consequences?" + ), ) technical.group = 1 sustainable = RichTextField( label=_("Reasonable, realistic and sustainable"), - help_text="Is the requested amount reasonable, realistic, and justified? If OTF doesn’t support the project, " - "is it likely to be realized? Does the project provide a detailed and realistic description of effort and " - "schedule? I.e. is the project capable of creating a work plan including objectives, activities, and " - "deliverable(s)? Does the project have a clear support model? Is there a known sustainability plan for the " - "future? What in-kind support or other revenue streams is the project receiving? I.e. volunteer developers, " - "service or product sales. Is the project receiving any financial support from the USG? Is this information " - "disclosed? Is the project receiving any other financial support? Is this information disclosed? Are existing " - "supporters approachable? Are they likely aware and/or comfortable with the Intellectual property language " - "within USG contracts?", + help_text=_( + "Is the requested amount reasonable, realistic, and justified? If OTF doesn’t support the project, " + "is it likely to be realized? Does the project provide a detailed and realistic description of effort and " + "schedule? I.e. is the project capable of creating a work plan including objectives, activities, and " + "deliverable(s)? Does the project have a clear support model? Is there a known sustainability plan for the " + "future? What in-kind support or other revenue streams is the project receiving? I.e. volunteer developers, " + "service or product sales. Is the project receiving any financial support from the USG? Is this information " + "disclosed? Is the project receiving any other financial support? Is this information disclosed? Are existing " + "supporters approachable? Are they likely aware and/or comfortable with the Intellectual property language " + "within USG contracts?" + ), ) sustainable.group = 1 @@ -242,11 +254,11 @@ class BaseConceptDeterminationForm(forms.Form): class BaseProposalDeterminationForm(forms.Form): titles = { - 1: "A. Determination", - 2: "B. General thoughts", - 3: "C. Specific aspects", - 4: "D. Rationale and appropriateness consideration", - 5: "E. General recommendation", + 1: _("A. Determination"), + 2: _("B. General thoughts"), + 3: _("C. Specific aspects"), + 4: _("D. Rationale and appropriateness consideration"), + 5: _("E. General recommendation"), } # A. Determination @@ -254,33 +266,41 @@ class BaseProposalDeterminationForm(forms.Form): outcome = forms.ChoiceField( choices=DETERMINATION_CHOICES, label=_("Determination"), - help_text="Do you recommend requesting a proposal based on this concept note?", + help_text=_( + "Do you recommend requesting a proposal based on this concept note?" + ), ) outcome.group = 1 message = RichTextField( label=_("Determination message"), - help_text="This text will be e-mailed to the applicant. " - "Ones when text is first added and then every time the text is changed.", + help_text=_( + "This text will be e-mailed to the applicant. " + "Ones when text is first added and then every time the text is changed." + ), ) message.group = 1 # B. General thoughts liked = RichTextField( label=_("Positive aspects"), - help_text="Any general or specific aspects that got you really excited or that you like about this proposal.", + help_text=_( + "Any general or specific aspects that got you really excited or that you like about this proposal." + ), ) liked.group = 2 concerns = RichTextField( label=_("Concerns"), - help_text="Any general or specific aspects that concern you or leave you feeling uneasy about this proposal.", + help_text=_( + "Any general or specific aspects that concern you or leave you feeling uneasy about this proposal." + ), ) concerns.group = 2 red_flags = RichTextField( label=_("Items that must be addressed"), - help_text="Anything you think should be flagged for our attention.", + help_text=_("Anything you think should be flagged for our attention."), ) red_flags.group = 2 @@ -489,7 +509,9 @@ class Meta: error_messages = { NON_FIELD_ERRORS: { - "unique_together": "You have already created a determination for this submission", + "unique_together": _( + "You have already created a determination for this submission" + ), } } @@ -578,7 +600,9 @@ class BatchDeterminationForm(StreamBaseForm, forms.Form, metaclass=FormMixedMeta outcome = forms.ChoiceField( choices=DETERMINATION_CHOICES, label=_("Determination"), - help_text="Do you recommend requesting a proposal based on this concept note?", + help_text=_( + "Do you recommend requesting a proposal based on this concept note?" + ), widget=forms.HiddenInput(), ) diff --git a/hypha/apply/determinations/models.py b/hypha/apply/determinations/models.py index 0c8cad47f9..fe075f7666 100644 --- a/hypha/apply/determinations/models.py +++ b/hypha/apply/determinations/models.py @@ -196,19 +196,19 @@ class DeterminationMessageSettings(BaseSiteSetting): wagtail_reference_index_ignore = True class Meta: - verbose_name = "determination messages" + verbose_name = _("determination messages") - request_accepted = RichTextField("Approved", blank=True) - request_rejected = RichTextField("Dismissed", blank=True) - request_more_info = RichTextField("Needs more info", blank=True) + request_accepted = RichTextField(_("Approved"), blank=True) + request_rejected = RichTextField(_("Dismissed"), blank=True) + request_more_info = RichTextField(_("Needs more info"), blank=True) - concept_accepted = RichTextField("Approved", blank=True) - concept_rejected = RichTextField("Dismissed", blank=True) - concept_more_info = RichTextField("Needs more info", blank=True) + concept_accepted = RichTextField(_("Approved"), blank=True) + concept_rejected = RichTextField(_("Dismissed"), blank=True) + concept_more_info = RichTextField(_("Needs more info"), blank=True) - proposal_accepted = RichTextField("Approved", blank=True) - proposal_rejected = RichTextField("Dismissed", blank=True) - proposal_more_info = RichTextField("Needs more info", blank=True) + proposal_accepted = RichTextField(_("Approved"), blank=True) + proposal_rejected = RichTextField(_("Dismissed"), blank=True) + proposal_more_info = RichTextField(_("Needs more info"), blank=True) def get_for_stage(self, stage_name): message_templates = {} @@ -254,79 +254,83 @@ def get_for_stage(self, stage_name): @register_setting class DeterminationFormSettings(BaseSiteSetting): class Meta: - verbose_name = "determination form settings" + verbose_name = _("determination form settings") concept_principles_label = models.CharField( - "label", default="Goals and principles", max_length=255 + _("label"), default=_("Goals and principles"), max_length=255 ) - concept_principles_help_text = models.TextField("help text", blank=True) + concept_principles_help_text = models.TextField(_("help text"), blank=True) concept_technical_label = models.CharField( - "label", default="Technical merit", max_length=255 + _("label"), default=_("Technical merit"), max_length=255 ) - concept_technical_help_text = models.TextField("help text", blank=True) + concept_technical_help_text = models.TextField(_("help text"), blank=True) concept_sustainable_label = models.CharField( - "label", default="Reasonable, realistic and sustainable", max_length=255 + _("label"), default=_("Reasonable, realistic and sustainable"), max_length=255 ) - concept_sustainable_help_text = models.TextField("help text", blank=True) + concept_sustainable_help_text = models.TextField(_("help text"), blank=True) proposal_liked_label = models.CharField( - "label", default="Positive aspects", max_length=255 + _("label"), default=_("Positive aspects"), max_length=255 ) - proposal_liked_help_text = models.TextField("help text", blank=True) + proposal_liked_help_text = models.TextField(_("help text"), blank=True) proposal_concerns_label = models.CharField( - "label", default="Concerns", max_length=255 + _("label"), default=_("Concerns"), max_length=255 ) - proposal_concerns_help_text = models.TextField("help text", blank=True) + proposal_concerns_help_text = models.TextField(_("help text"), blank=True) proposal_red_flags_label = models.CharField( - "label", default="Items that must be addressed", max_length=255 + _("label"), default=_("Items that must be addressed"), max_length=255 ) - proposal_red_flags_help_text = models.TextField("help text", blank=True) + proposal_red_flags_help_text = models.TextField(_("help text"), blank=True) proposal_overview_label = models.CharField( - "label", default="Project overview questions and comments", max_length=255 + _("label"), default=_("Project overview questions and comments"), max_length=255 ) - proposal_overview_help_text = models.TextField("help text", blank=True) + proposal_overview_help_text = models.TextField(_("help text"), blank=True) proposal_objectives_label = models.CharField( - "label", default="Objectives questions and comments", max_length=255 + _("label"), default=_("Objectives questions and comments"), max_length=255 ) - proposal_objectives_help_text = models.TextField("help text", blank=True) + proposal_objectives_help_text = models.TextField(_("help text"), blank=True) proposal_strategy_label = models.CharField( - "label", default="Methods and strategy questions and comments", max_length=255 + _("label"), + default=_("Methods and strategy questions and comments"), + max_length=255, ) - proposal_strategy_help_text = models.TextField("help text", blank=True) + proposal_strategy_help_text = models.TextField(_("help text"), blank=True) proposal_technical_label = models.CharField( - "label", default="Technical feasibility questions and comments", max_length=255 + _("label"), + default=_("Technical feasibility questions and comments"), + max_length=255, ) - proposal_technical_help_text = models.TextField("help text", blank=True) + proposal_technical_help_text = models.TextField(_("help text"), blank=True) proposal_alternative_label = models.CharField( - "label", - default='Alternative analysis - "red teaming" questions and comments', + _("label"), + default=_('Alternative analysis - "red teaming" questions and comments'), max_length=255, ) - proposal_alternative_help_text = models.TextField("help text", blank=True) + proposal_alternative_help_text = models.TextField(_("help text"), blank=True) proposal_usability_label = models.CharField( - "label", default="Usability questions and comments", max_length=255 + _("label"), default=_("Usability questions and comments"), max_length=255 ) - proposal_usability_help_text = models.TextField("help text", blank=True) + proposal_usability_help_text = models.TextField(_("help text"), blank=True) proposal_sustainability_label = models.CharField( - "label", default="Sustainability questions and comments", max_length=255 + _("label"), default=_("Sustainability questions and comments"), max_length=255 ) - proposal_sustainability_help_text = models.TextField("help text", blank=True) + proposal_sustainability_help_text = models.TextField(_("help text"), blank=True) proposal_collaboration_label = models.CharField( - "label", default="Collaboration questions and comments", max_length=255 + _("label"), default=_("Collaboration questions and comments"), max_length=255 ) - proposal_collaboration_help_text = models.TextField("help text", blank=True) + proposal_collaboration_help_text = models.TextField(_("help text"), blank=True) proposal_realism_label = models.CharField( - "label", default="Cost realism questions and comments", max_length=255 + _("label"), default=_("Cost realism questions and comments"), max_length=255 ) - proposal_realism_help_text = models.TextField("help text", blank=True) + proposal_realism_help_text = models.TextField(_("help text"), blank=True) proposal_qualifications_label = models.CharField( - "label", default="Qualifications questions and comments", max_length=255 + _("label"), default=_("Qualifications questions and comments"), max_length=255 ) - proposal_qualifications_help_text = models.TextField("help text", blank=True) + proposal_qualifications_help_text = models.TextField(_("help text"), blank=True) proposal_evaluation_label = models.CharField( - "label", default="Evaluation questions and comments", max_length=255 + _("label"), default=_("Evaluation questions and comments"), max_length=255 ) - proposal_evaluation_help_text = models.TextField("help text", blank=True) + proposal_evaluation_help_text = models.TextField(_("help text"), blank=True) concept_help_text_tab_panels = [ MultiFieldPanel( @@ -334,21 +338,21 @@ class Meta: FieldPanel("concept_principles_label"), FieldPanel("concept_principles_help_text"), ], - "concept principles", + _("concept principles"), ), MultiFieldPanel( [ FieldPanel("concept_technical_label"), FieldPanel("concept_technical_help_text"), ], - "concept technical", + _("concept technical"), ), MultiFieldPanel( [ FieldPanel("concept_sustainable_label"), FieldPanel("concept_sustainable_help_text"), ], - "concept sustainable", + _("concept sustainable"), ), ] @@ -358,98 +362,98 @@ class Meta: FieldPanel("proposal_liked_label"), FieldPanel("proposal_liked_help_text"), ], - "proposal liked", + _("proposal liked"), ), MultiFieldPanel( [ FieldPanel("proposal_concerns_label"), FieldPanel("proposal_concerns_help_text"), ], - "proposal concerns", + _("proposal concerns"), ), MultiFieldPanel( [ FieldPanel("proposal_red_flags_label"), FieldPanel("proposal_red_flags_help_text"), ], - "proposal red flags", + _("proposal red flags"), ), MultiFieldPanel( [ FieldPanel("proposal_overview_label"), FieldPanel("proposal_overview_help_text"), ], - "proposal overview", + _("proposal overview"), ), MultiFieldPanel( [ FieldPanel("proposal_objectives_label"), FieldPanel("proposal_objectives_help_text"), ], - "proposal objectives", + _("proposal objectives"), ), MultiFieldPanel( [ FieldPanel("proposal_strategy_label"), FieldPanel("proposal_strategy_help_text"), ], - "proposal strategy", + _("proposal strategy"), ), MultiFieldPanel( [ FieldPanel("proposal_technical_label"), FieldPanel("proposal_technical_help_text"), ], - "proposal technical", + _("proposal technical"), ), MultiFieldPanel( [ FieldPanel("proposal_alternative_label"), FieldPanel("proposal_alternative_help_text"), ], - "proposal alternative", + _("proposal alternative"), ), MultiFieldPanel( [ FieldPanel("proposal_usability_label"), FieldPanel("proposal_usability_help_text"), ], - "proposal usability", + _("proposal usability"), ), MultiFieldPanel( [ FieldPanel("proposal_sustainability_label"), FieldPanel("proposal_sustainability_help_text"), ], - "proposal sustainability", + _("proposal sustainability"), ), MultiFieldPanel( [ FieldPanel("proposal_collaboration_label"), FieldPanel("proposal_collaboration_help_text"), ], - "proposal collaboration", + _("proposal collaboration"), ), MultiFieldPanel( [ FieldPanel("proposal_realism_label"), FieldPanel("proposal_realism_help_text"), ], - "proposal realism", + _("proposal realism"), ), MultiFieldPanel( [ FieldPanel("proposal_qualifications_label"), FieldPanel("proposal_qualifications_help_text"), ], - "proposal qualifications", + _("proposal qualifications"), ), MultiFieldPanel( [ FieldPanel("proposal_evaluation_label"), FieldPanel("proposal_evaluation_help_text"), ], - "proposal evaluation", + _("proposal evaluation"), ), ] diff --git a/hypha/apply/flags/models.py b/hypha/apply/flags/models.py index 8806afce58..eff46186e3 100644 --- a/hypha/apply/flags/models.py +++ b/hypha/apply/flags/models.py @@ -2,14 +2,15 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models +from django.utils.translation import gettext_lazy as _ class Flag(models.Model): STAFF = "staff" USER = "user" FLAG_TYPES = { - STAFF: "Staff", - USER: "User", + STAFF: _("Staff"), + USER: _("User"), } target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) target_object_id = models.PositiveIntegerField() diff --git a/hypha/apply/funds/admin.py b/hypha/apply/funds/admin.py index 9ee1880437..150e47c33d 100644 --- a/hypha/apply/funds/admin.py +++ b/hypha/apply/funds/admin.py @@ -1,5 +1,6 @@ from django.urls import re_path from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ from wagtail_modeladmin.helpers import PermissionHelper from wagtail_modeladmin.options import ModelAdmin, ModelAdminGroup @@ -94,34 +95,34 @@ class ScreeningStatusAdmin(ModelAdmin): class SealedRoundAdmin(BaseRoundAdmin): model = SealedRound menu_icon = str(AdminIcon.SEALED_ROUND) - menu_label = "Sealed Rounds" + menu_label = _("Sealed Rounds") list_display = ("title", "fund", "start_date", "end_date") class FundAdmin(ModelAdmin, RelatedFormsMixin): model = FundType menu_icon = str(AdminIcon.FUND) - menu_label = "Funds" + menu_label = _("Funds") list_display = ("title", "application_forms", "review_forms", "determination_forms") class RFPAdmin(ModelAdmin): model = RequestForPartners menu_icon = str(AdminIcon.REQUEST_FOR_PARTNERS) - menu_label = "Request For Partners" + menu_label = _("Request For Partners") class LabAdmin(ModelAdmin, RelatedFormsMixin): model = LabType menu_icon = str(AdminIcon.LAB) - menu_label = "Labs" + menu_label = _("Labs") list_display = ("title", "application_forms", "review_forms", "determination_forms") class ReviewerRoleAdmin(ModelAdmin): model = ReviewerRole menu_icon = str(AdminIcon.REVIEWER_ROLE) - menu_label = "Reviewer Roles" + menu_label = _("Reviewer Roles") class DeletePermission(PermissionHelper, ListRelatedMixin): @@ -178,7 +179,7 @@ class ReviewerSettingAdmin(SettingModelAdmin): class ApplyAdminGroup(ModelAdminGroup): - menu_label = "Apply" + menu_label = _("Apply") menu_icon = str(AdminIcon.APPLY) items = ( RoundAdmin, diff --git a/hypha/apply/funds/admin_forms.py b/hypha/apply/funds/admin_forms.py index 9610c4ba52..89494f1c7c 100644 --- a/hypha/apply/funds/admin_forms.py +++ b/hypha/apply/funds/admin_forms.py @@ -55,10 +55,12 @@ def validate_application_forms(self, workflow, forms): for stage in range(1, number_of_stages + 1): is_form_present = True if stages_counter.get(stage, 0) > 0 else False if not is_form_present: - error_list.append(f"Please provide form for Stage {stage}.") + error_list.append( + _("Please provide form for Stage {stage}.").format(stage=stage) + ) if stage == 1 and stages_counter.get(stage, 0) > 1: - error_list.append("Only 1 form can be selected for 1st Stage.") + error_list.append(_("Only 1 form can be selected for 1st Stage.")) if error_list: self.add_error( @@ -66,7 +68,10 @@ def validate_application_forms(self, workflow, forms): error_list, ) - def validate_stages_equal_forms(self, workflow, forms, form_type="form"): + def validate_stages_equal_forms(self, workflow, forms, form_type=None): + if form_type is None: + form_type = _("form") + if forms.is_valid(): valid_forms = [form for form in forms if not form.cleaned_data["DELETE"]] number_of_forms = len(valid_forms) @@ -76,7 +81,7 @@ def validate_stages_equal_forms(self, workflow, forms, form_type="form"): plural_stage = "s" if number_of_stages > 1 else "" # External Review Form is optional and should be single if provided - if form_type == "External Review form": + if form_type == _("External Review form"): if number_of_forms > 1: self.add_error( None, @@ -98,7 +103,7 @@ def validate_stages_equal_forms(self, workflow, forms, form_type="form"): for form in valid_forms[number_of_stages:]: form.add_error( "form", - "Exceeds required number of forms for stage, please remove.", + _("Exceeds required number of forms for stage, please remove."), ) def validate_paf_form(self, forms): @@ -114,7 +119,7 @@ def clean(self): start_date = cleaned_data["start_date"] if not start_date: - self.add_error("start_date", "Please select start date.") + self.add_error("start_date", _("Please select start date.")) return cleaned_data diff --git a/hypha/apply/funds/admin_helpers.py b/hypha/apply/funds/admin_helpers.py index 77da19afcb..3fb755368b 100644 --- a/hypha/apply/funds/admin_helpers.py +++ b/hypha/apply/funds/admin_helpers.py @@ -44,9 +44,9 @@ def preview_button(self, obj, classnames_add, classnames_exclude): cn = self.finalise_classname(classname, classnames_exclude) return { "url": reverse("wagtailadmin_pages:view_draft", args=(obj.id,)), - "label": "Preview", + "label": _("Preview"), "classname": cn, - "title": "Preview this %s" % self.verbose_name, + "title": _("Preview this %s") % self.verbose_name, } def get_buttons_for_obj( @@ -63,7 +63,7 @@ def get_buttons_for_obj( class FormsFundRoundListFilter(admin.SimpleListFilter): - title = "usage" + title = _("usage") parameter_name = "form-usage" def lookups(self, request, model_admin): @@ -82,7 +82,7 @@ def queryset(self, request, queryset): class RoundStateListFilter(admin.SimpleListFilter): - title = "state" + title = _("state") parameter_name = "form-state" def lookups(self, request, model_admin): @@ -115,8 +115,8 @@ def copy_form_button(self, pk, form_name, **kwargs): ) return { "classname": classname, - "label": "Copy", - "title": f"Copy {form_name}", + "label": _("Copy"), + "title": _("Copy {form_name}").format(form_name=form_name), "url": self.url_helper.get_action_url("copy_form", admin.utils.quote(pk)), } diff --git a/hypha/apply/funds/blocks.py b/hypha/apply/funds/blocks.py index 4416796e91..50061a6129 100644 --- a/hypha/apply/funds/blocks.py +++ b/hypha/apply/funds/blocks.py @@ -2,6 +2,7 @@ from django import forms from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext_lazy from wagtail import blocks from hypha.addressfield.fields import ADDRESS_FIELDS_ORDER, AddressField @@ -27,7 +28,7 @@ class ApplicationMustIncludeFieldBlock(MustIncludeFieldBlock): class TitleBlock(ApplicationMustIncludeFieldBlock): name = "title" - description = "The title of the project" + description = _("The title of the project") field_label = blocks.CharBlock( label=_("Label"), default=_("What is the title of your application?") ) @@ -50,7 +51,7 @@ def get_field_kwargs(self, struct_value): class ValueBlock(ApplicationSingleIncludeFieldBlock): name = "value" - description = "The value of the project" + description = _("The value of the project") widget = forms.NumberInput(attrs={"min": 0}) field_class = forms.FloatField @@ -66,7 +67,7 @@ def prepare_data(self, value, data, serialize): class OrganizationNameBlock(ApplicationSingleIncludeFieldBlock): name = "organization_name" - description = "The name of the organization" + description = _("The name of the organization") widget = forms.TextInput() class Meta: @@ -75,7 +76,7 @@ class Meta: class EmailBlock(ApplicationMustIncludeFieldBlock): name = "email" - description = "The applicant email address" + description = _("The applicant email address") field_label = blocks.CharBlock( label=_("Label"), default=_("What email address should we use to contact you?") ) @@ -96,7 +97,7 @@ class Meta: class AddressFieldBlock(ApplicationSingleIncludeFieldBlock): name = "address" - description = "The postal address of the user" + description = _("The postal address of the user") field_class = AddressField @@ -122,7 +123,7 @@ def prepare_data(self, value, data, serialize): class FullNameBlock(ApplicationMustIncludeFieldBlock): name = "full_name" - description = "Full name" + description = _("Full name") field_label = blocks.CharBlock(label=_("Label"), default=_("What is your name?")) help_text = blocks.RichTextBlock( required=False, @@ -145,55 +146,29 @@ class Meta: class DurationBlock(ApplicationSingleIncludeFieldBlock): name = "duration" - description = "Duration" + description = _("Duration") DAYS = "days" WEEKS = "weeks" MONTHS = "months" - DURATION_TYPE_CHOICES = ((DAYS, "Days"), (WEEKS, "Weeks"), (MONTHS, "Months")) + DURATION_TYPE_CHOICES = ( + (DAYS, _("Days")), + (WEEKS, _("Weeks")), + (MONTHS, _("Months")), + ) DURATION_DAY_OPTIONS = { - 1: "1 day", - 2: "2 days", - 3: "3 days", - 4: "4 days", - 5: "5 days", - 6: "6 days", - 7: "7 days", + i: ngettext_lazy("{} day", "{} days", i).format(i) for i in range(1, 8) } DURATION_WEEK_OPTIONS = { - 1: "1 week", - 2: "2 weeks", - 3: "3 weeks", - 4: "4 weeks", - 5: "5 weeks", - 6: "6 weeks", - 7: "7 weeks", - 8: "8 weeks", - 9: "9 weeks", - 10: "10 weeks", - 11: "11 weeks", - 12: "12 weeks", + i: ngettext_lazy("{} week", "{} weeks", i).format(i) for i in range(1, 13) } DURATION_MONTH_OPTIONS = { - 1: "1 month", - 2: "2 months", - 3: "3 months", - 4: "4 months", - 5: "5 months", - 6: "6 months", - 7: "7 months", - 8: "8 months", - 9: "9 months", - 10: "10 months", - 11: "11 months", - 12: "12 months", - 18: "18 months", - 24: "24 months", - 36: "36 months", + i: ngettext_lazy("{} month", "{} months", i).format(i) + for i in [*range(1, 13), 18, 24, 36] } field_class = forms.ChoiceField duration_type = blocks.ChoiceBlock( - help_text=( + help_text=_( "Duration type is used to display duration choices in Days, Weeks or Months in application forms. " "Be careful, changing the duration type in the active round can result in data inconsistency." ), diff --git a/hypha/apply/funds/forms.py b/hypha/apply/funds/forms.py index cab7bb1c0a..3764488fbc 100644 --- a/hypha/apply/funds/forms.py +++ b/hypha/apply/funds/forms.py @@ -408,7 +408,7 @@ class UpdateMetaTermsForm(ApplicationSubmissionModelForm): meta_terms = GroupedModelMultipleChoiceField( queryset=None, # updated in init method widget=MetaTermWidget( - attrs={"data-placeholder": "Select...", "data-js-choices": ""} + attrs={"data-placeholder": _("Select..."), "data-js-choices": ""} ), label=_("Tags"), choices_groupby="get_parent", @@ -462,9 +462,9 @@ class Meta: class InviteCoApplicantForm(forms.ModelForm): - invited_user_email = forms.EmailField(required=True, label="Email") + invited_user_email = forms.EmailField(required=True, label=_("Email")) role = forms.ChoiceField( - choices=CoApplicantRole.choices, label="Role", required=False + choices=CoApplicantRole.choices, label=_("Role"), required=False ) project_permission = forms.MultipleChoiceField( choices=CoApplicantProjectPermission.choices, @@ -497,7 +497,7 @@ class Meta: class EditCoApplicantForm(forms.ModelForm): role = forms.ChoiceField( - choices=CoApplicantRole.choices, label="Role", required=False + choices=CoApplicantRole.choices, label=_("Role"), required=False ) project_permission = forms.MultipleChoiceField( choices=CoApplicantProjectPermission.choices, diff --git a/hypha/apply/funds/models/applications.py b/hypha/apply/funds/models/applications.py index 83610cc04e..2168715f94 100644 --- a/hypha/apply/funds/models/applications.py +++ b/hypha/apply/funds/models/applications.py @@ -28,6 +28,7 @@ from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.functional import cached_property +from django.utils.html import format_html, format_html_join from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from django_ratelimit.decorators import ratelimit @@ -423,7 +424,7 @@ def clean(self): if self.start_date and self.end_date and self.start_date > self.end_date: raise ValidationError( { - "end_date": "End date must come after the start date", + "end_date": _("End date must come after the start date"), } ) @@ -452,15 +453,21 @@ def clean(self): conflicting_rounds = base_query.filter(conflict_query).exclude(id=self.id) if conflicting_rounds.exists(): - error_message = mark_safe( - "Overlaps with the following rounds:
{}".format( - "
".join( - [ - f'{round.title}: {round.start_date} - {round.end_date}' - for round in conflicting_rounds - ] - ) - ) + error_message = format_html( + _("Overlaps with the following rounds:
{}"), + format_html_join( + sep=mark_safe("
"), + format_string='{title}: {start_date} - {end_date}', + args_generator=[ + { + "url": admin_url(round), + "title": round.title, + "start_date": round.start_date, + "end_date": round.end_date, + } + for round in conflicting_rounds + ], + ), ) error = { "start_date": error_message, @@ -483,7 +490,7 @@ def get_initial_data_open_call_submission(self, submission_id): title_block_id = submission.named_blocks.get("title") if title_block_id: field_data = submission.data(title_block_id) - initial_values[title_block_id] = field_data + " (please edit)" + initial_values[title_block_id] = field_data + _(" (please edit)") for field_id in submission.first_group_normal_text_blocks: field_data = submission.data(field_id) @@ -881,7 +888,7 @@ class ApplicationSettings(BaseSiteSetting): wagtail_reference_index_ignore = True class Meta: - verbose_name = "application settings" + verbose_name = _("application settings") extra_text_round = RichTextField(blank=True) extra_text_lab = RichTextField(blank=True) @@ -892,6 +899,6 @@ class Meta: FieldPanel("extra_text_round"), FieldPanel("extra_text_lab"), ], - "extra text on application landing page", + _("extra text on application landing page"), ), ] diff --git a/hypha/apply/funds/models/forms.py b/hypha/apply/funds/models/forms.py index 721396c231..b3eb5b5627 100644 --- a/hypha/apply/funds/models/forms.py +++ b/hypha/apply/funds/models/forms.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ from modelcluster.fields import ParentalKey from wagtail.admin.panels import FieldPanel from wagtail.fields import StreamField @@ -28,8 +29,8 @@ class AbstractRelatedForm(Orderable): FIRST_STAGE = 1 SECOND_STAGE = 2 STAGE_CHOICES = [ - (FIRST_STAGE, "1st Stage"), - (SECOND_STAGE, "2nd Stage"), + (FIRST_STAGE, _("1st Stage")), + (SECOND_STAGE, _("2nd Stage")), ] form = models.ForeignKey("ApplicationForm", on_delete=models.PROTECT) stage = models.PositiveSmallIntegerField(choices=STAGE_CHOICES) diff --git a/hypha/apply/funds/models/reminders.py b/hypha/apply/funds/models/reminders.py index 44057f9802..8eef4dfbf6 100644 --- a/hypha/apply/funds/models/reminders.py +++ b/hypha/apply/funds/models/reminders.py @@ -2,6 +2,8 @@ from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone +from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ from hypha.apply.activity.messaging import MESSAGES @@ -9,7 +11,7 @@ class Reminder(models.Model): REVIEW = "reviewers_review" ACTIONS = { - REVIEW: "Remind reviewers to Review", + REVIEW: _("Remind reviewers to Review"), } EMAIL = "email" MEDIUM = {REVIEW: EMAIL} @@ -45,7 +47,7 @@ class Meta: def clean(self): if self.title == "": - raise ValidationError("Title is Empty") + raise ValidationError(gettext("Title is Empty")) @property def is_expired(self): diff --git a/hypha/apply/funds/models/reviewer_role.py b/hypha/apply/funds/models/reviewer_role.py index 6cddf51e55..49ffe1e63a 100644 --- a/hypha/apply/funds/models/reviewer_role.py +++ b/hypha/apply/funds/models/reviewer_role.py @@ -39,24 +39,24 @@ def __str__(self): @register_setting class ReviewerSettings(BaseSiteSetting): SUBMISSIONS = [ - ("all", "All Submissions"), - ("reviewed", "Only reviewed Submissions"), + ("all", _("All Submissions")), + ("reviewed", _("Only reviewed Submissions")), ] STATES = [ - ("all", "All States"), - ("ext_state_or_higher", "Only External review and higher"), - ("ext_state_only", "Only External review"), + ("all", _("All States")), + ("ext_state_or_higher", _("Only External review and higher")), + ("ext_state_only", _("Only External review")), ] OUTCOMES = [ - ("all", "All Outcomes"), - ("all_except_dismissed", "All Outcomes Except Dismissed"), - ("accepted", "Only Accepted"), + ("all", _("All Outcomes")), + ("all_except_dismissed", _("All Outcomes Except Dismissed")), + ("accepted", _("Only Accepted")), ] class Meta: - verbose_name = "Reviewer Settings" + verbose_name = _("Reviewer Settings") submission = models.CharField( choices=SUBMISSIONS, diff --git a/hypha/apply/funds/models/screening.py b/hypha/apply/funds/models/screening.py index 0abc0318e5..cc2138c1ad 100644 --- a/hypha/apply/funds/models/screening.py +++ b/hypha/apply/funds/models/screening.py @@ -20,8 +20,8 @@ class ScreeningStatus(models.Model): base_form_class = ScreeningStatusAdminForm class Meta: - verbose_name = "Screening Decision" - verbose_name_plural = "screening decisions" + verbose_name = _("Screening Decision") + verbose_name_plural = _("screening decisions") def __str__(self): return self.title diff --git a/hypha/apply/funds/models/utils.py b/hypha/apply/funds/models/utils.py index 8a17c45a5a..fac4197614 100644 --- a/hypha/apply/funds/models/utils.py +++ b/hypha/apply/funds/models/utils.py @@ -151,7 +151,7 @@ def render_landing_page(self, request, form_submission=None, *args, **kwargs): "external_review_forms", label=_("External Review Forms"), max_num=1, - help_text="Add a form to be used by external reviewers.", + help_text=_("Add a form to be used by external reviewers."), ), InlinePanel("determination_forms", label=_("Determination Forms")), ] diff --git a/hypha/apply/funds/permissions.py b/hypha/apply/funds/permissions.py index 06290b5cac..2b318e5762 100644 --- a/hypha/apply/funds/permissions.py +++ b/hypha/apply/funds/permissions.py @@ -1,5 +1,6 @@ from django.conf import settings from django.core.exceptions import PermissionDenied +from django.utils.translation import gettext as _ from rolepermissions.permissions import register_object_checker from hypha.apply.funds.models.co_applicants import CoApplicant, CoApplicantRole @@ -20,20 +21,20 @@ def has_permission(action, user, object=None, raise_exception=True): def can_take_submission_actions(user, submission): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if submission.is_archive: - return False, "Archived Submission" + return False, _("Archived Submission") return True, "" def can_edit_submission(user, submission): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if submission.is_archive: - return False, "Archived Submission" + return False, _("Archived Submission") if submission.phase.permissions.can_edit(user): co_applicant = submission.co_applicants.filter(user=user).first() @@ -41,10 +42,12 @@ def can_edit_submission(user, submission): if co_applicant.role not in [CoApplicantRole.VIEW, CoApplicantRole.COMMENT]: return ( True, - "Co-applicant with read/view only or comment access can't edit submission", + _( + "Co-applicant with read/view only or comment access can't edit submission" + ), ) return False, "" - return True, "User can edit in current phase" + return True, _("User can edit in current phase") return False, "" @@ -149,10 +152,10 @@ def can_alter_archived_submissions(user, submission=None) -> (bool, str): archive_access_groups = get_archive_alter_groups() if user.is_apply_staff and STAFF_GROUP_NAME in archive_access_groups: - return True, "Staff is set to alter archive" + return True, _("Staff is set to alter archive") if user.is_apply_staff_admin and TEAMADMIN_GROUP_NAME in archive_access_groups: - return True, "Staff Admin is set to alter archive" - return False, "Forbidden Error" + return True, _("Staff Admin is set to alter archive") + return False, _("Forbidden Error") def can_bulk_archive_submissions(user) -> bool: @@ -204,10 +207,10 @@ def can_export_submissions(user) -> bool: def is_user_has_access_to_view_submission(user, submission): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if submission.is_archive and not can_view_archived_submissions(user): - return False, "Archived Submission" + return False, _("Archived Submission") if ( user.is_apply_staff @@ -227,57 +230,58 @@ def is_user_has_access_to_view_submission(user, submission): def can_view_submission_screening(user, submission): - submission_view, _ = is_user_has_access_to_view_submission(user, submission) + # __ to avoid shadowing the gettext alias + submission_view, __ = is_user_has_access_to_view_submission(user, submission) if not submission_view: - return False, "No access to view submission" + return False, _("No access to view submission") if submission.user == user: - return False, "Applicant cannot view submission screening" + return False, _("Applicant cannot view submission screening") return True, "" def can_invite_co_applicants(user, submission): if submission.is_archive: - return False, "Co-applicant can't be added to archived submission" + return False, _("Co-applicant can't be added to archived submission") if hasattr(submission, "project"): from hypha.apply.projects.models.project import COMPLETE if submission.project.status == COMPLETE: - return False, "Co-applicants can't be invited to completed projects" + return False, _("Co-applicants can't be invited to completed projects") if ( submission.co_applicant_invites.all().count() >= settings.SUBMISSIONS_COAPPLICANT_INVITES_LIMIT ): - return False, "Limit reached for this submission" + return False, _("Limit reached for this submission") if user.is_applicant and user == submission.user: - return True, "Applicants can invite co-applicants to their application" + return True, _("Applicants can invite co-applicants to their application") if user.is_apply_staff: - return True, "Staff can invite co-applicant on behalf of applicant" - return False, "Forbidden Error" + return True, _("Staff can invite co-applicant on behalf of applicant") + return False, _("Forbidden Error") def can_view_co_applicants(user, submission): if user.is_applicant and user == submission.user: - return True, "Submission user can access their submission's co-applicants" + return True, _("Submission user can access their submission's co-applicants") if user.is_apply_staff: - return True, "Staff can access each submissions' co-applicants" - return False, "Forbidden Error" + return True, _("Staff can access each submissions' co-applicants") + return False, _("Forbidden Error") def can_update_co_applicant(user, invite): if invite.submission.is_archive: - return False, "Co-applicant can't be updated to archived submission" + return False, _("Co-applicant can't be updated to archived submission") if hasattr(invite.submission, "project"): from hypha.apply.projects.models.project import COMPLETE if invite.submission.project.status == COMPLETE: - return False, "Co-applicants can't be updated to completed projects" + return False, _("Co-applicants can't be updated to completed projects") if invite.invited_by == user: - return True, "Same user who invited can delete the co-applicant" + return True, _("Same user who invited can delete the co-applicant") if invite.submission.user == user: - return True, "Submission owner can delete the co-applicant" + return True, _("Submission owner can delete the co-applicant") if user.is_apply_staff: - return True, "Staff can delete any co-applicant of any submission" - return False, "Forbidden Error" + return True, _("Staff can delete any co-applicant of any submission") + return False, _("Forbidden Error") def user_can_view_post_comment_form(user, submission): diff --git a/hypha/apply/funds/services.py b/hypha/apply/funds/services.py index 0e23ffbeff..358805d690 100644 --- a/hypha/apply/funds/services.py +++ b/hypha/apply/funds/services.py @@ -16,6 +16,7 @@ from django.db.models.functions import Coalesce from django.http import HttpRequest from django.utils.translation import gettext as _ +from django.utils.translation import ngettext from hypha.apply.activity.messaging import MESSAGES, messenger from hypha.apply.activity.models import Activity, Event @@ -41,7 +42,10 @@ def bulk_archive_submissions( submissions.update(is_archive=True) messages.success( - request, _("{number} submissions archived.").format(number=len(submissions)) + request, + ngettext( + "{} submission archived.", "{} submissions archived.", len(submissions) + ).format(len(submissions)), ) messenger( @@ -73,7 +77,10 @@ def bulk_delete_submissions( ).delete() messages.success( - request, _("{number} submissions deleted.").format(number=len(submissions)) + request, + ngettext( + "{} submission deleted.", "{} submissions deleted.", len(submissions) + ).format(len(submissions)), ) messenger( diff --git a/hypha/apply/funds/tables.py b/hypha/apply/funds/tables.py index bef7d1a0c0..550ccc26b2 100644 --- a/hypha/apply/funds/tables.py +++ b/hypha/apply/funds/tables.py @@ -150,7 +150,7 @@ class BaseAdminSubmissionsTable(SubmissionsTable): reviews_stats = tables.TemplateColumn( template_name="funds/tables/column_reviews.html", verbose_name=mark_safe( - 'Reviews
Comp. / Assgn.
' + _('Reviews
Comp. / Assgn.
') ), orderable=False, ) @@ -435,7 +435,7 @@ def __init__(self, *args, **kwargs): super().__init__( self, *args, - choices=[("active", "Active"), ("inactive", "Inactive")], + choices=[("active", _("Active")), ("inactive", _("Inactive"))], **kwargs, ) @@ -455,7 +455,11 @@ def __init__(self, *args, **kwargs): super().__init__( self, *args, - choices=[("open", "Open"), ("closed", "Closed"), ("new", "Not Started")], + choices=[ + ("open", _("Open")), + ("closed", _("Closed")), + ("new", _("Not Started")), + ], **kwargs, ) diff --git a/hypha/apply/funds/templatetags/table_tags.py b/hypha/apply/funds/templatetags/table_tags.py index 2c35fdd517..45ed6fb62a 100644 --- a/hypha/apply/funds/templatetags/table_tags.py +++ b/hypha/apply/funds/templatetags/table_tags.py @@ -2,6 +2,7 @@ import math from django import forms, template +from django.utils.translation import gettext as _ from django_filters.fields import DateRangeField from hypha.apply.categories.models import MetaTerm @@ -60,7 +61,7 @@ def get_field_choices(form, field_name): @register.filter def get_dropdown_heading(field): - return f"Filter by {field.label}" + return _("Filter by {label}").format(label=field.label) @register.filter diff --git a/hypha/apply/funds/utils.py b/hypha/apply/funds/utils.py index a088243c50..5a8405634e 100644 --- a/hypha/apply/funds/utils.py +++ b/hypha/apply/funds/utils.py @@ -12,6 +12,7 @@ from django.utils.html import strip_tags from django.utils.http import urlsafe_base64_encode from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy # from django.contrib.sites.models import Site from hypha.apply.funds.models.submissions import ApplicationSubmission @@ -114,14 +115,14 @@ def export_submissions_to_csv( submissions_list: Iterable[ApplicationSubmission], base_uri: str ): csv_stream = StringIO() - header_row = ["Application #", "URL"] + header_row = [gettext_lazy("Application #"), gettext_lazy("URL")] index = 2 data_list = [] for submission in submissions_list: values = {} - values["Application #"] = submission.id - values["URL"] = f"{base_uri}{submission.get_absolute_url().lstrip('/')}" + values[_("Application #")] = submission.id + values[_("URL")] = f"{base_uri}{submission.get_absolute_url().lstrip('/')}" for field_id in submission.question_text_field_ids: question_field = submission.serialize(field_id) field_name = question_field["question"] diff --git a/hypha/apply/funds/views/submission_detail.py b/hypha/apply/funds/views/submission_detail.py index 1a23b25daf..ece1fc7df1 100644 --- a/hypha/apply/funds/views/submission_detail.py +++ b/hypha/apply/funds/views/submission_detail.py @@ -12,6 +12,7 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.utils.text import slugify +from django.utils.translation import gettext as _ from django.views import View from django.views.generic import ( DetailView, @@ -319,7 +320,7 @@ def get(self, request, *args, **kwargs): context["round"] = self.object.round context["lead"] = self.object.lead context["show_header"] = True - context["header_title"] = "Submission details" + context["header_title"] = _("Submission details") template_path = "funds/submission-pdf.html" return render_as_pdf( request=request, diff --git a/hypha/apply/funds/views/translate.py b/hypha/apply/funds/views/translate.py index 6d452dbb4b..cd8cb78718 100644 --- a/hypha/apply/funds/views/translate.py +++ b/hypha/apply/funds/views/translate.py @@ -43,7 +43,7 @@ def dispatch(self, request, *args, **kwargs): if not request.user.is_org_faculty: messages.warning( self.request, - "User attempted to translate submission but is not org faculty", + _("User attempted to translate submission but is not org faculty"), ) return HttpResponseRedirect(self.submission.get_absolute_url()) return super(TranslateSubmissionView, self).dispatch( diff --git a/hypha/apply/funds/workflows/registry.py b/hypha/apply/funds/workflows/registry.py index 29785b407d..57026dcb75 100644 --- a/hypha/apply/funds/workflows/registry.py +++ b/hypha/apply/funds/workflows/registry.py @@ -14,6 +14,8 @@ from collections import defaultdict from typing import List +from django.utils.translation import gettext_lazy as _ + from .definitions.double_stage import DoubleStageDefinition from .definitions.single_stage import SingleStageDefinition from .definitions.single_stage_community import SingleStageCommunityDefinition @@ -61,28 +63,28 @@ def unpack_phases(phases): } -Request = Workflow("Request", "single", **phase_data(SingleStageDefinition)) +Request = Workflow(_("Request"), "single", **phase_data(SingleStageDefinition)) RequestSameTime = Workflow( - "Request with same time review", + _("Request with same time review"), "single_same", **phase_data(SingleStageSameDefinition), ) RequestExternal = Workflow( - "Request with external review", + _("Request with external review"), "single_ext", **phase_data(SingleStageExternalDefinition), ) RequestCommunity = Workflow( - "Request with community review", + _("Request with community review"), "single_com", **phase_data(SingleStageCommunityDefinition), ) ConceptProposal = Workflow( - "Concept & Proposal", "double", **phase_data(DoubleStageDefinition) + _("Concept & Proposal"), "double", **phase_data(DoubleStageDefinition) ) WORKFLOWS = { diff --git a/hypha/apply/projects/admin.py b/hypha/apply/projects/admin.py index 075d2cf8df..68b7440e44 100644 --- a/hypha/apply/projects/admin.py +++ b/hypha/apply/projects/admin.py @@ -1,3 +1,4 @@ +from django.utils.translation import gettext_lazy as _ from wagtail_modeladmin.options import ModelAdmin, ModelAdminGroup from hypha.apply.utils.admin import AdminIcon, ListRelatedMixin @@ -41,7 +42,7 @@ class ContractDocumentCategoryAdmin(ModelAdmin): class ProjectFormAdmin(ListRelatedMixin, ModelAdmin): model = ProjectForm - menu_label = "Project Forms" + menu_label = _("Project Forms") menu_icon = str(AdminIcon.PROJECT_FORM) list_display = ( "name", @@ -58,7 +59,7 @@ class ProjectFormAdmin(ListRelatedMixin, ModelAdmin): class ProjectSOWFormAdmin(ListRelatedMixin, ModelAdmin): model = ProjectSOWForm - menu_label = "SOW Forms" + menu_label = _("SOW Forms") menu_icon = str(AdminIcon.PROJECT_SOW_FORM) list_display = ( "name", @@ -75,7 +76,7 @@ class ProjectSOWFormAdmin(ListRelatedMixin, ModelAdmin): class ProjectReportFormAdmin(ListRelatedMixin, ModelAdmin): model = ProjectReportForm - menu_label = "Report Forms" + menu_label = _("Report Forms") menu_icon = str(AdminIcon.PROJECT_REPORT_FORM) list_display = ( "name", @@ -95,7 +96,7 @@ class ProjectSettingsAdmin(SettingModelAdmin): class ProjectAdminGroup(ModelAdminGroup): - menu_label = "Projects" + menu_label = _("Projects") menu_icon = str(AdminIcon.PROJECT) items = ( ContractDocumentCategoryAdmin, diff --git a/hypha/apply/projects/filters.py b/hypha/apply/projects/filters.py index 7d1e59d323..b32018def1 100644 --- a/hypha/apply/projects/filters.py +++ b/hypha/apply/projects/filters.py @@ -21,8 +21,8 @@ User = get_user_model() REPORTING_CHOICES = ( - (0, "Up to date"), - (1, "Behind schedule"), + (0, _("Up to date")), + (1, _("Behind schedule")), ) @@ -63,7 +63,7 @@ class ProjectListFilter(filters.FilterSet): choices=REPORTING_CHOICES, method="filter_reporting", field_name="reporting", - label="Reporting", + label=_("Reporting"), ) class Meta: diff --git a/hypha/apply/projects/models/payment.py b/hypha/apply/projects/models/payment.py index 50cb7b46dd..ca332e2c32 100644 --- a/hypha/apply/projects/models/payment.py +++ b/hypha/apply/projects/models/payment.py @@ -115,7 +115,7 @@ class Invoice(models.Model): message_for_pm = models.TextField( blank=True, verbose_name=_("Comment"), - help_text="This will be displayed as a comment in the comments tab", + help_text=_("This will be displayed as a comment in the comments tab"), ) comment = models.TextField(blank=True) invoice_number = models.CharField( diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py index cea5d5d10e..012969b65d 100644 --- a/hypha/apply/projects/models/project.py +++ b/hypha/apply/projects/models/project.py @@ -28,6 +28,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.html import strip_tags +from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.models import ClusterableModel @@ -71,8 +72,8 @@ def contract_document_path(instance, filename): APPROVE = "approve" REQUEST_CHANGE = "request_change" PAF_STATUS_CHOICES = ( - (APPROVE, "Approve"), - (REQUEST_CHANGE, "Request changes or more information"), + (APPROVE, _("Approve")), + (REQUEST_CHANGE, _("Request changes or more information")), ) DRAFT = "draft" @@ -209,15 +210,19 @@ def for_reporting_table(self): .order_by("end_date") .annotate( report_status=Case( - When(draft__isnull=False, then=Value("In progress")), - When(current__isnull=False, then=Value("Submitted")), - default=Value("Not started"), + When( + draft__isnull=False, then=Value(gettext("In progress")) + ), + When( + current__isnull=False, then=Value(gettext("Submitted")) + ), + default=Value(gettext("Not started")), ) ) .values("report_status")[:1], output_field=models.CharField(), ), - Value("Not started"), + Value(gettext("Not started")), ), ) @@ -260,7 +265,7 @@ class Project(BaseStreamForm, AccessFormData, models.Model): is_locked = models.BooleanField(default=False) submitted_contract_documents = models.BooleanField( - "Submit Contracting Documents", default=False + _("Submit Contracting Documents"), default=False ) activities = GenericRelation( @@ -273,16 +278,16 @@ class Project(BaseStreamForm, AccessFormData, models.Model): external_projectid = models.CharField( max_length=30, blank=True, - help_text="ID of this project at integrated payment service.", + help_text=_("ID of this project at integrated payment service."), ) external_project_information = models.JSONField( default=dict, - help_text="More details of the project integrated at payment service.", + help_text=_("More details of the project integrated at payment service."), ) sent_to_compliance_at = models.DateTimeField(null=True) paf_reviews_meta_data = models.JSONField( - default=dict, help_text="Reviewers role and their actions/comments" + default=dict, help_text=_("Reviewers role and their actions/comments") ) objects = ProjectQuerySet.as_manager() @@ -582,12 +587,12 @@ class FrequencyRelation(models.TextChoices): @register_setting class ProjectSettings(BaseSiteSetting, ClusterableModel): contracting_gp_email = models.TextField( - "Contracting Group Email", null=True, blank=True + _("Contracting Group Email"), null=True, blank=True ) - finance_gp_email = models.TextField("Finance Group Email", null=True, blank=True) - staff_gp_email = models.TextField("Staff Group Email", null=True, blank=True) + finance_gp_email = models.TextField(_("Finance Group Email"), null=True, blank=True) + staff_gp_email = models.TextField(_("Staff Group Email"), null=True, blank=True) paf_approval_sequential = models.BooleanField( - default=True, help_text="Uncheck it to approve project parallelly" + default=True, help_text=_("Uncheck it to approve project parallelly") ) panels = [ @@ -597,7 +602,7 @@ class ProjectSettings(BaseSiteSetting, ClusterableModel): MultiFieldPanel( [ FieldPanel( - "paf_approval_sequential", heading="Approve Project Sequentially" + "paf_approval_sequential", heading=_("Approve Project Sequentially") ), InlinePanel( "paf_reviewers_roles", label=_("Project Form Reviewers Roles") @@ -674,9 +679,9 @@ class Contract(models.Model): file = models.FileField(upload_to=contract_path, storage=PrivateStorage()) - signed_and_approved = models.BooleanField("Signed and approved", default=False) + signed_and_approved = models.BooleanField(_("Signed and approved"), default=False) - signed_by_applicant = models.BooleanField("Counter Signed?", default=False) + signed_by_applicant = models.BooleanField(_("Counter Signed?"), default=False) uploaded_by_contractor_at = models.DateTimeField(null=True) uploaded_by_applicant_at = models.DateTimeField(null=True) created_at = models.DateTimeField(auto_now_add=True) @@ -775,7 +780,8 @@ def __str__(self): class Meta: ordering = ("-required", "name") - verbose_name_plural = "Project Document Categories" + verbose_name = _("Document Category") + verbose_name_plural = _("Project Document Categories") panels = [ FieldPanel("name"), @@ -808,7 +814,8 @@ def __str__(self): class Meta: ordering = ("-required", "name") - verbose_name_plural = "Contract Document Categories" + verbose_name = _("Contrat Document Category") + verbose_name_plural = _("Contract Document Categories") panels = [ FieldPanel("name"), diff --git a/hypha/apply/projects/permissions.py b/hypha/apply/projects/permissions.py index 702c4d9bb3..8730e7f377 100644 --- a/hypha/apply/projects/permissions.py +++ b/hypha/apply/projects/permissions.py @@ -1,5 +1,6 @@ from django.conf import settings from django.core.exceptions import PermissionDenied +from django.utils.translation import gettext as _ from rolepermissions.permissions import register_object_checker from hypha.apply.activity.adapters.utils import get_users_for_groups @@ -34,30 +35,32 @@ def has_permission(action, user, object=None, raise_exception=True, **kwargs): def can_approve_contract(user, project, **kwargs): if project.status != CONTRACTING: - return False, "Project is not in Contracting State" + return False, _("Project is not in Contracting State") if not project.submitted_contract_documents: - return False, "No contract documents submission yet" + return False, _("No contract documents submission yet") if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if user.is_apply_staff and not user.is_applicant: - return True, "Only Staff can approve the contract" + return True, _("Only Staff can approve the contract") - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_upload_contract(user, project, **kwargs): if project.status != CONTRACTING: - return False, "Project is not in Contracting State" + return False, _("Project is not in Contracting State") if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if user.is_applicant and project.contracts.exists(): if user == project.user: - return True, "Project Owner can only re-upload contract with countersigned" + return True, _( + "Project Owner can only re-upload contract with countersigned" + ) co_applicant = project.submission.co_applicants.filter(user=user).first() if ( co_applicant @@ -67,30 +70,32 @@ def can_upload_contract(user, project, **kwargs): ): return ( True, - "Co-applicant with edit permission for project's contracting document can upload contract", + _( + "Co-applicant with edit permission for project's contracting document can upload contract" + ), ) - return False, "Forbidden Error" + return False, _("Forbidden Error") if user.is_contracting: - return True, "Contracting team can upload the contract" + return True, _("Contracting team can upload the contract") if user.is_apply_staff and settings.STAFF_UPLOAD_CONTRACT: - return True, "Staff can upload contract as set in settings" + return True, _("Staff can upload contract as set in settings") - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_submit_contract_documents(user, project, **kwargs): if project.status != CONTRACTING: - return False, "Project is not in Contracting State" + return False, _("Project is not in Contracting State") if not user.is_applicant: - return False, "Only Applicants can submit contracting documents" + return False, _("Only Applicants can submit contracting documents") if not kwargs.get("contract", None): - return False, "Can not submit without contract" + return False, _("Can not submit without contract") if not project.submitted_contract_documents: if user == project.user: - return True, "Vendor can submit contracting documents" + return True, _("Vendor can submit contracting documents") co_applicant = project.submission.co_applicants.filter(user=user).first() if ( co_applicant @@ -100,31 +105,37 @@ def can_submit_contract_documents(user, project, **kwargs): ): return ( True, - "Co-applicant with edit permission for project's contracting document can submit contracting documents", + _( + "Co-applicant with edit permission for project's contracting document can submit contracting documents" + ), ) return ( False, - "Only applicant and co-applicant with appropriate permission can submit docs", + _( + "Only applicant and co-applicant with appropriate permission can submit docs" + ), ) - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_update_paf_approvers(user, project, **kwargs): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if project.status != INTERNAL_APPROVAL: return ( False, - "Project form approvers can be updated only in Internal approval state", + _("Project form approvers can be updated only in Internal approval state"), ) if user == project.lead: - return True, "Lead can update approvers in approval state" + return True, _("Lead can update approvers in approval state") if not project.paf_approvals.exists(): return ( False, - "No user can update approvers without project form approval, except lead (lead can add project form approvals)", + _( + "No user can update approvers without project form approval, except lead (lead can add project form approvals)" + ), ) request = kwargs.get("request") @@ -138,9 +149,11 @@ def can_update_paf_approvers(user, project, **kwargs): ): return ( True, - "Project form reviewer-roles users can update next approval approvers if any approvers assigned", + _( + "Project form reviewer-roles users can update next approval approvers if any approvers assigned" + ), ) - return False, "Forbidden Error" + return False, _("Forbidden Error") else: approvers_ids = [] for approval in project.paf_approvals.filter( @@ -153,8 +166,8 @@ def can_update_paf_approvers(user, project, **kwargs): ) ) if user.id in approvers_ids: - return True, "Project form reviewer-roles users can update approvers" - return False, "Forbidden Error" + return True, _("Project form reviewer-roles users can update approvers") + return False, _("Forbidden Error") def can_update_assigned_paf_approvers(user, project, **kwargs): @@ -163,14 +176,14 @@ def can_update_assigned_paf_approvers(user, project, **kwargs): UpdateAssignApproversView will be used by only approvers teams members. """ if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if project.status != INTERNAL_APPROVAL: return ( False, - "Project form approvers can be assigned only in Internal approval state", + _("Project form approvers can be assigned only in Internal approval state"), ) if not project.paf_approvals.exists(): - return False, "No user can assign approvers with paf_approvals" + return False, _("No user can assign approvers with paf_approvals") request = kwargs.get("request") project_settings = ProjectSettings.for_request(request) @@ -181,9 +194,9 @@ def can_update_assigned_paf_approvers(user, project, **kwargs): list(next_paf_approval.paf_reviewer_role.user_roles.all()), exact_match=True, ): - return True, "Project form reviewer-roles users can assign approvers" - return False, "Forbidden Error" - return False, "Forbidden Error" + return True, _("Project form reviewer-roles users can assign approvers") + return False, _("Forbidden Error") + return False, _("Forbidden Error") else: assigners_ids = [] for approval in project.paf_approvals.filter(approved=False): @@ -194,21 +207,21 @@ def can_update_assigned_paf_approvers(user, project, **kwargs): ) ) if user.id in assigners_ids: - return True, "Project form reviewer-roles users can assign approvers" - return False, "Forbidden Error" + return True, _("Project form reviewer-roles users can assign approvers") + return False, _("Forbidden Error") def can_assign_paf_approvers(user, project, **kwargs): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if project.status != INTERNAL_APPROVAL: return ( False, - "Project form approvers can be assigned only in Internal approval state", + _("Project form approvers can be assigned only in Internal approval state"), ) if not project.paf_approvals.exists(): - return False, "No user can assign approvers with project form approvals" + return False, _("No user can assign approvers with project form approvals") request = kwargs.get("request") project_settings = ProjectSettings.for_request(request) @@ -216,7 +229,7 @@ def can_assign_paf_approvers(user, project, **kwargs): next_paf_approval = project.paf_approvals.filter(approved=False).first() if next_paf_approval: if next_paf_approval.user: - return False, "User already assigned" + return False, _("User already assigned") else: if user in get_users_for_groups( list(next_paf_approval.paf_reviewer_role.user_roles.all()), @@ -224,10 +237,10 @@ def can_assign_paf_approvers(user, project, **kwargs): ): return ( True, - "Project form reviewer-roles users can assign approvers", + _("Project form reviewer-roles users can assign approvers"), ) - return False, "Forbidden Error" - return False, "Forbidden Error" + return False, _("Forbidden Error") + return False, _("Forbidden Error") else: assigners_ids = [] for approval in project.paf_approvals.filter(approved=False, user__isnull=True): @@ -239,19 +252,19 @@ def can_assign_paf_approvers(user, project, **kwargs): ) if user.id in assigners_ids: - return True, "Project form reviewer-roles users can assign approvers" - return False, "Forbidden Error" + return True, _("Project form reviewer-roles users can assign approvers") + return False, _("Forbidden Error") def can_update_paf_status(user, project, **kwargs): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if not project.paf_approvals.filter(approved=False).exists(): - return False, "No project form approvals exists" + return False, _("No project form approvals exists") if project.status != INTERNAL_APPROVAL: - return False, "Incorrect project status to approve project form" + return False, _("Incorrect project status to approve project form") request = kwargs.get("request") if request: @@ -267,11 +280,15 @@ def can_update_paf_status(user, project, **kwargs): if user.id in possible_approvers_ids: return ( True, - "Next approval group users can approve project form (for sequential approvals)", + _( + "Next approval group users can approve project form (for sequential approvals)" + ), ) return ( False, - "Only Next approval group can approve project form (for sequential approvals)", + _( + "Only Next approval group can approve project form (for sequential approvals)" + ), ) else: possible_approvers_ids = [] @@ -288,47 +305,55 @@ def can_update_paf_status(user, project, **kwargs): if user.id in possible_approvers_ids: return ( True, - "All approval group users can approve project form (for parallel approvals)", + _( + "All approval group users can approve project form (for parallel approvals)" + ), ) return ( False, - "Only approval group users can approve project form (for parallel approvals)", + _( + "Only approval group users can approve project form (for parallel approvals)" + ), ) - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_update_project_status(user, project, **kwargs): if project.status not in [DRAFT, COMPLETE, CLOSING, INVOICING_AND_REPORTING]: - return False, "Forbidden Error" + return False, _("Forbidden Error") if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if user.is_apply_staff or user.is_apply_staff_admin: if project.status == DRAFT: if no_pafreviewer_role(): return ( True, - "Staff and Staff Admin can skip the project form approval process", + _( + "Staff and Staff Admin can skip the project form approval process" + ), ) else: - return True, "Staff and Staff Admin can update status" + return True, _("Staff and Staff Admin can update status") - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_access_project(user, project): if not user.is_authenticated: - return False, "Login Required" + return False, _("Login Required") if user.is_apply_staff or user.is_finance or user.is_contracting: # Staff, Finance and Contracting are internal and trusted peoples, # Their action are limited, but they can view all projects. - return True, "Staff, Finance and Contracting can view project in all statuses" + return True, _( + "Staff, Finance and Contracting can view project in all statuses" + ) if user.is_applicant and user == project.user: - return True, "Vendor(project user) can view project in all statuses" + return True, _("Vendor(project user) can view project in all statuses") if ( user.is_applicant @@ -336,8 +361,8 @@ def can_access_project(user, project): ): co_applicant = project.submission.co_applicants.filter(user=user).first() if co_applicant.project_permission: - return True, "Co-applicant with project permission can access project" - return False, "Co-applicant without project permission can't access project" + return True, _("Co-applicant with project permission can access project") + return False, _("Co-applicant without project permission can't access project") if ( project.status in [DRAFT, INTERNAL_APPROVAL, CONTRACTING] @@ -357,17 +382,19 @@ def can_access_project(user, project): if user.id in paf_reviewer_roles_users_ids: return ( True, - "Project form approvers can access the project in Draft, Approval state and after approval state", + _( + "Project form approvers can access the project in Draft, Approval state and after approval state" + ), ) - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_view_contract_category_documents(user, project, **kwargs): if user.is_superuser: - return True, "Superuser can view all documents" + return True, _("Superuser can view all documents") if user == project.user: - return True, "Vendor can view all documents" + return True, _("Vendor can view all documents") if user.is_applicant: co_applicant = project.submission.co_applicants.filter(user=user).first() if ( @@ -375,26 +402,30 @@ def can_view_contract_category_documents(user, project, **kwargs): and CoApplicantProjectPermission.CONTRACTING_DOCUMENT in co_applicant.project_permission ): - return True, "Co-applicant with permissions can view contracting documents" + return True, _( + "Co-applicant with permissions can view contracting documents" + ) contract_category = kwargs.get("contract_category") if not contract_category: - return False, "Contract Category is required" + return False, _("Contract Category is required") allowed_group_users = User.objects.filter( groups__name__in=list(contract_category.document_access_view.all()) ) if allowed_group_users and user in allowed_group_users: - return True, "Access allowed" + return True, _("Access allowed") - return False, "Forbidden Error" + return False, _("Forbidden Error") def can_edit_paf(user, project): if no_pafreviewer_role() and project.status != COMPLETE: - return True, "Project form is editable for active projects if no reviewer roles" + return True, _( + "Project form is editable for active projects if no reviewer roles" + ) if project.editable_by(user): - return True, "Project form is editable in Draft by this user" - return False, "You are not allowed to edit the project at this time" + return True, _("Project form is editable in Draft by this user") + return False, _("You are not allowed to edit the project at this time") @register_object_checker() diff --git a/hypha/apply/projects/reports/forms.py b/hypha/apply/projects/reports/forms.py index 13ed4114b4..8dd8766029 100644 --- a/hypha/apply/projects/reports/forms.py +++ b/hypha/apply/projects/reports/forms.py @@ -1,6 +1,7 @@ from django import forms from django.db import transaction from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from hypha.apply.review.forms import MixedMetaClass from hypha.apply.stream_forms.forms import StreamBaseForm @@ -96,7 +97,7 @@ def save(self, commit=True, form_fields=dict): class ReportFrequencyForm(forms.ModelForm): - start = forms.DateField(label="Report on:", required=False) + start = forms.DateField(label=_("Report on:"), required=False) class Meta: model = ReportConfig diff --git a/hypha/apply/projects/reports/tables.py b/hypha/apply/projects/reports/tables.py index a117ac611f..bf57070dd1 100644 --- a/hypha/apply/projects/reports/tables.py +++ b/hypha/apply/projects/reports/tables.py @@ -1,6 +1,7 @@ import django_tables2 as tables from django.conf import settings from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ from hypha.apply.projects.models import Project from hypha.core.tables import RelativeTimeColumn @@ -20,19 +21,19 @@ class ReportingTable(tables.Table): }, ) organization_name = tables.Column( - accessor="submission__organization_name", verbose_name="Organization name" + accessor="submission__organization_name", verbose_name=_("Organization name") ) current_report_status = tables.Column( - attrs={"td": {"class": ""}}, verbose_name="Status" + attrs={"td": {"class": ""}}, verbose_name=_("Status") ) current_report_submitted_date = tables.Column( - verbose_name="Submitted date", accessor="current_report_submitted_date__date" + verbose_name=_("Submitted date"), accessor="current_report_submitted_date__date" ) current_report_due_date = tables.Column( - verbose_name="Due Date", accessor="report_config__current_report__end_date" + verbose_name=_("Due Date"), accessor="report_config__current_report__end_date" ) current_report_last_notified_date = tables.Column( - verbose_name="Last Notified", + verbose_name=_("Last Notified"), accessor="report_config__current_report__notified__date", ) diff --git a/hypha/apply/projects/tables.py b/hypha/apply/projects/tables.py index 6a92275fea..93f8a0a760 100644 --- a/hypha/apply/projects/tables.py +++ b/hypha/apply/projects/tables.py @@ -7,6 +7,7 @@ from django.utils.safestring import mark_safe from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext from django_tables2.utils import A from heroicons.templatetags.heroicons import heroicon_outline @@ -204,14 +205,17 @@ def render_reporting(self, record): return "-" if record.report_config.is_up_to_date(): - return "Up to date" + return _("Up to date") if record.report_config.has_very_late_reports(): display = f"{heroicon_outline(name='exclamation-triangle', size=20)}" else: display = "" - display += f"{record.report_config.outstanding_reports()} outstanding" + outstanding_count = record.report_config.outstanding_reports() + display += ngettext( + "{} outstanding", "{} outstanding", outstanding_count + ).format(outstanding_count) return mark_safe(display) diff --git a/hypha/apply/projects/views/payment.py b/hypha/apply/projects/views/payment.py index 489cbab52b..0407c0c216 100644 --- a/hypha/apply/projects/views/payment.py +++ b/hypha/apply/projects/views/payment.py @@ -6,6 +6,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied from django.db import transaction +from django.db.models import Q from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string @@ -481,9 +482,10 @@ def get_media(self, *args, **kwargs): PAYMENT_FAILED, ] and self.invoice.document.file.name.endswith(".pdf"): if activities := Activity.actions.filter( + Q(message__contains=APPROVED_BY_STAFF) + | Q(message__contains=APPROVED_BY_FINANCE), related_content_type__model="invoice", related_object_id=self.invoice.id, - message__icontains="Approved by", ).visible_to(self.request.user): approval_pdf_page = html_to_pdf( render_to_string( diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py index 8e89a9c5ca..7debaeb784 100644 --- a/hypha/apply/projects/views/project.py +++ b/hypha/apply/projects/views/project.py @@ -1074,7 +1074,7 @@ def post(self, *args, **kwargs): else: # should never be the case but still to avoid 500. raise PermissionDenied( - "User don't have project form approver roles" + _("User don't have project form approver roles") ) paf_status = form.cleaned_data.get("paf_status") diff --git a/hypha/apply/review/admin_helpers.py b/hypha/apply/review/admin_helpers.py index 74942f5735..9c9be0887b 100644 --- a/hypha/apply/review/admin_helpers.py +++ b/hypha/apply/review/admin_helpers.py @@ -1,3 +1,4 @@ +from django.utils.translation import gettext_lazy as _ from wagtail_modeladmin.helpers import PageButtonHelper @@ -7,9 +8,9 @@ def clone_button(self, obj, classnames_add, classnames_exclude): cn = self.finalise_classname(classname, classnames_exclude) return { "url": self.url_helper.get_action_url("clone", instance_pk=obj.pk), - "label": "Clone", + "label": _("Clone"), "classname": cn, - "title": "Clone this %s" % self.verbose_name, + "title": _("Clone this %s") % self.verbose_name, } def get_buttons_for_obj( diff --git a/hypha/apply/review/blocks.py b/hypha/apply/review/blocks.py index 74b16b5d1d..f2ed49b63b 100644 --- a/hypha/apply/review/blocks.py +++ b/hypha/apply/review/blocks.py @@ -100,7 +100,7 @@ def get_choices(self, choices): """ rate_choices = list(choices) rate_choices.pop(-1) - rate_choices.append(("", "n/a - choose not to answer")) + rate_choices.append(("", _("n/a - choose not to answer"))) return tuple(rate_choices) @@ -110,7 +110,7 @@ class ReviewMustIncludeFieldBlock(MustIncludeFieldBlock): class RecommendationBlock(ReviewMustIncludeFieldBlock): name = "recommendation" - description = "Overall recommendation" + description = _("Overall recommendation") field_class = forms.ChoiceField class Meta: @@ -131,7 +131,7 @@ def render(self, value, context=None): class RecommendationCommentsBlock(ReviewMustIncludeFieldBlock): name = "comments" - description = "Recommendation comments" + description = _("Recommendation comments") widget = RICH_TEXT_WIDGET_SHORT class Meta: @@ -146,7 +146,7 @@ def get_field_kwargs(self, struct_value): class VisibilityBlock(ReviewMustIncludeFieldBlock): name = "visibility" - description = "Visibility" + description = _("Visibility") field_class = forms.ChoiceField widget = forms.RadioSelect() diff --git a/hypha/apply/review/forms.py b/hypha/apply/review/forms.py index bddedfc1c4..03eaa80efa 100644 --- a/hypha/apply/review/forms.py +++ b/hypha/apply/review/forms.py @@ -2,6 +2,8 @@ from django.core.exceptions import NON_FIELD_ERRORS from django.utils.html import escape from django.utils.safestring import mark_safe +from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ from hypha.apply.review.options import NA from hypha.apply.stream_forms.forms import StreamBaseForm @@ -30,7 +32,9 @@ class Meta: error_messages = { NON_FIELD_ERRORS: { - "unique_together": "You have already posted a review for this submission", + "unique_together": _( + "You have already posted a review for this submission" + ), } } @@ -156,7 +160,9 @@ def clean(self): opinions = [cleaned_data.get(opinion.lower()) for _, opinion in OPINION_CHOICES] valid_opinions = [opinion for opinion in opinions if opinion is not None] if len(valid_opinions) > 1: - self.add_error(None, "Cant submit both an agreement and disagreement") + self.add_error( + None, gettext("Cant submit both an agreement and disagreement") + ) cleaned_data = {"opinion": valid_opinions[0]} return cleaned_data diff --git a/hypha/apply/review/views.py b/hypha/apply/review/views.py index 42d782f0da..95f1304cfa 100644 --- a/hypha/apply/review/views.py +++ b/hypha/apply/review/views.py @@ -416,11 +416,11 @@ def get_context_data(self, **kwargs): # Add the header rows review_data["title"] = {"question": "", "answers": []} - review_data["opinions"] = {"question": "Opinions", "answers": []} - review_data["score"] = {"question": "Overall Score", "answers": []} - review_data["recommendation"] = {"question": "Recommendation", "answers": []} - review_data["revision"] = {"question": "Revision", "answers": []} - review_data["comments"] = {"question": "Comments", "answers": []} + review_data["opinions"] = {"question": _("Opinions"), "answers": []} + review_data["score"] = {"question": _("Overall Score"), "answers": []} + review_data["recommendation"] = {"question": _("Recommendation"), "answers": []} + review_data["revision"] = {"question": _("Revision"), "answers": []} + review_data["comments"] = {"question": _("Comments"), "answers": []} responses = self.object_list.count() ordered_reviewers = ( @@ -455,9 +455,11 @@ def get_context_data(self, **kwargs): review.get_comments_display(include_question=False) ) if review.for_latest: - revision = "Current" + revision = _("Current") else: - revision = 'Compare'.format(review.get_compare_url()) + revision = '{}'.format( + review.get_compare_url(), _("Compare") + ) review_data["revision"]["answers"].append(revision) for field_id in review.fields: diff --git a/hypha/apply/stream_forms/blocks.py b/hypha/apply/stream_forms/blocks.py index d179a95c22..1d1af14adf 100644 --- a/hypha/apply/stream_forms/blocks.py +++ b/hypha/apply/stream_forms/blocks.py @@ -217,7 +217,7 @@ class GroupToggleBlock(FormFieldBlock): required = BooleanBlock(label=_("Required"), default=True, required=False) choices = ListBlock( CharBlock(label=_("Choice")), - help_text=( + help_text=_( "Please create only two choices for toggle. " "First choice will revel the group and the second hide it. " "Additional choices will be ignored." @@ -230,7 +230,7 @@ class GroupToggleBlock(FormFieldBlock): class Meta: label = _("Group fields") icon = "group" - help_text = "Remember to end group using Group fields end block." + help_text = _("Remember to end group using Group fields end block.") def get_field_kwargs(self, struct_value): kwargs = super().get_field_kwargs(struct_value) @@ -241,8 +241,8 @@ def get_field_kwargs(self, struct_value): field_choices = field_choices[:2] elif total_choices < 2: field_choices = [ - ("yes", "Yes"), - ("no", "No"), + ("yes", _("Yes")), + ("no", _("No")), ] kwargs["choices"] = field_choices return kwargs @@ -250,9 +250,9 @@ def get_field_kwargs(self, struct_value): class GroupToggleEndBlock(StaticBlock): class Meta: - label = "Group fields end" + label = _("Group fields end") icon = "group" - admin_text = "End of Group fields Block" + admin_text = _("End of Group fields Block") class DropdownFieldBlock(RadioButtonsFieldBlock): diff --git a/hypha/apply/stream_forms/forms.py b/hypha/apply/stream_forms/forms.py index 67f0b31f16..8106996261 100644 --- a/hypha/apply/stream_forms/forms.py +++ b/hypha/apply/stream_forms/forms.py @@ -4,6 +4,7 @@ from django.forms.fields import EmailField from django.forms.forms import DeclarativeFieldsMetaclass from django.utils.safestring import mark_safe +from django.utils.translation import gettext as _ from django_file_form.forms import FileFormMixin from wagtail.contrib.forms.forms import BaseForm @@ -97,32 +98,37 @@ def clean(self): if isinstance(value, EmailField): email = self.data.get(field) if email: - is_registered, _ = is_user_already_registered( + # Double __ to avoid shadowing gettext alias _: + is_registered, __ = is_user_already_registered( email=self.data.get(field) ) if is_registered: user = get_user_by_email(email=email) if not user: - self.add_error(field, "Found multiple account") + self.add_error(field, _("Found multiple account")) raise ValidationError( mark_safe( - "Found multiple account for the same email. " - "Please login with the correct credentials or " - '' - "contact to the support team" - ".".format(settings.ORG_EMAIL) + _( + "Found multiple account for the same email. " + "Please login with the correct credentials or " + '' + "contact to the support team" + "." + ).format(ORG_EMAIL=settings.ORG_EMAIL) ) ) elif not user.is_active: - self.add_error(field, "Found an inactive account") + self.add_error(field, _("Found an inactive account")) raise ValidationError( mark_safe( - "Found an inactive account for the same email. " - "Please use different email or " - '' - "contact to the support team" - ".".format(settings.ORG_EMAIL) + _( + "Found an inactive account for the same email. " + "Please use different email or " + '' + "contact to the support team" + "." + ).format(ORG_EMAIL=settings.ORG_EMAIL) ) ) diff --git a/hypha/apply/todo/options.py b/hypha/apply/todo/options.py index 45c2575e21..25629daa6e 100644 --- a/hypha/apply/todo/options.py +++ b/hypha/apply/todo/options.py @@ -1,6 +1,6 @@ import copy -from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy as _ from hypha.apply.activity.adapters.utils import link_to @@ -27,27 +27,33 @@ FAILED_SUBMISSIONS_EXPORT = "failed_submission_export" TASKS_CODE_CHOICES = ( - (COMMENT_TASK, "Comment Task"), - (SUBMISSION_DRAFT, "Submission Draft"), - (DETERMINATION_DRAFT, "Determination draft"), - (REVIEW_DRAFT, "Review Draft"), - (PROJECT_WAITING_PF, "Project waiting project form"), - (PROJECT_WAITING_SOW, "Project waiting scope of work"), - (PROJECT_SUBMIT_PAF, "Project submit project form(s)"), - (PROJECT_SUBMIT_SOW, "Project submit scope of work"), - (PAF_REQUIRED_CHANGES, "Project form required changes"), - (PAF_WAITING_ASSIGNEE, "Project form waiting assignee"), - (PAF_WAITING_APPROVAL, "Project form waiting approval"), - (PROJECT_WAITING_CONTRACT, "Project waiting contract"), - (PROJECT_WAITING_CONTRACT_DOCUMENT, "Project waiting contract document"), - (PROJECT_WAITING_CONTRACT_REVIEW, "Project waiting contract review"), - (PROJECT_WAITING_INVOICE, "Project waiting invoice"), - (INVOICE_REQUIRED_CHANGES, "Invoice required changes"), - (INVOICE_WAITING_APPROVAL, "Invoice waiting approval"), - (INVOICE_WAITING_PAID, "Invoice waiting paid"), - (REPORT_DUE, "Report due"), - (DOWNLOAD_SUBMISSIONS_EXPORT, "Download exported submissions"), - (FAILED_SUBMISSIONS_EXPORT, "Failed to generate submissions export file"), + (COMMENT_TASK, _("Comment Task")), + (SUBMISSION_DRAFT, _("Submission Draft")), + (DETERMINATION_DRAFT, _("Determination draft")), + (REVIEW_DRAFT, _("Review Draft")), + (PROJECT_WAITING_PF, _("Project waiting project form")), + (PROJECT_WAITING_SOW, _("Project waiting scope of work")), + (PROJECT_SUBMIT_PAF, _("Project submit project form(s)")), + (PROJECT_SUBMIT_SOW, _("Project submit scope of work")), + (PAF_REQUIRED_CHANGES, _("Project form required changes")), + (PAF_WAITING_ASSIGNEE, _("Project form waiting assignee")), + (PAF_WAITING_APPROVAL, _("Project form waiting approval")), + (PROJECT_WAITING_CONTRACT, _("Project waiting contract")), + ( + PROJECT_WAITING_CONTRACT_DOCUMENT, + _("Project waiting contract document"), + ), + (PROJECT_WAITING_CONTRACT_REVIEW, _("Project waiting contract review")), + (PROJECT_WAITING_INVOICE, _("Project waiting invoice")), + (INVOICE_REQUIRED_CHANGES, _("Invoice required changes")), + (INVOICE_WAITING_APPROVAL, _("Invoice waiting approval")), + (INVOICE_WAITING_PAID, _("Invoice waiting paid")), + (REPORT_DUE, _("Report due")), + (DOWNLOAD_SUBMISSIONS_EXPORT, _("Download exported submissions")), + ( + FAILED_SUBMISSIONS_EXPORT, + _("Failed to generate submissions export file"), + ), ) diff --git a/hypha/apply/todo/views.py b/hypha/apply/todo/views.py index be8640c78c..cf5f701619 100644 --- a/hypha/apply/todo/views.py +++ b/hypha/apply/todo/views.py @@ -4,6 +4,7 @@ from django.db.models import Count from django.shortcuts import get_object_or_404, render from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ from django.views.generic import ListView, View from django_htmx.http import trigger_client_event @@ -38,7 +39,7 @@ def dispatch(self, request, *args, **kwargs): request.user.groups.all() ): return super().dispatch(request, *args, **kwargs) - raise PermissionDenied("You can remove the tasks that are assigned to you.") + raise PermissionDenied(_("You can remove the tasks that are assigned to you.")) def delete(self, *args, **kwargs): source = self.task.related_object diff --git a/hypha/apply/translate/forms.py b/hypha/apply/translate/forms.py index c1f194246d..fde4f4e5e0 100644 --- a/hypha/apply/translate/forms.py +++ b/hypha/apply/translate/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import gettext as _ from hypha.apply.translate.fields import LanguageChoiceField from hypha.apply.translate.utils import get_available_translations @@ -21,10 +22,10 @@ def clean(self): 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", + _("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 + raise forms.ValidationError(_("Invalid input selected")) from err diff --git a/hypha/apply/users/admin_views.py b/hypha/apply/users/admin_views.py index e2ff1dffb5..b883f7e0f5 100644 --- a/hypha/apply/users/admin_views.py +++ b/hypha/apply/users/admin_views.py @@ -43,14 +43,16 @@ class UserFilterSet(WagtailFilterSet): STATUS_CHOICES = ( - ("inactive", "INACTIVE"), - ("active", "ACTIVE"), + ("inactive", gettext_lazy("INACTIVE")), + ("active", gettext_lazy("ACTIVE")), ) roles = django_filters.ModelChoiceFilter( - queryset=Group.objects.all(), label="Roles", method="filter_by_roles" + queryset=Group.objects.all(), + label=gettext_lazy("Roles"), + method="filter_by_roles", ) status = django_filters.ChoiceFilter( - choices=STATUS_CHOICES, label="Status", method="filter_by_status" + choices=STATUS_CHOICES, label=gettext_lazy("Status"), method="filter_by_status" ) class Meta: diff --git a/hypha/apply/users/forms.py b/hypha/apply/users/forms.py index 1b98f022d3..9a5a97031b 100644 --- a/hypha/apply/users/forms.py +++ b/hypha/apply/users/forms.py @@ -205,7 +205,7 @@ def clean_slack(self): if slack: slack = slack.strip() if " " in slack: - raise forms.ValidationError("Slack names must not include spaces") + raise forms.ValidationError(_("Slack names must not include spaces")) if not slack.startswith("@"): slack = "@" + slack diff --git a/hypha/apply/users/models.py b/hypha/apply/users/models.py index 32d7471ccc..bfe3d8c314 100644 --- a/hypha/apply/users/models.py +++ b/hypha/apply/users/models.py @@ -338,7 +338,7 @@ class AuthSettings(BaseGenericSetting): wagtail_reference_index_ignore = True class Meta: - verbose_name = "Auth Settings" + verbose_name = _("Auth Settings") consent_show = models.BooleanField(_("Show consent checkbox?"), default=False) consent_text = models.CharField(max_length=255, blank=True) @@ -387,7 +387,8 @@ def __str__(self): class Meta: ordering = ("created",) - verbose_name_plural = "Pending signups" + verbose_name = _("Pending signup") + verbose_name_plural = _("Pending signups") class ConfirmAccessToken(models.Model): @@ -405,4 +406,5 @@ def __str__(self): class Meta: ordering = ("modified",) - verbose_name_plural = "Confirm Access Tokens" + verbose_name = _("Confirm Access Token") + verbose_name_plural = _("Confirm Access Tokens") diff --git a/hypha/apply/users/services.py b/hypha/apply/users/services.py index 13f51668d1..ceaff096ba 100644 --- a/hypha/apply/users/services.py +++ b/hypha/apply/users/services.py @@ -7,6 +7,7 @@ from django.utils.crypto import get_random_string from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode +from django.utils.translation import gettext as _ from wagtail.models import Site from hypha.core.mail import MarkdownMail @@ -93,7 +94,9 @@ def _get_url_params(self) -> None | str: return None def send_email_no_account_found(self, to): - subject = f"Log in attempt at {settings.ORG_LONG_NAME}" + subject = _("Log in attempt at {ORG_LONG_NAME}").format( + ORG_LONG_NAME=settings.ORG_LONG_NAME + ) # Force subject to a single line to avoid header-injection issues. subject = "".join(subject.splitlines()) @@ -113,7 +116,9 @@ def send_login_email(self, user): "timeout_minutes": timeout_minutes, } - subject = f"Log in to {user.get_username()} at {settings.ORG_LONG_NAME}" + subject = _("Log in to {user} at {ORG_LONG_NAME}").format( + user=user.get_username(), ORG_LONG_NAME=settings.ORG_LONG_NAME + ) # Force subject to a single line to avoid header-injection issues. subject = "".join(subject.splitlines()) @@ -134,7 +139,9 @@ def send_new_account_login_email(self, signup_obj): "timeout_minutes": timeout_minutes, } - subject = f"Welcome to {settings.ORG_LONG_NAME}" + subject = _("Welcome to {ORG_LONG_NAME}").format( + ORG_LONG_NAME=settings.ORG_LONG_NAME + ) # Force subject to a single line to avoid header-injection issues. subject = "".join(subject.splitlines()) diff --git a/hypha/apply/users/utils.py b/hypha/apply/users/utils.py index da2709b7a4..d9c5b13c33 100644 --- a/hypha/apply/users/utils.py +++ b/hypha/apply/users/utils.py @@ -10,7 +10,7 @@ from django.utils.crypto import get_random_string from django.utils.encoding import force_bytes from django.utils.http import url_has_allowed_host_and_scheme, urlsafe_base64_encode -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ def get_user_by_email(email): @@ -127,7 +127,7 @@ def send_confirmation_email(user, token, updated_email=None, site=None): if site: context.update(site=site) - subject = "Confirmation email for {unverified_email} at {ORG_LONG_NAME}".format( + subject = _("Confirmation email for {unverified_email} at {ORG_LONG_NAME}").format( **context ) # Force subject to a single line to avoid header-injection issues. diff --git a/hypha/apply/users/views.py b/hypha/apply/users/views.py index 87d5c39ffc..a9cbf0b6a6 100644 --- a/hypha/apply/users/views.py +++ b/hypha/apply/users/views.py @@ -734,7 +734,9 @@ def send_confirm_access_email_view(request): "user": request.user, "timeout_minutes": settings.PASSWORDLESS_LOGIN_TIMEOUT // 60, } - subject = f"Confirmation code for {settings.ORG_LONG_NAME}: {token_obj.token}" + subject = _("Confirmation code for {ORG_LONG_NAME}: {token}").format( + ORG_LONG_NAME=settings.ORG_LONG_NAME, token=token_obj.token + ) email = MarkdownMail("users/emails/confirm_access.md") email.send( to=request.user.email, diff --git a/hypha/apply/users/wagtail_hooks.py b/hypha/apply/users/wagtail_hooks.py index db12f370ab..a0c9d77aa3 100644 --- a/hypha/apply/users/wagtail_hooks.py +++ b/hypha/apply/users/wagtail_hooks.py @@ -3,6 +3,7 @@ from hypha.apply.activity.messaging import MESSAGES, messenger +from .roles import SUPERADMIN from .utils import send_activation_email, update_is_staff @@ -23,7 +24,7 @@ def notify_after_create_user(request, user): def notify_after_edit_user(request, user): roles = list(user.groups.values_list("name", flat=True)) if user.is_superuser: - roles.append("Administrator") + roles.append(SUPERADMIN) if roles: roles = ", ".join(roles) messenger( diff --git a/hypha/apply/utils/blocks.py b/hypha/apply/utils/blocks.py index bbfc16ce5c..b74ac82959 100644 --- a/hypha/apply/utils/blocks.py +++ b/hypha/apply/utils/blocks.py @@ -111,28 +111,28 @@ def clean(self, value, ignore_required_constraints=False): all_errors = [] if missing: all_errors.append( - "You are missing the following required fields: {}".format( + _("You are missing the following required fields: {}").format( ", ".join(prettify_names(missing)) ) ) if duplicates: all_errors.append( - "The following fields must be included only once: {}".format( + _("The following fields must be included only once: {}").format( ", ".join(prettify_names(duplicates)) ) ) for i, block_name in enumerate(block_types): if block_name in duplicates: - self.add_error_to_child(error_dict, i, "info", "Duplicate field") + self.add_error_to_child(error_dict, i, "info", _("Duplicate field")) for block in value: if hasattr(block.block, "child_blocks"): for child_block_name, child_block in block.block.child_blocks.items(): if child_block.required and not block.value[child_block_name]: all_errors.append( - "{} cannot be empty for {}".format( - child_block.label, block.block.label + _("{child} cannot be empty for {parent}").format( + child=child_block.label, parent=block.block.label ) ) if ( @@ -142,8 +142,9 @@ def clean(self, value, ignore_required_constraints=False): for child_value in block.value[child_block_name]: if not child_value: all_errors.append( - "{} cannot be empty for {}".format( - child_block.label, block.block.label + _("{child} cannot be empty for {parent}").format( + child=child_block.label, + parent=block.block.label, ) ) @@ -187,7 +188,7 @@ def __init__(self, *args, description="", **kwargs): super().__init__(*args, **kwargs) class Meta: - admin_text = "Must be included in the form only once." + admin_text = _("Must be included in the form only once.") def render_form(self, *args, **kwargs): errors = kwargs.pop("errors") @@ -211,10 +212,8 @@ def deconstruct(self): class SingleIncludeMixin: def __init__(self, *args, **kwargs): - info_name = ( - f"{self._meta_class.label} Field" - if self._meta_class.label - else f"{self.name.title()} Field" + info_name = _("{} Field").format( + self._meta_class.label if self._meta_class.label else self.name.title() ) child_blocks = [ ("info", SingleIncludeStatic(label=info_name, description=self.description)) diff --git a/hypha/apply/utils/models.py b/hypha/apply/utils/models.py index 5b94b0e984..8031c60241 100644 --- a/hypha/apply/utils/models.py +++ b/hypha/apply/utils/models.py @@ -10,13 +10,13 @@ class PDFPageSettings(BaseGenericSetting): LEGAL = "legal" LETTER = "letter" PAGE_SIZES = [ - (A4, "A4"), - (LEGAL, "Legal"), - (LETTER, "Letter"), + (A4, _("A4")), + (LEGAL, _("Legal")), + (LETTER, _("Letter")), ] class Meta: - verbose_name = "pdf settings" + verbose_name = _("pdf settings") download_page_size = models.CharField( choices=PAGE_SIZES, diff --git a/hypha/cookieconsent/models.py b/hypha/cookieconsent/models.py index 3631ddcc82..1cbd62b897 100644 --- a/hypha/cookieconsent/models.py +++ b/hypha/cookieconsent/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ from wagtail.admin.panels import FieldPanel, MultiFieldPanel from wagtail.contrib.settings.models import BaseGenericSetting, register_setting from wagtail.fields import RichTextField @@ -7,37 +8,43 @@ @register_setting class CookieConsentSettings(BaseGenericSetting): class Meta: - verbose_name = "Cookie banner settings" + verbose_name = _("Cookie banner settings") cookieconsent_active = models.BooleanField( - "Activate cookie pop-up banner", + _("Activate cookie pop-up banner"), default=False, ) cookieconsent_title = models.CharField( - "Cookie banner title", + _("Cookie banner title"), max_length=255, - default="Your cookie settings", + default=_("Your cookie settings"), ) cookieconsent_message = RichTextField( - "Cookie consent message", - default='

This website deploys cookies for basic functionality and to keep it secure. These cookies are strictly necessary. Optional analysis cookies which provide us with statistical information about the use of the website may also be deployed, but only with your consent. Please review our Privacy & Data Policy for more information.

', + _("Cookie consent message"), + default=_( + '

This website deploys cookies for basic functionality and to keep it secure. These cookies are strictly necessary. Optional analysis cookies which provide us with statistical information about the use of the website may also be deployed, but only with your consent. Please review our Privacy & Data Policy for more information.

' + ), ) cookieconsent_essential_about = RichTextField( - 'Essential cookies information to be displayed under "Learn More"', - default="

Strictly necessary for the operation of a website because they enable you to navigate around the site and use features. These cookies cannot be switched off in our systems and do not store any personally identifiable information.

", + _('Essential cookies information to be displayed under "Learn More"'), + default=_( + "

Strictly necessary for the operation of a website because they enable you to navigate around the site and use features. These cookies cannot be switched off in our systems and do not store any personally identifiable information.

" + ), ) cookieconsent_analytics = models.BooleanField( - "Include consent option for analytics cookies", + _("Include consent option for analytics cookies"), default=False, ) cookieconsent_analytics_about = RichTextField( - 'Analytics cookies information to be displayed under "Learn More"', - default="

With these cookies we count visits and traffic sources to help improve the performance of our services through metrics. These cookies show us which pages on our services are the most and the least popular, and how users navigate our services. The information collected is aggregated and contains no personally identifiable information. If you block these cookies, then we will not know when you have used our services.

", + _('Analytics cookies information to be displayed under "Learn More"'), + default=_( + "

With these cookies we count visits and traffic sources to help improve the performance of our services through metrics. These cookies show us which pages on our services are the most and the least popular, and how users navigate our services. The information collected is aggregated and contains no personally identifiable information. If you block these cookies, then we will not know when you have used our services.

" + ), ) panels = [ @@ -50,6 +57,6 @@ class Meta: FieldPanel("cookieconsent_analytics"), FieldPanel("cookieconsent_analytics_about"), ], - "Cookie banner", + _("Cookie banner"), ), ] diff --git a/hypha/core/models/system_settings.py b/hypha/core/models/system_settings.py index b48eddfcbc..3642ef2b84 100644 --- a/hypha/core/models/system_settings.py +++ b/hypha/core/models/system_settings.py @@ -14,7 +14,7 @@ @register_setting class SystemSettings(BaseGenericSetting): class Meta: - verbose_name = "System settings" + verbose_name = _("System settings") db_table = "system_settings" home_title = models.CharField( @@ -33,7 +33,9 @@ class Meta: home_no_applications_msg = RichTextField( _("No open applications message"), help_text=_("The message to be displayed when there are no open applications."), - default="

There are currently no open applications, check back later!

", + default=_( + "

There are currently no open applications, check back later!

" + ), ) site_logo_default = models.ForeignKey( @@ -63,7 +65,7 @@ class Meta: ) nav_content = models.TextField( - "Front page navigation content", + _("Front page navigation content"), help_text=_( "This will overwrite the default front page navigation bar, html tags is allowed." ), @@ -71,30 +73,34 @@ class Meta: ) footer_content = models.TextField( - "Footer content", - default='

Configure this text in Wagtail admin -> Settings -> System settings.

\n
\nCookie Settings', + _("Footer content"), + default=_( + '

Configure this text in Wagtail admin -> Settings -> System settings.

\n
\nCookie Settings' + ), help_text=_("This will be added to the footer, html tags is allowed."), blank=True, ) title_404 = models.CharField( - "Title", + _("Title"), max_length=255, - default="Page not found", + default=_("Page not found"), ) body_404 = RichTextField( - "Text", - default="

You may be trying to find a page that doesn’t exist or has been moved.

", + _("Text"), + default=_( + "

You may be trying to find a page that doesn’t exist or has been moved.

" + ), ) title_403 = models.CharField( - "Title", + _("Title"), max_length=255, - default="Permission Denied", + default=_("Permission Denied"), ) body_403 = RichTextField( - "Text", - default="

You might not have access to the requested resource.

", + _("Text"), + default=_("

You might not have access to the requested resource.

"), ) panels = [ @@ -104,7 +110,7 @@ class Meta: FieldPanel("home_strapline"), FieldPanel("home_no_applications_msg"), ], - "Homepage", + _("Homepage"), ), MultiFieldPanel( [ @@ -112,7 +118,7 @@ class Meta: FieldPanel("site_logo_mobile"), FieldPanel("site_logo_link"), ], - "Site logo", + _("Site logo"), ), FieldPanel("nav_content"), FieldPanel("footer_content"), @@ -121,13 +127,13 @@ class Meta: FieldPanel("title_404"), FieldPanel("body_404"), ], - "404 page", + _("404 page"), ), MultiFieldPanel( [ FieldPanel("title_403"), FieldPanel("body_403"), ], - "403 page", + _("403 page"), ), ] diff --git a/hypha/core/navigation.py b/hypha/core/navigation.py index 5d2dcaed47..889019e29f 100644 --- a/hypha/core/navigation.py +++ b/hypha/core/navigation.py @@ -130,10 +130,13 @@ def get_primary_navigation_items(request): for item in original_nav_items: nav_item = item.copy() - if nav_item["title"] == "Projects" and not settings.PROJECTS_ENABLED: + if nav_item["title"] == _("Projects") and not settings.PROJECTS_ENABLED: continue - if nav_item["title"] == "Submissions" and settings.APPLY_NAV_SUBMISSIONS_ITEMS: + if ( + nav_item["title"] == _("Submissions") + and settings.APPLY_NAV_SUBMISSIONS_ITEMS + ): nav_item["sub_items"] = settings.APPLY_NAV_SUBMISSIONS_ITEMS if not _check_permission(request.user, nav_item["permission_method"]): From 9f7265c9a325d615a13a5bf1ae0381ef64378160 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Tue, 3 Mar 2026 15:58:39 +0100 Subject: [PATCH 2/8] Revert translations for legacy module hypha.apply.determination.forms --- hypha/apply/determinations/forms.py | 128 +++++++++++----------------- 1 file changed, 52 insertions(+), 76 deletions(-) diff --git a/hypha/apply/determinations/forms.py b/hypha/apply/determinations/forms.py index 9ade2635ad..b9c553f4d1 100644 --- a/hypha/apply/determinations/forms.py +++ b/hypha/apply/determinations/forms.py @@ -93,9 +93,7 @@ class Meta: error_messages = { NON_FIELD_ERRORS: { - "unique_together": _( - "You have already created a determination for this submission" - ), + "unique_together": "You have already created a determination for this submission", } } @@ -173,78 +171,68 @@ def save(self): class BaseConceptDeterminationForm(forms.Form): titles = { - 1: _("Feedback"), + 1: "Feedback", } outcome = forms.ChoiceField( choices=DETERMINATION_CHOICES, label=_("Determination"), - help_text=_( - "Do you recommend requesting a proposal based on this concept note?" - ), + help_text="Do you recommend requesting a proposal based on this concept note?", ) outcome.group = 1 message = RichTextField( label=_("Determination message"), - help_text=_( - "This text will be e-mailed to the applicant. " - "Ones when text is first added and then every time the text is changed." - ), + help_text="This text will be e-mailed to the applicant. " + "Ones when text is first added and then every time the text is changed.", ) message.group = 1 principles = RichTextField( label=_("Goals and principles"), - help_text=_( - "Does the project contribute and/or have relevance to OTF goals and principles?" - "Are the goals and objectives of the project clear? Is it a technology research, development, or deployment " - "project? Can project’s effort be explained to external audiences and non-technical people? What problem are " - "they trying to solve and is the solution strategical or tactical? Is the project strategically or tactically " - "important to OTF’s goals, principles and rationale and other OTF efforts? Is it clear how? What tools, if any, " - "currently exist to solve this problem? How is this project different? Does the effort have any overlap with " - "existing OTF and/or USG supported projects? Is the overlap complementary or duplicative? If complementary, " - "can it be explained clearly? I.e. geographic focus, technology, organization profile, etc. What are the " - "liabilities and risks of taking on this project? I.e. political personalities, financial concerns, technical " - "controversial, etc. Is the organization or its members known within any relevant communities? If yes, what is " - "their reputation and why? What is the entity’s motivation and principles? What are the entity member(s) " - "motivations and principles? Where is the organization physically and legally based? If the organization is " - "distributed, where is the main point of contact? Does the organization have any conflicts of interest with " - "RFA, OTF, the Advisory Council, or other RFA-OTF projects? Is the project team an organization, community " - "or an individual?" - ), + help_text="Does the project contribute and/or have relevance to OTF goals and principles?" + "Are the goals and objectives of the project clear? Is it a technology research, development, or deployment " + "project? Can project’s effort be explained to external audiences and non-technical people? What problem are " + "they trying to solve and is the solution strategical or tactical? Is the project strategically or tactically " + "important to OTF’s goals, principles and rationale and other OTF efforts? Is it clear how? What tools, if any, " + "currently exist to solve this problem? How is this project different? Does the effort have any overlap with " + "existing OTF and/or USG supported projects? Is the overlap complementary or duplicative? If complementary, " + "can it be explained clearly? I.e. geographic focus, technology, organization profile, etc. What are the " + "liabilities and risks of taking on this project? I.e. political personalities, financial concerns, technical " + "controversial, etc. Is the organization or its members known within any relevant communities? If yes, what is " + "their reputation and why? What is the entity’s motivation and principles? What are the entity member(s) " + "motivations and principles? Where is the organization physically and legally based? If the organization is " + "distributed, where is the main point of contact? Does the organization have any conflicts of interest with " + "RFA, OTF, the Advisory Council, or other RFA-OTF projects? Is the project team an organization, community " + "or an individual?", ) principles.group = 1 technical = RichTextField( label=_("Technical merit"), - help_text=_( - "Does the project clearly articulate the technical problem, solution, and approach? " - "Is the problem clearly justifiable? Does the project clearly articulate the technological objectives? " - "Is it an open or closed development project? I.e. Open source like Android or open source like Firefox OS " - "or closed like iOS. Does a similar technical solution already exist? If so, what are the differentiating " - "factors? Is the effort to sustain an existing technical approach? If so, are these considered successful? " - "Is the effort a new technical approach or improvement to an existing solution? If so, how? Is the effort " - "a completely new technical approach fostering new solutions in the field? Does the project’s technical " - "approach solve the problem? What are the limitations of the project’s technical approach and solution? " - "What are the unintended or illicit uses and consequences of this technology? Has the project identified " - "and/or developed any safeguards for these consequences?" - ), + help_text="Does the project clearly articulate the technical problem, solution, and approach? " + "Is the problem clearly justifiable? Does the project clearly articulate the technological objectives? " + "Is it an open or closed development project? I.e. Open source like Android or open source like Firefox OS " + "or closed like iOS. Does a similar technical solution already exist? If so, what are the differentiating " + "factors? Is the effort to sustain an existing technical approach? If so, are these considered successful? " + "Is the effort a new technical approach or improvement to an existing solution? If so, how? Is the effort " + "a completely new technical approach fostering new solutions in the field? Does the project’s technical " + "approach solve the problem? What are the limitations of the project’s technical approach and solution? " + "What are the unintended or illicit uses and consequences of this technology? Has the project identified " + "and/or developed any safeguards for these consequences?", ) technical.group = 1 sustainable = RichTextField( label=_("Reasonable, realistic and sustainable"), - help_text=_( - "Is the requested amount reasonable, realistic, and justified? If OTF doesn’t support the project, " - "is it likely to be realized? Does the project provide a detailed and realistic description of effort and " - "schedule? I.e. is the project capable of creating a work plan including objectives, activities, and " - "deliverable(s)? Does the project have a clear support model? Is there a known sustainability plan for the " - "future? What in-kind support or other revenue streams is the project receiving? I.e. volunteer developers, " - "service or product sales. Is the project receiving any financial support from the USG? Is this information " - "disclosed? Is the project receiving any other financial support? Is this information disclosed? Are existing " - "supporters approachable? Are they likely aware and/or comfortable with the Intellectual property language " - "within USG contracts?" - ), + help_text="Is the requested amount reasonable, realistic, and justified? If OTF doesn’t support the project, " + "is it likely to be realized? Does the project provide a detailed and realistic description of effort and " + "schedule? I.e. is the project capable of creating a work plan including objectives, activities, and " + "deliverable(s)? Does the project have a clear support model? Is there a known sustainability plan for the " + "future? What in-kind support or other revenue streams is the project receiving? I.e. volunteer developers, " + "service or product sales. Is the project receiving any financial support from the USG? Is this information " + "disclosed? Is the project receiving any other financial support? Is this information disclosed? Are existing " + "supporters approachable? Are they likely aware and/or comfortable with the Intellectual property language " + "within USG contracts?", ) sustainable.group = 1 @@ -254,11 +242,11 @@ class BaseConceptDeterminationForm(forms.Form): class BaseProposalDeterminationForm(forms.Form): titles = { - 1: _("A. Determination"), - 2: _("B. General thoughts"), - 3: _("C. Specific aspects"), - 4: _("D. Rationale and appropriateness consideration"), - 5: _("E. General recommendation"), + 1: "A. Determination", + 2: "B. General thoughts", + 3: "C. Specific aspects", + 4: "D. Rationale and appropriateness consideration", + 5: "E. General recommendation", } # A. Determination @@ -266,41 +254,33 @@ class BaseProposalDeterminationForm(forms.Form): outcome = forms.ChoiceField( choices=DETERMINATION_CHOICES, label=_("Determination"), - help_text=_( - "Do you recommend requesting a proposal based on this concept note?" - ), + help_text="Do you recommend requesting a proposal based on this concept note?", ) outcome.group = 1 message = RichTextField( label=_("Determination message"), - help_text=_( - "This text will be e-mailed to the applicant. " - "Ones when text is first added and then every time the text is changed." - ), + help_text="This text will be e-mailed to the applicant. " + "Ones when text is first added and then every time the text is changed.", ) message.group = 1 # B. General thoughts liked = RichTextField( label=_("Positive aspects"), - help_text=_( - "Any general or specific aspects that got you really excited or that you like about this proposal." - ), + help_text="Any general or specific aspects that got you really excited or that you like about this proposal.", ) liked.group = 2 concerns = RichTextField( label=_("Concerns"), - help_text=_( - "Any general or specific aspects that concern you or leave you feeling uneasy about this proposal." - ), + help_text="Any general or specific aspects that concern you or leave you feeling uneasy about this proposal.", ) concerns.group = 2 red_flags = RichTextField( label=_("Items that must be addressed"), - help_text=_("Anything you think should be flagged for our attention."), + help_text="Anything you think should be flagged for our attention.", ) red_flags.group = 2 @@ -509,9 +489,7 @@ class Meta: error_messages = { NON_FIELD_ERRORS: { - "unique_together": _( - "You have already created a determination for this submission" - ), + "unique_together": "You have already created a determination for this submission", } } @@ -600,9 +578,7 @@ class BatchDeterminationForm(StreamBaseForm, forms.Form, metaclass=FormMixedMeta outcome = forms.ChoiceField( choices=DETERMINATION_CHOICES, label=_("Determination"), - help_text=_( - "Do you recommend requesting a proposal based on this concept note?" - ), + help_text="Do you recommend requesting a proposal based on this concept note?", widget=forms.HiddenInput(), ) From d46afa4db8db6336d61a7aedd99fa929450d39a4 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Wed, 25 Feb 2026 15:55:36 +0100 Subject: [PATCH 3/8] Add missing strings for translations (templates) --- .../activity/include/activity_list.html | 2 +- .../activity/ui/activity-comment-item.html | 6 ++-- .../includes/project_status_bar.html | 4 +-- .../batch_determination_form.html | 6 +++- .../determinations/determination_detail.html | 2 +- hypha/apply/flags/templates/flags/flags.html | 8 +++--- .../templates/cotton/submission_list.html | 18 ++++++------ .../funds/applicationrevision_list.html | 4 +-- .../apply/funds/templates/funds/comments.html | 2 +- .../modal_archive_submission_confirm.html | 6 ++-- .../modal_unarchive_submission_confirm.html | 2 +- .../templates/funds/includes/round-block.html | 10 +++---- .../screening_status_block-button.html | 10 +++---- .../funds/includes/submission-list-row.html | 6 ++-- .../funds/includes/submission-table-row.html | 2 +- .../includes/table_filter_and_search.html | 4 +-- .../funds/modals/update_meta_terms_form.html | 2 +- .../funds/modals/update_partner_form.html | 2 +- .../funds/reminder_confirm_delete.html | 4 +-- .../templates/funds/submission_sealed.html | 2 +- .../funds/templates/submissions/all.html | 28 +++++++++---------- .../submission-reviews-list-multi.html | 4 +-- .../submissions/submenu/category.html | 4 +-- .../submissions/submenu/change-status.html | 2 +- .../submissions/submenu/meta-terms.html | 4 +-- .../submissions/submenu/reviewers.html | 4 +-- .../templates/reports/report_form.html | 2 +- .../templates/reports/report_list.html | 2 +- .../reports/templates/reports/reporting.html | 2 +- .../includes/contracting_documents.html | 2 +- .../includes/project_documents.html | 2 +- .../contracting_category_documents.html | 2 +- .../partials/supporting_documents.html | 2 +- .../review/includes/review_button.html | 2 +- .../review/templates/review/review_list.html | 2 +- .../stream_forms/includes/file_field.html | 2 +- .../users/templates/elevate/elevate.html | 10 ++++++- .../two_factor/core/backup_tokens.html | 6 ++-- .../apply/users/templates/users/account.html | 2 +- hypha/apply/users/templates/users/login.html | 2 +- .../templates/users/register-success.html | 2 +- .../core/navigation/primarynav-apply.html | 2 +- .../home/includes/fund-list-item.html | 4 +-- hypha/templates/base-pdf.html | 2 +- hypha/templates/base.html | 2 +- hypha/templates/cotton/badge/action.html | 4 +-- hypha/templates/cotton/dropdown_menu.html | 4 +-- hypha/templates/cotton/modal/confirm.html | 5 ++-- hypha/templates/cotton/pagination.html | 2 +- .../includes/_toast-placeholder.html | 4 +-- .../templates/includes/language-switcher.html | 2 +- hypha/templates/includes/user_menu.html | 10 +++---- 52 files changed, 123 insertions(+), 108 deletions(-) diff --git a/hypha/apply/activity/templates/activity/include/activity_list.html b/hypha/apply/activity/templates/activity/include/activity_list.html index 0b16080534..37bd424de6 100644 --- a/hypha/apply/activity/templates/activity/include/activity_list.html +++ b/hypha/apply/activity/templates/activity/include/activity_list.html @@ -21,7 +21,7 @@ hx-target="this" hx-swap="outerHTML transition:true" hx-select=".h-timeline" - >Show more... + >{% trans "Show more..." %} {% endif %} diff --git a/hypha/apply/activity/templates/activity/ui/activity-comment-item.html b/hypha/apply/activity/templates/activity/ui/activity-comment-item.html index dc35b2c6d4..bcba4da93c 100644 --- a/hypha/apply/activity/templates/activity/ui/activity-comment-item.html +++ b/hypha/apply/activity/templates/activity/ui/activity-comment-item.html @@ -25,7 +25,7 @@ {% for role in activity.user.get_role_names %} {{ role }} @@ -34,7 +34,7 @@ {% endif %} - commented + {% trans "commented" %} + data-tippy-content="{% blocktrans %}This is visible to {{ visibility_text }}{% endblocktrans %}"> {% heroicon_outline "eye" size=14 class="inline" aria_hidden=true %} {{ visibility_text }} diff --git a/hypha/apply/dashboard/templates/dashboard/includes/project_status_bar.html b/hypha/apply/dashboard/templates/dashboard/includes/project_status_bar.html index 915dd778ff..0b8a1cb8f7 100644 --- a/hypha/apply/dashboard/templates/dashboard/includes/project_status_bar.html +++ b/hypha/apply/dashboard/templates/dashboard/includes/project_status_bar.html @@ -1,4 +1,4 @@ -{% load dashboard_statusbar_tags %} +{% load i18n dashboard_statusbar_tags %}
-
+
{{ current_status_name }}
diff --git a/hypha/apply/determinations/templates/determinations/batch_determination_form.html b/hypha/apply/determinations/templates/determinations/batch_determination_form.html index d4e8f61240..43f26c31df 100644 --- a/hypha/apply/determinations/templates/determinations/batch_determination_form.html +++ b/hypha/apply/determinations/templates/determinations/batch_determination_form.html @@ -15,7 +15,11 @@
- {% trans "Determining" %} {{ submissions.count }} {% trans "submission" %}{{ submissions.count|pluralize }} {% trans "as" %} "{{ action_name }}" + {% blocktrans count submission_count=submissions.count %} + Determining {{ submission_count }} submission as "{{ action_name }}" + {% plural %} + Determining {{ submission_count }} submissions as "{{ action_name }}" + {% endblocktrans %}
diff --git a/hypha/apply/determinations/templates/determinations/determination_detail.html b/hypha/apply/determinations/templates/determinations/determination_detail.html index 64d7e57d66..96c362be0d 100644 --- a/hypha/apply/determinations/templates/determinations/determination_detail.html +++ b/hypha/apply/determinations/templates/determinations/determination_detail.html @@ -59,7 +59,7 @@

{{ group.title|nh3 }}

{{ question }}

{% if answer %} {% if answer == True or answer == False %} - {{ answer|yesno:"Agree,Disagree" }} + {{ answer|yesno:_("Agree,Disagree") }} {% else %}
{{ answer|nh3 }} diff --git a/hypha/apply/flags/templates/flags/flags.html b/hypha/apply/flags/templates/flags/flags.html index cb5af13ab2..f4e83ea2b6 100644 --- a/hypha/apply/flags/templates/flags/flags.html +++ b/hypha/apply/flags/templates/flags/flags.html @@ -10,9 +10,9 @@ hx-swap="outerHTML transition:true" hx-target="#submission-flags" {% if is_flagged %} - data-tippy-content="Remove from your flagged submissions" + data-tippy-content="{% trans 'Remove from your flagged submissions' %}" {% else %} - data-tippy-content="Add to your flagged submissions" + data-tippy-content="{% trans 'Add to your flagged submissions' %}" {% endif %} {% endif %} > @@ -36,9 +36,9 @@ hx-swap="outerHTML transition:true" hx-target="#submission-flags" {% if is_flagged %} - data-tippy-content="Remove staff flag" + data-tippy-content="{% trans 'Remove staff flag' %}" {% else %} - data-tippy-content="Flag for staff review" + data-tippy-content="{% trans 'Flag for staff review' %}" {% endif %} {% endif %} > diff --git a/hypha/apply/funds/templates/cotton/submission_list.html b/hypha/apply/funds/templates/cotton/submission_list.html index c3e42c7deb..f3612eb082 100644 --- a/hypha/apply/funds/templates/cotton/submission_list.html +++ b/hypha/apply/funds/templates/cotton/submission_list.html @@ -1,4 +1,4 @@ -{% load querystrings heroicons %} +{% load querystrings heroicons i18n %} @@ -17,25 +17,25 @@ {% if enable_selection %}{% endif %} - Title + {% trans 'Title' %} - Status + {% trans 'Status' %} - Applicant + {% trans 'Applicant' %} {% if 'organization_name' not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %} - / Organization + / {% trans 'Organization' %} {% endif %} {% if "round" not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %} - Round + {% trans 'Round' %} {% endif %} {% if 'lead' not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %} - Lead + {% trans 'Lead' %} {% endif %} - {% if 'reviews' not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %}Reviews /{% endif %}
Comments + {% if 'reviews' not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %}{% trans 'Reviews' %} /{% endif %}
{% trans 'Comments' %} @@ -54,6 +54,6 @@
{% if show_more and show_more_link %}
{% endif %} diff --git a/hypha/apply/funds/templates/funds/applicationrevision_list.html b/hypha/apply/funds/templates/funds/applicationrevision_list.html index 0d50c12491..56f94fa704 100644 --- a/hypha/apply/funds/templates/funds/applicationrevision_list.html +++ b/hypha/apply/funds/templates/funds/applicationrevision_list.html @@ -6,7 +6,7 @@ @@ -16,7 +16,7 @@ {% block content %}
-

Application revisions

+

{% trans 'Application revisions' %}

    {% for revision in object_list %} diff --git a/hypha/apply/funds/templates/funds/comments.html b/hypha/apply/funds/templates/funds/comments.html index 6ae1c1300f..1866548adf 100644 --- a/hypha/apply/funds/templates/funds/comments.html +++ b/hypha/apply/funds/templates/funds/comments.html @@ -1,7 +1,7 @@ {% extends "base-apply.html" %} {% load i18n static heroicons project_tags %} -{% block title %}Comments: {{ object.title_text_display }}{% endblock %} +{% block title %}{% blocktrans with title=object.title_text_display %}Comments: {{ title }}{% endblocktrans %}{% endblock %} {% block hero %} diff --git a/hypha/apply/funds/templates/funds/includes/modal_archive_submission_confirm.html b/hypha/apply/funds/templates/funds/includes/modal_archive_submission_confirm.html index 86ec11d29b..a4ae61a145 100644 --- a/hypha/apply/funds/templates/funds/includes/modal_archive_submission_confirm.html +++ b/hypha/apply/funds/templates/funds/includes/modal_archive_submission_confirm.html @@ -8,10 +8,12 @@ {% heroicon_outline "exclamation-triangle" class="w-6 h-6 text-error" stroke_width="1.5" aria_hidden="true" %}
- +

- {% blocktrans %} + {% blocktrans trimmed %} Are you sure you want to archive submission? Archived submissions will be hidden from the main list of submissions and can not be edited. {% endblocktrans %} diff --git a/hypha/apply/funds/templates/funds/includes/modal_unarchive_submission_confirm.html b/hypha/apply/funds/templates/funds/includes/modal_unarchive_submission_confirm.html index d0078b747a..128a02e0c7 100644 --- a/hypha/apply/funds/templates/funds/includes/modal_unarchive_submission_confirm.html +++ b/hypha/apply/funds/templates/funds/includes/modal_unarchive_submission_confirm.html @@ -13,7 +13,7 @@

aria-labelledby="tab-closed-rounds" id="panel-closed-rounds"> {% if page_type == 'dashboard' %} - {% include "funds/includes/no_round_block_dashboard.html" with rounds=closed_rounds display_text="Closed" query=closed_query type="Closed" %} + {% include "funds/includes/no_round_block_dashboard.html" with rounds=closed_rounds display_text=_("Closed") query=closed_query type=_("Closed") %} {% else %} - {% include "funds/includes/round-block-listing.html" with rounds=closed_rounds display_text="Closed" query=closed_query type="Closed" %} + {% include "funds/includes/round-block-listing.html" with rounds=closed_rounds display_text=_("Closed") query=closed_query type=_("Closed") %} {% endif %}

- \ No newline at end of file + diff --git a/hypha/apply/funds/templates/funds/includes/screening_status_block-button.html b/hypha/apply/funds/templates/funds/includes/screening_status_block-button.html index 6399b3c8af..c24a3f94fc 100644 --- a/hypha/apply/funds/templates/funds/includes/screening_status_block-button.html +++ b/hypha/apply/funds/templates/funds/includes/screening_status_block-button.html @@ -31,10 +31,10 @@ {% endif %} {% if current_status %} - hx-confirm='Are you sure you want to remove the "{{ current_status }}" screening decision?' - data-tippy-content="Remove" + hx-confirm='{% blocktrans %}Are you sure you want to remove the "{{ current_status }}" screening decision?{% endblocktrans %}' + data-tippy-content="{% trans 'Remove' %}" {% else %} - data-tippy-content="Mark as {{ default_status }}" + data-tippy-content="{% blocktrans %}Mark as {{ default_status }}{% endblocktrans %}" {% endif %} > {% if type == "yes" %} @@ -67,7 +67,7 @@ @keydown.up.prevent="$focus.wrap().previous()" >
- Options + {% trans "Options" %} @@ -104,7 +104,7 @@ hx-trigger="click" hx-target="#screening-status-{{ object.id }}" hx-vals='{"action": "clear"}' - >Remove Decision + >{% trans "Remove Decision" %} {% endif %}
diff --git a/hypha/apply/funds/templates/funds/includes/submission-list-row.html b/hypha/apply/funds/templates/funds/includes/submission-list-row.html index 9e8edb2e46..b2e8096ab5 100644 --- a/hypha/apply/funds/templates/funds/includes/submission-list-row.html +++ b/hypha/apply/funds/templates/funds/includes/submission-list-row.html @@ -30,7 +30,7 @@ {% heroicon_mini "hand-thumb-down" aria_hidden="true" size=21 class="inline -mt-1 align-text-bottom fill-red-400 stroke-1.5" data_tippy_placement='right' data_tippy_content=s.get_current_screening_status data_tippy_delay=200 %} {% endif %} {% else %} - {% heroicon_outline "question-mark-circle" aria_hidden="true" size=21 class="inline -mt-1 align-text-bottom stroke-slate-300 stroke-1.5" data_tippy_placement='right' data_tippy_content="Awaiting Screening" data_tippy_delay=200 %} + {% heroicon_outline "question-mark-circle" aria_hidden="true" size=21 class="inline -mt-1 align-text-bottom stroke-slate-300 stroke-1.5" data_tippy_placement='right' data_tippy_content=_("Awaiting Screening") data_tippy_delay=200 %} {% endif %} @@ -133,11 +133,11 @@ {% comment %} Lead {% endcomment %}
- {% comment %} Lead: {% endcomment %} + {% comment %} {% trans "Lead:" %} {% endcomment %} {% heroicon_micro "user-circle" aria_hidden="true" size=16 class="fill-fg-muted min-w-[16px]" %} diff --git a/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html b/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html index 7dd6a7bef9..e773144fb0 100644 --- a/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html +++ b/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html @@ -105,8 +105,8 @@

{{ heading }}

{% else %} - {% heroicon_micro "chevron-left" aria_label="Previous" slot="previous" aria_hidden=true size=18 %} - {% heroicon_micro "chevron-right" aria_label="Next" slot="next" aria_hidden=true size=18 %} + {% heroicon_micro "chevron-left" aria_label=_("Previous") slot="previous" aria_hidden=true size=18 %} + {% heroicon_micro "chevron-right" aria_label=_("Next") slot="next" aria_hidden=true size=18 %} diff --git a/hypha/apply/funds/templates/funds/modals/update_meta_terms_form.html b/hypha/apply/funds/templates/funds/modals/update_meta_terms_form.html index ae71aa50e8..8e5d05e91f 100644 --- a/hypha/apply/funds/templates/funds/modals/update_meta_terms_form.html +++ b/hypha/apply/funds/templates/funds/modals/update_meta_terms_form.html @@ -1,5 +1,5 @@ {% load i18n %} -Update tags +{% trans "Update tags" %}
{% include 'includes/dialog_form_base.html' with form=form value=value %} diff --git a/hypha/apply/funds/templates/funds/modals/update_partner_form.html b/hypha/apply/funds/templates/funds/modals/update_partner_form.html index d86fc80ab4..0817bb67c6 100644 --- a/hypha/apply/funds/templates/funds/modals/update_partner_form.html +++ b/hypha/apply/funds/templates/funds/modals/update_partner_form.html @@ -1,5 +1,5 @@ {% load i18n %} -Update partners +{% trans "Update partners" %}
{% include 'includes/dialog_form_base.html' with form=form value=value %} diff --git a/hypha/apply/funds/templates/funds/reminder_confirm_delete.html b/hypha/apply/funds/templates/funds/reminder_confirm_delete.html index f29c771a0a..cef4c7ed39 100644 --- a/hypha/apply/funds/templates/funds/reminder_confirm_delete.html +++ b/hypha/apply/funds/templates/funds/reminder_confirm_delete.html @@ -23,8 +23,8 @@

- Title: {{ object.title }}
- Type: {{ object.get_action_display }}
+ {% trans "Title:" %} {{ object.title }}
+ {% trans "Type:" %} {{ object.get_action_display }}

diff --git a/hypha/apply/funds/templates/funds/submission_sealed.html b/hypha/apply/funds/templates/funds/submission_sealed.html index 4ded12043c..7bf81e1df0 100644 --- a/hypha/apply/funds/templates/funds/submission_sealed.html +++ b/hypha/apply/funds/templates/funds/submission_sealed.html @@ -23,7 +23,7 @@

- Application Sealed + {% trans 'Application Sealed' %} {% trans "Go back" %}

diff --git a/hypha/apply/funds/templates/submissions/all.html b/hypha/apply/funds/templates/submissions/all.html index a594daf4a4..bd01d956f4 100644 --- a/hypha/apply/funds/templates/submissions/all.html +++ b/hypha/apply/funds/templates/submissions/all.html @@ -324,7 +324,7 @@ x-show="!showSelectedSubmissions" class="flex flex-wrap gap-2 items-center menu-filters" > - + {% heroicon_micro "chevron-left" aria_label="Previous" slot="previous" aria_hidden=true size=18 %} {% heroicon_micro "chevron-right" aria_label="Next" slot="next" aria_hidden=true size=18 %} @@ -332,7 +332,7 @@ - + {% heroicon_micro "chevron-left" aria_label="Previous" slot="previous" aria_hidden=true size=18 %} {% heroicon_micro "chevron-right" aria_label="Next" slot="next" aria_hidden=true size=18 %} @@ -340,7 +340,7 @@ - +
diff --git a/hypha/apply/funds/templates/funds/includes/rendered_answers.html b/hypha/apply/funds/templates/funds/includes/rendered_answers.html index 4c85c9409f..02cb7bff1f 100644 --- a/hypha/apply/funds/templates/funds/includes/rendered_answers.html +++ b/hypha/apply/funds/templates/funds/includes/rendered_answers.html @@ -5,7 +5,9 @@ {# For active translations #} diff --git a/hypha/apply/users/templates/users/login.html b/hypha/apply/users/templates/users/login.html index 55ade123a4..9fa1528483 100644 --- a/hypha/apply/users/templates/users/login.html +++ b/hypha/apply/users/templates/users/login.html @@ -10,22 +10,38 @@
{% if wizard.steps.current == 'token' %} {% if device.method == 'call' %} -

{% blocktrans trimmed %}We are calling your phone right now, please enter the - digits you hear.{% endblocktrans %}

+

+ {% blocktrans trimmed %} + We are calling your phone right now, please enter the digits you hear. + {% endblocktrans %} +

{% elif device.method == 'sms' %} -

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we - sent.{% endblocktrans %}

+

+ {% blocktrans trimmed %} + We sent you a text message, please enter the tokens we sent. + {% endblocktrans %} +

{% else %}

{% trans "Two factor verification" %}

-

{% blocktrans trimmed %}Please enter the 6-digit verification code generated by your Authenticator App.{% endblocktrans %}

+

+ {% blocktrans trimmed %} + Please enter the 6-digit verification code generated by your + Authenticator App. + {% endblocktrans %} +

{% endif %} {% elif wizard.steps.current == 'backup' %}

{% trans "Two factor verification" %}

- {% blocktrans trimmed %}Please enter one of the backup codes to log in to your account.{% endblocktrans %} + {% blocktrans trimmed %} + Please enter one of the backup codes to log in to your account. + {% endblocktrans %}

- {% blocktrans trimmed %}Those codes were generated for you during 2FA setup to print or keep safe in a password manager.{% endblocktrans %} + {% blocktrans trimmed %} + Those codes were generated for you during 2FA setup to print or + keep safe in a password manager. + {% endblocktrans %}

{% endif %} @@ -41,7 +57,11 @@

{% trans "Two factor verification" %}

} -

{% blocktrans %}Log in to {{ ORG_SHORT_NAME }}{% endblocktrans %}

+

+ {% blocktrans trimmed %} + Log in to {{ ORG_SHORT_NAME }} + {% endblocktrans %} +

{% for field in form %}
{% include "forms/includes/field.html" %} diff --git a/hypha/apply/users/templates/users/partials/passwordless_login_signup_sent.html b/hypha/apply/users/templates/users/partials/passwordless_login_signup_sent.html index bd4f75c4c7..69ee3e7a1a 100644 --- a/hypha/apply/users/templates/users/partials/passwordless_login_signup_sent.html +++ b/hypha/apply/users/templates/users/partials/passwordless_login_signup_sent.html @@ -19,7 +19,9 @@

- {% blocktrans %}Check your "Spam" folder, if you don't find the email in your inbox.{% endblocktrans %} + {% blocktrans trimmed %} + Check your "Spam" folder, if you don't find the email in your inbox. + {% endblocktrans %}

diff --git a/hypha/apply/users/templates/users/password_reset/done.html b/hypha/apply/users/templates/users/password_reset/done.html index f2993904f6..a113440c7f 100644 --- a/hypha/apply/users/templates/users/password_reset/done.html +++ b/hypha/apply/users/templates/users/password_reset/done.html @@ -12,10 +12,15 @@

{% trans "Check your inbox for a password reset email!" %}

- {% blocktrans %}We have sent an email to you with a password recovery link, open the link in the email to change your password.{% endblocktrans %} + {% blocktrans %} + We have sent an email to you with a password recovery link, + open the link in the email to change your password. + {% endblocktrans %}

- {% blocktrans %}Check your "Spam" folder, if you don't find the email in your inbox.{% endblocktrans %} + {% blocktrans trimmed %} + Check your "Spam" folder, if you don't find the email in your inbox. + {% endblocktrans %}

{% endblock %} diff --git a/hypha/templates/400.html b/hypha/templates/400.html index 8c74531b2c..291e22d17a 100644 --- a/hypha/templates/400.html +++ b/hypha/templates/400.html @@ -25,7 +25,13 @@

{% trans "Bad Request" %}

-

{% blocktranslate %}The request could not be understood by the server due to malformed syntax or invalid parameters. Please check your request and try again.{% endblocktranslate %}

+

+ {% blocktranslate trimmed %} + The request could not be understood by the server due to + malformed syntax or invalid parameters. + Please check your request and try again. + {% endblocktranslate %} +

diff --git a/hypha/templates/cotton/dropdown_menu.html b/hypha/templates/cotton/dropdown_menu.html index 0d9ad76b79..1e4b05aaf7 100644 --- a/hypha/templates/cotton/dropdown_menu.html +++ b/hypha/templates/cotton/dropdown_menu.html @@ -65,7 +65,11 @@ {% if enable_search %} - + Date: Fri, 27 Feb 2026 14:27:49 +0100 Subject: [PATCH 6/8] Added verbose_name and verbose_name_plural to all models --- hypha/apply/activity/models.py | 15 ++++ hypha/apply/categories/models.py | 5 ++ hypha/apply/determinations/models.py | 8 ++ hypha/apply/flags/models.py | 5 ++ hypha/apply/funds/models/__init__.py | 11 +++ .../funds/models/application_revisions.py | 3 + hypha/apply/funds/models/applications.py | 14 ++++ .../apply/funds/models/assigned_reviewers.py | 3 + hypha/apply/funds/models/co_applicants.py | 4 + hypha/apply/funds/models/forms.py | 76 +++++++++++++++++++ hypha/apply/funds/models/reminders.py | 2 + hypha/apply/funds/models/reviewer_role.py | 4 + hypha/apply/funds/models/submissions.py | 2 + hypha/apply/funds/models/utils.py | 4 + hypha/apply/projects/models/payment.py | 8 ++ hypha/apply/projects/models/project.py | 42 +++++++++- hypha/apply/projects/reports/models.py | 9 +++ hypha/apply/review/models.py | 8 ++ hypha/apply/todo/models.py | 3 + hypha/apply/users/models.py | 2 + hypha/home/models.py | 5 ++ hypha/images/models.py | 7 ++ 22 files changed, 238 insertions(+), 2 deletions(-) diff --git a/hypha/apply/activity/models.py b/hypha/apply/activity/models.py index c3d8cae455..32eddf1e83 100644 --- a/hypha/apply/activity/models.py +++ b/hypha/apply/activity/models.py @@ -13,6 +13,7 @@ from django.utils import timezone from django.utils.text import get_valid_filename from django.utils.translation import gettext as _ +from django.utils.translation import gettext_lazy from hypha.apply.utils.storage import PrivateStorage @@ -183,6 +184,10 @@ class ActivityAttachment(models.Model): upload_to=get_attachment_upload_path, storage=PrivateStorage() ) + class Meta: + verbose_name = gettext_lazy("activity attachment") + verbose_name_plural = gettext_lazy("activity attachments") + @property def filename(self): return os.path.basename(self.file.name) @@ -240,6 +245,8 @@ class Activity(models.Model): class Meta: ordering = ["-timestamp"] base_manager_name = "objects" + verbose_name = gettext_lazy("activity") + verbose_name_plural = gettext_lazy("activities") def get_absolute_url(self): # coverup for both submission and project as source. @@ -381,6 +388,10 @@ class Event(models.Model): object_id = models.PositiveIntegerField(blank=True, null=True) source = GenericForeignKey("content_type", "object_id") + class Meta: + verbose_name = gettext_lazy("event") + verbose_name_plural = gettext_lazy("events") + def __str__(self): if self.source and hasattr(self.source, "title"): return f"{self.by} {self.get_type_display()} - {self.source.title}" @@ -416,6 +427,10 @@ class Message(models.Model): sent_in_email_digest = models.BooleanField(default=False) objects = MessagesQueryset.as_manager() + class Meta: + verbose_name = gettext_lazy("message") + verbose_name_plural = gettext_lazy("messages") + def __str__(self): return f"[{self.type}][{self.status}] {self.content}" diff --git a/hypha/apply/categories/models.py b/hypha/apply/categories/models.py index ad97bf4ae6..c47c2d2f30 100644 --- a/hypha/apply/categories/models.py +++ b/hypha/apply/categories/models.py @@ -18,6 +18,10 @@ class Option(Orderable): value = models.CharField(max_length=255) category = ParentalKey("Category", related_name="options") + class Meta: + verbose_name = _("option") + verbose_name_plural = _("options") + def __str__(self): return self.value @@ -46,6 +50,7 @@ def __str__(self): return self.name class Meta: + verbose_name = _("Category") verbose_name_plural = _("Categories") diff --git a/hypha/apply/determinations/models.py b/hypha/apply/determinations/models.py index fe075f7666..b800d6ae7d 100644 --- a/hypha/apply/determinations/models.py +++ b/hypha/apply/determinations/models.py @@ -88,6 +88,10 @@ class DeterminationForm(DeterminationFormFieldsMixin, models.Model): FieldPanel("form_fields"), ] + class Meta: + verbose_name = _("determination form") + verbose_name_plural = _("determination forms") + def __str__(self): return self.name @@ -124,6 +128,10 @@ class Determination(DeterminationFormFieldsMixin, AccessFormData, models.Model): objects = DeterminationQuerySet.as_manager() + class Meta: + verbose_name = _("determination") + verbose_name_plural = _("determinations") + @property def stripped_message(self): return nh3.clean(self.message, tags=set()) diff --git a/hypha/apply/flags/models.py b/hypha/apply/flags/models.py index eff46186e3..d7175d1192 100644 --- a/hypha/apply/flags/models.py +++ b/hypha/apply/flags/models.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import gettext_lazy as _ +from django.utils.translation import pgettext_lazy class Flag(models.Model): @@ -25,3 +26,7 @@ class Flag(models.Model): settings.AUTH_USER_MODEL, on_delete=models.PROTECT, ) + + class Meta: + verbose_name = pgettext_lazy("computing", "flag") + verbose_name_plural = pgettext_lazy("computing", "flags") diff --git a/hypha/apply/funds/models/__init__.py b/hypha/apply/funds/models/__init__.py index 8a4bba9e17..d610dc7979 100644 --- a/hypha/apply/funds/models/__init__.py +++ b/hypha/apply/funds/models/__init__.py @@ -37,6 +37,7 @@ class FundType(ApplicationBase): class Meta: verbose_name = _("Fund") + verbose_name_plural = _("Funds") class RequestForPartners(ApplicationBase): @@ -44,15 +45,24 @@ class RequestForPartners(ApplicationBase): class Meta: verbose_name = _("RFP") + verbose_name_plural = _("RFPs") class Round(RoundBase): parent_page_types = ["funds.FundType", "funds.RequestForPartners"] + class Meta: + verbose_name = _("Round") + verbose_name_plural = _("Rounds") + class SealedRound(RoundBase): parent_page_types = ["funds.RequestForPartners"] + class Meta: + verbose_name = _("Sealed round") + verbose_name_plural = _("Sealed rounds") + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sealed = True @@ -61,3 +71,4 @@ def __init__(self, *args, **kwargs): class LabType(LabBase): class Meta: verbose_name = _("Lab") + verbose_name_plural = _("Labs") diff --git a/hypha/apply/funds/models/application_revisions.py b/hypha/apply/funds/models/application_revisions.py index 516f91eaed..03521d3004 100644 --- a/hypha/apply/funds/models/application_revisions.py +++ b/hypha/apply/funds/models/application_revisions.py @@ -1,6 +1,7 @@ from django.conf import settings from django.db import models from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from hypha.apply.stream_forms.files import StreamFieldDataEncoder from hypha.apply.stream_forms.models import BaseStreamForm @@ -27,6 +28,8 @@ class ApplicationRevision(BaseStreamForm, AccessFormData, models.Model): class Meta: ordering = ["-timestamp"] + verbose_name = _("application revision") + verbose_name_plural = _("application revisions") def __str__(self): return f"Revision for {self.submission.title_text_display} by {self.author} " diff --git a/hypha/apply/funds/models/applications.py b/hypha/apply/funds/models/applications.py index 2168715f94..115fb9c508 100644 --- a/hypha/apply/funds/models/applications.py +++ b/hypha/apply/funds/models/applications.py @@ -168,6 +168,10 @@ class ApplicationBase(EmailForm, WorkflowStreamForm, AsJsonMixin): # type: igno parent_page_types = ["apply_home.ApplyHomePage"] + class Meta: + verbose_name = _("application base") + verbose_name_plural = _("application bases") + def get_template(self, request, *args, **kwargs): # We want to force children to use our base template # template attribute is ignored by children @@ -345,6 +349,10 @@ def get_url(self, request: Optional[WSGIRequest] = None) -> Optional[str]: ] ) + class Meta: + verbose_name = _("round base") + verbose_name_plural = _("round bases") + def get_template(self, request, *args, **kwargs): # Make sure all children use the shared template return "funds/round.html" @@ -683,6 +691,10 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm, AsJsonMixin) ] ) + class Meta: + verbose_name = _("lab base") + verbose_name_plural = _("lab bases") + def get_submit_meta_data(self, **kwargs): return super().get_submit_meta_data( page=self, @@ -857,6 +869,8 @@ class RoundsAndLabs(Page): class Meta: proxy = True + verbose_name = _("round and lab") + verbose_name_plural = _("rounds and labs") def __eq__(self, other): # This is one way equality RoundAndLab == Round/Lab diff --git a/hypha/apply/funds/models/assigned_reviewers.py b/hypha/apply/funds/models/assigned_reviewers.py index 8dd0900d86..73bf42d4e9 100644 --- a/hypha/apply/funds/models/assigned_reviewers.py +++ b/hypha/apply/funds/models/assigned_reviewers.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import Group from django.db import models from django.db.models import F, Q +from django.utils.translation import gettext_lazy as _ from hypha.apply.review.options import AGREE, DISAGREE @@ -179,6 +180,8 @@ class AssignedReviewers(models.Model): class Meta: unique_together = (("submission", "role"), ("submission", "reviewer")) + verbose_name = _("assigned reviewer") + verbose_name_plural = _("assigned reviewers") def __hash__(self): return hash(self.pk) diff --git a/hypha/apply/funds/models/co_applicants.py b/hypha/apply/funds/models/co_applicants.py index f2672b5443..007c3674de 100644 --- a/hypha/apply/funds/models/co_applicants.py +++ b/hypha/apply/funds/models/co_applicants.py @@ -53,6 +53,8 @@ class CoApplicantInvite(models.Model): class Meta: unique_together = ("submission", "invited_user_email") + verbose_name = _("co-applicant invite") + verbose_name_plural = _("co-applicant invites") def __str__(self): return f"{self.invited_user_email} invited to {self.submission})" @@ -78,6 +80,8 @@ class CoApplicant(models.Model): class Meta: unique_together = ("submission", "user") + verbose_name = _("co-applicant") + verbose_name_plural = _("co-applicants") def __str__(self): return self.user.get_display_name() diff --git a/hypha/apply/funds/models/forms.py b/hypha/apply/funds/models/forms.py index b3eb5b5627..c392861c85 100644 --- a/hypha/apply/funds/models/forms.py +++ b/hypha/apply/funds/models/forms.py @@ -21,6 +21,10 @@ class ApplicationForm(models.Model): FieldPanel("form_fields"), ] + class Meta: + verbose_name = _("application form") + verbose_name_plural = _("applications forms") + def __str__(self): return self.name @@ -69,14 +73,26 @@ def __str__(self): class ApplicationBaseForm(AbstractRelatedForm): application = ParentalKey("ApplicationBase", related_name="forms") + class Meta: + verbose_name = _("application base form") + verbose_name_plural = _("application base forms") + class RoundBaseForm(AbstractRelatedForm): round = ParentalKey("RoundBase", related_name="forms") + class Meta: + verbose_name = _("round base form") + verbose_name_plural = _("round base forms") + class LabBaseForm(AbstractRelatedForm): lab = ParentalKey("LabBase", related_name="forms") + class Meta: + verbose_name = _("lab base form") + verbose_name_plural = _("lab base forms") + class AbstractRelatedDeterminationForm(Orderable): class Meta(Orderable.Meta): @@ -159,38 +175,74 @@ def __str__(self): class ApplicationBaseReviewForm(AbstractRelatedReviewForm): application = ParentalKey("ApplicationBase", related_name="review_forms") + class Meta: + verbose_name = _("application base review form") + verbose_name_plural = _("application base review forms") + class ApplicationBaseExternalReviewForm(AbstractRelatedReviewForm): application = ParentalKey("ApplicationBase", related_name="external_review_forms") + class Meta: + verbose_name = _("application base external review form") + verbose_name_plural = _("application base external review form") + class RoundBaseReviewForm(AbstractRelatedReviewForm): round = ParentalKey("RoundBase", related_name="review_forms") + class Meta: + verbose_name = _("round base review form") + verbose_name_plural = _("round base review forms") + class RoundBaseExternalReviewForm(AbstractRelatedReviewForm): round = ParentalKey("RoundBase", related_name="external_review_forms") + class Meta: + verbose_name = _("round base externa review form") + verbose_name_plural = _("round base external review forms") + class LabBaseReviewForm(AbstractRelatedReviewForm): lab = ParentalKey("LabBase", related_name="review_forms") + class Meta: + verbose_name = _("lab base review form") + verbose_name_plural = _("lab base review forms") + class LabBaseExternalReviewForm(AbstractRelatedReviewForm): lab = ParentalKey("LabBase", related_name="external_review_forms") + class Meta: + verbose_name = _("lab base external review form") + verbose_name_plural = _("lab base external review forms") + class ApplicationBaseDeterminationForm(AbstractRelatedDeterminationForm): application = ParentalKey("ApplicationBase", related_name="determination_forms") + class Meta: + verbose_name = _("application base determination form") + verbose_name_plural = _("application base determination forms") + class RoundBaseDeterminationForm(AbstractRelatedDeterminationForm): round = ParentalKey("RoundBase", related_name="determination_forms") + class Meta: + verbose_name = _("round base determination form") + verbose_name_plural = _("round base determination forms") + class LabBaseDeterminationForm(AbstractRelatedDeterminationForm): lab = ParentalKey("LabBase", related_name="determination_forms") + class Meta: + verbose_name = _("lab base determination form") + verbose_name_plural = _("lab base determination forms") + class AbstractRelatedProjectForm(Orderable): class Meta(Orderable.Meta): @@ -259,18 +311,34 @@ def __str__(self): class ApplicationBaseProjectForm(AbstractRelatedProjectForm): application = ParentalKey("ApplicationBase", related_name="approval_forms") + class Meta: + verbose_name = _("application base project form") + verbose_name_plural = _("application base project forms") + class ApplicationBaseProjectSOWForm(AbstractRelatedProjectSOWForm): application = ParentalKey("ApplicationBase", related_name="sow_forms") + class Meta: + verbose_name = _("application base project SOW form") + verbose_name_plural = _("application base project SOW forms") + class LabBaseProjectForm(AbstractRelatedProjectForm): lab = ParentalKey("LabBase", related_name="approval_forms") + class Meta: + verbose_name = _("lab base project form") + verbose_name_plural = _("lab base project forms") + class LabBaseProjectSOWForm(AbstractRelatedProjectSOWForm): lab = ParentalKey("LabBase", related_name="sow_forms") + class Meta: + verbose_name = _("lab base project SOW form") + verbose_name_plural = _("lab base project SOW forms") + class AbstractRelatedProjectReportForm(Orderable): class Meta(Orderable.Meta): @@ -304,6 +372,14 @@ def __str__(self): class ApplicationBaseProjectReportForm(AbstractRelatedProjectReportForm): application = ParentalKey("ApplicationBase", related_name="report_forms") + class Meta: + verbose_name = _("application base project report form") + verbose_name_plural = _("application base project report forms") + class LabBaseProjectReportForm(AbstractRelatedProjectReportForm): lab = ParentalKey("LabBase", related_name="report_forms") + + class Meta: + verbose_name = _("lab base project report form") + verbose_name_plural = _("lab base project report forms") diff --git a/hypha/apply/funds/models/reminders.py b/hypha/apply/funds/models/reminders.py index 8eef4dfbf6..11b10931c6 100644 --- a/hypha/apply/funds/models/reminders.py +++ b/hypha/apply/funds/models/reminders.py @@ -44,6 +44,8 @@ def __str__(self): class Meta: ordering = ["-time"] + verbose_name = _("reminder") + verbose_name_plural = _("reminders") def clean(self): if self.title == "": diff --git a/hypha/apply/funds/models/reviewer_role.py b/hypha/apply/funds/models/reviewer_role.py index 49ffe1e63a..feb1733a06 100644 --- a/hypha/apply/funds/models/reviewer_role.py +++ b/hypha/apply/funds/models/reviewer_role.py @@ -27,6 +27,10 @@ class ReviewerRole(models.Model): FieldPanel("order"), ] + class Meta: + verbose_name = _("reviewer role") + verbose_name_plural = _("reviewer roles") + def icon_url(self, filter_spec): return generate_image_url(self.icon, filter_spec) diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py index 01112ec0f2..ba35f5cc9b 100644 --- a/hypha/apply/funds/models/submissions.py +++ b/hypha/apply/funds/models/submissions.py @@ -536,6 +536,8 @@ class Meta: indexes = [ GinIndex(fields=["search_document"]), ] + verbose_name = _("application submission") + verbose_name_plural = _("application submissions") @property def is_draft(self): diff --git a/hypha/apply/funds/models/utils.py b/hypha/apply/funds/models/utils.py index fac4197614..73d8d30817 100644 --- a/hypha/apply/funds/models/utils.py +++ b/hypha/apply/funds/models/utils.py @@ -233,6 +233,10 @@ class SubmissionExportManager(models.Model): total_export = models.IntegerField(null=True) + class Meta: + verbose_name = _("submission export manager") + verbose_name_plural = _("submission export managers") + def set_completed_and_save(self) -> None: """Sets the status to completed and saves the object""" self.status = "success" diff --git a/hypha/apply/projects/models/payment.py b/hypha/apply/projects/models/payment.py index ca332e2c32..db0c616b2f 100644 --- a/hypha/apply/projects/models/payment.py +++ b/hypha/apply/projects/models/payment.py @@ -139,6 +139,10 @@ class Invoice(models.Model): wagtail_reference_index_ignore = True + class Meta: + verbose_name = _("invoice") + verbose_name_plural = _("invoices") + def __str__(self): return _("Invoice requested for {project}").format(project=self.project) @@ -287,6 +291,10 @@ class SupportingDocument(models.Model): related_name="supporting_documents", ) + class Meta: + verbose_name = _("supporting document") + verbose_name_plural = _("supporting documents") + def __str__(self): return "{invoice}".format(invoice=self.invoice) + " -> " + self.document.name diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py index 012969b65d..1c0366fe52 100644 --- a/hypha/apply/projects/models/project.py +++ b/hypha/apply/projects/models/project.py @@ -28,7 +28,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.html import strip_tags -from django.utils.translation import gettext +from django.utils.translation import gettext, pgettext_lazy from django.utils.translation import gettext_lazy as _ from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.models import ClusterableModel @@ -294,6 +294,10 @@ class Project(BaseStreamForm, AccessFormData, models.Model): wagtail_reference_index_ignore = True + class Meta: + verbose_name = _("project") + verbose_name_plural = _("projects") + def __str__(self): return self.title @@ -505,6 +509,11 @@ class ProjectSOW(BaseStreamForm, AccessFormData, models.Model): form_data = models.JSONField(encoder=StreamFieldDataEncoder, default=dict) form_fields = StreamField(ProjectFormCustomFormFieldsBlock(), null=True) + class Meta: + # Translators: SOW = Statement of Work + verbose_name = pgettext_lazy("singular", "project SOW") + verbose_name_plural = pgettext_lazy("plural", "project SOW") + class ProjectBaseStreamForm(BaseStreamForm, models.Model): name = models.CharField(max_length=255) @@ -525,11 +534,15 @@ def __str__(self): class ProjectForm(ProjectBaseStreamForm): class Meta: db_table = "project_form" + verbose_name = _("project form") + verbose_name_plural = _("project forms") class ProjectSOWForm(ProjectBaseStreamForm): class Meta: db_table = "project_sow_form" + verbose_name = _("project SOW form") + verbose_name_plural = _("project SOW forms") class ProjectReportForm(ProjectBaseStreamForm): @@ -540,7 +553,9 @@ class ProjectReportForm(ProjectBaseStreamForm): See Also ReportVersion where the fields from the form get copied and the response data gets filled in. """ - pass + class Meta: + verbose_name = _("project report form") + verbose_name_plural = _("project report forms") class PAFReviewersRole(Orderable, ClusterableModel): @@ -560,6 +575,10 @@ class PAFReviewersRole(Orderable, ClusterableModel): FieldPanel("user_roles", widget=forms.CheckboxSelectMultiple), ] + class Meta: + verbose_name = _("PAF reviewers role") + verbose_name_plural = _("PAF reviewers roles") + def __str__(self): return str(self.label) @@ -583,6 +602,10 @@ class FrequencyRelation(models.TextChoices): FieldPanel("relation", heading=_("Relation to report due date")), ] + class Meta: + verbose_name = _("project reminder frequency") + verbose_name_plural = _("project reminder frequencies") + @register_setting class ProjectSettings(BaseSiteSetting, ClusterableModel): @@ -625,6 +648,9 @@ class ProjectSettings(BaseSiteSetting, ClusterableModel): ), ] + class Meta: + verbose_name = _("project settings") + class PAFApprovals(models.Model): project = models.ForeignKey( @@ -648,6 +674,8 @@ class PAFApprovals(models.Model): class Meta: unique_together = ["project", "paf_reviewer_role"] ordering = ["paf_reviewer_role__sort_order"] + verbose_name = _("PAF approval") + verbose_name_plural = _("PAF approvals") def __str__(self): return _("Approval of {project} by {user}").format( @@ -690,6 +718,10 @@ class Contract(models.Model): objects = ContractQuerySet.as_manager() + class Meta: + verbose_name = _("contract") + verbose_name_plural = _("contracts") + def save(self, *args, **kwargs): self.updated_at = timezone.now() return super().save(*args, **kwargs) @@ -729,6 +761,8 @@ def __str__(self): class Meta: ordering = ("-created_at",) + verbose_name = _("packet files") + verbose_name_plural = _("packet files") @receiver(post_delete, sender=PacketFile) @@ -754,6 +788,10 @@ class ContractPacketFile(models.Model): ) created_at = models.DateField(auto_now_add=True, null=True) + class Meta: + verbose_name = _("contract packet file") + verbose_name_plural = _("contract packet files") + def __str__(self): return _("Contract file: {title}").format(title=self.title) diff --git a/hypha/apply/projects/reports/models.py b/hypha/apply/projects/reports/models.py index 8b2f25a1d4..9ac4c372ef 100644 --- a/hypha/apply/projects/reports/models.py +++ b/hypha/apply/projects/reports/models.py @@ -12,6 +12,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ +from django.utils.translation import pgettext_lazy from wagtail.fields import StreamField from hypha.apply.funds.models.mixins import AccessFormData @@ -120,6 +121,8 @@ class Report(BaseStreamForm, AccessFormData, models.Model): class Meta: ordering = ("-end_date",) db_table = "application_projects_report" + verbose_name = pgettext_lazy("project report (noun)", "report") + verbose_name_plural = pgettext_lazy("project report (noun)", "reports") def get_absolute_url(self): return reverse("apply:projects:reports:detail", kwargs={"pk": self.pk}) @@ -198,6 +201,8 @@ class ReportVersion(BaseStreamForm, AccessFormData, models.Model): class Meta: db_table = "application_projects_reportversion" + verbose_name = _("report version") + verbose_name_plural = _("report versions") @property def form_fields(self): @@ -212,6 +217,8 @@ class ReportPrivateFiles(models.Model): class Meta: db_table = "application_projects_reportprivatefiles" + verbose_name = _("report private file") + verbose_name_plural = _("report private files") @property def filename(self): @@ -252,6 +259,8 @@ class ReportConfig(models.Model): class Meta: db_table = "application_projects_reportconfig" + verbose_name = _("report config") + verbose_name_plural = _("report configs") def get_frequency_display(self): if self.disable_reporting: diff --git a/hypha/apply/review/models.py b/hypha/apply/review/models.py index ac4c9b2d61..4cb4a7515e 100644 --- a/hypha/apply/review/models.py +++ b/hypha/apply/review/models.py @@ -87,6 +87,10 @@ class ReviewForm(ReviewFormFieldsMixin, models.Model): FieldPanel("form_fields"), ] + class Meta: + verbose_name = _("review form") + verbose_name_plural = _("review forms") + def __str__(self): return self.name @@ -185,6 +189,8 @@ class Review(ReviewFormFieldsMixin, BaseStreamForm, AccessFormData, models.Model class Meta: unique_together = ("author", "submission") + verbose_name = _("review") + verbose_name_plural = _("reviews") @property def outcome(self): @@ -244,6 +250,8 @@ class ReviewOpinion(models.Model): class Meta: unique_together = ("author", "review") + verbose_name = _("review opinion") + verbose_name_plural = _("review opinions") def __str__(self): return f"Review Opinion for {self.review}" diff --git a/hypha/apply/todo/models.py b/hypha/apply/todo/models.py index f9dab5ded9..eaf0e0a9e2 100644 --- a/hypha/apply/todo/models.py +++ b/hypha/apply/todo/models.py @@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models +from django.utils.translation import gettext_lazy as _ from hypha.apply.users.models import User @@ -31,6 +32,8 @@ class Task(models.Model): class Meta: ordering = ("-created_at",) + verbose_name = _("task") + verbose_name_plural = _("tasks") def save(self, **kwargs): return super().save(**kwargs) diff --git a/hypha/apply/users/models.py b/hypha/apply/users/models.py index bfe3d8c314..bb15796145 100644 --- a/hypha/apply/users/models.py +++ b/hypha/apply/users/models.py @@ -328,6 +328,8 @@ def get_absolute_url(self): class Meta: ordering = ("full_name", "email") + verbose_name = _("user") + verbose_name_plural = _("users") def __repr__(self): return f"<{self.__class__.__name__}: {self.full_name} ({self.email})>" diff --git a/hypha/home/models.py b/hypha/home/models.py index c415cb37cc..7372f73637 100644 --- a/hypha/home/models.py +++ b/hypha/home/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ from wagtail.models import Page @@ -8,3 +9,7 @@ class ApplyHomePage(Page): subpage_types = ["funds.FundType", "funds.LabType", "funds.RequestForPartners"] strapline = models.CharField(blank=True, max_length=255) + + class Meta: + verbose_name = _("Apply home page") + verbose_name_plural = _("Apply home page") diff --git a/hypha/images/models.py b/hypha/images/models.py index a3e71bbd3e..4db68aebf7 100644 --- a/hypha/images/models.py +++ b/hypha/images/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ from wagtail.images.models import AbstractImage, AbstractRendition, Image @@ -16,6 +17,10 @@ class CustomImage(AbstractImage): "credit", ) + class Meta: + verbose_name = _("custom image") + verbose_name_plural = _("custom images") + # When you save the image, check if alt text has been set. If not, set it as the title. def save(self, *args, **kwargs): if not self.alt: @@ -31,3 +36,5 @@ class Rendition(AbstractRendition): class Meta: unique_together = (("image", "filter_spec", "focal_point_key"),) + verbose_name = _("rendition") + verbose_name_plural = _("renditions") From 88b34e46ed8971c90e80165166a0d65848fb88de Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Tue, 3 Mar 2026 15:59:59 +0100 Subject: [PATCH 7/8] Add missing migrations after model verbose_name update --- .../0089_alter_activity_options_and_more.py | 36 +++ ...r_category_options_alter_option_options.py | 20 ++ ...19_alter_determination_options_and_more.py | 26 ++ .../migrations/0002_alter_flag_options.py | 16 ++ ..._alter_applicationbase_options_and_more.py | 251 ++++++++++++++++++ .../0103_alter_contract_options_and_more.py | 116 ++++++++ ...ons_alter_reportconfig_options_and_more.py | 41 +++ ...tions_alter_reviewform_options_and_more.py | 30 +++ .../migrations/0009_alter_task_options.py | 20 ++ ...ter_confirmaccesstoken_options_and_more.py | 36 +++ .../0003_alter_applyhomepage_options.py | 19 ++ ...omimage_options_alter_rendition_options.py | 23 ++ 12 files changed, 634 insertions(+) create mode 100644 hypha/apply/activity/migrations/0089_alter_activity_options_and_more.py create mode 100644 hypha/apply/categories/migrations/0008_alter_category_options_alter_option_options.py create mode 100644 hypha/apply/determinations/migrations/0019_alter_determination_options_and_more.py create mode 100644 hypha/apply/flags/migrations/0002_alter_flag_options.py create mode 100644 hypha/apply/funds/migrations/0132_alter_applicationbase_options_and_more.py create mode 100644 hypha/apply/projects/migrations/0103_alter_contract_options_and_more.py create mode 100644 hypha/apply/projects/reports/migrations/0003_alter_report_options_alter_reportconfig_options_and_more.py create mode 100644 hypha/apply/review/migrations/0028_alter_review_options_alter_reviewform_options_and_more.py create mode 100644 hypha/apply/todo/migrations/0009_alter_task_options.py create mode 100644 hypha/apply/users/migrations/0028_alter_confirmaccesstoken_options_and_more.py create mode 100644 hypha/home/migrations/0003_alter_applyhomepage_options.py create mode 100644 hypha/images/migrations/0009_alter_customimage_options_alter_rendition_options.py diff --git a/hypha/apply/activity/migrations/0089_alter_activity_options_and_more.py b/hypha/apply/activity/migrations/0089_alter_activity_options_and_more.py new file mode 100644 index 0000000000..63bdcca4ca --- /dev/null +++ b/hypha/apply/activity/migrations/0089_alter_activity_options_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("activity", "0088_activity_deleted"), + ] + + operations = [ + migrations.AlterModelOptions( + name="activity", + options={ + "base_manager_name": "objects", + "ordering": ["-timestamp"], + "verbose_name": "activity", + "verbose_name_plural": "activities", + }, + ), + migrations.AlterModelOptions( + name="activityattachment", + options={ + "verbose_name": "activity attachment", + "verbose_name_plural": "activity attachments", + }, + ), + migrations.AlterModelOptions( + name="event", + options={"verbose_name": "event", "verbose_name_plural": "events"}, + ), + migrations.AlterModelOptions( + name="message", + options={"verbose_name": "message", "verbose_name_plural": "messages"}, + ), + ] diff --git a/hypha/apply/categories/migrations/0008_alter_category_options_alter_option_options.py b/hypha/apply/categories/migrations/0008_alter_category_options_alter_option_options.py new file mode 100644 index 0000000000..ffed0f045f --- /dev/null +++ b/hypha/apply/categories/migrations/0008_alter_category_options_alter_option_options.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("categories", "0007_rename_meta_terms_to_tags"), + ] + + operations = [ + migrations.AlterModelOptions( + name="category", + options={"verbose_name": "Category", "verbose_name_plural": "Categories"}, + ), + migrations.AlterModelOptions( + name="option", + options={"verbose_name": "option", "verbose_name_plural": "options"}, + ), + ] diff --git a/hypha/apply/determinations/migrations/0019_alter_determination_options_and_more.py b/hypha/apply/determinations/migrations/0019_alter_determination_options_and_more.py new file mode 100644 index 0000000000..1e560beea5 --- /dev/null +++ b/hypha/apply/determinations/migrations/0019_alter_determination_options_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("determinations", "0018_help_text_rich_text"), + ] + + operations = [ + migrations.AlterModelOptions( + name="determination", + options={ + "verbose_name": "determination", + "verbose_name_plural": "determinations", + }, + ), + migrations.AlterModelOptions( + name="determinationform", + options={ + "verbose_name": "determination form", + "verbose_name_plural": "determination forms", + }, + ), + ] diff --git a/hypha/apply/flags/migrations/0002_alter_flag_options.py b/hypha/apply/flags/migrations/0002_alter_flag_options.py new file mode 100644 index 0000000000..51980878eb --- /dev/null +++ b/hypha/apply/flags/migrations/0002_alter_flag_options.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("flags", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="flag", + options={"verbose_name": "flag", "verbose_name_plural": "flags"}, + ), + ] diff --git a/hypha/apply/funds/migrations/0132_alter_applicationbase_options_and_more.py b/hypha/apply/funds/migrations/0132_alter_applicationbase_options_and_more.py new file mode 100644 index 0000000000..77235c26f7 --- /dev/null +++ b/hypha/apply/funds/migrations/0132_alter_applicationbase_options_and_more.py @@ -0,0 +1,251 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("funds", "0131_delete_orphaned_attachments"), + ] + + operations = [ + migrations.AlterModelOptions( + name="applicationbase", + options={ + "verbose_name": "application base", + "verbose_name_plural": "application bases", + }, + ), + migrations.AlterModelOptions( + name="applicationbasedeterminationform", + options={ + "verbose_name": "application base determination form", + "verbose_name_plural": "application base determination forms", + }, + ), + migrations.AlterModelOptions( + name="applicationbaseexternalreviewform", + options={ + "verbose_name": "application base external review form", + "verbose_name_plural": "application base external review form", + }, + ), + migrations.AlterModelOptions( + name="applicationbaseform", + options={ + "verbose_name": "application base form", + "verbose_name_plural": "application base forms", + }, + ), + migrations.AlterModelOptions( + name="applicationbaseprojectform", + options={ + "verbose_name": "application base project form", + "verbose_name_plural": "application base project forms", + }, + ), + migrations.AlterModelOptions( + name="applicationbaseprojectreportform", + options={ + "verbose_name": "application base project report form", + "verbose_name_plural": "application base project report forms", + }, + ), + migrations.AlterModelOptions( + name="applicationbaseprojectsowform", + options={ + "verbose_name": "application base project SOW form", + "verbose_name_plural": "application base project SOW forms", + }, + ), + migrations.AlterModelOptions( + name="applicationbasereviewform", + options={ + "verbose_name": "application base review form", + "verbose_name_plural": "application base review forms", + }, + ), + migrations.AlterModelOptions( + name="applicationform", + options={ + "verbose_name": "application form", + "verbose_name_plural": "applications forms", + }, + ), + migrations.AlterModelOptions( + name="applicationrevision", + options={ + "ordering": ["-timestamp"], + "verbose_name": "application revision", + "verbose_name_plural": "application revisions", + }, + ), + migrations.AlterModelOptions( + name="applicationsubmission", + options={ + "verbose_name": "application submission", + "verbose_name_plural": "application submissions", + }, + ), + migrations.AlterModelOptions( + name="assignedreviewers", + options={ + "verbose_name": "assigned reviewer", + "verbose_name_plural": "assigned reviewers", + }, + ), + migrations.AlterModelOptions( + name="coapplicant", + options={ + "verbose_name": "co-applicant", + "verbose_name_plural": "co-applicants", + }, + ), + migrations.AlterModelOptions( + name="coapplicantinvite", + options={ + "verbose_name": "co-applicant invite", + "verbose_name_plural": "co-applicant invites", + }, + ), + migrations.AlterModelOptions( + name="fundtype", + options={"verbose_name": "Fund", "verbose_name_plural": "Funds"}, + ), + migrations.AlterModelOptions( + name="labbase", + options={"verbose_name": "lab base", "verbose_name_plural": "lab bases"}, + ), + migrations.AlterModelOptions( + name="labbasedeterminationform", + options={ + "verbose_name": "lab base determination form", + "verbose_name_plural": "lab base determination forms", + }, + ), + migrations.AlterModelOptions( + name="labbaseexternalreviewform", + options={ + "verbose_name": "lab base external review form", + "verbose_name_plural": "lab base external review forms", + }, + ), + migrations.AlterModelOptions( + name="labbaseform", + options={ + "verbose_name": "lab base form", + "verbose_name_plural": "lab base forms", + }, + ), + migrations.AlterModelOptions( + name="labbaseprojectform", + options={ + "verbose_name": "lab base project form", + "verbose_name_plural": "lab base project forms", + }, + ), + migrations.AlterModelOptions( + name="labbaseprojectreportform", + options={ + "verbose_name": "lab base project report form", + "verbose_name_plural": "lab base project report forms", + }, + ), + migrations.AlterModelOptions( + name="labbaseprojectsowform", + options={ + "verbose_name": "lab base project SOW form", + "verbose_name_plural": "lab base project SOW forms", + }, + ), + migrations.AlterModelOptions( + name="labbasereviewform", + options={ + "verbose_name": "lab base review form", + "verbose_name_plural": "lab base review forms", + }, + ), + migrations.AlterModelOptions( + name="labtype", + options={"verbose_name": "Lab", "verbose_name_plural": "Labs"}, + ), + migrations.AlterModelOptions( + name="reminder", + options={ + "ordering": ["-time"], + "verbose_name": "reminder", + "verbose_name_plural": "reminders", + }, + ), + migrations.AlterModelOptions( + name="requestforpartners", + options={"verbose_name": "RFP", "verbose_name_plural": "RFPs"}, + ), + migrations.AlterModelOptions( + name="reviewerrole", + options={ + "verbose_name": "reviewer role", + "verbose_name_plural": "reviewer roles", + }, + ), + migrations.AlterModelOptions( + name="round", + options={"verbose_name": "Round", "verbose_name_plural": "Rounds"}, + ), + migrations.AlterModelOptions( + name="roundbase", + options={ + "verbose_name": "round base", + "verbose_name_plural": "round bases", + }, + ), + migrations.AlterModelOptions( + name="roundbasedeterminationform", + options={ + "verbose_name": "round base determination form", + "verbose_name_plural": "round base determination forms", + }, + ), + migrations.AlterModelOptions( + name="roundbaseexternalreviewform", + options={ + "verbose_name": "round base externa review form", + "verbose_name_plural": "round base external review forms", + }, + ), + migrations.AlterModelOptions( + name="roundbaseform", + options={ + "verbose_name": "round base form", + "verbose_name_plural": "round base forms", + }, + ), + migrations.AlterModelOptions( + name="roundbasereviewform", + options={ + "verbose_name": "round base review form", + "verbose_name_plural": "round base review forms", + }, + ), + migrations.AlterModelOptions( + name="roundsandlabs", + options={ + "verbose_name": "round and lab", + "verbose_name_plural": "rounds and labs", + }, + ), + migrations.AlterModelOptions( + name="sealedround", + options={ + "verbose_name": "Sealed round", + "verbose_name_plural": "Sealed rounds", + }, + ), + migrations.AlterModelOptions( + name="submissionexportmanager", + options={ + "verbose_name": "submission export manager", + "verbose_name_plural": "submission export managers", + }, + ), + ] diff --git a/hypha/apply/projects/migrations/0103_alter_contract_options_and_more.py b/hypha/apply/projects/migrations/0103_alter_contract_options_and_more.py new file mode 100644 index 0000000000..0578c0120d --- /dev/null +++ b/hypha/apply/projects/migrations/0103_alter_contract_options_and_more.py @@ -0,0 +1,116 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("application_projects", "0102_alter_invoice_status"), + ] + + operations = [ + migrations.AlterModelOptions( + name="contract", + options={"verbose_name": "contract", "verbose_name_plural": "contracts"}, + ), + migrations.AlterModelOptions( + name="contractdocumentcategory", + options={ + "ordering": ("-required", "name"), + "verbose_name": "Contrat Document Category", + "verbose_name_plural": "Contract Document Categories", + }, + ), + migrations.AlterModelOptions( + name="contractpacketfile", + options={ + "verbose_name": "contract packet file", + "verbose_name_plural": "contract packet files", + }, + ), + migrations.AlterModelOptions( + name="documentcategory", + options={ + "ordering": ("-required", "name"), + "verbose_name": "Document Category", + "verbose_name_plural": "Project Document Categories", + }, + ), + migrations.AlterModelOptions( + name="invoice", + options={"verbose_name": "invoice", "verbose_name_plural": "invoices"}, + ), + migrations.AlterModelOptions( + name="packetfile", + options={ + "ordering": ("-created_at",), + "verbose_name": "packet files", + "verbose_name_plural": "packet files", + }, + ), + migrations.AlterModelOptions( + name="pafapprovals", + options={ + "ordering": ["paf_reviewer_role__sort_order"], + "verbose_name": "PAF approval", + "verbose_name_plural": "PAF approvals", + }, + ), + migrations.AlterModelOptions( + name="pafreviewersrole", + options={ + "verbose_name": "PAF reviewers role", + "verbose_name_plural": "PAF reviewers roles", + }, + ), + migrations.AlterModelOptions( + name="project", + options={"verbose_name": "project", "verbose_name_plural": "projects"}, + ), + migrations.AlterModelOptions( + name="projectform", + options={ + "verbose_name": "project form", + "verbose_name_plural": "project forms", + }, + ), + migrations.AlterModelOptions( + name="projectreminderfrequency", + options={ + "verbose_name": "project reminder frequency", + "verbose_name_plural": "project reminder frequencies", + }, + ), + migrations.AlterModelOptions( + name="projectreportform", + options={ + "verbose_name": "project report form", + "verbose_name_plural": "project report forms", + }, + ), + migrations.AlterModelOptions( + name="projectsettings", + options={"verbose_name": "project settings"}, + ), + migrations.AlterModelOptions( + name="projectsow", + options={ + "verbose_name": "project SOW", + "verbose_name_plural": "project SOW", + }, + ), + migrations.AlterModelOptions( + name="projectsowform", + options={ + "verbose_name": "project SOW form", + "verbose_name_plural": "project SOW forms", + }, + ), + migrations.AlterModelOptions( + name="supportingdocument", + options={ + "verbose_name": "supporting document", + "verbose_name_plural": "supporting documents", + }, + ), + ] diff --git a/hypha/apply/projects/reports/migrations/0003_alter_report_options_alter_reportconfig_options_and_more.py b/hypha/apply/projects/reports/migrations/0003_alter_report_options_alter_reportconfig_options_and_more.py new file mode 100644 index 0000000000..f815cd3116 --- /dev/null +++ b/hypha/apply/projects/reports/migrations/0003_alter_report_options_alter_reportconfig_options_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("project_reports", "0002_report_author"), + ] + + operations = [ + migrations.AlterModelOptions( + name="report", + options={ + "ordering": ("-end_date",), + "verbose_name": "report", + "verbose_name_plural": "reports", + }, + ), + migrations.AlterModelOptions( + name="reportconfig", + options={ + "verbose_name": "report config", + "verbose_name_plural": "report configs", + }, + ), + migrations.AlterModelOptions( + name="reportprivatefiles", + options={ + "verbose_name": "report private file", + "verbose_name_plural": "report private files", + }, + ), + migrations.AlterModelOptions( + name="reportversion", + options={ + "verbose_name": "report version", + "verbose_name_plural": "report versions", + }, + ), + ] diff --git a/hypha/apply/review/migrations/0028_alter_review_options_alter_reviewform_options_and_more.py b/hypha/apply/review/migrations/0028_alter_review_options_alter_reviewform_options_and_more.py new file mode 100644 index 0000000000..6d301b1327 --- /dev/null +++ b/hypha/apply/review/migrations/0028_alter_review_options_alter_reviewform_options_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("review", "0027_help_text_rich_text"), + ] + + operations = [ + migrations.AlterModelOptions( + name="review", + options={"verbose_name": "review", "verbose_name_plural": "reviews"}, + ), + migrations.AlterModelOptions( + name="reviewform", + options={ + "verbose_name": "review form", + "verbose_name_plural": "review forms", + }, + ), + migrations.AlterModelOptions( + name="reviewopinion", + options={ + "verbose_name": "review opinion", + "verbose_name_plural": "review opinions", + }, + ), + ] diff --git a/hypha/apply/todo/migrations/0009_alter_task_options.py b/hypha/apply/todo/migrations/0009_alter_task_options.py new file mode 100644 index 0000000000..4ff9ac7f26 --- /dev/null +++ b/hypha/apply/todo/migrations/0009_alter_task_options.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("todo", "0008_alter_task_code"), + ] + + operations = [ + migrations.AlterModelOptions( + name="task", + options={ + "ordering": ("-created_at",), + "verbose_name": "task", + "verbose_name_plural": "tasks", + }, + ), + ] diff --git a/hypha/apply/users/migrations/0028_alter_confirmaccesstoken_options_and_more.py b/hypha/apply/users/migrations/0028_alter_confirmaccesstoken_options_and_more.py new file mode 100644 index 0000000000..1376e26292 --- /dev/null +++ b/hypha/apply/users/migrations/0028_alter_confirmaccesstoken_options_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0027_remove_drupal_id_field"), + ] + + operations = [ + migrations.AlterModelOptions( + name="confirmaccesstoken", + options={ + "ordering": ("modified",), + "verbose_name": "Confirm Access Token", + "verbose_name_plural": "Confirm Access Tokens", + }, + ), + migrations.AlterModelOptions( + name="pendingsignup", + options={ + "ordering": ("created",), + "verbose_name": "Pending signup", + "verbose_name_plural": "Pending signups", + }, + ), + migrations.AlterModelOptions( + name="user", + options={ + "ordering": ("full_name", "email"), + "verbose_name": "user", + "verbose_name_plural": "users", + }, + ), + ] diff --git a/hypha/home/migrations/0003_alter_applyhomepage_options.py b/hypha/home/migrations/0003_alter_applyhomepage_options.py new file mode 100644 index 0000000000..87322e2c62 --- /dev/null +++ b/hypha/home/migrations/0003_alter_applyhomepage_options.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("apply_home", "0002_add_apply_homepage"), + ] + + operations = [ + migrations.AlterModelOptions( + name="applyhomepage", + options={ + "verbose_name": "Apply home page", + "verbose_name_plural": "Apply home page", + }, + ), + ] diff --git a/hypha/images/migrations/0009_alter_customimage_options_alter_rendition_options.py b/hypha/images/migrations/0009_alter_customimage_options_alter_rendition_options.py new file mode 100644 index 0000000000..cc04e7fe3d --- /dev/null +++ b/hypha/images/migrations/0009_alter_customimage_options_alter_rendition_options.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.11 on 2026-03-03 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("images", "0008_customimage_description"), + ] + + operations = [ + migrations.AlterModelOptions( + name="customimage", + options={ + "verbose_name": "custom image", + "verbose_name_plural": "custom images", + }, + ), + migrations.AlterModelOptions( + name="rendition", + options={"verbose_name": "rendition", "verbose_name_plural": "renditions"}, + ), + ] From e2bc666fa130b0134ce0b54b7b28330d993a5704 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Tue, 3 Mar 2026 15:18:26 +0100 Subject: [PATCH 8/8] Update django-nh3 to fix failing test --- pyproject.toml | 2 +- requirements/dev.txt | 6 +++--- requirements/prod.txt | 6 +++--- uv.lock | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aa2e468d18..229770d91a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "django-heroku~=0.3.1", "django-hijack~=3.7.6", "django-htmx~=1.27.0", - "django-nh3~=0.2.1", + "django-nh3==0.3.1", "django-pagedown~=2.2.1", "django-ratelimit~=4.1.0", "django-role-permissions~=3.2.0", diff --git a/requirements/dev.txt b/requirements/dev.txt index f0e55bbe3f..fc61c8e754 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -608,9 +608,9 @@ django-modelcluster==6.4.1 \ --hash=sha256:ccc190cd9e22c24900ea2410bff64d444d48f43f0f4aedeed0f6cd94e2536698 \ --hash=sha256:e736fcee925f83b63218dbf9c869ab50618b0f5e98869a5aa497f7a5331aa263 # via wagtail -django-nh3==0.2.1 \ - --hash=sha256:0f99cb6ea099d76a9e401cdc09ec10e001204562230bd5a3b0e6f27bccd69178 \ - --hash=sha256:de453061c16c12fa5ebfcc6d0691d35ca1802cc477632ed504a2f21ac878a4a8 +django-nh3==0.3.1 \ + --hash=sha256:5682981d5aeff0577ada7414a57e7a0fd1f59a2ca682c5408d0b197aec8d5d80 \ + --hash=sha256:858444153cc3073a741951fdf7b108785d2a267fc98dc1401b6e7206fc13e524 # via hypha django-otp==1.7.0 \ --hash=sha256:406d2d7f797dc313569270e06d6c360c7d986c9f653eab80b190d663ed5f1133 \ diff --git a/requirements/prod.txt b/requirements/prod.txt index 7771f82848..235e630ee8 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -450,9 +450,9 @@ django-modelcluster==6.4.1 \ --hash=sha256:ccc190cd9e22c24900ea2410bff64d444d48f43f0f4aedeed0f6cd94e2536698 \ --hash=sha256:e736fcee925f83b63218dbf9c869ab50618b0f5e98869a5aa497f7a5331aa263 # via wagtail -django-nh3==0.2.1 \ - --hash=sha256:0f99cb6ea099d76a9e401cdc09ec10e001204562230bd5a3b0e6f27bccd69178 \ - --hash=sha256:de453061c16c12fa5ebfcc6d0691d35ca1802cc477632ed504a2f21ac878a4a8 +django-nh3==0.3.1 \ + --hash=sha256:5682981d5aeff0577ada7414a57e7a0fd1f59a2ca682c5408d0b197aec8d5d80 \ + --hash=sha256:858444153cc3073a741951fdf7b108785d2a267fc98dc1401b6e7206fc13e524 # via hypha django-otp==1.7.0 \ --hash=sha256:406d2d7f797dc313569270e06d6c360c7d986c9f653eab80b190d663ed5f1133 \ diff --git a/uv.lock b/uv.lock index 2dfc32d809..1a2bb2e06b 100644 --- a/uv.lock +++ b/uv.lock @@ -1148,16 +1148,16 @@ wheels = [ [[package]] name = "django-nh3" -version = "0.2.1" +version = "0.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "nh3" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/82/8e5035eb6a11f8f6060669e5761f30548f31945bbf90346453cca2f16496/django_nh3-0.2.1.tar.gz", hash = "sha256:de453061c16c12fa5ebfcc6d0691d35ca1802cc477632ed504a2f21ac878a4a8", size = 13908, upload-time = "2025-11-09T23:50:58.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/89/8a38f9325e794de02b386e5596bc14921a67484a6c15c82946ff3a104d35/django_nh3-0.3.1.tar.gz", hash = "sha256:5682981d5aeff0577ada7414a57e7a0fd1f59a2ca682c5408d0b197aec8d5d80", size = 16144, upload-time = "2026-02-28T00:48:29.107Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/a4/485bcf6640d30aab65be85d7ca8b1dff9f19f5fbb8565ede77df27afd082/django_nh3-0.2.1-py3-none-any.whl", hash = "sha256:0f99cb6ea099d76a9e401cdc09ec10e001204562230bd5a3b0e6f27bccd69178", size = 9545, upload-time = "2025-11-09T23:50:57.516Z" }, + { url = "https://files.pythonhosted.org/packages/11/dd/65a4ff54541caec25cc794efa2835bfab8cd5fb12beb8f6349a6d238f370/django_nh3-0.3.1-py3-none-any.whl", hash = "sha256:858444153cc3073a741951fdf7b108785d2a267fc98dc1401b6e7206fc13e524", size = 11114, upload-time = "2026-02-28T00:48:27.982Z" }, ] [[package]] @@ -1857,7 +1857,7 @@ requires-dist = [ { name = "django-heroku", specifier = "~=0.3.1" }, { name = "django-hijack", specifier = "~=3.7.6" }, { name = "django-htmx", specifier = "~=1.27.0" }, - { name = "django-nh3", specifier = "~=0.2.1" }, + { name = "django-nh3", specifier = "==0.3.1" }, { name = "django-pagedown", specifier = "~=2.2.1" }, { name = "django-ratelimit", specifier = "~=4.1.0" }, { name = "django-role-permissions", specifier = "~=3.2.0" },