89011d64f7
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 49s
CI / test-go-edu-search (push) Successful in 45s
CI / test-python-klausur (push) Failing after 3m41s
CI / test-python-agent-core (push) Successful in 38s
CI / test-nodejs-website (push) Successful in 49s
256 lines
11 KiB
Go
256 lines
11 KiB
Go
package notifications
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// Vars are the placeholder values rendered into a template string.
|
||
// The fields here must match the {{…}} markers in templates below.
|
||
type Vars struct {
|
||
Title string
|
||
Date string // YYYY-MM-DD
|
||
DatePretty string // e.g. "Donnerstag, 15. Oktober"
|
||
ClassName string // empty = whole school
|
||
TeacherName string
|
||
}
|
||
|
||
// Render picks the right template based on event type, audience, lead-day
|
||
// bucket and language, then substitutes the {{var}} placeholders.
|
||
//
|
||
// lead is grouped into three buckets:
|
||
//
|
||
// 0 → "today"
|
||
// 1 → "tomorrow"
|
||
// >=2 → "in X days" with X = lead value
|
||
//
|
||
// Falls back through (lang → de) and (event_type → "andere") so we never
|
||
// fail to render even with custom rule combos.
|
||
func Render(eventType, audience string, lead int, lang string, v Vars) (subject, body string) {
|
||
bucket := bucketFor(lead)
|
||
lc := strings.ToLower(lang)
|
||
if _, ok := templates[lc]; !ok {
|
||
lc = "de"
|
||
}
|
||
t := lookup(lc, eventType, audience, bucket)
|
||
subject = substitute(t.Subject, lead, v)
|
||
body = substitute(t.Body, lead, v)
|
||
return subject, body
|
||
}
|
||
|
||
func bucketFor(lead int) string {
|
||
switch {
|
||
case lead <= 0:
|
||
return "today"
|
||
case lead == 1:
|
||
return "tomorrow"
|
||
default:
|
||
return "days"
|
||
}
|
||
}
|
||
|
||
// lookup walks the templates map applying the (lang → de) and
|
||
// (event_type → andere) fallbacks. The bucket is guaranteed to exist for
|
||
// every audience because we always define today/tomorrow/days in the de
|
||
// baseline.
|
||
func lookup(lang, eventType, audience, bucket string) tmpl {
|
||
byEvent, ok := templates[lang][eventType]
|
||
if !ok {
|
||
byEvent = templates[lang]["andere"]
|
||
}
|
||
byAudience, ok := byEvent[audience]
|
||
if !ok {
|
||
byAudience = byEvent["parents"]
|
||
}
|
||
t, ok := byAudience[bucket]
|
||
if !ok {
|
||
t = byAudience["days"]
|
||
}
|
||
return t
|
||
}
|
||
|
||
func substitute(s string, lead int, v Vars) string {
|
||
classSuffix := ""
|
||
if v.ClassName != "" {
|
||
classSuffix = " (" + v.ClassName + ")"
|
||
}
|
||
repl := strings.NewReplacer(
|
||
"{{title}}", v.Title,
|
||
"{{date}}", v.Date,
|
||
"{{date_pretty}}", v.DatePretty,
|
||
"{{class_name}}", v.ClassName,
|
||
"{{class_suffix}}", classSuffix,
|
||
"{{teacher_name}}", v.TeacherName,
|
||
"{{lead}}", fmt.Sprintf("%d", lead),
|
||
)
|
||
return repl.Replace(s)
|
||
}
|
||
|
||
type tmpl struct {
|
||
Subject string
|
||
Body string
|
||
}
|
||
|
||
// templates[lang][eventType][audience][bucket] → tmpl
|
||
//
|
||
// Only the eight parent languages we ship subject-i18n.ts for are covered.
|
||
// Custom event_types fall back to the "andere" branch. Custom languages
|
||
// fall back to "de". This keeps the file under 500 LOC; if more locales
|
||
// are needed, split into one file per language.
|
||
var templates = map[string]map[string]map[string]map[string]tmpl{
|
||
"de": deTemplates(),
|
||
"en": enTemplates(),
|
||
"tr": trTemplates(),
|
||
"ar": arTemplates(),
|
||
"uk": ukTemplates(),
|
||
"ru": ruTemplates(),
|
||
"pl": plTemplates(),
|
||
"fr": frTemplates(),
|
||
}
|
||
|
||
func deTemplates() map[string]map[string]map[string]tmpl {
|
||
// All event types share the same template family; we only vary by audience
|
||
// and bucket. Specialise where wording really differs (e.g. fortbildung
|
||
// is school-free, schulfeier invites attendance).
|
||
parentToday := tmpl{
|
||
Subject: "Heute: {{title}}{{class_suffix}}",
|
||
Body: "Liebe Eltern, heute findet {{title}} statt{{class_suffix}}. Datum: {{date_pretty}}.",
|
||
}
|
||
parentTomorrow := tmpl{
|
||
Subject: "Morgen: {{title}}{{class_suffix}}",
|
||
Body: "Liebe Eltern, morgen ({{date_pretty}}) findet {{title}} statt{{class_suffix}}.",
|
||
}
|
||
parentDays := tmpl{
|
||
Subject: "In {{lead}} Tagen: {{title}}{{class_suffix}}",
|
||
Body: "Liebe Eltern, in {{lead}} Tagen ({{date_pretty}}) findet {{title}} statt{{class_suffix}}.",
|
||
}
|
||
studentToday := tmpl{
|
||
Subject: "Heute: {{title}}",
|
||
Body: "Heute ist {{title}}{{class_suffix}}.",
|
||
}
|
||
studentTomorrow := tmpl{
|
||
Subject: "Morgen: {{title}}",
|
||
Body: "Morgen ({{date_pretty}}) ist {{title}}{{class_suffix}}.",
|
||
}
|
||
studentDays := tmpl{
|
||
Subject: "In {{lead}} Tagen: {{title}}",
|
||
Body: "In {{lead}} Tagen ({{date_pretty}}) ist {{title}}{{class_suffix}}.",
|
||
}
|
||
bucket := func(today, tomorrow, days tmpl) map[string]tmpl {
|
||
return map[string]tmpl{"today": today, "tomorrow": tomorrow, "days": days}
|
||
}
|
||
universal := map[string]map[string]tmpl{
|
||
"parents": bucket(parentToday, parentTomorrow, parentDays),
|
||
"students": bucket(studentToday, studentTomorrow, studentDays),
|
||
}
|
||
return map[string]map[string]map[string]tmpl{
|
||
"fortbildung": universal,
|
||
"schulfeier": universal,
|
||
"klassenfahrt": universal,
|
||
"projekttag": universal,
|
||
"eltern_info": universal,
|
||
"andere": universal,
|
||
}
|
||
}
|
||
|
||
// The non-DE templates use the same structure; only the strings change.
|
||
// Defined in a single helper that takes the translation map and reuses the
|
||
// DE family glue.
|
||
|
||
func makeFamily(
|
||
pT, pTm, pD, sT, sTm, sD tmpl,
|
||
) map[string]map[string]map[string]tmpl {
|
||
bucket := func(today, tomorrow, days tmpl) map[string]tmpl {
|
||
return map[string]tmpl{"today": today, "tomorrow": tomorrow, "days": days}
|
||
}
|
||
universal := map[string]map[string]tmpl{
|
||
"parents": bucket(pT, pTm, pD),
|
||
"students": bucket(sT, sTm, sD),
|
||
}
|
||
return map[string]map[string]map[string]tmpl{
|
||
"fortbildung": universal,
|
||
"schulfeier": universal,
|
||
"klassenfahrt": universal,
|
||
"projekttag": universal,
|
||
"eltern_info": universal,
|
||
"andere": universal,
|
||
}
|
||
}
|
||
|
||
func enTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"Today: {{title}}{{class_suffix}}", "Dear parents, today {{title}} takes place{{class_suffix}}. Date: {{date_pretty}}."},
|
||
tmpl{"Tomorrow: {{title}}{{class_suffix}}", "Dear parents, tomorrow ({{date_pretty}}) {{title}} takes place{{class_suffix}}."},
|
||
tmpl{"In {{lead}} days: {{title}}{{class_suffix}}", "Dear parents, in {{lead}} days ({{date_pretty}}) {{title}} takes place{{class_suffix}}."},
|
||
tmpl{"Today: {{title}}", "Today is {{title}}{{class_suffix}}."},
|
||
tmpl{"Tomorrow: {{title}}", "Tomorrow ({{date_pretty}}) is {{title}}{{class_suffix}}."},
|
||
tmpl{"In {{lead}} days: {{title}}", "In {{lead}} days ({{date_pretty}}) is {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|
||
|
||
func trTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"Bugün: {{title}}{{class_suffix}}", "Sayın veliler, bugün {{title}} gerçekleşiyor{{class_suffix}}. Tarih: {{date_pretty}}."},
|
||
tmpl{"Yarın: {{title}}{{class_suffix}}", "Sayın veliler, yarın ({{date_pretty}}) {{title}} gerçekleşiyor{{class_suffix}}."},
|
||
tmpl{"{{lead}} gün sonra: {{title}}{{class_suffix}}", "Sayın veliler, {{lead}} gün sonra ({{date_pretty}}) {{title}} gerçekleşiyor{{class_suffix}}."},
|
||
tmpl{"Bugün: {{title}}", "Bugün {{title}}{{class_suffix}}."},
|
||
tmpl{"Yarın: {{title}}", "Yarın ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
tmpl{"{{lead}} gün sonra: {{title}}", "{{lead}} gün sonra ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|
||
|
||
func arTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"اليوم: {{title}}{{class_suffix}}", "أعزائي أولياء الأمور، اليوم يقام {{title}}{{class_suffix}}. التاريخ: {{date_pretty}}."},
|
||
tmpl{"غدًا: {{title}}{{class_suffix}}", "أعزائي أولياء الأمور، غدًا ({{date_pretty}}) يقام {{title}}{{class_suffix}}."},
|
||
tmpl{"بعد {{lead}} أيام: {{title}}{{class_suffix}}", "أعزائي أولياء الأمور، بعد {{lead}} أيام ({{date_pretty}}) يقام {{title}}{{class_suffix}}."},
|
||
tmpl{"اليوم: {{title}}", "اليوم {{title}}{{class_suffix}}."},
|
||
tmpl{"غدًا: {{title}}", "غدًا ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
tmpl{"بعد {{lead}} أيام: {{title}}", "بعد {{lead}} أيام ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|
||
|
||
func ukTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"Сьогодні: {{title}}{{class_suffix}}", "Шановні батьки, сьогодні відбудеться {{title}}{{class_suffix}}. Дата: {{date_pretty}}."},
|
||
tmpl{"Завтра: {{title}}{{class_suffix}}", "Шановні батьки, завтра ({{date_pretty}}) відбудеться {{title}}{{class_suffix}}."},
|
||
tmpl{"Через {{lead}} днів: {{title}}{{class_suffix}}", "Шановні батьки, через {{lead}} днів ({{date_pretty}}) відбудеться {{title}}{{class_suffix}}."},
|
||
tmpl{"Сьогодні: {{title}}", "Сьогодні {{title}}{{class_suffix}}."},
|
||
tmpl{"Завтра: {{title}}", "Завтра ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
tmpl{"Через {{lead}} днів: {{title}}", "Через {{lead}} днів ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|
||
|
||
func ruTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"Сегодня: {{title}}{{class_suffix}}", "Уважаемые родители, сегодня состоится {{title}}{{class_suffix}}. Дата: {{date_pretty}}."},
|
||
tmpl{"Завтра: {{title}}{{class_suffix}}", "Уважаемые родители, завтра ({{date_pretty}}) состоится {{title}}{{class_suffix}}."},
|
||
tmpl{"Через {{lead}} дней: {{title}}{{class_suffix}}", "Уважаемые родители, через {{lead}} дней ({{date_pretty}}) состоится {{title}}{{class_suffix}}."},
|
||
tmpl{"Сегодня: {{title}}", "Сегодня {{title}}{{class_suffix}}."},
|
||
tmpl{"Завтра: {{title}}", "Завтра ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
tmpl{"Через {{lead}} дней: {{title}}", "Через {{lead}} дней ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|
||
|
||
func plTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"Dzisiaj: {{title}}{{class_suffix}}", "Drodzy rodzice, dzisiaj odbywa się {{title}}{{class_suffix}}. Data: {{date_pretty}}."},
|
||
tmpl{"Jutro: {{title}}{{class_suffix}}", "Drodzy rodzice, jutro ({{date_pretty}}) odbywa się {{title}}{{class_suffix}}."},
|
||
tmpl{"Za {{lead}} dni: {{title}}{{class_suffix}}", "Drodzy rodzice, za {{lead}} dni ({{date_pretty}}) odbywa się {{title}}{{class_suffix}}."},
|
||
tmpl{"Dzisiaj: {{title}}", "Dzisiaj {{title}}{{class_suffix}}."},
|
||
tmpl{"Jutro: {{title}}", "Jutro ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
tmpl{"Za {{lead}} dni: {{title}}", "Za {{lead}} dni ({{date_pretty}}) {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|
||
|
||
func frTemplates() map[string]map[string]map[string]tmpl {
|
||
return makeFamily(
|
||
tmpl{"Aujourd'hui : {{title}}{{class_suffix}}", "Chers parents, aujourd'hui a lieu {{title}}{{class_suffix}}. Date : {{date_pretty}}."},
|
||
tmpl{"Demain : {{title}}{{class_suffix}}", "Chers parents, demain ({{date_pretty}}) a lieu {{title}}{{class_suffix}}."},
|
||
tmpl{"Dans {{lead}} jours : {{title}}{{class_suffix}}", "Chers parents, dans {{lead}} jours ({{date_pretty}}) a lieu {{title}}{{class_suffix}}."},
|
||
tmpl{"Aujourd'hui : {{title}}", "Aujourd'hui c'est {{title}}{{class_suffix}}."},
|
||
tmpl{"Demain : {{title}}", "Demain ({{date_pretty}}) c'est {{title}}{{class_suffix}}."},
|
||
tmpl{"Dans {{lead}} jours : {{title}}", "Dans {{lead}} jours ({{date_pretty}}) c'est {{title}}{{class_suffix}}."},
|
||
)
|
||
}
|