Skip to content

Commit

Permalink
Add email translation, add part of french translations
Browse files Browse the repository at this point in the history
Admin translation from @Killianbe, Email translation from
@Cornichon420. French is currently not functional, a few things are
missing which i'm waiting on.
  • Loading branch information
hrfee committed Jan 15, 2021
1 parent 965c449 commit bc99dc3
Show file tree
Hide file tree
Showing 19 changed files with 283 additions and 75 deletions.
4 changes: 2 additions & 2 deletions api.go
Expand Up @@ -455,7 +455,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) {
app.err.Printf("%s: Failed to send to %s", userID, address)
app.debug.Printf("%s: Error: %s", userID, err)
} 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))
}
Expand Down Expand Up @@ -1176,7 +1176,7 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
break
}
}
} else {
} else if value.(string) != app.config.Section(section).Key(setting).MustString("") {
tempConfig.Section(section).Key(setting).SetValue(value.(string))
}
}
Expand Down
7 changes: 4 additions & 3 deletions config.go
Expand Up @@ -52,7 +52,7 @@ func (app *appContext) loadConfig() error {
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("") == "" {
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
// }
Expand Down Expand Up @@ -80,8 +80,6 @@ func (app *appContext) loadConfig() error {
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.email = NewEmailer(app)

substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")

oldFormLang := app.config.Section("ui").Key("language").MustString("")
Expand All @@ -93,6 +91,9 @@ func (app *appContext) loadConfig() error {
app.storage.lang.chosenFormLang = newFormLang
}
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
}
12 changes: 12 additions & 0 deletions config/config-base.json
Expand Up @@ -277,6 +277,18 @@
"description": "General email settings. Ignore if not using email features."
},
"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": {
"name": "Use email addresses as username",
"required": false,
Expand Down
66 changes: 43 additions & 23 deletions email.go
Expand Up @@ -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.
type Emailer struct {
fromAddr, fromName string
lang *EmailLang
cLang string
sender emailClient
}

Expand Down Expand Up @@ -108,6 +110,8 @@ func NewEmailer(app *appContext) *Emailer {
emailer := &Emailer{
fromAddr: app.config.Section("email").Key("address").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()
if method == "smtp" {
Expand Down Expand Up @@ -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) {
lang := emailer.cLang
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
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
Expand All @@ -170,11 +175,13 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
}
var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{
"expiry_date": d,
"expiry_time": t,
"expires_in": expiresIn,
"invite_link": inviteLink,
"message": message,
"hello": emailer.lang.get(lang, "inviteEmail", "hello"),
"youHaveBeenInvited": emailer.lang.get(lang, "inviteEmail", "youHaveBeenInvited"),
"toJoin": emailer.lang.get(lang, "inviteEmail", "toJoin"),
"inviteExpiry": emailer.lang.format(lang, "inviteEmail", "inviteExpiry", d, t, expiresIn),
"linkButton": emailer.lang.get(lang, "inviteEmail", "linkButton"),
"invite_link": inviteLink,
"message": message,
})
if err != nil {
return nil, err
Expand All @@ -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) {
lang := emailer.cLang
email := &Email{
subject: "Notice: Invite expired",
subject: emailer.lang.get(lang, "inviteExpiry", "title"),
}
expiry := app.formatDatetime(invite.ValidTill)
for _, key := range []string{"html", "text"} {
Expand All @@ -201,8 +209,9 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
}
var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{
"code": code,
"expiry": expiry,
"inviteExpired": emailer.lang.get(lang, "inviteExpiry", "inviteExpired"),
"expiredAt": emailer.lang.format(lang, "inviteExpiry", "expiredAt", "\""+code+"\"", expiry),
"notificationNotice": emailer.lang.get(lang, "inviteExpiry", "notificationNotice"),
})
if err != nil {
return nil, err
Expand All @@ -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) {
lang := emailer.cLang
email := &Email{
subject: "Notice: User created",
subject: emailer.lang.get(lang, "userCreated", "title"),
}
created := app.formatDatetime(invite.Created)
var tplAddress string
Expand All @@ -235,10 +245,14 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
}
var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{
"code": code,
"username": username,
"address": tplAddress,
"time": created,
"aUserWasCreated": emailer.lang.format(lang, "userCreated", "aUserWasCreated", "\""+code+"\""),
"name": emailer.lang.get(lang, "userCreated", "name"),
"address": emailer.lang.get(lang, "userCreated", "emailAddress"),
"time": emailer.lang.get(lang, "userCreated", "time"),
"nameVal": username,
"addressVal": tplAddress,
"timeVal": created,
"notificationNotice": emailer.lang.get(lang, "userCreated", "notificationNotice"),
})
if err != nil {
return nil, err
Expand All @@ -253,8 +267,9 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
}

func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Email, error) {
lang := emailer.cLang
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)
message := app.config.Section("email").Key("message").String()
Expand All @@ -266,12 +281,14 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Ema
}
var tplData bytes.Buffer
err = tpl.Execute(&tplData, map[string]string{
"username": pwr.Username,
"expiry_date": d,
"expiry_time": t,
"expires_in": expiresIn,
"pin": pwr.Pin,
"message": message,
"helloUser": emailer.lang.format(lang, "passwordReset", "helloUser", pwr.Username),
"someoneHasRequestedReset": emailer.lang.get(lang, "passwordReset", "someoneHasRequestedReset"),
"ifItWasYou": emailer.lang.get(lang, "passwordReset", "ifItWasYou"),
"codeExpiry": emailer.lang.format(lang, "passwordReset", "codeExpiry", d, t, expiresIn),
"ifItWasNotYou": emailer.lang.get(lang, "passwordReset", "ifItWasNotYou"),
"pin": emailer.lang.get(lang, "passwordReset", "pin"),
"pinVal": pwr.Pin,
"message": message,
})
if err != nil {
return nil, err
Expand All @@ -286,8 +303,9 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Ema
}

func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email, error) {
lang := emailer.cLang
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"} {
fpath := app.config.Section("deletion").Key("email_" + key).String()
Expand All @@ -297,7 +315,9 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email
}
var tplData bytes.Buffer
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 {
return nil, err
Expand Down
78 changes: 78 additions & 0 deletions lang/admin/fr-fr.json
@@ -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 changes: 41 additions & 0 deletions lang/email/en-us.json
@@ -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 changes: 32 additions & 0 deletions lang/email/fr-fr.json
@@ -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.",

This comment has been minimized.

Copy link
@Tonomis

Tonomis Jan 15, 2021

Contributor

a "s" is missing
"youHaveBeenInvited": "Tu as é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"
}
}

0 comments on commit bc99dc3

Please sign in to comment.