1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-29 12:30:11 +00:00

Add email translation, add part of french translations

Admin translation from @Killianbe, Email translation from
@Cornichon420. French is currently not functional, a few things are
missing which i'm waiting on.
This commit is contained in:
Harvey Tindall 2021-01-14 17:51:12 +00:00
parent c4342b7f1e
commit fe62422a2a
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
19 changed files with 283 additions and 75 deletions

4
api.go
View File

@ -455,7 +455,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) {
app.err.Printf("%s: Failed to send to %s", userID, address) app.err.Printf("%s: Failed to send to %s", userID, address)
app.debug.Printf("%s: Error: %s", userID, err) app.debug.Printf("%s: Error: %s", userID, err)
} else { } else {
app.info.Printf("%s: Sent invite email to %s", userID, address) app.info.Printf("%s: Sent deletion email to %s", userID, address)
} }
}(userID, req.Reason, addr.(string)) }(userID, req.Reason, addr.(string))
} }
@ -1176,7 +1176,7 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
break break
} }
} }
} else { } else if value.(string) != app.config.Section(section).Key(setting).MustString("") {
tempConfig.Section(section).Key(setting).SetValue(value.(string)) tempConfig.Section(section).Key(setting).SetValue(value.(string))
} }
} }

View File

@ -52,7 +52,7 @@ func (app *appContext) loadConfig() error {
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json")))) key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
} }
} }
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template"} { for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template"} {
// if app.config.Section("files").Key(key).MustString("") == "" { // if app.config.Section("files").Key(key).MustString("") == "" {
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json"))) // key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
// } // }
@ -80,8 +80,6 @@ func (app *appContext) loadConfig() error {
app.config.Section("jellyfin").Key("device").SetValue("jfa-go") app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", VERSION, COMMIT)) app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", VERSION, COMMIT))
app.email = NewEmailer(app)
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("") substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
oldFormLang := app.config.Section("ui").Key("language").MustString("") oldFormLang := app.config.Section("ui").Key("language").MustString("")
@ -93,6 +91,9 @@ func (app *appContext) loadConfig() error {
app.storage.lang.chosenFormLang = newFormLang app.storage.lang.chosenFormLang = newFormLang
} }
app.storage.lang.chosenAdminLang = app.config.Section("ui").Key("language-admin").MustString("en-us") app.storage.lang.chosenAdminLang = app.config.Section("ui").Key("language-admin").MustString("en-us")
app.storage.lang.chosenEmailLang = app.config.Section("email").Key("language").MustString("en-us")
app.email = NewEmailer(app)
return nil return nil
} }

View File

@ -277,6 +277,18 @@
"description": "General email settings. Ignore if not using email features." "description": "General email settings. Ignore if not using email features."
}, },
"settings": { "settings": {
"language": {
"name": "Email Language",
"required": false,
"requires_restart": false,
"depends_true": "method",
"type": "select",
"options": [
"en-us"
],
"value": "en-us",
"description": "Default email language. Submit a PR on github if you'd like to translate."
},
"no_username": { "no_username": {
"name": "Use email addresses as username", "name": "Use email addresses as username",
"required": false, "required": false,

View File

@ -73,6 +73,8 @@ func (sm *SMTP) send(address, fromName, fromAddr string, email *Email) error {
// Emailer contains the email sender, email content, and methods to construct message content. // Emailer contains the email sender, email content, and methods to construct message content.
type Emailer struct { type Emailer struct {
fromAddr, fromName string fromAddr, fromName string
lang *EmailLang
cLang string
sender emailClient sender emailClient
} }
@ -108,6 +110,8 @@ func NewEmailer(app *appContext) *Emailer {
emailer := &Emailer{ emailer := &Emailer{
fromAddr: app.config.Section("email").Key("address").String(), fromAddr: app.config.Section("email").Key("address").String(),
fromName: app.config.Section("email").Key("from").String(), fromName: app.config.Section("email").Key("from").String(),
lang: &(app.storage.lang.Email),
cLang: app.storage.lang.chosenEmailLang,
} }
method := app.config.Section("email").Key("method").String() method := app.config.Section("email").Key("method").String()
if method == "smtp" { if method == "smtp" {
@ -153,8 +157,9 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
} }
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext) (*Email, error) { func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext) (*Email, error) {
lang := emailer.cLang
email := &Email{ email := &Email{
subject: app.config.Section("invite_emails").Key("subject").String(), subject: app.config.Section("invite_emails").Key("subject").MustString(emailer.lang.get(lang, "inviteEmail", "title")),
} }
expiry := invite.ValidTill expiry := invite.ValidTill
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern) d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
@ -170,11 +175,13 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
} }
var tplData bytes.Buffer var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{ err = tpl.Execute(&tplData, map[string]string{
"expiry_date": d, "hello": emailer.lang.get(lang, "inviteEmail", "hello"),
"expiry_time": t, "youHaveBeenInvited": emailer.lang.get(lang, "inviteEmail", "youHaveBeenInvited"),
"expires_in": expiresIn, "toJoin": emailer.lang.get(lang, "inviteEmail", "toJoin"),
"invite_link": inviteLink, "inviteExpiry": emailer.lang.format(lang, "inviteEmail", "inviteExpiry", d, t, expiresIn),
"message": message, "linkButton": emailer.lang.get(lang, "inviteEmail", "linkButton"),
"invite_link": inviteLink,
"message": message,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -189,8 +196,9 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
} }
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext) (*Email, error) { func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext) (*Email, error) {
lang := emailer.cLang
email := &Email{ email := &Email{
subject: "Notice: Invite expired", subject: emailer.lang.get(lang, "inviteExpiry", "title"),
} }
expiry := app.formatDatetime(invite.ValidTill) expiry := app.formatDatetime(invite.ValidTill)
for _, key := range []string{"html", "text"} { for _, key := range []string{"html", "text"} {
@ -201,8 +209,9 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
} }
var tplData bytes.Buffer var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{ err = tpl.Execute(&tplData, map[string]string{
"code": code, "inviteExpired": emailer.lang.get(lang, "inviteExpiry", "inviteExpired"),
"expiry": expiry, "expiredAt": emailer.lang.format(lang, "inviteExpiry", "expiredAt", "\""+code+"\"", expiry),
"notificationNotice": emailer.lang.get(lang, "inviteExpiry", "notificationNotice"),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -217,8 +226,9 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
} }
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext) (*Email, error) { func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext) (*Email, error) {
lang := emailer.cLang
email := &Email{ email := &Email{
subject: "Notice: User created", subject: emailer.lang.get(lang, "userCreated", "title"),
} }
created := app.formatDatetime(invite.Created) created := app.formatDatetime(invite.Created)
var tplAddress string var tplAddress string
@ -235,10 +245,14 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
} }
var tplData bytes.Buffer var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{ err = tpl.Execute(&tplData, map[string]string{
"code": code, "aUserWasCreated": emailer.lang.format(lang, "userCreated", "aUserWasCreated", "\""+code+"\""),
"username": username, "name": emailer.lang.get(lang, "userCreated", "name"),
"address": tplAddress, "address": emailer.lang.get(lang, "userCreated", "emailAddress"),
"time": created, "time": emailer.lang.get(lang, "userCreated", "time"),
"nameVal": username,
"addressVal": tplAddress,
"timeVal": created,
"notificationNotice": emailer.lang.get(lang, "userCreated", "notificationNotice"),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -253,8 +267,9 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
} }
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Email, error) { func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Email, error) {
lang := emailer.cLang
email := &Email{ email := &Email{
subject: app.config.Section("password_resets").Key("subject").MustString("Password reset - Jellyfin"), subject: emailer.lang.get(lang, "passwordReset", "title"),
} }
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern) d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
message := app.config.Section("email").Key("message").String() message := app.config.Section("email").Key("message").String()
@ -266,12 +281,14 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Ema
} }
var tplData bytes.Buffer var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{ err = tpl.Execute(&tplData, map[string]string{
"username": pwr.Username, "helloUser": emailer.lang.format(lang, "passwordReset", "helloUser", pwr.Username),
"expiry_date": d, "someoneHasRequestedReset": emailer.lang.get(lang, "passwordReset", "someoneHasRequestedReset"),
"expiry_time": t, "ifItWasYou": emailer.lang.get(lang, "passwordReset", "ifItWasYou"),
"expires_in": expiresIn, "codeExpiry": emailer.lang.format(lang, "passwordReset", "codeExpiry", d, t, expiresIn),
"pin": pwr.Pin, "ifItWasNotYou": emailer.lang.get(lang, "passwordReset", "ifItWasNotYou"),
"message": message, "pin": emailer.lang.get(lang, "passwordReset", "pin"),
"pinVal": pwr.Pin,
"message": message,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -286,8 +303,9 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Ema
} }
func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email, error) { func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email, error) {
lang := emailer.cLang
email := &Email{ email := &Email{
subject: app.config.Section("deletion").Key("subject").MustString("Your account was deleted - Jellyfin"), subject: emailer.lang.get(lang, "userDeleted", "title"),
} }
for _, key := range []string{"html", "text"} { for _, key := range []string{"html", "text"} {
fpath := app.config.Section("deletion").Key("email_" + key).String() fpath := app.config.Section("deletion").Key("email_" + key).String()
@ -297,7 +315,9 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email
} }
var tplData bytes.Buffer var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{ err = tpl.Execute(&tplData, map[string]string{
"reason": reason, "yourAccountWasDeleted": emailer.lang.get(lang, "userDeleted", "yourAccountWasDeleted"),
"reason": emailer.lang.get(lang, "userDeleted", "reason"),
"reasonVal": reason,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

78
lang/admin/fr-fr.json Normal file
View File

@ -0,0 +1,78 @@
{
"meta": {
"name": "Francais (FR)",
"author": "https://github.com/Killianbe"
},
"strings": {
"invites": "Invite",
"accounts": "Comptes",
"settings": "Reglages",
"theme": "Thème",
"inviteDays": "Jours",
"inviteHours": "Heures",
"inviteMinutes": "Minutes",
"inviteNumberOfUses": "Nombre d'utilisateur",
"warning": "Attention",
"inviteInfiniteUsesWarning": "les invitations infinies peuvent être utilisées abusivement",
"inviteSendToEmail": "Envoyer à",
"login": "S'identifier",
"logout": "Se déconecter",
"create": "Créer",
"apply": "Appliquer",
"delete": "Effacer",
"submit": "Soumettre",
"name": "Nom",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"emailAddress": "Addresse Email",
"lastActiveTime": "Dernière activité",
"from": "De",
"user": "Utilisateur",
"aboutProgram": "A propros",
"version": "Version",
"commitNoun": "Commettre",
"newUser": "Nouvel utilisateur",
"profile": "Profil",
"modifySettings": "Modifier les paramètres",
"modifySettingsDescription": "Appliquez les paramètres à partir d'un profil existant ou obtenez-les directement auprès d'un utilisateur.",
"applyHomescreenLayout": "Appliquer la disposition de l'écran d'accueil",
"sendDeleteNotificationEmail": "Envoyer un e-mail de notification ",
"sendDeleteNotifiationExample": "Votre compte a été supprimé. ",
"settingsRestartRequired": "Redémarrage nécessaire ",
"settingsRestartRequiredDescription": "Un redémarrage est nécessaire pour appliquer certains paramètres que vous avez modifiés. Redémarrer maintenant ou plus tard?",
"settingsApplyRestartLater": "Appliquer, redémarrer plus tard ",
"settingsApplyRestartNow": "Appliquer et redémarrer ",
"settingsApplied": "Paramètres appliqués.",
"settingsRefreshPage": "Actualisez la page dans quelques secondes ",
"settingsRequiredOrRestartMessage": "Remarque: {n} indique un champ obligatoire, {n} indique que les modifications nécessitent un redémarrage. ",
"settingsSave": "Sauver",
"ombiUserDefaults": "Paramètres par défaut de l'utilisateur Ombi",
"ombiUserDefaultsDescription": "Créez un utilisateur Ombi et configurez-le, puis sélectionnez-le ci-dessous. Ses paramètres / autorisations seront stockés et appliqués aux nouveaux utilisateurs Ombi créés par jfa-go ",
"userProfiles": "Profils d'utilisateurs",
"userProfilesDescription": "Les profils sont appliqués aux utilisateurs lorsqu'ils créent un compte. Un profil inclut les droits d'accès à la bibliothèque et la disposition de l'écran d'accueil. ",
"userProfilesIsDefault": "Défaut",
"userProfilesLibraries": "Bibliothèques",
"addProfile": "Ajouter un profil",
"addProfileDescription": "Créez un utilisateur Jellyfin et configurez-le, puis sélectionnez-le ci-dessous. Lorsque ce profil est appliqué à une invitation, de nouveaux utilisateurs seront créés avec les paramètres. ",
"addProfileNameOf": "Nom de profil",
"addProfileStoreHomescreenLayout": "Enregistrer la disposition de l'écran d'accueil"
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Modifier les paramètres pour {n} utilisateur",
"plural": "Modifier les paramètres pour {n} utilisateurs"
},
"deleteNUsers": {
"singular": "Supprimer {n} utilisateur",
"plural": "Supprimer {n} utilisateurs"
},
"addUser": {
"singular": "Ajouter un utilisateur",
"plural": "Ajouter des utilisateurs"
},
"deleteUser": {
"singular": "Supprimer l'utilisateur",
"plural": "Supprimer les utilisateurs"
}
}
}

41
lang/email/en-us.json Normal file
View File

@ -0,0 +1,41 @@
{
"meta": {
"name": "English (US)"
},
"userCreated": {
"title": "Notice: User created",
"aUserWasCreated": "A user was created using code {n}.",
"name": "Name",
"emailAddress": "Address",
"time": "Time",
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
},
"inviteExpiry": {
"title": "Notice: Invite expired",
"inviteExpired": "Invite expired.",
"expiredAt": "Code {n} expired at {n}.",
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
},
"passwordReset": {
"title": "Password reset requested - Jellyfin",
"helloUser": "Hi {n},",
"someoneHasRequestedReset": "Someone has recently requested a password reset on Jellyfin.",
"ifItWasYou": "If this was you, enter the pin below into the prompt.",
"codeExpiry": "The code will expire on {n}, at {n} UTC, which is in {n}.",
"ifItWasNotYou": "If this wasn't you, please ignore this email.",
"pin": "PIN"
},
"userDeleted": {
"title": "Your account was deleted - Jellyfin",
"yourAccountWasDeleted": "Your Jellyfin account was deleted.",
"reason": "Reason"
},
"inviteEmail": {
"title": "Invite - Jellyfin",
"hello": "Hi",
"youHaveBeenInvited": "You've been invited to Jellyfin.",
"toJoin": "To join, follow the below link.",
"inviteExpiry": "This invite will expire on {n}, at {n}, which is in {n}, so act quick.",
"linkButton": "Setup your account"
}
}

32
lang/email/fr-fr.json Normal file
View File

@ -0,0 +1,32 @@
{
"meta": {
"name": "Francais (FR)",
"author": "https://github.com/Cornichon420"
},
"userCreated": {
"aUserWasCreated": "Un utilisateur a été créé avec ce code {n}",
"name": "Nom",
"emailAddress": "Adresse",
"time": "Date",
"notificationNotice": ""
},
"passwordReset": {
"helloUser": "Salut {n},",
"someoneHasRequestedReset": "Quelqu'un vient de demander une réinitialisation du mot de passe via Jellyfin.",
"ifItWasYou": "Si c'était bien toi, renseigne le code PIN en dessous.",
"codeExpiry": "Ce code expirera le {n}, à {n} UTC, soit dans {n}.",
"ifItWasNotYou": "Si ce n'était pas toi, tu peux ignorer ce mail.",
"pin": "PIN"
},
"userDeleted": {
"yourAccountWasDeleted": "Ton compte Jellyfin a été supprimé.",
"reason": "Motif"
},
"inviteEmail": {
"hello": "Salut",
"youHaveBeenInvited": "Tu a été invité à rejoindre Jellyfin.",
"toJoin": "Pour continuer, suis le lien en dessous.",
"inviteExpiry": "L'invitation expirera le {n}, à {n}, sout dans {n}, alors fais vite !",
"linkButton": "Lien"
}
}

View File

@ -20,26 +20,25 @@
<mj-section mj-class="bg"> <mj-section mj-class="bg">
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<h3>User Created</h3> <p>{{ .aUserWasCreated }}</p>
<p>A user was created using code {{ .code }}.</p>
</mj-text> </mj-text>
<mj-table mj-class="text" container-background-color="#242424"> <mj-table mj-class="text" container-background-color="#242424">
<tr style="text-align: left;"> <tr style="text-align: left;">
<th>Name</th> <th>{{ .name }}</th>
<th>Address</th> <th>{{ .address }}</th>
<th>Time</th> <th>{{ .time }}</th>
</tr> </tr>
<tr style="font-style: italic; text-align: left; color: rgb(153,153,153);"> <tr style="font-style: italic; text-align: left; color: rgb(153,153,153);">
<th>{{ .username }}</th> <th>{{ .nameVal }}</th>
<th>{{ .address }}</th> <th>{{ .addressVal }}</th>
<th>{{ .time }}</th> <th>{{ .timeVal }}</th>
</mj-table> </mj-table>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">
<mj-column> <mj-column>
<mj-text mj-class="secondary" font-style="italic" font-size="14px"> <mj-text mj-class="secondary" font-style="italic" font-size="14px">
Notification emails can be toggled on the admin dashboard. {{ .notificationNotice }}
</mj-text> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -1,7 +1,7 @@
A user was created using code {{ .code }}. {{ .aUserWasCreated }}
Name: {{ .username }} {{ .name }}: {{ .nameVal }}
Address: {{ .address }} {{ .address }}: {{ .addressVal }}
Time: {{ .time }} {{ .time }}: {{ .timeVal }}
Note: Notification emails can be toggled on the admin dashboard. {{ .notificationNotice }}

View File

@ -20,8 +20,8 @@
<mj-section mj-class="bg"> <mj-section mj-class="bg">
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<h3>Your account was deleted.</h3> <h3>{{ .yourAccountWasDeleted }}</h3>
<p>Reason: <i>{{ .reason }}</i></p> <p>{{ .reason }}: <i>{{ .reasonVal }}</i></p>
</mj-text> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -1,4 +1,4 @@
Your Jellyfin account was deleted. {{ .yourAccountWasDeleted }}
Reason: {{ .reason }} {{ .reason }}: {{ .reasonVal }}
{{ .message }} {{ .message }}

View File

@ -20,13 +20,13 @@
<mj-section mj-class="bg"> <mj-section mj-class="bg">
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<p>Hi {{ .username }},</p> <p>{{ .helloUser }}</p>
<p> Someone has recently requested a password reset on Jellyfin.</p> <p>{{ .someoneHasRequestedReset }}</p>
<p>If this was you, enter the below pin into the prompt.</p> <p>{{ .ifItWasYou }}</p>
<p>The code will expire on {{ .expiry_date }}, at {{ .expiry_time }} UTC, which is in {{ .expires_in }}.</p> <p>{{ .codeExpiry }}</p>
<p>If this wasn't you, please ignore this email.</p> <p>{{ .ifItWasNotYou }}</p>
</mj-text> </mj-text>
<mj-button mj-class="blue bold">{{ .pin }}</mj-button> <mj-button mj-class="blue bold">{{ .pinVal }}</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">

View File

@ -1,10 +1,10 @@
Hi {{ .username }}, {{ .helloUser }}
Someone has recently requests a password reset on Jellyfin. {{ .someoneHasRequestedReset }}
If this was you, enter the below pin into the prompt. {{ .ifItWasYou }}
This code will expire on {{ .expiry_date }}, at {{ .expiry_time }} UTC, which is in {{ .expires_in }}. {{ .codeExpiry }}
If this wasn't you, please ignore this email. {{ .ifItWasNotYou }}
PIN: {{ .pin }} {{ .pin }}: {{ .pinVal }}
{{ .message }} {{ .message }}

View File

@ -20,15 +20,15 @@
<mj-section mj-class="bg"> <mj-section mj-class="bg">
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<h3>Invite Expired.</h3> <h3>{{ .inviteExpired }}</h3>
<p>Code {{ .code }} expired at {{ .expiry }}.</p> <p>{{ .expiredAt }}</p>
</mj-text> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">
<mj-column> <mj-column>
<mj-text mj-class="secondary" font-style="italic" font-size="14px"> <mj-text mj-class="secondary" font-style="italic" font-size="14px">
Notification emails can be toggled on the admin dashboard. {{ .notificationNotice }}
</mj-text> </mj-text>
</mj-column> </mj-column>
</mj-section> </mj-section>

View File

@ -1,5 +1,5 @@
Invite expired. {{ .inviteExpired }}
Code {{ .code }} expired at {{ .expiry }}. {{ .expiredAt }}
Note: Notification emails can be toggled on the admin dashboard. {{ .notificationNotice }}

View File

@ -20,12 +20,12 @@
<mj-section mj-class="bg"> <mj-section mj-class="bg">
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<p>Hi,</p> <p>{{ .hello }},</p>
<h3>You've been invited to Jellyfin.</h3> <h3>{{ .youHaveBeenInvited }}</h3>
<p>To join, click the button below.</p> <p>{{ .toJoin }}</p>
<p>This invite will expire on {{ .expiry_date }}, at {{ .expiry_time }}, which is in {{ .expires_in }}, so act quick.</p> <p>{{ .inviteExpiry }}</p>
</mj-text> </mj-text>
<mj-button mj-class="blue bold" href="{{ .invite_link }}">Setup your account</mj-button> <mj-button mj-class="blue bold" href="{{ .invite_link }}">{{ .linkButton }}</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
<mj-section mj-class="bg2"> <mj-section mj-class="bg2">

View File

@ -1,7 +1,7 @@
Hi, {{ .hello }},
You've been invited to Jellyfin. {{ .youHaveBeenInvited }}
To join, follow the below link. {{ .toJoin }}
This invite will expire on {{ .expiry_date }}, at {{ .expiry_time }}, which is in {{ .expires_in }}, so act quick. {{ .inviteExpiry }}
{{ .invite_link }} {{ .invite_link }}

View File

@ -370,7 +370,6 @@ func start(asDaemon, firstCall bool) {
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String() app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
app.storage.loadProfiles() app.storage.loadProfiles()
if !(len(app.storage.policy) == 0 && len(app.storage.configuration) == 0 && len(app.storage.displayprefs) == 0) { if !(len(app.storage.policy) == 0 && len(app.storage.configuration) == 0 && len(app.storage.displayprefs) == 0) {
app.info.Println("Migrating user template files to new profile format") app.info.Println("Migrating user template files to new profile format")
app.storage.migrateToProfile() app.storage.migrateToProfile()
@ -515,6 +514,7 @@ func start(asDaemon, firstCall bool) {
} }
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form") app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin") app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin")
app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email")
err = app.storage.loadLang() err = app.storage.loadLang()
if err != nil { if err != nil {
app.info.Fatalf("Failed to load language files: %+v\n", err) app.info.Fatalf("Failed to load language files: %+v\n", err)

View File

@ -20,14 +20,28 @@ type Storage struct {
lang Lang lang Lang
} }
type EmailLang map[string]map[string]map[string]interface{} // Map of lang codes to email name to fields
func (el *EmailLang) format(lang, email, field string, vals ...string) string {
text := (*el)[lang][email][field].(string)
for _, val := range vals {
text = strings.Replace(text, "{n}", val, 1)
}
return text
}
func (el *EmailLang) get(lang, email, field string) string { return (*el)[lang][email][field].(string) }
type Lang struct { type Lang struct {
chosenFormLang string chosenFormLang string
chosenAdminLang string chosenAdminLang string
chosenEmailLang string
AdminPath string AdminPath string
Admin map[string]map[string]interface{} Admin map[string]map[string]interface{}
AdminJSON map[string]string AdminJSON map[string]string
FormPath string FormPath string
Form map[string]map[string]interface{} Form map[string]map[string]interface{}
EmailPath string
Email EmailLang
} }
// timePattern: %Y-%m-%dT%H:%M:%S.%f // timePattern: %Y-%m-%dT%H:%M:%S.%f
@ -80,7 +94,7 @@ func (st *Storage) loadLang() error {
if err != nil { if err != nil {
file = []byte("{}") file = []byte("{}")
} }
// Replace Jellyfin with emby on form // Replace Jellyfin with something if necessary
if substituteStrings != "" { if substituteStrings != "" {
fileString := strings.ReplaceAll(string(file), "Jellyfin", substituteStrings) fileString := strings.ReplaceAll(string(file), "Jellyfin", substituteStrings)
file = []byte(fileString) file = []byte(fileString)
@ -121,6 +135,17 @@ func (st *Storage) loadLang() error {
adminJSON, admin, err := loadData(st.lang.AdminPath, true) adminJSON, admin, err := loadData(st.lang.AdminPath, true)
st.lang.Admin = admin st.lang.Admin = admin
st.lang.AdminJSON = adminJSON st.lang.AdminJSON = adminJSON
_, emails, err := loadData(st.lang.EmailPath, false)
fixedEmails := map[string]map[string]map[string]interface{}{}
for lang, e := range emails {
f := map[string]map[string]interface{}{}
for field, vals := range e {
f[field] = vals.(map[string]interface{})
}
fixedEmails[lang] = f
}
st.lang.Email = fixedEmails
return err return err
} }