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/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/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/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/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/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/categories/models.py b/hypha/apply/categories/models.py index 53d0bda898..c47c2d2f30 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 @@ -17,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 @@ -45,7 +50,8 @@ def __str__(self): return self.name class Meta: - verbose_name_plural = "Categories" + verbose_name = _("Category") + verbose_name_plural = _("Categories") class MetaTerm(index.Indexed, MP_Node): @@ -106,13 +112,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 +126,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 +166,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 +180,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/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/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/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/determinations/models.py b/hypha/apply/determinations/models.py index 0c8cad47f9..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()) @@ -196,19 +204,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 +262,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 +346,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 +370,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/determinations/templates/determinations/batch_determination_form.html b/hypha/apply/determinations/templates/determinations/batch_determination_form.html index d4e8f61240..500d59e395 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 trimmed %} + 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/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/flags/models.py b/hypha/apply/flags/models.py index 8806afce58..d7175d1192 100644 --- a/hypha/apply/flags/models.py +++ b/hypha/apply/flags/models.py @@ -2,14 +2,16 @@ 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 django.utils.translation import pgettext_lazy 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() @@ -24,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/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/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/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/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 83610cc04e..115fb9c508 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 @@ -167,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 @@ -344,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" @@ -423,7 +432,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 +461,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 +498,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) @@ -676,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, @@ -850,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 @@ -881,7 +902,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 +913,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/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 721396c231..c392861c85 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 @@ -20,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 @@ -28,8 +33,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) @@ -68,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): @@ -158,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): @@ -258,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): @@ -303,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 44057f9802..11b10931c6 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} @@ -42,10 +44,12 @@ def __str__(self): class Meta: ordering = ["-time"] + verbose_name = _("reminder") + verbose_name_plural = _("reminders") 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..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) @@ -39,24 +43,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/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 8a17c45a5a..73d8d30817 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")), ] @@ -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/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/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/admin/parent_chooser.html b/hypha/apply/funds/templates/funds/admin/parent_chooser.html index 3b30146b74..f59a9f1ad4 100644 --- a/hypha/apply/funds/templates/funds/admin/parent_chooser.html +++ b/hypha/apply/funds/templates/funds/admin/parent_chooser.html @@ -15,7 +15,12 @@

{% blocktrans %}Choose a Fund{% endblocktrans %}

-

{% blocktrans %}Rounds must be associated with a Fund or RFP. What are you creating a new Round for?{% endblocktrans %}

+

+ {% blocktrans trimmed %} + Rounds must be associated with a Fund or RFP. + What are you creating a new Round for? + {% endblocktrans %} +

{% csrf_token %} diff --git a/hypha/apply/funds/templates/funds/application_base.html b/hypha/apply/funds/templates/funds/application_base.html index ac42620817..db751e8720 100644 --- a/hypha/apply/funds/templates/funds/application_base.html +++ b/hypha/apply/funds/templates/funds/application_base.html @@ -35,7 +35,9 @@

{{ page.title }}

{# the page has no open rounds and we arent on a round page #} {% verbose_name page as name %}

- {% blocktrans %}Sorry, this {{ name }} is not currently accepting applications.{% endblocktrans %} + {% blocktrans trimmed %} + Sorry, this {{ name }} is not currently accepting applications. + {% endblocktrans %} {% trans "See other funds" %} {% heroicon_mini "arrow-right" class="inline mb-0.5 align-text-bottom" %} 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/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html index 1149290e39..b801bf15d9 100644 --- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html +++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html @@ -59,9 +59,15 @@
    {% if request.user|has_edit_perm:object and object.status == 'draft_proposal' and not request.user.is_apply_staff %}

    {% trans "Congratulations!" %}

    -
    {% blocktrans with stage=object.previous.stage %}Your {{ stage }} application has been accepted.{% endblocktrans %}
    +
    + {% blocktrans with stage=object.previous.stage trimmed %} + Your {{ stage }} application has been accepted. + {% endblocktrans %} +
    - {% blocktrans with stage=object.stage %}Start your {{ stage }} application.{% endblocktrans %} + {% blocktrans with stage=object.stage trimmed %} + Start your {{ stage }} application. + {% endblocktrans %} {% else %} diff --git a/hypha/apply/funds/templates/funds/coapplicant_invite_landing_page.html b/hypha/apply/funds/templates/funds/coapplicant_invite_landing_page.html index 083b93dff3..febe157a3d 100644 --- a/hypha/apply/funds/templates/funds/coapplicant_invite_landing_page.html +++ b/hypha/apply/funds/templates/funds/coapplicant_invite_landing_page.html @@ -9,13 +9,16 @@

    {% trans "Accept invitation" %}

    -

    {% blocktranslate with title=invite.submission.title %} +

    {% blocktranslate with title=invite.submission.title trimmed %} You have been invited to join the application "{{ title }}" as a co-applicant. {% endblocktranslate %}

    {% if not request.user.is_authenticated %}

    - {% blocktrans with email=invite.invited_user_email %}If you accept this invitation, an account will be created using the email {{ email }} and you will be redirected to{% endblocktrans %} + {% blocktrans with email=invite.invited_user_email trimmed %} + If you accept this invitation, an account will be created using + the email {{ email }} and you will be redirected to + {% endblocktrans %} {% if two_factor_required %}{% trans "two-factor authentication and then to" %} {% endif %} {% trans "the application. If you wish to update your name/email, you can do it by visiting 'My Account' section." %}

    {% endif %} 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..f52cfa54f3 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,12 +8,15 @@ {% heroicon_outline "exclamation-triangle" class="w-6 h-6 text-error" stroke_width="1.5" aria_hidden="true" %}
    - +

    - {% blocktrans %} - Are you sure you want to archive submission? Archived submissions will be hidden from the - main list of submissions and can not be edited. + {% 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..e1fde70909 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,9 +13,10 @@
    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 #} @@ -34,10 +34,10 @@

    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-success.html b/hypha/apply/funds/templates/funds/submission-success.html index 8e7b9749d0..765f8a40e8 100644 --- a/hypha/apply/funds/templates/funds/submission-success.html +++ b/hypha/apply/funds/templates/funds/submission-success.html @@ -16,12 +16,16 @@

{% trans "Your application is saved as a draft." %}

{% endif %} {% else %} -

{% blocktrans %}Thank you for your submission to the {{ ORG_LONG_NAME }}.{% endblocktrans %}

+

+ {% blocktrans trimmed %} + Thank you for your submission to the {{ ORG_LONG_NAME }}. + {% endblocktrans %} +

{% trans "An e-mail with more information has been sent to the address you entered." %}

- {% blocktrans with email=ORG_EMAIL|urlize %} + {% blocktrans with email=ORG_EMAIL|urlize trimmed %} If you do not receive an e-mail within 15 minutes please check your spam folder and contact {{ email }} for further assistance. 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/funds/submissions_result.html b/hypha/apply/funds/templates/funds/submissions_result.html index 69b91b4323..e478aa016e 100644 --- a/hypha/apply/funds/templates/funds/submissions_result.html +++ b/hypha/apply/funds/templates/funds/submissions_result.html @@ -41,7 +41,10 @@ {% if not count_values == submission_count %} {% with submission_count|subtract:count_values as count_diff %}

- {% blocktrans %}* Total {{ submission_count }} but {{ count_diff }} submission(s) lack requested amount fields or data and are not included.{% endblocktrans %} + {% blocktrans trimmed %} + * Total {{ submission_count }} but {{ count_diff }} submission(s) + lack requested amount fields or data and are not included. + {% endblocktrans %}

{% endwith %} {% endif %} 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 @@ - +
    @@ -378,7 +378,7 @@ - +
      @@ -406,39 +406,39 @@ - + {% url "apply:submissions:submenu-funds" %}{% remove_from_query "only_query_string" "page" %} - + {% url "apply:submissions:submenu-rounds" %}{% remove_from_query "only_query_string" "page" %} {% if 'category_options' not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %} - + {% url "apply:submissions:submenu-category-options" %}{% remove_from_query "only_query_string" "page" %} {% endif %} {% if "tags" not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %} - + {% url "apply:submissions:submenu-meta-terms" %}{% remove_from_query "only_query_string" "page" %} {% endif %} {% if 'lead' not in SUBMISSIONS_TABLE_EXCLUDED_FIELDS %} - + {% url "apply:submissions:submenu-leads" %}{% remove_from_query "only_query_string" "page" %} {% endif %} {% if not request.user.is_reviewer %} - + {% url "apply:submissions:submenu-reviewers" %}{% remove_from_query "only_query_string" "page" %} {% endif %} - + {% for sort_option in sort_options %} - + {% url "apply:submissions:submenu-update-status" %} - + {% url "apply:submissions:submenu-bulk-update-lead" %}{% remove_from_query "only_query_string" "page" %} - + {% url "apply:submissions:submenu-bulk-update-reviewers" %}{% remove_from_query "only_query_string" "page" %} diff --git a/hypha/apply/funds/templates/submissions/partials/submission-reviews-list-multi.html b/hypha/apply/funds/templates/submissions/partials/submission-reviews-list-multi.html index 4936592524..696f1fb0c7 100644 --- a/hypha/apply/funds/templates/submissions/partials/submission-reviews-list-multi.html +++ b/hypha/apply/funds/templates/submissions/partials/submission-reviews-list-multi.html @@ -1,9 +1,9 @@ -{% load review_tags %} +{% load i18n review_tags %} {% for s in submissions %} - {{ s.review_submitted_count|default:'0' }} + {{ s.review_submitted_count|default:'0' }} / {% if s.stage.has_external_review %} diff --git a/hypha/apply/funds/templates/submissions/submenu/category.html b/hypha/apply/funds/templates/submissions/submenu/category.html index cc31f47ff5..c37a935eb6 100644 --- a/hypha/apply/funds/templates/submissions/submenu/category.html +++ b/hypha/apply/funds/templates/submissions/submenu/category.html @@ -18,11 +18,11 @@ {% if item.selected %} href="{% url "apply:submissions:list" %}{% remove_from_query "only_query_string" "page" category_options=item.id %}" hx-get="{% url "apply:submissions:list" %}{% remove_from_query "only_query_string" "page" category_options=item.id %}" - title="Remove {{ item.title }} from current filters" + title="{% blocktrans with item=item.title %}Remove {{ item }} from current filters{% endblocktrans %}" {% else %} href="{% url "apply:submissions:list" %}{% add_to_query "only_query_string" "page" category_options=item.id %}" hx-get="{% url "apply:submissions:list" %}{% add_to_query "only_query_string" "page" category_options=item.id %}" - title="Add {{ item.title }} to current filters" + title="{% blocktrans with item=item.title %}Add {{ item }} to current filters{% endblocktrans %}" {% endif %} hx-push-url="true" class="flex {% if item.selected %}ps-2 font-medium bg-base-200{% else %}ps-8{% endif %} pe-3 py-2 text-base-content/80 border-b items-center hover:bg-base-200 focus:bg-base-200"> diff --git a/hypha/apply/funds/templates/submissions/submenu/change-status.html b/hypha/apply/funds/templates/submissions/submenu/change-status.html index e2a1af26cb..7f38faac45 100644 --- a/hypha/apply/funds/templates/submissions/submenu/change-status.html +++ b/hypha/apply/funds/templates/submissions/submenu/change-status.html @@ -8,7 +8,7 @@ hx-vals='{"action": "{{ slug }}"}' hx-include="[name=selectedSubmissionIds]" hx-confirm='{% blocktrans %}Are you sure you want to change the status of the selected submissions to "{{ value }}"?{% endblocktrans %}' - title="Change status to {{ value }}" + title="{% blocktrans %}Change status to {{ value }}{% endblocktrans %}" class="flex items-center py-2 cursor-pointer text-base-content/80 ps-4 pe-3 hover:bg-base-200 focus:bg-base-200"> {{ value }} diff --git a/hypha/apply/funds/templates/submissions/submenu/meta-terms.html b/hypha/apply/funds/templates/submissions/submenu/meta-terms.html index 50cc21e777..45b38bb176 100644 --- a/hypha/apply/funds/templates/submissions/submenu/meta-terms.html +++ b/hypha/apply/funds/templates/submissions/submenu/meta-terms.html @@ -17,11 +17,11 @@ {% if meta_term.selected %} href="{% url "apply:submissions:list" %}{% remove_from_query "only_query_string" "page" meta_terms=meta_term.id %}" hx-get="{% url "apply:submissions:list" %}{% remove_from_query "only_query_string" "page" meta_terms=meta_term.id %}" - title="Remove {{ meta_term.title }} from current filters" + title="{% blocktrans with term=meta_term.title %}Remove {{ term }} from current filters{% endblocktrans %}" {% else %} href="{% url "apply:submissions:list" %}{% add_to_query "only_query_string" "page" meta_terms=meta_term.id %}" hx-get="{% url "apply:submissions:list" %}{% add_to_query "only_query_string" "page" meta_terms=meta_term.id %}" - title="Add {{ meta_term.title }} to current filters" + title="{% blocktrans with term=meta_term.title %}Add {{ term }} to current filters{% endblocktrans %}" {% endif %} hx-push-url="true" class="flex {% if meta_term.selected %}ps-2 font-medium bg-base-200{% else %}ps-8{% endif %} pe-3 py-2 text-base-content/80 items-center hover:bg-base-200 focus:bg-base-200"> diff --git a/hypha/apply/funds/templates/submissions/submenu/reviewers.html b/hypha/apply/funds/templates/submissions/submenu/reviewers.html index 8d92323024..c634a19f1f 100644 --- a/hypha/apply/funds/templates/submissions/submenu/reviewers.html +++ b/hypha/apply/funds/templates/submissions/submenu/reviewers.html @@ -17,11 +17,11 @@ {% if user.selected %} href="{% url "apply:submissions:list" %}{% remove_from_query "only_query_string" "page" reviewers=user.id %}" hx-get="{% url "apply:submissions:list" %}{% remove_from_query "only_query_string" "page" reviewers=user.id %}" - title="Remove {{ user.title }} from current filters" + title="{% blocktrans with user=user.title %}Remove {{ user }} from current filters{% endblocktrans %}" {% else %} href="{% url "apply:submissions:list" %}{% add_to_query "only_query_string" "page" reviewers=user.id %}" hx-get="{% url "apply:submissions:list" %}{% add_to_query "only_query_string" "page" reviewers=user.id %}" - title="Add {{ user.title }} to current filters" + title="{% blocktrans with user=user.title %}Add {{ user }} to current filters{% endblocktrans %}" {% endif %} hx-push-url="true" class="flex {% if user.selected %}ps-2 font-medium bg-base-200{% else %}ps-8{% endif %} pe-3 py-2 text-base-content/80 items-center hover:bg-base-200 focus:bg-base-200"> 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/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/models/payment.py b/hypha/apply/projects/models/payment.py index 50cb7b46dd..db0c616b2f 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( @@ -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 cea5d5d10e..1c0366fe52 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, pgettext_lazy 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,22 +278,26 @@ 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() wagtail_reference_index_ignore = True + class Meta: + verbose_name = _("project") + verbose_name_plural = _("projects") + def __str__(self): return self.title @@ -500,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) @@ -520,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): @@ -535,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): @@ -555,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) @@ -578,16 +602,20 @@ 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): 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 +625,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") @@ -620,6 +648,9 @@ class ProjectSettings(BaseSiteSetting, ClusterableModel): ), ] + class Meta: + verbose_name = _("project settings") + class PAFApprovals(models.Model): project = models.ForeignKey( @@ -643,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( @@ -674,9 +707,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) @@ -685,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) @@ -724,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) @@ -749,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) @@ -775,7 +818,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 +852,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/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/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/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/reports/templates/reports/report_form.html b/hypha/apply/projects/reports/templates/reports/report_form.html index cc7a59ca20..9dbf580d8b 100644 --- a/hypha/apply/projects/reports/templates/reports/report_form.html +++ b/hypha/apply/projects/reports/templates/reports/report_form.html @@ -55,7 +55,7 @@ id="submit-report-form-submit" name="submit" class="sm:w-auto btn btn-block btn-primary" - onclick="return confirm('Are you sure you want to submit your report?')" + onclick="return confirm('{% trans 'Are you sure you want to submit your report?' %}')" > {% trans "Submit" %} diff --git a/hypha/apply/projects/reports/templates/reports/report_list.html b/hypha/apply/projects/reports/templates/reports/report_list.html index 748b247458..134f86248e 100644 --- a/hypha/apply/projects/reports/templates/reports/report_list.html +++ b/hypha/apply/projects/reports/templates/reports/report_list.html @@ -9,7 +9,7 @@ {% endblock %} diff --git a/hypha/apply/projects/reports/templates/reports/reporting.html b/hypha/apply/projects/reports/templates/reports/reporting.html index 4e11b35786..5109a18330 100644 --- a/hypha/apply/projects/reports/templates/reports/reporting.html +++ b/hypha/apply/projects/reports/templates/reports/reporting.html @@ -9,7 +9,7 @@ {% endblock %} 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/templates/application_projects/includes/contracting_documents.html b/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html index 4db2857495..53575234a1 100644 --- a/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html +++ b/hypha/apply/projects/templates/application_projects/includes/contracting_documents.html @@ -9,7 +9,7 @@ x-on:click="collapsed = ! collapsed" x-bind:aria-expanded="collapsed ? 'false' : 'true'" role="button" - aria-label="Toggle Contract documents visibility" + aria-label="{% trans 'Toggle Contract documents visibility' %}" aria-controls="contract-documents-elements" {% endif %}> diff --git a/hypha/apply/projects/templates/application_projects/includes/project_documents.html b/hypha/apply/projects/templates/application_projects/includes/project_documents.html index 5ecaa2292f..6d2a11e82e 100644 --- a/hypha/apply/projects/templates/application_projects/includes/project_documents.html +++ b/hypha/apply/projects/templates/application_projects/includes/project_documents.html @@ -8,7 +8,7 @@ {% if collapsible_header %} x-on:click="collapsed = ! collapsed" role="button" - aria-label="Toggle Project documents visibility" + aria-label="{% trans 'Toggle Project documents visibility' %}" aria-controls="project-documents-elements" x-bind:class="collapsed ? 'border-b-0': ''" {% endif %} diff --git a/hypha/apply/projects/templates/application_projects/modals/batch_invoice_status_update.html b/hypha/apply/projects/templates/application_projects/modals/batch_invoice_status_update.html index 8ba4055b0d..800b41edbc 100644 --- a/hypha/apply/projects/templates/application_projects/modals/batch_invoice_status_update.html +++ b/hypha/apply/projects/templates/application_projects/modals/batch_invoice_status_update.html @@ -5,7 +5,7 @@
      - {% blocktrans count counter=invoices|length %} + {% blocktrans count counter=invoices|length trimmed %} {{ counter }} invoice selected {% plural %} {{ counter }} invoices selected diff --git a/hypha/apply/projects/templates/application_projects/partials/contracting_category_documents.html b/hypha/apply/projects/templates/application_projects/partials/contracting_category_documents.html index fa8a400df7..07f6bfc8ae 100644 --- a/hypha/apply/projects/templates/application_projects/partials/contracting_category_documents.html +++ b/hypha/apply/projects/templates/application_projects/partials/contracting_category_documents.html @@ -76,7 +76,7 @@

      class="btn btn-outline btn-error btn-sm" hx-delete="{% url "apply:projects:remove_contracting_document" object.submission.pk latest_file.pk %}" hx-swap="none" - hx-confirm="Are you sure you want to remove this document?" + hx-confirm="{% trans 'Are you sure you want to remove this document?' %}" > {% csrf_token %} {% heroicon_micro "trash" aria_hidden=true class="opacity-80 size-4" %} diff --git a/hypha/apply/projects/templates/application_projects/partials/supporting_documents.html b/hypha/apply/projects/templates/application_projects/partials/supporting_documents.html index 8a0b0cf014..5e1f37f3cb 100644 --- a/hypha/apply/projects/templates/application_projects/partials/supporting_documents.html +++ b/hypha/apply/projects/templates/application_projects/partials/supporting_documents.html @@ -73,7 +73,7 @@

      class="btn btn-square btn-sm btn-error btn-soft" hx-delete="{% url "apply:projects:remove_supporting_document" object.submission.pk latest_file.pk %}" hx-swap="none" - hx-confirm="Are you sure you want to remove this document?" + hx-confirm="{% trans 'Are you sure you want to remove this document?' %}" > {% heroicon_micro "trash" class="size-4" aria_hidden=true %} {% trans "Remove" %} diff --git a/hypha/apply/projects/templates/application_projects/pdf_invoice_approved_page.html b/hypha/apply/projects/templates/application_projects/pdf_invoice_approved_page.html index 6bbbcdd69d..2683eb297f 100644 --- a/hypha/apply/projects/templates/application_projects/pdf_invoice_approved_page.html +++ b/hypha/apply/projects/templates/application_projects/pdf_invoice_approved_page.html @@ -9,7 +9,10 @@

      {% trans "Invoice Status" %}

      {% extract_status activity request.user as activity_status %} {% get_current_timezone as TIME_ZONE %}
    • - {% blocktrans with email=activity.user.email time=activity.timestamp|localtime %} {{ activity_status }} ({{ email }}) on {{ time }} {{ TIME_ZONE }}{% endblocktrans %} + {% blocktrans with email=activity.user.email time=activity.timestamp|localtime trimmed %} + {{ activity_status }} ({{ email }}) + on {{ time }} {{ TIME_ZONE }} + {% endblocktrans %}
    • {% endfor %}
    diff --git a/hypha/apply/projects/templates/application_projects/project_detail.html b/hypha/apply/projects/templates/application_projects/project_detail.html index ea42ad7c03..16b6227b21 100644 --- a/hypha/apply/projects/templates/application_projects/project_detail.html +++ b/hypha/apply/projects/templates/application_projects/project_detail.html @@ -43,7 +43,9 @@ {% if show_banner %}
    {% display_project_status object request.user as project_status %} - {% blocktrans with status=project_status %} This project is in {{ status }} state. {% endblocktrans %} + {% blocktrans with status=project_status trimmed %} + This project is in {{ status }} state. + {% endblocktrans %}
    {% endif %} {% endblock %} 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/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/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/review/templates/review/includes/review_button.html b/hypha/apply/review/templates/review/includes/review_button.html index 567a89670e..1b8dae5ae7 100644 --- a/hypha/apply/review/templates/review/includes/review_button.html +++ b/hypha/apply/review/templates/review/includes/review_button.html @@ -5,7 +5,7 @@ class="btn btn-primary {{ class }}" > {% if request.user|has_draft:submission %} - {{ draft_text|default:"Update draft" }} + {{ draft_text|default:_("Update draft") }} {% elif request.user|can_review:submission %} {% trans "Add a review" %} {% endif %} diff --git a/hypha/apply/review/templates/review/review_list.html b/hypha/apply/review/templates/review/review_list.html index 1302859dea..2de2522b64 100644 --- a/hypha/apply/review/templates/review/review_list.html +++ b/hypha/apply/review/templates/review/review_list.html @@ -28,7 +28,7 @@ {% for answer in answers.answers %} {% if forloop.parentloop.first %} {{ answer|safe }} - {% elif answers.question == "Opinions"%} + {% elif answers.question == _("Opinions") %} {{ answer }} {% else %} {{ answer|nh3 }} 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/stream_forms/templates/stream_forms/includes/file_field.html b/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html index d53fdc6dad..c7a2168c1f 100644 --- a/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html +++ b/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html @@ -4,7 +4,7 @@ href="{{ file.url }}" target="_blank" rel="noopener noreferrer" - title="View file - {{ file.filename }}" + title="{% blocktrans with filename=file.filename %}View file - {{ filename }}{% endblocktrans %}" > {% heroicon_mini "document-text" class="inline align-text-bottom" aria_hidden=true %} 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/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/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/templates/todo/todolist_item.html b/hypha/apply/todo/templates/todo/todolist_item.html index c4697d51f1..528f78065c 100644 --- a/hypha/apply/todo/templates/todo/todolist_item.html +++ b/hypha/apply/todo/templates/todo/todolist_item.html @@ -13,7 +13,7 @@ {{ task.text|nh3 }} - {% if task.type == "Draft" %} + {% if task.type == _("Draft") %} {{ task.type }} {% endif %} 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/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/apply/users/models.py b/hypha/apply/users/models.py index 32d7471ccc..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})>" @@ -338,7 +340,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 +389,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 +408,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/templates/elevate/elevate.html b/hypha/apply/users/templates/elevate/elevate.html index b2fb51e0c6..e4dca46b38 100644 --- a/hypha/apply/users/templates/elevate/elevate.html +++ b/hypha/apply/users/templates/elevate/elevate.html @@ -10,7 +10,15 @@

    {% trans "Confirm access" %}

    - Signed in as {% if request.user.full_name %} {{ request.user.full_name }} ({{ request.user.email }}) {% else %}{{ request.user.email }} {% endif %} + {% if request.user.full_name %} + {% blocktrans with name=request.user.full_name email=request.user.email trimmed %} + Signed in as {{ name }}({{ email }}) + {% endblocktrans %} + {% else %} + {% blocktrans with email=request.user.email trimmed %} + Signed in as {{ email }} + {% endblocktrans %} + {% endif %}

    @@ -67,8 +75,9 @@

    {% trans "Confirm access" %}

    - {% blocktrans %} - Tip: You are entering sudo mode. After you've performed a sudo-protected + {% trans "Tip:" %} + {% blocktrans trimmed %} + You are entering sudo mode. After you've performed a sudo-protected action, you'll only be asked to re-authenticate again after a few hours of inactivity. {% endblocktrans %}

    diff --git a/hypha/apply/users/templates/two_factor/core/backup_tokens.html b/hypha/apply/users/templates/two_factor/core/backup_tokens.html index ee2a181076..e991d8aba7 100644 --- a/hypha/apply/users/templates/two_factor/core/backup_tokens.html +++ b/hypha/apply/users/templates/two_factor/core/backup_tokens.html @@ -7,8 +7,10 @@

    - {% blocktrans %}You should now print these codes or copy them to your - clipboard and store them in your password manager.{% endblocktrans %} + {% blocktrans trimmed %} + You should now print these codes or copy them to your + clipboard and store them in your password manager. + {% endblocktrans %}

    {% if device.token_set.count %} @@ -42,18 +44,25 @@

- {% blocktrans trimmed %} Note: Each of the code can be used only once. When they are used up, you can generate a new set of backup codes.{% endblocktrans %} + {% trans "Note:" %} + {% blocktrans trimmed %} + Each of the code can be used only once. + When they are used up, you can generate a new set of backup codes. + {% endblocktrans %}

- {% blocktrans %}Once done, acknowledge you have stored the codes securely and then click "Finish".{% endblocktrans %} + {% blocktrans trimmed %} + Once done, acknowledge you have stored the codes securely + and then click "Finish". + {% endblocktrans %}

- +
tooltip.show(); }); clipboard.on('error', function (e) { - tooltip.setContent("Use ctrl/cmd + C to copy the backup codes.") + tooltip.setContent("{% trans 'Use ctrl/cmd + C to copy the backup codes.' %}") tooltip.show(); }); } diff --git a/hypha/apply/users/templates/two_factor/core/backup_tokens_password.html b/hypha/apply/users/templates/two_factor/core/backup_tokens_password.html index 10f1ef5265..24e70831ba 100644 --- a/hypha/apply/users/templates/two_factor/core/backup_tokens_password.html +++ b/hypha/apply/users/templates/two_factor/core/backup_tokens_password.html @@ -3,13 +3,15 @@ {% block content %}

{% block title %}{% trans "Backup Codes" %}{% endblock %}

-

{% blocktrans trimmed %}If you loose your smartphone, or your Authenticator app is not available, +

{% blocktrans trimmed %} + If you loose your smartphone, or your Authenticator app is not available, you can use a backup code along with your username and password to login until you recover your smartphone. Each backup code can be used only once.

These codes should be kept in a secure, private place (print them or store them in your password manager) - for when you need them. When they are used up, you can generate a new set of backup codes.{% endblocktrans %}

+ for when you need them. When they are used up, you can generate a new set of backup codes. + {% endblocktrans %}

{% if form.non_field_errors %} diff --git a/hypha/apply/users/templates/two_factor/core/setup.html b/hypha/apply/users/templates/two_factor/core/setup.html index ad298fbd4e..aba450dc48 100644 --- a/hypha/apply/users/templates/two_factor/core/setup.html +++ b/hypha/apply/users/templates/two_factor/core/setup.html @@ -4,49 +4,71 @@ {% block content_inner %}
{% if wizard.steps.current == 'welcome' %} -

{% blocktrans trimmed %}2FA is an extra layer of security used to make sure that people trying to gain access to an +

{% blocktrans trimmed %} + 2FA is an extra layer of security used to make sure that people trying to gain access to an online account are who they say they are. We recommend using Authy or another - authenticator app.{% endblocktrans %}

-

{% blocktrans trimmed %}Please contact {{ ORG_EMAIL }} if you have technical difficulty - enabling 2FA.{% endblocktrans %}

+ authenticator app. + {% endblocktrans %}

+

{% blocktrans trimmed %} + Please contact {{ ORG_EMAIL }} if you have technical difficulty + enabling 2FA. + {% endblocktrans %}

{% elif wizard.steps.current == 'method' %} -

{% blocktrans trimmed %}Please select which authentication method you would - like to use.{% endblocktrans %}

- {% elif wizard.steps.current == 'generator' %} -

{% blocktrans trimmed %}2FA requires a verification code to pair your smartphone with your account. +

{% blocktrans trimmed %} + Please select which authentication method you would like to use. {% endblocktrans %}

-

{% blocktrans trimmed %}Step 1: Open the Authenticator app on your phone and scan the QR code displayed below. + {% elif wizard.steps.current == 'generator' %} +

{% blocktrans trimmed %} + 2FA requires a verification code to pair your smartphone with your account. {% endblocktrans %}

+

+ {% trans "Step 1:" %} + {% blocktrans trimmed %} + Open the Authenticator app on your phone and scan the QR code displayed below. + {% endblocktrans %} +

QR Code

{% trans "Unable to scan the QR code? Try this link:" %} {{ otpauth_url }}

-

{% blocktrans trimmed %}Step 2: Enter the 6-digit verification code generated by the app below.{% endblocktrans %}

+

+ {% trans "Step 2:" %} + {% blocktrans trimmed %} + Enter the 6-digit verification code generated by the app below. + {% endblocktrans %} +

{% elif wizard.steps.current == 'sms' %} -

{% blocktrans trimmed %}Please enter the phone number you wish to receive the +

{% blocktrans trimmed %} + Please enter the phone number you wish to receive the text messages on. This number will be validated in the next step. {% endblocktrans %}

{% elif wizard.steps.current == 'call' %} -

{% blocktrans trimmed %}Please enter the phone number you wish to be called on. - This number will be validated in the next step. {% endblocktrans %}

+

{% blocktrans trimmed %} + Please enter the phone number you wish to be called on. + This number will be validated in the next step. + {% endblocktrans %}

{% elif wizard.steps.current == 'validation' %} {% if challenge_succeeded %} {% 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 %}

{% endif %} {% else %} - + persists, contact the site administrator. + {% endblocktrans %}

{% endif %} {% elif wizard.steps.current == 'yubikey' %} -

{% blocktrans trimmed %}To identify and verify your YubiKey, please insert a - token in the field below. Your YubiKey will be linked to your - account.{% endblocktrans %}

+

{% blocktrans trimmed %} + To identify and verify your YubiKey, please insert a + token in the field below. Your YubiKey will be linked to your account. + {% endblocktrans %}

{% endif %}
{% csrf_token %} diff --git a/hypha/apply/users/templates/two_factor/core/setup_complete.html b/hypha/apply/users/templates/two_factor/core/setup_complete.html index d0f29a33bc..a976a71ab3 100644 --- a/hypha/apply/users/templates/two_factor/core/setup_complete.html +++ b/hypha/apply/users/templates/two_factor/core/setup_complete.html @@ -4,16 +4,20 @@ {% block content_inner %}
-

{% blocktrans trimmed %}Congratulations, you've successfully enabled two-factor - authentication.{% endblocktrans %}

+

{% blocktrans trimmed %} + Congratulations, you've successfully enabled two-factor authentication. + {% endblocktrans %}

-

{% blocktrans trimmed %}We strongly recommend you to save the backup codes. - To get the backup codes you can continue to Show Codes.{% endblocktrans %}

+

{% blocktrans trimmed %} + We strongly recommend you to save the backup codes. + To get the backup codes you can continue to Show Codes. + {% endblocktrans %}

{% if phone_methods %} -

{% blocktrans trimmed %}However, it might happen that you don't have access to - your primary token device. To enable account recovery, add a phone - number.{% endblocktrans %}

+

{% blocktrans trimmed %} + However, it might happen that you don't have access to + your primary token device. To enable account recovery, add a phone number. + {% endblocktrans %}

diff --git a/hypha/apply/users/templates/two_factor/profile/disable.html b/hypha/apply/users/templates/two_factor/profile/disable.html index c0086024b2..6bb73bc451 100644 --- a/hypha/apply/users/templates/two_factor/profile/disable.html +++ b/hypha/apply/users/templates/two_factor/profile/disable.html @@ -4,8 +4,12 @@ {% block content_inner %}

{% block title %}{% trans "Disable Two-factor Authentication" %}{% endblock %}

-

{% blocktrans trimmed %}Disabling Two-factor authentication weakens your account security. - We recommend reenabling it when you can.{% endblocktrans %}

+

+ {% blocktrans trimmed %} + Disabling Two-factor authentication weakens your account security. + We recommend reenabling it when you can. + {% endblocktrans %} +

{% if form.non_field_errors %} diff --git a/hypha/apply/users/templates/users/account.html b/hypha/apply/users/templates/users/account.html index 60b80fceef..b3561c7feb 100644 --- a/hypha/apply/users/templates/users/account.html +++ b/hypha/apply/users/templates/users/account.html @@ -97,7 +97,7 @@

{% comment %} {% can_use_oauth as show_oauth_link %} {% if show_oauth_link %} -

Manage OAuth

+

{% trans "Manage OAuth" %}

{% trans "Manage OAuth" %} diff --git a/hypha/apply/users/templates/users/activation/invalid.html b/hypha/apply/users/templates/users/activation/invalid.html index 32333da7b2..9baea2c160 100644 --- a/hypha/apply/users/templates/users/activation/invalid.html +++ b/hypha/apply/users/templates/users/activation/invalid.html @@ -20,8 +20,11 @@

{% trans "We couldn't activate your accou

{% trans "This usually happens because your activation link has expired, or your account is already activated." %}

- {% blocktrans %}Try resetting your password first. If you're still having trouble, contact us at {{ ORG_SHORT_NAME }}:{% endblocktrans %} - {{ ORG_EMAIL }} + {% blocktrans trimmed %} + Try resetting your password first. + If you're still having trouble, contact us at {{ ORG_SHORT_NAME }}: + {{ ORG_EMAIL }}. + {% endblocktrans %}

diff --git a/hypha/apply/users/templates/users/email_change/invalid_link.html b/hypha/apply/users/templates/users/email_change/invalid_link.html index b9debf1b9a..84ffd192e2 100644 --- a/hypha/apply/users/templates/users/email_change/invalid_link.html +++ b/hypha/apply/users/templates/users/email_change/invalid_link.html @@ -17,7 +17,13 @@

{% trans "Invalid Confirmation URL" %}

{% trans "The confirmation link you used is either invalid or has expired." %}

-

{% blocktrans %}Try again to change email from accounts page. If that fails please contact {{ ORG_SHORT_NAME }} at{% endblocktrans %} {{ ORG_EMAIL }}

+

+ {% blocktrans trimmed %} + Try again to change email from accounts page. + If that fails please contact {{ ORG_SHORT_NAME }} at + {{ ORG_EMAIL }}. + {% endblocktrans %} +

diff --git a/hypha/apply/users/templates/users/login.html b/hypha/apply/users/templates/users/login.html index 7f69b8ceae..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" %} @@ -111,7 +131,7 @@

{% blocktrans %}Log in to {{ ORG_SHORT_NAME }}{% endblocktr name="wizard_goto_step" type="submit" value="backup" - aria-label="Click here to use a backup code" + aria-label="{% trans 'Click here to use a backup code' %}" class="font-semibold link link-primary" > {% trans "Use a Backup Code" %} 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/apply/users/templates/users/register-success.html b/hypha/apply/users/templates/users/register-success.html index 557aa77d29..49edf6fa4a 100644 --- a/hypha/apply/users/templates/users/register-success.html +++ b/hypha/apply/users/templates/users/register-success.html @@ -12,7 +12,7 @@ {% heroicon_outline "document-check" aria_hidden="true" size=64 %}

- Account created, check your email to activate! + {% trans "Account created, check your email to activate!" %}

{% if request.GET.name %} {% trans "Hi " %}{{ request.GET.name }}, {% endif %} 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"]): diff --git a/hypha/core/templates/core/navigation/primarynav-apply.html b/hypha/core/templates/core/navigation/primarynav-apply.html index 0e2747faa8..86d1c2ab2b 100644 --- a/hypha/core/templates/core/navigation/primarynav-apply.html +++ b/hypha/core/templates/core/navigation/primarynav-apply.html @@ -1,7 +1,7 @@ {% load i18n apply_tags heroicons %}
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" },