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

Compare commits

..

6 Commits

Author SHA1 Message Date
75796a3981
add optional tls/http2 support
Allows for http2 server push, see the advanced section.
2021-01-15 14:41:44 +00:00
781047058f
update CONTRIBUTING.md 2021-01-15 13:40:42 +00:00
0ac61a59b2
fix display of username box on add account modal 2021-01-15 13:37:09 +00:00
732ed5b0b8
add finished french for admin 2021-01-15 13:30:29 +00:00
42ddbab6cf
fix spelling in french email 2021-01-14 21:39:06 +00:00
432d795880
Fix email language selection, add finished french emails 2021-01-14 20:24:28 +00:00
8 changed files with 155 additions and 15 deletions

3
.gitignore vendored
View File

@ -9,3 +9,6 @@ docs/*
lang/langtostruct.py lang/langtostruct.py
config-payload.json config-payload.json
!docs/go.mod !docs/go.mod
server.key
server.pem
server.crt

View File

@ -1,5 +1,5 @@
#### Translation #### Translation
Currently only the account creation form can be translated. Strings are defined in `lang/form/<country-code>.json` (country code as in `en-us`, `fr-fr`, e.g). You can see the existing ones [here](https://github.com/hrfee/jfa-go/tree/main/lang/form). Currently the admin page, account creation form and emails can be translated. Strings are defined in `lang/<admin/form/email>/<country-code>.json` (country code as in `en-us`, `fr-fr`, e.g). You can see the existing ones [here](https://github.com/hrfee/jfa-go/tree/main/lang).
Make sure to define `name` in the `meta` section, and you can optionally add an `author` value there as well. If you can, make a pull request with your new file. If not, email me or create an issue. Make sure to define `name` in the `meta` section, and you can optionally add an `author` value there as well. If you can, make a pull request with your new file. If not, email me or create an issue.
#### Code #### Code

27
api.go
View File

@ -1087,7 +1087,7 @@ func (app *appContext) GetConfig(gc *gin.Context) {
// Load language options // Load language options
loadLangs := func(langs *map[string]map[string]interface{}, settingsKey string) (string, []string) { loadLangs := func(langs *map[string]map[string]interface{}, settingsKey string) (string, []string) {
langOptions := make([]string, len(*langs)) langOptions := make([]string, len(*langs))
chosenLang := app.config.Section("ui").Key("language-" + settingsKey).MustString("en-us") chosenLang := app.config.Section("ui").Key("language" + settingsKey).MustString("en-us")
chosenLangName := (*langs)[chosenLang]["meta"].(map[string]interface{})["name"].(string) chosenLangName := (*langs)[chosenLang]["meta"].(map[string]interface{})["name"].(string)
i := 0 i := 0
for _, lang := range *langs { for _, lang := range *langs {
@ -1096,14 +1096,25 @@ func (app *appContext) GetConfig(gc *gin.Context) {
} }
return chosenLangName, langOptions return chosenLangName, langOptions
} }
formChosen, formOptions := loadLangs(&app.storage.lang.Form, "form") formChosen, formOptions := loadLangs(&app.storage.lang.Form, "-form")
fl := resp.Sections["ui"].Settings["language-form"] fl := resp.Sections["ui"].Settings["language-form"]
fl.Options = formOptions fl.Options = formOptions
fl.Value = formChosen fl.Value = formChosen
adminChosen, adminOptions := loadLangs(&app.storage.lang.Admin, "admin") adminChosen, adminOptions := loadLangs(&app.storage.lang.Admin, "-admin")
al := resp.Sections["ui"].Settings["language-admin"] al := resp.Sections["ui"].Settings["language-admin"]
al.Options = adminOptions al.Options = adminOptions
al.Value = adminChosen al.Value = adminChosen
emailOptions := make([]string, len(app.storage.lang.Email))
chosenLang := app.config.Section("email").Key("language").MustString("en-us")
emailChosen := app.storage.lang.Email.get(chosenLang, "meta", "name")
i := 0
for langName := range app.storage.lang.Email {
emailOptions[i] = app.storage.lang.Email.get(langName, "meta", "name")
i++
}
el := resp.Sections["email"].Settings["language"]
el.Options = emailOptions
el.Value = emailChosen
for sectName, section := range resp.Sections { for sectName, section := range resp.Sections {
for settingName, setting := range section.Settings { for settingName, setting := range section.Settings {
val := app.config.Section(sectName).Key(settingName) val := app.config.Section(sectName).Key(settingName)
@ -1121,10 +1132,11 @@ func (app *appContext) GetConfig(gc *gin.Context) {
} }
resp.Sections["ui"].Settings["language-form"] = fl resp.Sections["ui"].Settings["language-form"] = fl
resp.Sections["ui"].Settings["language-admin"] = al resp.Sections["ui"].Settings["language-admin"] = al
resp.Sections["email"].Settings["language"] = el
t := resp.Sections["jellyfin"].Settings["type"] t := resp.Sections["jellyfin"].Settings["type"]
opts := make([]string, len(serverTypes)) opts := make([]string, len(serverTypes))
i := 0 i = 0
for _, v := range serverTypes { for _, v := range serverTypes {
opts[i] = v opts[i] = v
i++ i++
@ -1169,6 +1181,13 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
break break
} }
} }
} else if section == "email" && setting == "language" {
for key := range app.storage.lang.Email {
if app.storage.lang.Email.get(key, "meta", "name") == value.(string) {
tempConfig.Section("email").Key("language").SetValue(key)
break
}
}
} else if section == "jellyfin" && setting == "type" { } else if section == "jellyfin" && setting == "type" {
for k, v := range serverTypes { for k, v := range serverTypes {
if v == value.(string) { if v == value.(string) {

View File

@ -219,6 +219,50 @@
} }
} }
}, },
"advanced": {
"order": [],
"meta": {
"name": "Advanced",
"description": "Advanced settings."
},
"settings": {
"tls": {
"name": "TLS/HTTP2",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Enable TLS, and by extension HTTP2. This enables server push, where required files are pushed to the web browser before they request them, allowing quicker page loads."
},
"tls_port": {
"name": "TLS Port",
"depends_true": "tls",
"required": false,
"requires_restart": true,
"type": "number",
"value": 8057,
"description": "Port to run TLS server on"
},
"tls_cert": {
"name": "Path to TLS Certificate",
"depends_true": "tls",
"required": false,
"requires_restart": true,
"type": "text",
"value": "",
"description": "Path to .crt file. See jfa-go wiki for more info."
},
"tls_key": {
"name": "Path to TLS Key file",
"depends_true": "tls",
"required": false,
"requires_restart": true,
"type": "text",
"value": "",
"description": "Path to .key file. See jfa-go wiki for more info."
}
}
},
"password_validation": { "password_validation": {
"order": [], "order": [],
"meta": { "meta": {

View File

@ -8,7 +8,7 @@
window.notificationsEnabled = {{ .notifications }}; window.notificationsEnabled = {{ .notifications }};
window.emailEnabled = {{ .email_enabled }}; window.emailEnabled = {{ .email_enabled }};
window.ombiEnabled = {{ .ombiEnabled }}; window.ombiEnabled = {{ .ombiEnabled }};
window.usernamesEnabled = {{ .username }}; window.usernameEnabled = {{ .username }};
window.langFile = JSON.parse({{ .language }}); window.langFile = JSON.parse({{ .language }});
</script> </script>
{{ template "header.html" . }} {{ template "header.html" . }}
@ -99,7 +99,7 @@
<span class="heading">{{ .strings.settingsRestartRequired }} <span class="modal-close">&times;</span></span> <span class="heading">{{ .strings.settingsRestartRequired }} <span class="modal-close">&times;</span></span>
<p class="content pb-1">{{ .strings.settingsRestartRequiredDescription }}</p> <p class="content pb-1">{{ .strings.settingsRestartRequiredDescription }}</p>
<div class="fr"> <div class="fr">
<span class="button ~info !normal" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span> <span class="button ~info !normal mb-half" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
<span class="button ~critical !normal" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span> <span class="button ~critical !normal" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
</div> </div>
</div> </div>

View File

@ -22,17 +22,21 @@
"delete": "Effacer", "delete": "Effacer",
"submit": "Soumettre", "submit": "Soumettre",
"name": "Nom", "name": "Nom",
"date": "Date",
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
"password": "Mot de passe", "password": "Mot de passe",
"emailAddress": "Addresse Email", "emailAddress": "Addresse Email",
"lastActiveTime": "Dernière activité", "lastActiveTime": "Dernière activité",
"from": "De", "from": "De",
"user": "Utilisateur", "user": "Utilisateur",
"aboutProgram": "A propros", "aboutProgram": "A propos",
"version": "Version", "version": "Version",
"commitNoun": "Commettre", "commitNoun": "Commettre",
"newUser": "Nouvel utilisateur", "newUser": "Nouvel utilisateur",
"profile": "Profil", "profile": "Profil",
"success": "Succès",
"error": "Erreur",
"unknown": "Inconnu",
"modifySettings": "Modifier les paramètres", "modifySettings": "Modifier les paramètres",
"modifySettingsDescription": "Appliquez les paramètres à partir d'un profil existant ou obtenez-les directement auprès d'un utilisateur.", "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", "applyHomescreenLayout": "Appliquer la disposition de l'écran d'accueil",
@ -55,7 +59,47 @@
"addProfile": "Ajouter un profil", "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. ", "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", "addProfileNameOf": "Nom de profil",
"addProfileStoreHomescreenLayout": "Enregistrer la disposition de l'écran d'accueil" "addProfileStoreHomescreenLayout": "Enregistrer la disposition de l'écran d'accueil",
"inviteNoUsersCreated": "Aucun pour l'instant!",
"inviteUsersCreated": "Utilisateurs créer",
"inviteNoProfile": "Aucun profil",
"copy": "Copier",
"inviteDateCreated": "Créer",
"inviteRemainingUses": "Utilisations restantes",
"inviteNoInvites": "Aucune",
"inviteExpiresInTime": "Expires dans {n}",
"notifyEvent": "Notifier sur:",
"notifyInviteExpiry": "À l'expiration",
"notifyUserCreation": "à la création de l'utilisateur"
},
"notifications": {
"changedEmailAddress": "Adresse e-mail modifiée de {n}.",
"userCreated": "L'utilisateur {n} a été créé.",
"createProfile": "Profil créé {n}.",
"saveSettings": "Les paramètres ont été enregistrés",
"setOmbiDefaults": "Valeurs par défaut de Ombi.",
"errorConnection": "Impossible de se connecter à jfa-go.",
"error401Unauthorized": "Non autorisé. Essayez d'actualiser la page.",
"errorSettingsAppliedNoHomescreenLayout": "Les paramètres ont été appliqués, mais l'application de la disposition de l'écran d'accueil a peut-être échoué.",
"errorHomescreenAppliedNoSettings": "La disposition de l'écran d'accueil a été appliquée, mais l'application des paramètres a peut-être échoué.",
"errorSettingsFailed": "L'application a échoué.",
"errorLoginBlank": "Le nom d'utilisateur et / ou le mot de passe sont vides",
"errorUnknown": "Erreur inconnue.",
"errorBlankFields": "Les champs sont vides",
"errorDeleteProfile": "Échec de la suppression du profil {n}",
"errorLoadProfiles": "Échec du chargement des profils.",
"errorCreateProfile": "Échec de la création du profil {n}",
"errorSetDefaultProfile": "Échec de la définition du profil par défaut",
"errorLoadUsers": "Échec du chargement des utilisateurs.",
"errorSaveSettings": "Impossible d'enregistrer les paramètres.",
"errorLoadSettings": "Échec du chargement des paramètres.",
"errorSetOmbiDefaults": "Impossible de stocker les valeurs par défaut d'Ombi.",
"errorLoadOmbiUsers": "Échec du chargement des utilisateurs Ombi.",
"errorChangedEmailAddress": "Impossible de modifier l'adresse e-mail de {n}.",
"errorFailureCheckLogs": "Échec (vérifier la console / les journaux)",
"errorPartialFailureCheckLogs": "Panne partielle (vérifier la console / les journaux)"
}, },
"quantityStrings": { "quantityStrings": {
"modifySettingsFor": { "modifySettingsFor": {
@ -73,6 +117,14 @@
"deleteUser": { "deleteUser": {
"singular": "Supprimer l'utilisateur", "singular": "Supprimer l'utilisateur",
"plural": "Supprimer les utilisateurs" "plural": "Supprimer les utilisateurs"
},
"deletedUser": {
"singular": "Supprimer {n} utilisateur.",
"plural": "Supprimer {n} utilisateurs."
},
"appliedSettings": {
"singular": "Appliquer le paramètre {n} utilisteur.",
"plural": "Appliquer les paramètres {n} utilisteurs."
} }
} }
} }

View File

@ -4,13 +4,21 @@
"author": "https://github.com/Cornichon420" "author": "https://github.com/Cornichon420"
}, },
"userCreated": { "userCreated": {
"title": "Notification : Utilisateur créé",
"aUserWasCreated": "Un utilisateur a été créé avec ce code {n}", "aUserWasCreated": "Un utilisateur a été créé avec ce code {n}",
"name": "Nom", "name": "Nom",
"emailAddress": "Adresse", "emailAddress": "Adresse",
"time": "Date", "time": "Date",
"notificationNotice": "" "notificationNotice": ""
}, },
"inviteExpiry": {
"title": "Notification : Invitation expirée",
"inviteExpired": "Invitation expirée.",
"expiredAt": "Le code {n} a expiré à {n}.",
"notificationNotice": ""
},
"passwordReset": { "passwordReset": {
"title": "Réinitialisation de mot du passe demandée - Jellyfin",
"helloUser": "Salut {n},", "helloUser": "Salut {n},",
"someoneHasRequestedReset": "Quelqu'un vient de demander une réinitialisation du mot de passe via Jellyfin.", "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.", "ifItWasYou": "Si c'était bien toi, renseigne le code PIN en dessous.",
@ -19,14 +27,16 @@
"pin": "PIN" "pin": "PIN"
}, },
"userDeleted": { "userDeleted": {
"title": "Ton compte a été désactivé - Jellyfin",
"yourAccountWasDeleted": "Ton compte Jellyfin a été supprimé.", "yourAccountWasDeleted": "Ton compte Jellyfin a été supprimé.",
"reason": "Motif" "reason": "Motif"
}, },
"inviteEmail": { "inviteEmail": {
"title": "Invitation - Jellyfin",
"hello": "Salut", "hello": "Salut",
"youHaveBeenInvited": "Tu a été invité à rejoindre Jellyfin.", "youHaveBeenInvited": "Tu as été invité à rejoindre Jellyfin.",
"toJoin": "Pour continuer, suis le lien en dessous.", "toJoin": "Pour continuer, suis le lien en dessous.",
"inviteExpiry": "L'invitation expirera le {n}, à {n}, sout dans {n}, alors fais vite !", "inviteExpiry": "L'invitation expirera le {n}, à {n}, soit dans {n}, alors fais vite !",
"linkButton": "Lien" "linkButton": "Lien"
} }
} }

20
main.go
View File

@ -331,7 +331,12 @@ func start(asDaemon, firstCall bool) {
if !firstRun { if !firstRun {
app.host = app.config.Section("ui").Key("host").String() app.host = app.config.Section("ui").Key("host").String()
app.port = app.config.Section("ui").Key("port").MustInt(8056) if app.config.Section("advanced").Key("tls").MustBool(false) {
app.info.Println("Using TLS/HTTP2")
app.port = app.config.Section("advanced").Key("tls_port").MustInt(8057)
} else {
app.port = app.config.Section("ui").Key("port").MustInt(8056)
}
if *HOST != app.host && *HOST != "" { if *HOST != app.host && *HOST != "" {
app.host = *HOST app.host = *HOST
@ -350,7 +355,6 @@ func start(asDaemon, firstCall bool) {
} }
} }
} }
address = fmt.Sprintf("%s:%d", app.host, app.port) address = fmt.Sprintf("%s:%d", app.host, app.port)
app.debug.Printf("Loaded config file \"%s\"", app.configPath) app.debug.Printf("Loaded config file \"%s\"", app.configPath)
@ -625,8 +629,16 @@ func start(asDaemon, firstCall bool) {
Handler: router, Handler: router,
} }
go func() { go func() {
if err := SRV.ListenAndServe(); err != nil { if app.config.Section("advanced").Key("tls").MustBool(false) {
app.err.Printf("Failure serving: %s", err) cert := app.config.Section("advanced").Key("tls_cert").MustString("")
key := app.config.Section("advanced").Key("tls_key").MustString("")
if err := SRV.ListenAndServeTLS(cert, key); err != nil {
app.err.Printf("Failure serving: %s", err)
}
} else {
if err := SRV.ListenAndServe(); err != nil {
app.err.Printf("Failure serving: %s", err)
}
} }
}() }()
app.quit = make(chan os.Signal) app.quit = make(chan os.Signal)