From 3fbbc7f620a8d9f87dee79f4a79694603afc8cd6 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Mon, 11 Jan 2021 19:17:43 +0000 Subject: [PATCH] add language selector to form --- api.go | 59 +++++++++++++++++++++---------------- config.go | 2 ++ config/config-base.json | 4 +-- css/base.css | 5 ++++ html/form.html | 10 +++++++ main.go | 20 ++++--------- models.go | 2 ++ storage.go | 64 +++++++++++++++++++++++++---------------- ts/form.ts | 20 ++++++++++++- views.go | 8 +++++- 10 files changed, 126 insertions(+), 68 deletions(-) diff --git a/api.go b/api.go index 9747ab7..f50191b 100644 --- a/api.go +++ b/api.go @@ -3,8 +3,6 @@ package main import ( "encoding/json" "fmt" - "io/ioutil" - "path/filepath" "strconv" "strings" "time" @@ -1088,23 +1086,17 @@ func (app *appContext) GetConfig(gc *gin.Context) { app.info.Println("Config requested") resp := app.configBase // Load language options - langPath := filepath.Join(app.localPath, "lang", "form") - app.lang.langFiles, _ = ioutil.ReadDir(langPath) - app.lang.langOptions = make([]string, len(app.lang.langFiles)) - chosenLang := app.config.Section("ui").Key("language").MustString("en-us") + ".json" - for i, f := range app.lang.langFiles { - if f.Name() == chosenLang { - app.lang.chosenIndex = i - } - var langFile map[string]interface{} - file, _ := ioutil.ReadFile(filepath.Join(langPath, f.Name())) - json.Unmarshal(file, &langFile) - - if meta, ok := langFile["meta"]; ok { - app.lang.langOptions[i] = meta.(map[string]interface{})["name"].(string) - } + langOptions := make([]string, len(app.storage.lang.Form)) + chosenLang := app.config.Section("ui").Key("language").MustString("en-us") + chosenLangName := app.storage.lang.Form[chosenLang]["meta"].(map[string]interface{})["name"].(string) + i := 0 + for _, lang := range app.storage.lang.Form { + langOptions[i] = lang["meta"].(map[string]interface{})["name"].(string) + i++ } - s := resp.Sections["ui"].Settings["language"] + l := resp.Sections["ui"].Settings["language"] + l.Options = langOptions + l.Value = chosenLangName for sectName, section := range resp.Sections { for settingName, setting := range section.Settings { val := app.config.Section(sectName).Key(settingName) @@ -1120,13 +1112,11 @@ func (app *appContext) GetConfig(gc *gin.Context) { resp.Sections[sectName].Settings[settingName] = s } } - s.Options = app.lang.langOptions - s.Value = app.lang.langOptions[app.lang.chosenIndex] - resp.Sections["ui"].Settings["language"] = s + resp.Sections["ui"].Settings["language"] = l t := resp.Sections["jellyfin"].Settings["type"] opts := make([]string, len(serverTypes)) - i := 0 + i = 0 for _, v := range serverTypes { opts[i] = v i++ @@ -1158,9 +1148,9 @@ func (app *appContext) ModifyConfig(gc *gin.Context) { } for setting, value := range settings.(map[string]interface{}) { if section == "ui" && setting == "language" { - for i, lang := range app.lang.langOptions { - if value.(string) == lang { - tempConfig.Section(section).Key(setting).SetValue(strings.Replace(app.lang.langFiles[i].Name(), ".json", "", 1)) + for key, lang := range app.storage.lang.Form { + if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) { + tempConfig.Section("ui").Key("language").SetValue(key) break } } @@ -1225,6 +1215,25 @@ func (app *appContext) Logout(gc *gin.Context) { respondBool(200, true, gc) } +// @Summary Returns a map of available language codes to their full names, usable in the lang query parameter. +// @Produce json +// @Success 200 {object} langDTO +// @Failure 500 {object} stringResponse +// @Router /lang [get] +// @tags Other +func (app *appContext) GetLanguages(gc *gin.Context) { + resp := langDTO{} + for key, lang := range app.storage.lang.Form { + fmt.Printf("%+v\n", lang["meta"]) + resp[key] = lang["meta"].(map[string]interface{})["name"].(string) + } + if len(resp) == 0 { + respond(500, "Couldn't get languages", gc) + return + } + gc.JSON(200, resp) +} + // func Restart() error { // defer func() { // if r := recover(); r != nil { diff --git a/config.go b/config.go index d9c3a44..2e72432 100644 --- a/config.go +++ b/config.go @@ -84,5 +84,7 @@ func (app *appContext) loadConfig() error { substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("") + app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us") + return nil } diff --git a/config/config-base.json b/config/config-base.json index 44fb06b..d32796c 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -85,7 +85,7 @@ }, "settings": { "language": { - "name": "Language", + "name": "Default Form Language", "required": false, "requires_restart": true, "type": "select", @@ -93,7 +93,7 @@ "en-us" ], "value": "en-US", - "description": "UI Language. Currently only implemented for account creation form. Submit a PR on github if you'd like to translate." + "description": "Default UI Language. Currently only implemented for account creation form. Submit a PR on github if you'd like to translate." }, "theme": { "name": "Default Look", diff --git a/css/base.css b/css/base.css index 2b919d2..75fde04 100644 --- a/css/base.css +++ b/css/base.css @@ -370,3 +370,8 @@ p.top { bottom: 1rem; z-index: 16; } + +.dropdown { + padding-bottom: 0.5rem; + margin-bottom: -0.5rem; +} diff --git a/html/form.html b/html/form.html index 111529a..26706d1 100644 --- a/html/form.html +++ b/html/form.html @@ -14,6 +14,16 @@
+ + + + + + +
diff --git a/main.go b/main.go index 8c0ca3d..5f5a8c7 100644 --- a/main.go +++ b/main.go @@ -76,17 +76,9 @@ type appContext struct { port int version string quit chan os.Signal - lang Languages URLBase string } -// Languages stores the names and filenames of language files, and the index of that which is currently selected. -type Languages struct { - langFiles []os.FileInfo // Language filenames - langOptions []string // Language names - chosenIndex int -} - func (app *appContext) loadHTML(router *gin.Engine) { customPath := app.config.Section("files").Key("html_templates").MustString("") templatePath := filepath.Join(app.localPath, "html") @@ -521,13 +513,11 @@ func start(asDaemon, firstCall bool) { } } } - - lang := app.config.Section("ui").Key("language").MustString("en-us") - app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form", lang+".json") - if _, err := os.Stat(app.storage.lang.FormPath); os.IsNotExist(err) { - app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form", "en-us.json") + app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form") + err = app.storage.loadLang() + if err != nil { + app.info.Fatalf("Failed to load language files: %+v\n", err) } - app.storage.loadLang() app.authJf, _ = mediabrowser.NewServer(serverType, server, "jfa-go", app.version, "auth", "auth", timeoutHandler, cacheTimeout) @@ -574,6 +564,7 @@ func start(asDaemon, firstCall bool) { router.Use(gin.Recovery()) 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) router.NoRoute(app.NoRouteHandler) if debugMode { @@ -585,6 +576,7 @@ func start(asDaemon, firstCall bool) { router.GET("/accounts", app.AdminPage) router.GET("/settings", app.AdminPage) + router.GET("/lang", app.GetLanguages) router.GET("/token/login", app.getTokenLogin) router.GET("/token/refresh", app.getTokenRefresh) router.POST("/newUser", app.NewUser) diff --git a/models.go b/models.go index d8d8381..8c2b5de 100644 --- a/models.go +++ b/models.go @@ -156,3 +156,5 @@ type settings struct { Order []string `json:"order"` Sections map[string]section `json:"sections"` } + +type langDTO map[string]string diff --git a/storage.go b/storage.go index 4786ca2..e5b9ea6 100644 --- a/storage.go +++ b/storage.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io/ioutil" "log" + "path/filepath" "strconv" "strings" "time" @@ -20,8 +21,9 @@ type Storage struct { } type Lang struct { - FormPath string - Form map[string]interface{} + chosenFormLang string + FormPath string + Form map[string]map[string]interface{} } // timePattern: %Y-%m-%dT%H:%M:%S.%f @@ -58,33 +60,45 @@ func (st *Storage) storeInvites() error { } func (st *Storage) loadLang() error { - if substituteStrings != "" { - var file []byte - var err error - file, err = ioutil.ReadFile(st.lang.FormPath) - if err != nil { - file = []byte("{}") - } - // Replace Jellyfin with emby on form - file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings)) - err = json.Unmarshal(file, &st.lang.Form) - if err != nil { - log.Printf("ERROR: Failed to read \"%s\": %s", st.lang.FormPath, err) - } - return err - } - err := loadJSON(st.lang.FormPath, &st.lang.Form) + formFiles, err := ioutil.ReadDir(st.lang.FormPath) + st.lang.Form = map[string]map[string]interface{}{} if err != nil { return err } - strings := st.lang.Form["strings"].(map[string]interface{}) - validationStrings := strings["validationStrings"].(map[string]interface{}) - vS, err := json.Marshal(validationStrings) - if err != nil { - return err + for _, f := range formFiles { + index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + var data map[string]interface{} + if substituteStrings != "" { + var file []byte + var err error + file, err = ioutil.ReadFile(filepath.Join(st.lang.FormPath, f.Name())) + if err != nil { + file = []byte("{}") + } + // Replace Jellyfin with emby on form + file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings)) + err = json.Unmarshal(file, &data) + if err != nil { + log.Printf("ERROR: Failed to read \"%s\": %s", st.lang.FormPath, err) + return err + } + } else { + err := loadJSON(filepath.Join(st.lang.FormPath, f.Name()), &data) + if err != nil { + return err + } + } + + strings := data["strings"].(map[string]interface{}) + validationStrings := strings["validationStrings"].(map[string]interface{}) + vS, err := json.Marshal(validationStrings) + if err != nil { + return err + } + strings["validationStrings"] = string(vS) + data["strings"] = strings + st.lang.Form[index] = data } - strings["validationStrings"] = string(vS) - st.lang.Form["strings"] = strings return nil } diff --git a/ts/form.ts b/ts/form.ts index f918ca8..4d7666c 100644 --- a/ts/form.ts +++ b/ts/form.ts @@ -1,5 +1,5 @@ import { Modal } from "./modules/modal.js"; -import { _post, toggleLoader } from "./modules/common.js"; +import { _get, _post, toggleLoader } from "./modules/common.js"; interface formWindow extends Window { validationStrings: pwValStrings; @@ -21,6 +21,24 @@ interface pwValStrings { [ type: string ]: pwValString; } +_get("/lang", null, (req: XMLHttpRequest) => { + if (req.readyState == 4) { + if (req.status != 200) { + document.getElementById("lang-dropdown").remove(); + return; + } + const list = document.getElementById("lang-list") as HTMLDivElement; + let innerHTML = ''; + for (let code in req.response) { + innerHTML += `${req.response[code]}`; + } + list.innerHTML = innerHTML; + } +}); + + + + window.modal = new Modal(document.getElementById("modal-success")); declare var window: formWindow; diff --git a/views.go b/views.go index 7575312..7f54628 100644 --- a/views.go +++ b/views.go @@ -31,6 +31,12 @@ func (app *appContext) AdminPage(gc *gin.Context) { func (app *appContext) InviteProxy(gc *gin.Context) { code := gc.Param("invCode") + lang := gc.Query("lang") + if lang == "" { + lang = app.storage.lang.chosenFormLang + } else if _, ok := app.storage.lang.Form[lang]; !ok { + lang = app.storage.lang.chosenFormLang + } /* Don't actually check if the invite is valid, just if it exists, just so the page loads quicker. Invite is actually checked on submit anyway. */ // if app.checkInvite(code, false, "") { if _, ok := app.storage.invites[code]; ok { @@ -49,7 +55,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) { "requirements": app.validator.getCriteria(), "email": email, "username": !app.config.Section("email").Key("no_username").MustBool(false), - "lang": app.storage.lang.Form["strings"], + "lang": app.storage.lang.Form[lang]["strings"], }) } else { gcHTML(gc, 404, "invalidCode.html", gin.H{