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}}."}, ) }