dnscms: translate events app

This commit is contained in:
2026-05-19 21:25:28 +02:00
parent 696e6b8f11
commit 9ca9f5db11
5 changed files with 356 additions and 78 deletions
+11 -8
View File
@@ -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%"),
]
+70 -61
View File
@@ -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 <strong>0</strong> om gratis. "
"Tomt felt skjuler priskategorien. Om mulig, skriv kun tall."
_(
"Write <strong>0</strong> 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 <em>om ingen av lokalene som kan velges til venstre</em> passer. "
"F.eks. <em>Frederikkeplassen</em> eller <em>Sirkusteltet</em>."
_(
"Use this <em>if none of the venues that can be selected on the left</em> fit. "
"E.g. <em>Frederikkeplassen</em> or <em>Sirkusteltet</em>."
)
),
)
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")
+5 -4
View File
@@ -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"]
Binary file not shown.
+270 -5
View File
@@ -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 <strong>0</strong> for free. An empty field hides the price category. "
"If possible, write digits only."
msgstr ""
"Skriv <strong>0</strong> 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 <em>if none of the venues that can be selected on the left</em> "
"fit. E.g. <em>Frederikkeplassen</em> or <em>Sirkusteltet</em>."
msgstr ""
"Bruk denne <em>om ingen av lokalene som kan velges til venstre</em> passer. "
"F.eks. <em>Frederikkeplassen</em> eller <em>Sirkusteltet</em>."
#: 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"