mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +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:
parent
965c449f1c
commit
bc99dc34ee
4
api.go
4
api.go
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
66
email.go
66
email.go
@ -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
78
lang/admin/fr-fr.json
Normal 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
41
lang/email/en-us.json
Normal 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
32
lang/email/fr-fr.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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 }}
|
||||||
|
@ -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>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Your Jellyfin account was deleted.
|
{{ .yourAccountWasDeleted }}
|
||||||
Reason: {{ .reason }}
|
{{ .reason }}: {{ .reasonVal }}
|
||||||
|
|
||||||
{{ .message }}
|
{{ .message }}
|
||||||
|
@ -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">
|
||||||
|
@ -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 }}
|
||||||
|
@ -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>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Invite expired.
|
{{ .inviteExpired }}
|
||||||
|
|
||||||
Code {{ .code }} expired at {{ .expiry }}.
|
{{ .expiredAt }}
|
||||||
|
|
||||||
Note: Notification emails can be toggled on the admin dashboard.
|
{{ .notificationNotice }}
|
||||||
|
@ -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">
|
||||||
|
@ -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 }}
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@ -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)
|
||||||
|
27
storage.go
27
storage.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user