diff --git a/dnscms/events/admin.py b/dnscms/events/admin.py
index cfb2fea..18e72df 100644
--- a/dnscms/events/admin.py
+++ b/dnscms/events/admin.py
@@ -2,6 +2,8 @@ from urllib.parse import urlencode
from django.urls import reverse
from django.utils import timezone
+from django.utils.translation import gettext, gettext_lazy as _
+from django.utils.translation import ngettext
from wagtail.admin.ui.tables import Column, DateColumn
from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn
from wagtail.admin.views.pages.choose_parent import ChooseParentView
@@ -18,8 +20,9 @@ class EventDateColumn(Column):
return "—"
if len(occurrences) == 1:
local = timezone.localtime(occurrences[0].start)
- return local.strftime("%Y-%m-%d kl %H:%M")
- return f"{len(occurrences)} forekomster"
+ return local.strftime(gettext("%Y-%m-%d at %H:%M"))
+ count = len(occurrences)
+ return ngettext("%(count)d occurrence", "%(count)d occurrences", count) % {"count": count}
class OrganizersColumn(Column):
@@ -64,22 +67,22 @@ class EventPageListingViewSet(PageListingViewSet):
index_view_class = EventPageIndexView
choose_parent_view_class = EventChooseParentView
icon = "date"
- menu_label = "Arrangementer"
+ menu_label = _("Events")
menu_order = 1
add_to_admin_menu = True
ordering = "-latest_revision_created_at"
columns = [
- PageTitleColumn("title", label="Tittel", sort_key="title", classname="title"),
- EventDateColumn("event_date", label="Dato", width="13%"),
- OrganizersColumn("organizers", label="Arrangører", width="12%"),
+ PageTitleColumn("title", label=_("Title"), sort_key="title", classname="title"),
+ EventDateColumn("event_date", label=_("Date"), width="13%"),
+ OrganizersColumn("organizers", label=_("Organizers"), width="12%"),
DateColumn(
"latest_revision_created_at",
- label="Oppdatert",
+ label=_("Updated"),
sort_key="latest_revision_created_at",
width="10%",
),
- PageStatusColumn("status", label="Status", sort_key="live", width="10%"),
+ PageStatusColumn("status", label=_("Status"), sort_key="live", width="10%"),
]
diff --git a/dnscms/events/models.py b/dnscms/events/models.py
index dff4395..e9d6e14 100644
--- a/dnscms/events/models.py
+++ b/dnscms/events/models.py
@@ -75,11 +75,12 @@ class EventCategory(models.Model):
help_text=_("The name of the category as it will appear in URLs."),
)
show_in_filters = models.BooleanField(
- default=False, help_text="Skal denne kategorien være mulig å filtrere på i programmet?"
+ default=False,
+ help_text=_("Should this category be available as a filter in the event programme?"),
)
PIG_CHOICES = [
- ("", "Ingen"),
+ ("", _("None")),
] + ALL_PIGS
pig = models.CharField(
@@ -87,14 +88,14 @@ class EventCategory(models.Model):
choices=PIG_CHOICES,
default="",
blank=True,
- help_text="Standardgris for arrangementer av denne typen.",
+ help_text=_("Default pig for events of this kind."),
)
panels = [
TitleFieldPanel("name"),
FieldPanel("slug"),
FieldPanel("show_in_filters"),
- FieldPanel("pig", heading="Gris"),
+ FieldPanel("pig", heading=_("Pig")),
]
graphql_fields = [
@@ -105,8 +106,8 @@ class EventCategory(models.Model):
]
class Meta:
- verbose_name = "Event category"
- verbose_name_plural = "Event categories"
+ verbose_name = _("event category")
+ verbose_name_plural = _("event categories")
ordering = ["name"]
def __str__(self):
@@ -134,8 +135,8 @@ class EventOrganizerLink(Orderable):
return f"{self.organizer.name}"
class Meta:
- verbose_name = "Arrangør"
- verbose_name_plural = "Arrangører"
+ verbose_name = _("organizer")
+ verbose_name_plural = _("organizers")
constraints = [
UniqueConstraint(
"event", "organizer", name="event_organizer_link_event_organizer_unique"
@@ -163,13 +164,13 @@ class EventOrganizer(ClusterableModel):
blank=True,
on_delete=models.PROTECT,
related_name="organizers",
- help_text="Om en samfundsforening eller et utvalg står bak, velg det her.",
+ help_text=_("If a DNS association or committee is behind it, choose it here."),
)
external_url = models.URLField(
blank=True,
max_length=512,
- help_text="Lenke til nettstedet til ekstern arrangør",
+ help_text=_("Link to the external organizer's website"),
)
panels = [
@@ -178,15 +179,15 @@ class EventOrganizer(ClusterableModel):
FieldPanel(
"association",
widget=AssociationChooserWidget(linked_fields={"association": "#id_association"}),
- heading="Intern arrangør",
+ heading=_("Internal organizer"),
),
MultiFieldPanel(
- heading="Ekstern arrangør",
+ heading=_("External organizer"),
children=[
FieldPanel(
"external_url",
- heading="Nettsted",
- help_text="La denne stå tom om arrangøren finnes i lista over.",
+ heading=_("Website"),
+ help_text=_("Leave this empty if the organizer exists in the list above."),
),
],
),
@@ -200,8 +201,8 @@ class EventOrganizer(ClusterableModel):
]
class Meta:
- verbose_name = "Event organizer"
- verbose_name_plural = "Event organizers"
+ verbose_name = _("event organizer")
+ verbose_name_plural = _("event organizers")
ordering = ["name"]
def __str__(self):
@@ -234,19 +235,19 @@ class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
blank=True,
on_delete=models.SET_NULL,
related_name="+",
- help_text=(
- "Velg et bilde til bruk i programmet og andre visningsflater. "
- "Bør være et bilde eller en illustrasjon uten tekst "
- "– ikke gjenbruk et Facebook-cover ukritisk!"
+ help_text=_(
+ "Choose an image for use in the programme and other surfaces. "
+ "Should be a photo or an illustration without too much text "
+ "– don't reuse a Facebook cover uncritically!"
),
)
subtitle = models.CharField(
blank=True,
max_length=128,
- help_text=(
- "En kort tekst som kommer rett under under tittelen. "
- "La denne gjerne stå tom om du fikk plass til det meste i tittelen."
+ help_text=_(
+ "A short text that appears right below the title. "
+ "Feel free to leave it empty if you fit most of it in the main title."
),
)
lead = RichTextField(features=["italic", "link"], blank=True)
@@ -262,8 +263,8 @@ class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
)
PIG_CHOICES = [
- ("", "Ingen"),
- ("automatic", "Automatisk"),
+ ("", _("None")),
+ ("automatic", _("Automatic")),
] + ALL_PIGS
pig = models.CharField(
@@ -271,21 +272,21 @@ class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
choices=PIG_CHOICES,
default="automatic",
blank=True,
- help_text=(
- "Grisen som henger på arrangementssiden. "
- "Automatisk fører til at en velges basert på arrangementets kategori."
+ help_text=_(
+ "The pig that hangs out on the event page. "
+ "Automatic causes one to be chosen based on the event's category."
),
)
ticket_url = models.URLField(
blank=True,
max_length=1024,
- help_text="Lenke direkte til billettkjøp, f.eks. TicketCo eller Ticketmaster",
+ help_text=_("Direct link to ticket purchase, e.g. TicketCo, Billetto or Ticketmaster"),
)
facebook_url = models.URLField(
blank=True,
max_length=1024,
- help_text="Lenke direkte til arrangementet på Facebook",
+ help_text=_("Direct link to the event on Facebook"),
)
free = models.BooleanField(null=False, default=False)
@@ -294,63 +295,65 @@ class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
price_member = models.CharField(max_length=32, blank=True)
ticket_panels = [
- FieldPanel("free", heading="Gratis", help_text="Er dette arrangementet gratis for alle?"),
+ FieldPanel("free", heading=_("Free"), help_text=_("Is this event free for everyone?")),
MultiFieldPanel(
children=[
FieldRowPanel(
children=[
- FieldPanel("price_regular", heading="Ordinær pris"),
- FieldPanel("price_student", heading="Pris for studenter"),
- FieldPanel("price_member", heading="Pris for medlemmer av DNS"),
+ FieldPanel("price_regular", heading=_("Regular price")),
+ FieldPanel("price_student", heading=_("Price for students")),
+ FieldPanel("price_member", heading=_("Price for DNS members")),
],
help_text="",
),
HelpPanel(
content=mark_safe(
- "Skriv 0 om gratis. "
- "Tomt felt skjuler priskategorien. Om mulig, skriv kun tall."
+ _(
+ "Write 0 for free. "
+ "An empty field hides the price category. "
+ "If possible, write digits only."
+ )
)
),
],
attrs={"id": "specific_pricing_panel"},
),
- FieldPanel("ticket_url", heading="Billettkjøpslenke"),
+ FieldPanel("ticket_url", heading=_("Ticket purchase link")),
]
content_panels = Page.content_panels + [
- FieldPanel("subtitle", heading="Undertittel"),
+ FieldPanel("subtitle", heading=_("Subtitle")),
FieldPanel("featured_image"),
- FieldPanel("lead", heading="Ingress"),
+ FieldPanel("lead", heading=_("Lead")),
FieldPanel("body"),
FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
MultiFieldPanel(
- heading="Arrangører",
+ heading=_("Organizers"),
children=[
HelpPanel(
- content=("Hvem står bak arrangementet?"),
+ content=_("Who is behind the event?"),
),
MultipleChooserPanel(
- "organizer_links", chooser_field_name="organizer", label="Arrangør"
+ "organizer_links", chooser_field_name="organizer", label=_("Organizer")
),
],
),
- FieldPanel("pig", heading="Gris"),
+ FieldPanel("pig", heading=_("Pig")),
FieldPanel(
"facebook_url",
- heading="Facebook-lenke",
- help_text="Lenke direkte til arrangementet på Facebook.",
+ heading=_("Facebook link"),
+ help_text=_("Direct link to the event on Facebook."),
),
- MultiFieldPanel(heading="Priser og billettkjøp", children=ticket_panels),
+ MultiFieldPanel(heading=_("Pricing and tickets"), children=ticket_panels),
MultiFieldPanel(
- heading="Dato, tid og lokale",
+ heading=_("Date, time and venue"),
children=[
HelpPanel(
- content=(
- "Om arrangementet går over flere dager, "
- "legg inn hver dag som en egen forekomst."
+ content=_(
+ "If the event spans several days, add each day as a separate occurrence."
),
),
- InlinePanel("occurrences", min_num=1, label="Forekomst"),
+ InlinePanel("occurrences", min_num=1, label=_("Occurrence")),
],
),
]
@@ -553,22 +556,24 @@ class EventOccurrence(Orderable):
blank=True,
max_length=128,
help_text=mark_safe(
- "Bruk denne om ingen av lokalene som kan velges til venstre passer. "
- "F.eks. Frederikkeplassen eller Sirkusteltet."
+ _(
+ "Use this if none of the venues that can be selected on the left fit. "
+ "E.g. Frederikkeplassen or Sirkusteltet."
+ )
),
)
panels = [
FieldRowPanel(
children=[
- FieldPanel("start", heading="Start"),
- FieldPanel("end", heading="Slutt"),
+ FieldPanel("start", heading=_("Start")),
+ FieldPanel("end", heading=_("End")),
],
),
FieldRowPanel(
children=[
- FieldPanel("venue", heading="Lokale"),
- FieldPanel("venue_custom", heading="Lokale som fritekst"),
+ FieldPanel("venue", heading=_("Venue")),
+ FieldPanel("venue_custom", heading=_("Venue as free text")),
],
),
]
@@ -583,14 +588,18 @@ class EventOccurrence(Orderable):
def clean(self):
if self.venue and self.venue_custom:
raise ValidationError(
- {"venue_custom": "Du kan ikke både velge et lokale og skrive noe i dette feltet."}
+ {
+ "venue_custom": _(
+ "You can't both pick a venue and write something in this field."
+ )
+ }
)
if not self.venue and not self.venue_custom:
- raise ValidationError({"venue": "Lokale er påkrevd."})
+ raise ValidationError({"venue": _("Venue is required.")})
def __str__(self):
return f"{self.start}--{self.end}"
class Meta:
- verbose_name = "Forekomst"
- verbose_name_plural = "Forekomster"
+ verbose_name = _("occurrence")
+ verbose_name_plural = _("occurrences")
diff --git a/dnscms/events/views.py b/dnscms/events/views.py
index 9b1bd66..889cb5d 100644
--- a/dnscms/events/views.py
+++ b/dnscms/events/views.py
@@ -1,3 +1,4 @@
+from django.utils.translation import gettext_lazy as _
from wagtail.admin.viewsets.chooser import ChooserViewSet
@@ -5,10 +6,10 @@ class EventOrganizerChooserViewSet(ChooserViewSet):
model = "events.EventOrganizer"
icon = "group"
per_page = 30
- page_title = "Choose organizers"
- choose_one_text = "Choose an organizer"
- choose_another_text = "Choose another organizer"
- edit_item_text = "Edit this organizer"
+ page_title = _("Choose organizers")
+ choose_one_text = _("Choose an organizer")
+ choose_another_text = _("Choose another organizer")
+ edit_item_text = _("Edit this organizer")
form_fields = ["name", "association", "external_url"]
diff --git a/dnscms/locale/nb/LC_MESSAGES/django.mo b/dnscms/locale/nb/LC_MESSAGES/django.mo
index 90a3774..4aad5b3 100644
Binary files a/dnscms/locale/nb/LC_MESSAGES/django.mo and b/dnscms/locale/nb/LC_MESSAGES/django.mo differ
diff --git a/dnscms/locale/nb/LC_MESSAGES/django.po b/dnscms/locale/nb/LC_MESSAGES/django.po
index 5605ad3..1052573 100644
--- a/dnscms/locale/nb/LC_MESSAGES/django.po
+++ b/dnscms/locale/nb/LC_MESSAGES/django.po
@@ -7,14 +7,49 @@ msgid ""
msgstr ""
"Project-Id-Version: dnscms\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-05-19 20:44+0200\n"
+"POT-Creation-Date: 2026-05-19 21:17+0200\n"
"Language: nb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: events/models.py:73 events/models.py:155
+#: events/admin.py:23
+msgid "%Y-%m-%d at %H:%M"
+msgstr "%Y-%m-%d kl %H:%M"
+
+#: events/admin.py:25
+#, python-format
+msgid "%(count)d occurrence"
+msgid_plural "%(count)d occurrences"
+msgstr[0] "%(count)d forekomst"
+msgstr[1] "%(count)d forekomster"
+
+#: events/admin.py:70
+msgid "Events"
+msgstr "Arrangementer"
+
+#: events/admin.py:76
+msgid "Title"
+msgstr "Tittel"
+
+#: events/admin.py:77
+msgid "Date"
+msgstr "Dato"
+
+#: events/admin.py:78 events/models.py:333
+msgid "Organizers"
+msgstr "Arrangører"
+
+#: events/admin.py:81
+msgid "Updated"
+msgstr "Oppdatert"
+
+#: events/admin.py:85
+msgid "Status"
+msgstr "Status"
+
+#: events/models.py:73 events/models.py:156
msgid "slug"
msgstr "permalenke"
@@ -22,18 +57,248 @@ msgstr "permalenke"
msgid "The name of the category as it will appear in URLs."
msgstr "Navnet på kategorien slik det vil vises i URL-er."
-#: events/models.py:157
+#: events/models.py:79
+msgid "Should this category be available as a filter in the event programme?"
+msgstr "Skal denne kategorien være mulig å filtrere på i programmet?"
+
+#: events/models.py:83 events/models.py:266
+msgid "None"
+msgstr "Ingen"
+
+#: events/models.py:91
+msgid "Default pig for events of this kind."
+msgstr "Standardgris for arrangementer av denne typen."
+
+#: events/models.py:98 events/models.py:343
+msgid "Pig"
+msgstr "Gris"
+
+#: events/models.py:109
+msgid "event category"
+msgstr "arrangementskategori"
+
+#: events/models.py:110
+msgid "event categories"
+msgstr "arrangementskategorier"
+
+#: events/models.py:138
+msgid "organizer"
+msgstr "arrangør"
+
+#: events/models.py:139
+msgid "organizers"
+msgstr "arrangører"
+
+#: events/models.py:158
msgid "The name of the organizer as it will appear in URLs."
msgstr "Navnet på arrangøren slik det vil vises i URL-er."
-#: events/models.py:395
+#: events/models.py:167
+msgid "If a DNS association or committee is behind it, choose it here."
+msgstr "Om en samfundsforening eller -utvalg står bak, velg det her."
+
+#: events/models.py:173
+msgid "Link to the external organizer's website"
+msgstr "Lenke til nettstedet til ekstern arrangør"
+
+#: events/models.py:182
+msgid "Internal organizer"
+msgstr "Intern arrangør"
+
+#: events/models.py:185
+msgid "External organizer"
+msgstr "Ekstern arrangør"
+
+#: events/models.py:189
+msgid "Website"
+msgstr "Nettsted"
+
+#: events/models.py:190
+msgid "Leave this empty if the organizer exists in the list above."
+msgstr "La denne stå tom om arrangøren finnes i lista over."
+
+#: events/models.py:204
+msgid "event organizer"
+msgstr "arrangør"
+
+#: events/models.py:205
+msgid "event organizers"
+msgstr "arrangører"
+
+#: events/models.py:239
+msgid ""
+"Choose an image for use in the programme and other surfaces. Should be a "
+"photo or an illustration without too much text – don't reuse a Facebook "
+"cover uncritically!"
+msgstr ""
+"Velg et bilde til bruk i programmet og andre visningsflater. Bør være et "
+"bilde eller en illustrasjon uten for mye tekst – ikke gjenbruk et Facebook-"
+"cover ukritisk!"
+
+#: events/models.py:249
+msgid ""
+"A short text that appears right below the title. Feel free to leave it empty "
+"if you fit most of it in the main title."
+msgstr ""
+"En kort tekst som kommer rett under tittelen. La denne gjerne stå tom om du "
+"fikk plass til det meste i hovedtittelen."
+
+#: events/models.py:267
+msgid "Automatic"
+msgstr "Automatisk"
+
+#: events/models.py:276
+msgid ""
+"The pig that hangs out on the event page. Automatic causes one to be chosen "
+"based on the event's category."
+msgstr ""
+"Grisen som henger på arrangementssiden. Automatisk fører til at en velges "
+"basert på arrangementets kategori."
+
+#: events/models.py:284
+msgid "Direct link to ticket purchase, e.g. TicketCo, Billetto or Ticketmaster"
+msgstr "Lenke direkte til billettkjøp, f.eks. TicketCo, Billetto eller Ticketmaster"
+
+#: events/models.py:289
+msgid "Direct link to the event on Facebook"
+msgstr "Lenke direkte til arrangementet på Facebook"
+
+#: events/models.py:299
+msgid "Free"
+msgstr "Gratis"
+
+#: events/models.py:299
+msgid "Is this event free for everyone?"
+msgstr "Er dette arrangementet gratis for alle?"
+
+#: events/models.py:305
+msgid "Regular price"
+msgstr "Ordinær pris"
+
+#: events/models.py:306
+msgid "Price for students"
+msgstr "Pris for studenter"
+
+#: events/models.py:307
+msgid "Price for DNS members"
+msgstr "Pris for medlemmer av DNS"
+
+#: events/models.py:314
+msgid ""
+"Write 0 for free. An empty field hides the price category. "
+"If possible, write digits only."
+msgstr ""
+"Skriv 0 om gratis. Tomt felt skjuler priskategorien. Om "
+"mulig, skriv kun tall."
+
+#: events/models.py:323
+msgid "Ticket purchase link"
+msgstr "Billettkjøpslenke"
+
+#: events/models.py:327
+msgid "Subtitle"
+msgstr "Undertittel"
+
+#: events/models.py:329
+msgid "Lead"
+msgstr "Ingress"
+
+#: events/models.py:336
+msgid "Who is behind the event?"
+msgstr "Hvem står bak arrangementet?"
+
+#: events/models.py:339
+msgid "Organizer"
+msgstr "Arrangør"
+
+#: events/models.py:346
+msgid "Facebook link"
+msgstr "Facebook-lenke"
+
+#: events/models.py:347
+msgid "Direct link to the event on Facebook."
+msgstr "Lenke direkte til arrangementet på Facebook."
+
+#: events/models.py:349
+msgid "Pricing and tickets"
+msgstr "Priser og billettkjøp"
+
+#: events/models.py:351
+msgid "Date, time and venue"
+msgstr "Dato, tid og lokale"
+
+#: events/models.py:355
+msgid "If the event spans several days, add each day as a separate occurrence."
+msgstr "Om arrangementet går over flere dager, legg inn hver dag som en egen forekomst."
+
+#: events/models.py:359
+msgid "Occurrence"
+msgstr "Forekomst"
+
+#: events/models.py:402
msgid "event"
msgstr "arrangement"
-#: events/models.py:396
+#: events/models.py:403
msgid "events"
msgstr "arrangementer"
+#: events/models.py:563
+msgid ""
+"Use this if none of the venues that can be selected on the left "
+"fit. E.g. Frederikkeplassen or Sirkusteltet."
+msgstr ""
+"Bruk denne om ingen av lokalene som kan velges til venstre passer. "
+"F.eks. Frederikkeplassen eller Sirkusteltet."
+
+#: events/models.py:572
+msgid "Start"
+msgstr "Start"
+
+#: events/models.py:573
+msgid "End"
+msgstr "Slutt"
+
+#: events/models.py:578
+msgid "Venue"
+msgstr "Lokale"
+
+#: events/models.py:579
+msgid "Venue as free text"
+msgstr "Lokale som fritekst"
+
+#: events/models.py:596
+msgid "You can't both pick a venue and write something in this field."
+msgstr "Du kan ikke både velge et lokale og skrive noe i dette feltet."
+
+#: events/models.py:601
+msgid "Venue is required."
+msgstr "Lokale er påkrevd."
+
+#: events/models.py:607
+msgid "occurrence"
+msgstr "forekomst"
+
+#: events/models.py:608
+msgid "occurrences"
+msgstr "forekomster"
+
+#: events/views.py:9
+msgid "Choose organizers"
+msgstr "Velg arrangører"
+
+#: events/views.py:10
+msgid "Choose an organizer"
+msgstr "Velg en arrangør"
+
+#: events/views.py:11
+msgid "Choose another organizer"
+msgstr "Velg en annen arrangør"
+
+#: events/views.py:12
+msgid "Edit this organizer"
+msgstr "Rediger denne arrangøren"
+
#: images/models.py:40
msgid "image"
msgstr "bilde"