1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

email: add new "expiry adjusted email"

just the email at this point. Also wrote up a little guide on adding new
emails on wiki.jfa-go.com.
This commit is contained in:
Harvey Tindall 2024-07-28 16:02:47 +01:00
parent 6f5fc0948a
commit fbbb03a47d
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
10 changed files with 290 additions and 69 deletions

View File

@ -26,18 +26,19 @@ func (app *appContext) GetCustomContent(gc *gin.Context) {
adminLang = app.storage.lang.chosenAdminLang adminLang = app.storage.lang.chosenAdminLang
} }
list := emailListDTO{ list := emailListDTO{
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.MustGetCustomContentKey("UserCreated").Enabled}, "UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.MustGetCustomContentKey("UserCreated").Enabled},
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.MustGetCustomContentKey("InviteExpiry").Enabled}, "InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.MustGetCustomContentKey("InviteExpiry").Enabled},
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.MustGetCustomContentKey("PasswordReset").Enabled}, "PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.MustGetCustomContentKey("PasswordReset").Enabled},
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.MustGetCustomContentKey("UserDeleted").Enabled}, "UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.MustGetCustomContentKey("UserDeleted").Enabled},
"UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserDisabled").Enabled}, "UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserDisabled").Enabled},
"UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserEnabled").Enabled}, "UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserEnabled").Enabled},
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.MustGetCustomContentKey("InviteEmail").Enabled}, "UserExpiryAdjusted": {Name: app.storage.lang.Email[lang].UserExpiryAdjusted["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpiryAdjusted").Enabled},
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled}, "InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.MustGetCustomContentKey("InviteEmail").Enabled},
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled}, "WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled},
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled}, "EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled},
"UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("UserLogin").Enabled}, "UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled},
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("UserPage").Enabled}, "UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("UserLogin").Enabled},
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("UserPage").Enabled},
} }
filter := gc.Query("filter") filter := gc.Query("filter")
@ -51,39 +52,6 @@ func (app *appContext) GetCustomContent(gc *gin.Context) {
gc.JSON(200, list) gc.JSON(200, list)
} }
// No longer needed, these are stored by string keys in the database now.
/* func (app *appContext) getCustomMessage(id string) *CustomContent {
switch id {
case "Announcement":
return &CustomContent{}
case "UserCreated":
return &app.storage.customEmails.UserCreated
case "InviteExpiry":
return &app.storage.customEmails.InviteExpiry
case "PasswordReset":
return &app.storage.customEmails.PasswordReset
case "UserDeleted":
return &app.storage.customEmails.UserDeleted
case "UserDisabled":
return &app.storage.customEmails.UserDisabled
case "UserEnabled":
return &app.storage.customEmails.UserEnabled
case "InviteEmail":
return &app.storage.customEmails.InviteEmail
case "WelcomeEmail":
return &app.storage.customEmails.WelcomeEmail
case "EmailConfirmation":
return &app.storage.customEmails.EmailConfirmation
case "UserExpired":
return &app.storage.customEmails.UserExpired
case "UserLogin":
return &app.storage.userPage.Login
case "UserPage":
return &app.storage.userPage.Page
}
return nil
} */
// @Summary Sets the corresponding custom content. // @Summary Sets the corresponding custom content.
// @Produce json // @Produce json
// @Param CustomContent body CustomContent true "Content = email (in markdown)." // @Param CustomContent body CustomContent true "Content = email (in markdown)."
@ -217,6 +185,11 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
msg, err = app.email.constructEnabled("", app, true) msg, err = app.email.constructEnabled("", app, true)
} }
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false) values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
case "UserExpiryAdjusted":
if noContent {
msg, err = app.email.constructExpiryAdjusted(time.Time{}, "", app, true)
}
values = app.email.expiryAdjustedValues(time.Now(), app.storage.lang.Email[lang].Strings.get("reason"), app, false, true)
case "InviteEmail": case "InviteEmail":
if noContent { if noContent {
msg, err = app.email.constructInvite("", Invite{}, app, true) msg, err = app.email.constructInvite("", Invite{}, app, true)

View File

@ -105,6 +105,9 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html") app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html")
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt") app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html")
app.MustSetValue("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt")
app.MustSetValue("matrix", "topic", "Jellyfin notifications") app.MustSetValue("matrix", "topic", "Jellyfin notifications")
app.MustSetValue("matrix", "show_on_reg", "true") app.MustSetValue("matrix", "show_on_reg", "true")

View File

@ -1713,7 +1713,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "User Expiry", "name": "User Expiry",
"description": "When set on an invite, users will be deleted or disabled a specified amount of time after they create their account." "description": "When set on an invite, users will be deleted or disabled a specified amount of time after they create their account. Expiries can also be set and extended for invididual users, optionally with a message why."
}, },
"settings": { "settings": {
"behaviour": { "behaviour": {
@ -1765,6 +1765,35 @@
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Path to custom email in plain text" "description": "Path to custom email in plain text"
},
"adjustment_subject": {
"name": "Adjustment: email subject",
"required": false,
"requires_restart": false,
"depends_true": "messages|enabled",
"type": "text",
"value": "",
"description": "Subject of adjustment emails, sent optionally when setting/extending an expiry."
},
"adjustment_email_html": {
"name": "Adjustment: Custom email (HTML)",
"required": false,
"requires_restart": false,
"advanced": true,
"depends_true": "messages|enabled",
"type": "text",
"value": "",
"description": "Path to custom email html"
},
"adjustment_email_text": {
"name": "Adjustment: Custom email (plaintext)",
"required": false,
"requires_restart": false,
"advanced": true,
"depends_true": "messages|enabled",
"type": "text",
"value": "",
"description": "Path to custom email in plain text"
} }
} }
}, },

View File

@ -741,6 +741,70 @@ func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub b
return email, nil return email, nil
} }
func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, app *appContext, noSub bool, custom bool) map[string]interface{} {
template := map[string]interface{}{
"yourExpiryWasAdjusted": emailer.lang.UserExpiryAdjusted.get("yourExpiryWasAdjusted"),
"ifPreviouslyDisabled": emailer.lang.UserExpiryAdjusted.get("ifPreviouslyDisabled"),
"reasonString": emailer.lang.Strings.get("reason"),
"newExpiry": "",
"message": "",
}
if noSub {
empty := []string{"reason", "newExpiry"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} else {
template["reason"] = reason
template["message"] = app.config.Section("messages").Key("message").String()
exp := app.formatDatetime(expiry)
if !expiry.IsZero() {
if custom {
template["newExpiry"] = exp
} else if !expiry.IsZero() {
template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{
"date": exp,
})
}
}
}
return template
}
func (emailer *Emailer) constructExpiryAdjusted(expiry time.Time, reason string, app *appContext, noSub bool) (*Message, error) {
email := &Message{
Subject: app.config.Section("user_expiry").Key("adjustment_subject").MustString(emailer.lang.UserExpiryAdjusted.get("title")),
}
var err error
var template map[string]interface{}
message := app.storage.MustGetCustomContentKey("UserExpiryAdjusted")
if message.Enabled {
template = emailer.expiryAdjustedValues(expiry, reason, app, noSub, true)
} else {
template = emailer.expiryAdjustedValues(expiry, reason, app, noSub, false)
}
if noSub {
template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{
"date": "{newExpiry}",
})
}
if message.Enabled {
content := templateEmail(
message.Content,
message.Variables,
nil,
template,
)
email, err = emailer.constructTemplate(email.Subject, content, app)
} else {
email.HTML, email.Text, email.Markdown, err = emailer.construct(app, "user_expiry", "adjustment_email_", template)
}
if err != nil {
return nil, err
}
return email, nil
}
func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *appContext, noSub bool, custom bool) map[string]interface{} { func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *appContext, noSub bool, custom bool) map[string]interface{} {
template := map[string]interface{}{ template := map[string]interface{}{
"welcome": emailer.lang.WelcomeEmail.get("welcome"), "welcome": emailer.lang.WelcomeEmail.get("welcome"),

25
lang.go
View File

@ -93,18 +93,19 @@ func (ls *emailLangs) getOptions() [][2]string {
} }
type emailLang struct { type emailLang struct {
Meta langMeta `json:"meta"` Meta langMeta `json:"meta"`
Strings langSection `json:"strings"` Strings langSection `json:"strings"`
UserCreated langSection `json:"userCreated"` UserCreated langSection `json:"userCreated"`
InviteExpiry langSection `json:"inviteExpiry"` InviteExpiry langSection `json:"inviteExpiry"`
PasswordReset langSection `json:"passwordReset"` PasswordReset langSection `json:"passwordReset"`
UserDeleted langSection `json:"userDeleted"` UserDeleted langSection `json:"userDeleted"`
UserDisabled langSection `json:"userDisabled"` UserDisabled langSection `json:"userDisabled"`
UserEnabled langSection `json:"userEnabled"` UserEnabled langSection `json:"userEnabled"`
InviteEmail langSection `json:"inviteEmail"` UserExpiryAdjusted langSection `json:"userExpiryAdjusted"`
WelcomeEmail langSection `json:"welcomeEmail"` InviteEmail langSection `json:"inviteEmail"`
EmailConfirmation langSection `json:"emailConfirmation"` WelcomeEmail langSection `json:"welcomeEmail"`
UserExpired langSection `json:"userExpired"` EmailConfirmation langSection `json:"emailConfirmation"`
UserExpired langSection `json:"userExpired"`
} }
type setupLangs map[string]setupLang type setupLangs map[string]setupLang

View File

@ -45,6 +45,13 @@
"title": "Your account has been re-enabled - Jellyfin", "title": "Your account has been re-enabled - Jellyfin",
"yourAccountWasEnabled": "Your account was re-enabled." "yourAccountWasEnabled": "Your account was re-enabled."
}, },
"userExpiryAdjusted": {
"name": "Expiry adjusted",
"title": "Account expiry adjusted - Jellyfin",
"yourExpiryWasAdjusted": "Your account's expiry date has been adjusted.",
"ifPreviouslyDisabled": "If your account was previously disabled, it has been re-enabled.",
"newExpiry": "Your account will now expire on {date}."
},
"inviteEmail": { "inviteEmail": {
"name": "Invite email", "name": "Invite email",
"title": "Invite - Jellyfin", "title": "Invite - Jellyfin",

81
mail/expiry-adjusted.mjml Normal file
View File

@ -0,0 +1,81 @@
<mjml>
<mj-head>
<mj-raw>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
</mj-raw>
<mj-style>
:root {
Color-scheme: light dark;
supported-color-schemes: light dark;
}
@media (prefers-color-scheme: light) {
Color-scheme: dark;
.body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsc] .body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsb] .body {
background: #242424 !important;
background-color: #242424 !important;
}
}
@media (prefers-color-scheme: dark) {
Color-scheme: dark;
.body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsc] .body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsb] .body {
background: #242424 !important;
background-color: #242424 !important;
}
}
</mj-style>
<mj-attributes>
<mj-class name="bg" background-color="#101010" />
<mj-class name="bg2" background-color="#242424" />
<mj-class name="text" color="#cacaca" />
<mj-class name="bold" color="rgba(255,255,255,0.87)" />
<mj-class name="secondary" color="rgb(153,153,153)" />
<mj-class name="blue" background-color="rgb(0,164,220)" />
</mj-attributes>
<mj-font name="Quicksand" href="https://fonts.googleapis.com/css2?family=Quicksand" />
<mj-font name="Noto Sans" href="https://fonts.googleapis.com/css2?family=Noto+Sans" />
</mj-head>
<mj-body>
<mj-section mj-class="bg2">
<mj-column>
<mj-text mj-class="bold" font-size="25px" font-family="Quicksand, Noto Sans, Helvetica, Arial, sans-serif"> {{ .jellyfin }} </mj-text>
</mj-column>
</mj-section>
<mj-section mj-class="bg">
<mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<h3>{{ .yourExpiryWasAdjusted }}</h3>
<p>{{ .ifPreviouslyDisabled }}</p>
<h4>{{ .newExpiry }}</h4>
<p>{{ .reasonString }}: <i>{{ .reason }}</i></p>
</mj-text>
</mj-column>
</mj-section>
<mj-section mj-class="bg2">
<mj-column>
<mj-text mj-class="secondary" font-style="italic" font-size="14px">
{{ .message }}
</mj-text>
</mj-column>
</mj-section>
</body>
</mjml>

10
mail/expiry-adjusted.txt Normal file
View File

@ -0,0 +1,10 @@
{{ .yourExpiryWasAdjusted }}
{{ .ifPreviouslyDisabled }}
{{ .newExpiry }}
{{ .reasonString }}: {{ .reason }}
{{ .message }}

View File

@ -17,6 +17,7 @@ func runMigrations(app *appContext) {
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
// migrateHyphens(app) // migrateHyphens(app)
migrateToBadger(app) migrateToBadger(app)
intialiseCustomContent(app)
} }
// Migrate pre-0.2.0 user templates to profiles // Migrate pre-0.2.0 user templates to profiles
@ -329,6 +330,8 @@ func migrateToBadger(app *appContext) {
app.storage.SetCustomContentKey("UserPage", app.storage.deprecatedUserPageContent.Page) app.storage.SetCustomContentKey("UserPage", app.storage.deprecatedUserPageContent.Page)
} }
// Custom content not present here was added post-badger.
err := app.storage.db.Upsert("migrated_to_db", MigrationStatus{true}) err := app.storage.db.Upsert("migrated_to_db", MigrationStatus{true})
if err != nil { if err != nil {
app.err.Fatalf("Failed to migrate to DB: %v\n", err) app.err.Fatalf("Failed to migrate to DB: %v\n", err)
@ -336,6 +339,53 @@ func migrateToBadger(app *appContext) {
app.info.Println("All data migrated to database. JSON files in the config folder can be deleted if you are sure all data is correct in the app. Create an issue if you have problems.") app.info.Println("All data migrated to database. JSON files in the config folder can be deleted if you are sure all data is correct in the app. Create an issue if you have problems.")
} }
// Simply creates an emply CC template if not imn the DB already.
// Add new CC types here!
func intialiseCustomContent(app *appContext) {
emptyCC := CustomContent{
Enabled: false,
}
if _, ok := app.storage.GetCustomContentKey("UserCreated"); !ok {
app.storage.SetCustomContentKey("UserCreated", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("InviteExpiry"); !ok {
app.storage.SetCustomContentKey("InviteExpiry", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("PasswordReset"); !ok {
app.storage.SetCustomContentKey("PasswordReset", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserDeleted"); !ok {
app.storage.SetCustomContentKey("UserDeleted", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserDisabled"); !ok {
app.storage.SetCustomContentKey("UserDisabled", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserEnabled"); !ok {
app.storage.SetCustomContentKey("UserEnabled", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("InviteEmail"); !ok {
app.storage.SetCustomContentKey("InviteEmail", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("WelcomeEmail"); !ok {
app.storage.SetCustomContentKey("WelcomeEmail", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("EmailConfirmation"); !ok {
app.storage.SetCustomContentKey("EmailConfirmation", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserExpired"); !ok {
app.storage.SetCustomContentKey("UserExpired", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserLogin"); !ok {
app.storage.SetCustomContentKey("UserLogin", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserPage"); !ok {
app.storage.SetCustomContentKey("UserPage", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserExpiryAdjusted"); !ok {
app.storage.SetCustomContentKey("UserExpiryAdjusted", emptyCC)
}
}
// Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled. // Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled.
// func migrateHyphens(app *appContext) { // func migrateHyphens(app *appContext) {
// checkVersion := func(version string) int { // checkVersion := func(version string) int {

View File

@ -610,16 +610,17 @@ type EmailAddress struct {
} }
type customEmails struct { type customEmails struct {
UserCreated CustomContent `json:"userCreated"` UserCreated CustomContent `json:"userCreated"`
InviteExpiry CustomContent `json:"inviteExpiry"` InviteExpiry CustomContent `json:"inviteExpiry"`
PasswordReset CustomContent `json:"passwordReset"` PasswordReset CustomContent `json:"passwordReset"`
UserDeleted CustomContent `json:"userDeleted"` UserDeleted CustomContent `json:"userDeleted"`
UserDisabled CustomContent `json:"userDisabled"` UserDisabled CustomContent `json:"userDisabled"`
UserEnabled CustomContent `json:"userEnabled"` UserEnabled CustomContent `json:"userEnabled"`
InviteEmail CustomContent `json:"inviteEmail"` UserExpiryAdjusted CustomContent `json:"userExpiryAdjusted"`
WelcomeEmail CustomContent `json:"welcomeEmail"` InviteEmail CustomContent `json:"inviteEmail"`
EmailConfirmation CustomContent `json:"emailConfirmation"` WelcomeEmail CustomContent `json:"welcomeEmail"`
UserExpired CustomContent `json:"userExpired"` EmailConfirmation CustomContent `json:"emailConfirmation"`
UserExpired CustomContent `json:"userExpired"`
} }
// CustomContent stores customized versions of jfa-go content, including emails and user messages. // CustomContent stores customized versions of jfa-go content, including emails and user messages.
@ -1225,6 +1226,7 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
patchLang(&lang.UserDeleted, &fallback.UserDeleted, &english.UserDeleted) patchLang(&lang.UserDeleted, &fallback.UserDeleted, &english.UserDeleted)
patchLang(&lang.UserDisabled, &fallback.UserDisabled, &english.UserDisabled) patchLang(&lang.UserDisabled, &fallback.UserDisabled, &english.UserDisabled)
patchLang(&lang.UserEnabled, &fallback.UserEnabled, &english.UserEnabled) patchLang(&lang.UserEnabled, &fallback.UserEnabled, &english.UserEnabled)
patchLang(&lang.UserExpiryAdjusted, &fallback.UserExpiryAdjusted, &english.UserExpiryAdjusted)
patchLang(&lang.InviteEmail, &fallback.InviteEmail, &english.InviteEmail) patchLang(&lang.InviteEmail, &fallback.InviteEmail, &english.InviteEmail)
patchLang(&lang.WelcomeEmail, &fallback.WelcomeEmail, &english.WelcomeEmail) patchLang(&lang.WelcomeEmail, &fallback.WelcomeEmail, &english.WelcomeEmail)
patchLang(&lang.EmailConfirmation, &fallback.EmailConfirmation, &english.EmailConfirmation) patchLang(&lang.EmailConfirmation, &fallback.EmailConfirmation, &english.EmailConfirmation)
@ -1239,6 +1241,7 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
patchLang(&lang.UserDeleted, &english.UserDeleted) patchLang(&lang.UserDeleted, &english.UserDeleted)
patchLang(&lang.UserDisabled, &english.UserDisabled) patchLang(&lang.UserDisabled, &english.UserDisabled)
patchLang(&lang.UserEnabled, &english.UserEnabled) patchLang(&lang.UserEnabled, &english.UserEnabled)
patchLang(&lang.UserExpiryAdjusted, &english.UserExpiryAdjusted)
patchLang(&lang.InviteEmail, &english.InviteEmail) patchLang(&lang.InviteEmail, &english.InviteEmail)
patchLang(&lang.WelcomeEmail, &english.WelcomeEmail) patchLang(&lang.WelcomeEmail, &english.WelcomeEmail)
patchLang(&lang.EmailConfirmation, &english.EmailConfirmation) patchLang(&lang.EmailConfirmation, &english.EmailConfirmation)