mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-10-31 23:40:11 +00:00
Restructure language loading to support incomplete translations
On startup, files are scanned and any missing values are replaced with the english version.
This commit is contained in:
parent
1aadd12006
commit
e834445b0b
51
api.go
51
api.go
@ -1085,33 +1085,15 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
app.info.Println("Config requested")
|
app.info.Println("Config requested")
|
||||||
resp := app.configBase
|
resp := app.configBase
|
||||||
// Load language options
|
// Load language options
|
||||||
loadLangs := func(langs *map[string]map[string]interface{}, settingsKey string) (string, []string) {
|
formChosen, formOptions := app.storage.lang.Form.getOptions(app.config.Section("ui").Key("language-form").MustString("en-us"))
|
||||||
langOptions := make([]string, len(*langs))
|
|
||||||
chosenLang := app.config.Section("ui").Key("language" + settingsKey).MustString("en-us")
|
|
||||||
chosenLangName := (*langs)[chosenLang]["meta"].(map[string]interface{})["name"].(string)
|
|
||||||
i := 0
|
|
||||||
for _, lang := range *langs {
|
|
||||||
langOptions[i] = lang["meta"].(map[string]interface{})["name"].(string)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return chosenLangName, langOptions
|
|
||||||
}
|
|
||||||
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 := app.storage.lang.Admin.getOptions(app.config.Section("ui").Key("language-admin").MustString("en-us"))
|
||||||
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))
|
emailChosen, emailOptions := app.storage.lang.Email.getOptions(app.config.Section("email").Key("language").MustString("en-us"))
|
||||||
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 := resp.Sections["email"].Settings["language"]
|
||||||
el.Options = emailOptions
|
el.Options = emailOptions
|
||||||
el.Value = emailChosen
|
el.Value = emailChosen
|
||||||
@ -1136,7 +1118,7 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
|
|
||||||
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,21 +1151,21 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
|
|||||||
for setting, value := range settings.(map[string]interface{}) {
|
for setting, value := range settings.(map[string]interface{}) {
|
||||||
if section == "ui" && setting == "language-form" {
|
if section == "ui" && setting == "language-form" {
|
||||||
for key, lang := range app.storage.lang.Form {
|
for key, lang := range app.storage.lang.Form {
|
||||||
if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) {
|
if lang.Meta.Name == value.(string) {
|
||||||
tempConfig.Section("ui").Key("language-form").SetValue(key)
|
tempConfig.Section("ui").Key("language-form").SetValue(key)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if section == "ui" && setting == "language-admin" {
|
} else if section == "ui" && setting == "language-admin" {
|
||||||
for key, lang := range app.storage.lang.Admin {
|
for key, lang := range app.storage.lang.Admin {
|
||||||
if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) {
|
if lang.Meta.Name == value.(string) {
|
||||||
tempConfig.Section("ui").Key("language-admin").SetValue(key)
|
tempConfig.Section("ui").Key("language-admin").SetValue(key)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if section == "email" && setting == "language" {
|
} else if section == "email" && setting == "language" {
|
||||||
for key := range app.storage.lang.Email {
|
for key, lang := range app.storage.lang.Email {
|
||||||
if app.storage.lang.Email.get(key, "meta", "name") == value.(string) {
|
if lang.Meta.Name == value.(string) {
|
||||||
tempConfig.Section("email").Key("language").SetValue(key)
|
tempConfig.Section("email").Key("language").SetValue(key)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1260,11 +1242,11 @@ func (app *appContext) GetLanguages(gc *gin.Context) {
|
|||||||
resp := langDTO{}
|
resp := langDTO{}
|
||||||
if page == "form" {
|
if page == "form" {
|
||||||
for key, lang := range app.storage.lang.Form {
|
for key, lang := range app.storage.lang.Form {
|
||||||
resp[key] = lang["meta"].(map[string]interface{})["name"].(string)
|
resp[key] = lang.Meta.Name
|
||||||
}
|
}
|
||||||
} else if page == "admin" {
|
} else if page == "admin" {
|
||||||
for key, lang := range app.storage.lang.Admin {
|
for key, lang := range app.storage.lang.Admin {
|
||||||
resp[key] = lang["meta"].(map[string]interface{})["name"].(string)
|
resp[key] = lang.Meta.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(resp) == 0 {
|
if len(resp) == 0 {
|
||||||
@ -1274,6 +1256,19 @@ func (app *appContext) GetLanguages(gc *gin.Context) {
|
|||||||
gc.JSON(200, resp)
|
gc.JSON(200, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *appContext) ServeLang(gc *gin.Context) {
|
||||||
|
page := gc.Param("page")
|
||||||
|
lang := strings.Replace(gc.Param("file"), ".json", "", 1)
|
||||||
|
if page == "admin" {
|
||||||
|
gc.JSON(200, app.storage.lang.Admin[lang])
|
||||||
|
return
|
||||||
|
} else if page == "form" {
|
||||||
|
gc.JSON(200, app.storage.lang.Form[lang])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
}
|
||||||
|
|
||||||
// func Restart() error {
|
// func Restart() error {
|
||||||
// defer func() {
|
// defer func() {
|
||||||
// if r := recover(); r != nil {
|
// if r := recover(); r != nil {
|
||||||
|
65
email.go
65
email.go
@ -73,8 +73,7 @@ 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
|
lang emailLang
|
||||||
cLang string
|
|
||||||
sender emailClient
|
sender emailClient
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +109,7 @@ 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),
|
lang: app.storage.lang.Email[app.storage.lang.chosenEmailLang],
|
||||||
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" {
|
||||||
@ -137,7 +135,7 @@ func (emailer *Emailer) NewMailgun(url, key string) {
|
|||||||
sender := &Mailgun{
|
sender := &Mailgun{
|
||||||
client: mailgun.NewMailgun(strings.Split(emailer.fromAddr, "@")[1], key),
|
client: mailgun.NewMailgun(strings.Split(emailer.fromAddr, "@")[1], key),
|
||||||
}
|
}
|
||||||
// Mailgun client takes the base url, so we need to trim off the end (e.g 'v3/messages'
|
// Mailgun client takes the base url, so we need to trim off the end (e.g 'v3/messages')
|
||||||
if strings.Contains(url, "messages") {
|
if strings.Contains(url, "messages") {
|
||||||
url = url[0:strings.LastIndex(url, "/")]
|
url = url[0:strings.LastIndex(url, "/")]
|
||||||
url = url[0:strings.LastIndex(url, "/")]
|
url = url[0:strings.LastIndex(url, "/")]
|
||||||
@ -157,9 +155,8 @@ 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").MustString(emailer.lang.get(lang, "inviteEmail", "title")),
|
subject: app.config.Section("invite_emails").Key("subject").MustString(emailer.lang.InviteEmail.get("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)
|
||||||
@ -175,11 +172,11 @@ 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{
|
||||||
"hello": emailer.lang.get(lang, "inviteEmail", "hello"),
|
"hello": emailer.lang.InviteEmail.get("hello"),
|
||||||
"youHaveBeenInvited": emailer.lang.get(lang, "inviteEmail", "youHaveBeenInvited"),
|
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
|
||||||
"toJoin": emailer.lang.get(lang, "inviteEmail", "toJoin"),
|
"toJoin": emailer.lang.InviteEmail.get("toJoin"),
|
||||||
"inviteExpiry": emailer.lang.format(lang, "inviteEmail", "inviteExpiry", d, t, expiresIn),
|
"inviteExpiry": emailer.lang.InviteEmail.format("inviteExpiry", d, t, expiresIn),
|
||||||
"linkButton": emailer.lang.get(lang, "inviteEmail", "linkButton"),
|
"linkButton": emailer.lang.InviteEmail.get("linkButton"),
|
||||||
"invite_link": inviteLink,
|
"invite_link": inviteLink,
|
||||||
"message": message,
|
"message": message,
|
||||||
})
|
})
|
||||||
@ -196,9 +193,8 @@ 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: emailer.lang.get(lang, "inviteExpiry", "title"),
|
subject: emailer.lang.InviteExpiry.get("title"),
|
||||||
}
|
}
|
||||||
expiry := app.formatDatetime(invite.ValidTill)
|
expiry := app.formatDatetime(invite.ValidTill)
|
||||||
for _, key := range []string{"html", "text"} {
|
for _, key := range []string{"html", "text"} {
|
||||||
@ -209,9 +205,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{
|
||||||
"inviteExpired": emailer.lang.get(lang, "inviteExpiry", "inviteExpired"),
|
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
|
||||||
"expiredAt": emailer.lang.format(lang, "inviteExpiry", "expiredAt", "\""+code+"\"", expiry),
|
"expiredAt": emailer.lang.InviteExpiry.format("expiredAt", "\""+code+"\"", expiry),
|
||||||
"notificationNotice": emailer.lang.get(lang, "inviteExpiry", "notificationNotice"),
|
"notificationNotice": emailer.lang.InviteExpiry.get("notificationNotice"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -226,9 +222,8 @@ 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: emailer.lang.get(lang, "userCreated", "title"),
|
subject: emailer.lang.UserCreated.get("title"),
|
||||||
}
|
}
|
||||||
created := app.formatDatetime(invite.Created)
|
created := app.formatDatetime(invite.Created)
|
||||||
var tplAddress string
|
var tplAddress string
|
||||||
@ -245,14 +240,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{
|
||||||
"aUserWasCreated": emailer.lang.format(lang, "userCreated", "aUserWasCreated", "\""+code+"\""),
|
"aUserWasCreated": emailer.lang.UserCreated.format("aUserWasCreated", "\""+code+"\""),
|
||||||
"name": emailer.lang.get(lang, "userCreated", "name"),
|
"name": emailer.lang.UserCreated.get("name"),
|
||||||
"address": emailer.lang.get(lang, "userCreated", "emailAddress"),
|
"address": emailer.lang.UserCreated.get("emailAddress"),
|
||||||
"time": emailer.lang.get(lang, "userCreated", "time"),
|
"time": emailer.lang.UserCreated.get("time"),
|
||||||
"nameVal": username,
|
"nameVal": username,
|
||||||
"addressVal": tplAddress,
|
"addressVal": tplAddress,
|
||||||
"timeVal": created,
|
"timeVal": created,
|
||||||
"notificationNotice": emailer.lang.get(lang, "userCreated", "notificationNotice"),
|
"notificationNotice": emailer.lang.UserCreated.get("notificationNotice"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -267,9 +262,8 @@ 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: emailer.lang.get(lang, "passwordReset", "title"),
|
subject: emailer.lang.PasswordReset.get("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()
|
||||||
@ -281,12 +275,12 @@ 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{
|
||||||
"helloUser": emailer.lang.format(lang, "passwordReset", "helloUser", pwr.Username),
|
"helloUser": emailer.lang.PasswordReset.format("helloUser", pwr.Username),
|
||||||
"someoneHasRequestedReset": emailer.lang.get(lang, "passwordReset", "someoneHasRequestedReset"),
|
"someoneHasRequestedReset": emailer.lang.PasswordReset.get("someoneHasRequestedReset"),
|
||||||
"ifItWasYou": emailer.lang.get(lang, "passwordReset", "ifItWasYou"),
|
"ifItWasYou": emailer.lang.PasswordReset.get("ifItWasYou"),
|
||||||
"codeExpiry": emailer.lang.format(lang, "passwordReset", "codeExpiry", d, t, expiresIn),
|
"codeExpiry": emailer.lang.PasswordReset.format("codeExpiry", d, t, expiresIn),
|
||||||
"ifItWasNotYou": emailer.lang.get(lang, "passwordReset", "ifItWasNotYou"),
|
"ifItWasNotYou": emailer.lang.PasswordReset.get("ifItWasNotYou"),
|
||||||
"pin": emailer.lang.get(lang, "passwordReset", "pin"),
|
"pin": emailer.lang.PasswordReset.get("pin"),
|
||||||
"pinVal": pwr.Pin,
|
"pinVal": pwr.Pin,
|
||||||
"message": message,
|
"message": message,
|
||||||
})
|
})
|
||||||
@ -303,9 +297,8 @@ 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: emailer.lang.get(lang, "userDeleted", "title"),
|
subject: emailer.lang.UserDeleted.get("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()
|
||||||
@ -315,8 +308,8 @@ 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{
|
||||||
"yourAccountWasDeleted": emailer.lang.get(lang, "userDeleted", "yourAccountWasDeleted"),
|
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||||
"reason": emailer.lang.get(lang, "userDeleted", "reason"),
|
"reason": emailer.lang.UserDeleted.get("reason"),
|
||||||
"reasonVal": reason,
|
"reasonVal": reason,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -253,9 +253,9 @@
|
|||||||
<div class="card ~neutral !low accounts mb-1">
|
<div class="card ~neutral !low accounts mb-1">
|
||||||
<span class="heading">{{ .strings.accounts }}</span>
|
<span class="heading">{{ .strings.accounts }}</span>
|
||||||
<div class="fr">
|
<div class="fr">
|
||||||
<span class="button ~neutral !normal" id="accounts-add-user">{{ .quantityStrings.addUser.singular }}</span>
|
<span class="button ~neutral !normal" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
|
||||||
<span class="button ~urge !normal" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
<span class="button ~urge !normal" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||||
<span class="button ~critical !normal" id="accounts-delete-user">{{ .quantityStrings.deleteUser.singular }}</span>
|
<span class="button ~critical !normal" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
|
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
76
lang.go
76
lang.go
@ -1,5 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
type langMeta struct {
|
type langMeta struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
@ -9,14 +11,80 @@ type quantityString struct {
|
|||||||
Plural string `json:"plural"`
|
Plural string `json:"plural"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type adminLangs map[string]adminLang
|
||||||
|
|
||||||
|
func (ls *adminLangs) getOptions(chosen string) (string, []string) {
|
||||||
|
opts := make([]string, len(*ls))
|
||||||
|
chosenLang := (*ls)[chosen].Meta.Name
|
||||||
|
i := 0
|
||||||
|
for _, lang := range *ls {
|
||||||
|
opts[i] = lang.Meta.Name
|
||||||
|
}
|
||||||
|
return chosenLang, opts
|
||||||
|
}
|
||||||
|
|
||||||
type adminLang struct {
|
type adminLang struct {
|
||||||
Meta langMeta `json:"meta"`
|
Meta langMeta `json:"meta"`
|
||||||
Strings map[string]string `json:"strings"`
|
Strings langSection `json:"strings"`
|
||||||
Notifications map[string]string `json:"notifications"`
|
Notifications langSection `json:"notifications"`
|
||||||
QuantityStrings map[string]quantityString `json:"quantityStrings"`
|
QuantityStrings map[string]quantityString `json:"quantityStrings"`
|
||||||
|
JSON string
|
||||||
|
}
|
||||||
|
|
||||||
|
type formLangs map[string]formLang
|
||||||
|
|
||||||
|
func (ls *formLangs) getOptions(chosen string) (string, []string) {
|
||||||
|
opts := make([]string, len(*ls))
|
||||||
|
chosenLang := (*ls)[chosen].Meta.Name
|
||||||
|
i := 0
|
||||||
|
for _, lang := range *ls {
|
||||||
|
opts[i] = lang.Meta.Name
|
||||||
|
}
|
||||||
|
return chosenLang, opts
|
||||||
}
|
}
|
||||||
|
|
||||||
type formLang struct {
|
type formLang struct {
|
||||||
Strings map[string]string `json:"strings"`
|
Meta langMeta `json:"meta"`
|
||||||
ValidationStrings map[string]quantityString `json:"validationStrings"`
|
Strings langSection `json:"strings"`
|
||||||
|
ValidationStrings map[string]quantityString `json:"validationStrings"`
|
||||||
|
validationStringsJSON string
|
||||||
|
}
|
||||||
|
|
||||||
|
type emailLangs map[string]emailLang
|
||||||
|
|
||||||
|
func (ls *emailLangs) getOptions(chosen string) (string, []string) {
|
||||||
|
opts := make([]string, len(*ls))
|
||||||
|
chosenLang := (*ls)[chosen].Meta.Name
|
||||||
|
i := 0
|
||||||
|
for _, lang := range *ls {
|
||||||
|
opts[i] = lang.Meta.Name
|
||||||
|
}
|
||||||
|
return chosenLang, opts
|
||||||
|
}
|
||||||
|
|
||||||
|
type emailLang struct {
|
||||||
|
Meta langMeta `json:"meta"`
|
||||||
|
UserCreated langSection `json:"userCreated"`
|
||||||
|
InviteExpiry langSection `json:"inviteExpiry"`
|
||||||
|
PasswordReset langSection `json:"passwordReset"`
|
||||||
|
UserDeleted langSection `json:"userDeleted"`
|
||||||
|
InviteEmail langSection `json:"inviteEmail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type langSection map[string]string
|
||||||
|
|
||||||
|
func (el *langSection) format(field string, vals ...string) string {
|
||||||
|
text := el.get(field)
|
||||||
|
for _, val := range vals {
|
||||||
|
text = strings.Replace(text, "{n}", val, 1)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (el *langSection) get(field string) string {
|
||||||
|
t, ok := (*el)[field]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
3
main.go
3
main.go
@ -569,7 +569,6 @@ func start(asDaemon, firstCall bool) {
|
|||||||
|
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
router.Use(static.Serve("/", static.LocalFile(filepath.Join(app.localPath, "web"), false)))
|
router.Use(static.Serve("/", static.LocalFile(filepath.Join(app.localPath, "web"), false)))
|
||||||
router.Use(static.Serve("/lang/", static.LocalFile(filepath.Join(app.localPath, "lang"), false)))
|
|
||||||
app.loadHTML(router)
|
app.loadHTML(router)
|
||||||
router.NoRoute(app.NoRouteHandler)
|
router.NoRoute(app.NoRouteHandler)
|
||||||
if debugMode {
|
if debugMode {
|
||||||
@ -580,7 +579,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
router.GET("/", app.AdminPage)
|
router.GET("/", app.AdminPage)
|
||||||
router.GET("/accounts", app.AdminPage)
|
router.GET("/accounts", app.AdminPage)
|
||||||
router.GET("/settings", app.AdminPage)
|
router.GET("/settings", app.AdminPage)
|
||||||
|
router.GET("/lang/:page/:file", app.ServeLang)
|
||||||
router.GET("/lang/:page", app.GetLanguages)
|
router.GET("/lang/:page", app.GetLanguages)
|
||||||
router.GET("/token/login", app.getTokenLogin)
|
router.GET("/token/login", app.getTokenLogin)
|
||||||
router.GET("/token/refresh", app.getTokenRefresh)
|
router.GET("/token/refresh", app.getTokenRefresh)
|
||||||
|
364
storage.go
364
storage.go
@ -20,36 +20,6 @@ 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.get(lang, email, field)
|
|
||||||
for _, val := range vals {
|
|
||||||
text = strings.Replace(text, "{n}", val, 1)
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
func (el *EmailLang) get(lang, email, field string) string {
|
|
||||||
t, ok := (*el)[lang][email][field]
|
|
||||||
if !ok {
|
|
||||||
t = (*el)["en-us"][email][field]
|
|
||||||
}
|
|
||||||
return t.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Lang struct {
|
|
||||||
chosenFormLang string
|
|
||||||
chosenAdminLang string
|
|
||||||
chosenEmailLang string
|
|
||||||
AdminPath string
|
|
||||||
Admin map[string]map[string]interface{}
|
|
||||||
AdminJSON map[string]string
|
|
||||||
FormPath string
|
|
||||||
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
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
@ -73,6 +43,202 @@ type Invite struct {
|
|||||||
Profile string `json:"profile"`
|
Profile string `json:"profile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Lang struct {
|
||||||
|
chosenFormLang string
|
||||||
|
chosenAdminLang string
|
||||||
|
chosenEmailLang string
|
||||||
|
AdminPath string
|
||||||
|
Admin adminLangs
|
||||||
|
AdminJSON map[string]string
|
||||||
|
FormPath string
|
||||||
|
Form formLangs
|
||||||
|
EmailPath string
|
||||||
|
Email emailLangs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadLang() (err error) {
|
||||||
|
err = st.loadLangAdmin()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = st.loadLangForm()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = st.loadLangEmail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a given language has missing values, fill it in with the english value.
|
||||||
|
func patchLang(english, other *langSection) {
|
||||||
|
for n, ev := range *english {
|
||||||
|
if v, ok := (*other)[n]; !ok || v == "" {
|
||||||
|
(*other)[n] = ev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func patchQuantityStrings(english, other *map[string]quantityString) {
|
||||||
|
for n, ev := range *english {
|
||||||
|
qs, ok := (*other)[n]
|
||||||
|
if !ok {
|
||||||
|
(*other)[n] = ev
|
||||||
|
return
|
||||||
|
} else if qs.Singular == "" {
|
||||||
|
qs.Singular = ev.Singular
|
||||||
|
} else if (*other)[n].Plural == "" {
|
||||||
|
qs.Plural = ev.Plural
|
||||||
|
}
|
||||||
|
(*other)[n] = qs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadLangAdmin() error {
|
||||||
|
st.lang.Admin = map[string]adminLang{}
|
||||||
|
var english adminLang
|
||||||
|
load := func(fname string) error {
|
||||||
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||||
|
lang := adminLang{}
|
||||||
|
f, err := ioutil.ReadFile(filepath.Join(st.lang.AdminPath, fname))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if substituteStrings != "" {
|
||||||
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(f, &lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fname != "en-us.json" {
|
||||||
|
patchLang(&english.Strings, &lang.Strings)
|
||||||
|
patchLang(&english.Notifications, &lang.Notifications)
|
||||||
|
patchQuantityStrings(&english.QuantityStrings, &lang.QuantityStrings)
|
||||||
|
}
|
||||||
|
stringAdmin, err := json.Marshal(lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lang.JSON = string(stringAdmin)
|
||||||
|
st.lang.Admin[index] = lang
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := load("en-us.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
english = st.lang.Admin["en-us"]
|
||||||
|
files, err := ioutil.ReadDir(st.lang.AdminPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name() != "en-us.json" {
|
||||||
|
err = load(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadLangForm() error {
|
||||||
|
st.lang.Form = map[string]formLang{}
|
||||||
|
var english formLang
|
||||||
|
load := func(fname string) error {
|
||||||
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||||
|
lang := formLang{}
|
||||||
|
f, err := ioutil.ReadFile(filepath.Join(st.lang.FormPath, fname))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if substituteStrings != "" {
|
||||||
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(f, &lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fname != "en-us.json" {
|
||||||
|
patchLang(&english.Strings, &lang.Strings)
|
||||||
|
patchQuantityStrings(&english.ValidationStrings, &lang.ValidationStrings)
|
||||||
|
}
|
||||||
|
validationStrings, err := json.Marshal(lang.ValidationStrings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lang.validationStringsJSON = string(validationStrings)
|
||||||
|
st.lang.Form[index] = lang
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := load("en-us.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
english = st.lang.Form["en-us"]
|
||||||
|
files, err := ioutil.ReadDir(st.lang.FormPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name() != "en-us.json" {
|
||||||
|
err = load(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadLangEmail() error {
|
||||||
|
st.lang.Email = map[string]emailLang{}
|
||||||
|
var english emailLang
|
||||||
|
load := func(fname string) error {
|
||||||
|
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||||
|
lang := emailLang{}
|
||||||
|
f, err := ioutil.ReadFile(filepath.Join(st.lang.EmailPath, fname))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if substituteStrings != "" {
|
||||||
|
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(f, &lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fname != "en-us.json" {
|
||||||
|
patchLang(&english.UserCreated, &lang.UserCreated)
|
||||||
|
patchLang(&english.InviteExpiry, &lang.InviteExpiry)
|
||||||
|
patchLang(&english.PasswordReset, &lang.PasswordReset)
|
||||||
|
patchLang(&english.UserDeleted, &lang.UserDeleted)
|
||||||
|
patchLang(&english.InviteEmail, &lang.InviteEmail)
|
||||||
|
}
|
||||||
|
st.lang.Email[index] = lang
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := load("en-us.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
english = st.lang.Email["en-us"]
|
||||||
|
files, err := ioutil.ReadDir(st.lang.EmailPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name() != "en-us.json" {
|
||||||
|
err = load(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Invites map[string]Invite
|
type Invites map[string]Invite
|
||||||
|
|
||||||
func (st *Storage) loadInvites() error {
|
func (st *Storage) loadInvites() error {
|
||||||
@ -83,75 +249,75 @@ func (st *Storage) storeInvites() error {
|
|||||||
return storeJSON(st.invite_path, st.invites)
|
return storeJSON(st.invite_path, st.invites)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *Storage) loadLang() error {
|
// func (st *Storage) loadLang() error {
|
||||||
loadData := func(path string, stringJson bool) (map[string]string, map[string]map[string]interface{}, error) {
|
// loadData := func(path string, stringJson bool) (map[string]string, map[string]map[string]interface{}, error) {
|
||||||
files, err := ioutil.ReadDir(path)
|
// files, err := ioutil.ReadDir(path)
|
||||||
outString := map[string]string{}
|
// outString := map[string]string{}
|
||||||
out := map[string]map[string]interface{}{}
|
// out := map[string]map[string]interface{}{}
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, err
|
// return nil, nil, err
|
||||||
}
|
// }
|
||||||
for _, f := range files {
|
// for _, f := range files {
|
||||||
index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
// index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
||||||
var data map[string]interface{}
|
// var data map[string]interface{}
|
||||||
var file []byte
|
// var file []byte
|
||||||
var err error
|
// var err error
|
||||||
file, err = ioutil.ReadFile(filepath.Join(path, f.Name()))
|
// file, err = ioutil.ReadFile(filepath.Join(path, f.Name()))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
file = []byte("{}")
|
// file = []byte("{}")
|
||||||
}
|
// }
|
||||||
// Replace Jellyfin with something if necessary
|
// // 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)
|
||||||
}
|
// }
|
||||||
err = json.Unmarshal(file, &data)
|
// err = json.Unmarshal(file, &data)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
// log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
||||||
return nil, nil, err
|
// return nil, nil, err
|
||||||
}
|
// }
|
||||||
if stringJson {
|
// if stringJson {
|
||||||
stringJSON, err := json.Marshal(data)
|
// stringJSON, err := json.Marshal(data)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, err
|
// return nil, nil, err
|
||||||
}
|
// }
|
||||||
outString[index] = string(stringJSON)
|
// outString[index] = string(stringJSON)
|
||||||
}
|
// }
|
||||||
out[index] = data
|
// out[index] = data
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
return outString, out, nil
|
// return outString, out, nil
|
||||||
}
|
// }
|
||||||
_, form, err := loadData(st.lang.FormPath, false)
|
// _, form, err := loadData(st.lang.FormPath, false)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
for index, lang := range form {
|
// for index, lang := range form {
|
||||||
validationStrings := lang["validationStrings"].(map[string]interface{})
|
// validationStrings := lang["validationStrings"].(map[string]interface{})
|
||||||
vS, err := json.Marshal(validationStrings)
|
// vS, err := json.Marshal(validationStrings)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
lang["validationStrings"] = string(vS)
|
// lang["validationStrings"] = string(vS)
|
||||||
form[index] = lang
|
// form[index] = lang
|
||||||
}
|
// }
|
||||||
st.lang.Form = form
|
// st.lang.Form = form
|
||||||
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)
|
// _, emails, err := loadData(st.lang.EmailPath, false)
|
||||||
fixedEmails := map[string]map[string]map[string]interface{}{}
|
// fixedEmails := map[string]map[string]map[string]interface{}{}
|
||||||
for lang, e := range emails {
|
// for lang, e := range emails {
|
||||||
f := map[string]map[string]interface{}{}
|
// f := map[string]map[string]interface{}{}
|
||||||
for field, vals := range e {
|
// for field, vals := range e {
|
||||||
f[field] = vals.(map[string]interface{})
|
// f[field] = vals.(map[string]interface{})
|
||||||
}
|
// }
|
||||||
fixedEmails[lang] = f
|
// fixedEmails[lang] = f
|
||||||
}
|
// }
|
||||||
st.lang.Email = fixedEmails
|
// st.lang.Email = fixedEmails
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (st *Storage) loadEmails() error {
|
func (st *Storage) loadEmails() error {
|
||||||
return loadJSON(st.emails_path, &st.emails)
|
return loadJSON(st.emails_path, &st.emails)
|
||||||
|
12
views.go
12
views.go
@ -45,7 +45,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
lang := gc.Query("lang")
|
lang := gc.Query("lang")
|
||||||
if lang == "" {
|
if lang == "" {
|
||||||
lang = app.storage.lang.chosenAdminLang
|
lang = app.storage.lang.chosenAdminLang
|
||||||
} else if _, ok := app.storage.lang.Form[lang]; !ok {
|
} else if _, ok := app.storage.lang.Admin[lang]; !ok {
|
||||||
lang = app.storage.lang.chosenAdminLang
|
lang = app.storage.lang.chosenAdminLang
|
||||||
}
|
}
|
||||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
||||||
@ -61,9 +61,9 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
"commit": COMMIT,
|
"commit": COMMIT,
|
||||||
"ombiEnabled": ombiEnabled,
|
"ombiEnabled": ombiEnabled,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"strings": app.storage.lang.Admin[lang]["strings"],
|
"strings": app.storage.lang.Admin[lang].Strings,
|
||||||
"quantityStrings": app.storage.lang.Admin[lang]["quantityStrings"],
|
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
||||||
"language": app.storage.lang.AdminJSON[lang],
|
"language": app.storage.lang.Admin[lang].JSON,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +94,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"requirements": app.validator.getCriteria(),
|
"requirements": app.validator.getCriteria(),
|
||||||
"email": email,
|
"email": email,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"strings": app.storage.lang.Form[lang]["strings"],
|
"strings": app.storage.lang.Form[lang].Strings,
|
||||||
"validationStrings": app.storage.lang.Form[lang]["validationStrings"],
|
"validationStrings": app.storage.lang.Form[lang].validationStringsJSON,
|
||||||
"code": code,
|
"code": code,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user