mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
add language selector to form
This commit is contained in:
parent
461efa7f60
commit
3fbbc7f620
59
api.go
59
api.go
@ -3,8 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -1088,23 +1086,17 @@ 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
|
||||||
langPath := filepath.Join(app.localPath, "lang", "form")
|
langOptions := make([]string, len(app.storage.lang.Form))
|
||||||
app.lang.langFiles, _ = ioutil.ReadDir(langPath)
|
chosenLang := app.config.Section("ui").Key("language").MustString("en-us")
|
||||||
app.lang.langOptions = make([]string, len(app.lang.langFiles))
|
chosenLangName := app.storage.lang.Form[chosenLang]["meta"].(map[string]interface{})["name"].(string)
|
||||||
chosenLang := app.config.Section("ui").Key("language").MustString("en-us") + ".json"
|
i := 0
|
||||||
for i, f := range app.lang.langFiles {
|
for _, lang := range app.storage.lang.Form {
|
||||||
if f.Name() == chosenLang {
|
langOptions[i] = lang["meta"].(map[string]interface{})["name"].(string)
|
||||||
app.lang.chosenIndex = i
|
i++
|
||||||
}
|
}
|
||||||
var langFile map[string]interface{}
|
l := resp.Sections["ui"].Settings["language"]
|
||||||
file, _ := ioutil.ReadFile(filepath.Join(langPath, f.Name()))
|
l.Options = langOptions
|
||||||
json.Unmarshal(file, &langFile)
|
l.Value = chosenLangName
|
||||||
|
|
||||||
if meta, ok := langFile["meta"]; ok {
|
|
||||||
app.lang.langOptions[i] = meta.(map[string]interface{})["name"].(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s := resp.Sections["ui"].Settings["language"]
|
|
||||||
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)
|
||||||
@ -1120,13 +1112,11 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
resp.Sections[sectName].Settings[settingName] = s
|
resp.Sections[sectName].Settings[settingName] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.Options = app.lang.langOptions
|
resp.Sections["ui"].Settings["language"] = l
|
||||||
s.Value = app.lang.langOptions[app.lang.chosenIndex]
|
|
||||||
resp.Sections["ui"].Settings["language"] = s
|
|
||||||
|
|
||||||
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++
|
||||||
@ -1158,9 +1148,9 @@ 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" {
|
if section == "ui" && setting == "language" {
|
||||||
for i, lang := range app.lang.langOptions {
|
for key, lang := range app.storage.lang.Form {
|
||||||
if value.(string) == lang {
|
if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) {
|
||||||
tempConfig.Section(section).Key(setting).SetValue(strings.Replace(app.lang.langFiles[i].Name(), ".json", "", 1))
|
tempConfig.Section("ui").Key("language").SetValue(key)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1225,6 +1215,25 @@ func (app *appContext) Logout(gc *gin.Context) {
|
|||||||
respondBool(200, true, gc)
|
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 {
|
// func Restart() error {
|
||||||
// defer func() {
|
// defer func() {
|
||||||
// if r := recover(); r != nil {
|
// if r := recover(); r != nil {
|
||||||
|
@ -84,5 +84,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
|
|
||||||
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"language": {
|
"language": {
|
||||||
"name": "Language",
|
"name": "Default Form Language",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
"type": "select",
|
"type": "select",
|
||||||
@ -93,7 +93,7 @@
|
|||||||
"en-us"
|
"en-us"
|
||||||
],
|
],
|
||||||
"value": "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": {
|
"theme": {
|
||||||
"name": "Default Look",
|
"name": "Default Look",
|
||||||
|
@ -370,3 +370,8 @@ p.top {
|
|||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
z-index: 16;
|
z-index: 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
margin-bottom: -0.5rem;
|
||||||
|
}
|
||||||
|
@ -14,6 +14,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
|
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||||
|
<span class="button ~urge dropdown-button">
|
||||||
|
<i class="ri-global-line"></i>
|
||||||
|
<span class="ml-1 chev"></span>
|
||||||
|
</span>
|
||||||
|
<div class="dropdown-display">
|
||||||
|
<div class="card ~neutral !low" id="lang-list">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="card ~neutral !low">
|
<div class="card ~neutral !low">
|
||||||
<div class="row baseline">
|
<div class="row baseline">
|
||||||
|
20
main.go
20
main.go
@ -76,17 +76,9 @@ type appContext struct {
|
|||||||
port int
|
port int
|
||||||
version string
|
version string
|
||||||
quit chan os.Signal
|
quit chan os.Signal
|
||||||
lang Languages
|
|
||||||
URLBase string
|
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) {
|
func (app *appContext) loadHTML(router *gin.Engine) {
|
||||||
customPath := app.config.Section("files").Key("html_templates").MustString("")
|
customPath := app.config.Section("files").Key("html_templates").MustString("")
|
||||||
templatePath := filepath.Join(app.localPath, "html")
|
templatePath := filepath.Join(app.localPath, "html")
|
||||||
@ -521,13 +513,11 @@ func start(asDaemon, firstCall bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
|
||||||
lang := app.config.Section("ui").Key("language").MustString("en-us")
|
err = app.storage.loadLang()
|
||||||
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form", lang+".json")
|
if err != nil {
|
||||||
if _, err := os.Stat(app.storage.lang.FormPath); os.IsNotExist(err) {
|
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
||||||
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form", "en-us.json")
|
|
||||||
}
|
}
|
||||||
app.storage.loadLang()
|
|
||||||
|
|
||||||
app.authJf, _ = mediabrowser.NewServer(serverType, server, "jfa-go", app.version, "auth", "auth", timeoutHandler, cacheTimeout)
|
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(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 {
|
||||||
@ -585,6 +576,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
router.GET("/accounts", app.AdminPage)
|
router.GET("/accounts", app.AdminPage)
|
||||||
router.GET("/settings", app.AdminPage)
|
router.GET("/settings", app.AdminPage)
|
||||||
|
|
||||||
|
router.GET("/lang", 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)
|
||||||
router.POST("/newUser", app.NewUser)
|
router.POST("/newUser", app.NewUser)
|
||||||
|
@ -156,3 +156,5 @@ type settings struct {
|
|||||||
Order []string `json:"order"`
|
Order []string `json:"order"`
|
||||||
Sections map[string]section `json:"sections"`
|
Sections map[string]section `json:"sections"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type langDTO map[string]string
|
||||||
|
28
storage.go
28
storage.go
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,8 +21,9 @@ type Storage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Lang struct {
|
type Lang struct {
|
||||||
|
chosenFormLang string
|
||||||
FormPath string
|
FormPath string
|
||||||
Form map[string]interface{}
|
Form map[string]map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
||||||
@ -58,33 +60,45 @@ func (st *Storage) storeInvites() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *Storage) loadLang() error {
|
func (st *Storage) loadLang() error {
|
||||||
|
formFiles, err := ioutil.ReadDir(st.lang.FormPath)
|
||||||
|
st.lang.Form = map[string]map[string]interface{}{}
|
||||||
|
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 != "" {
|
if substituteStrings != "" {
|
||||||
var file []byte
|
var file []byte
|
||||||
var err error
|
var err error
|
||||||
file, err = ioutil.ReadFile(st.lang.FormPath)
|
file, err = ioutil.ReadFile(filepath.Join(st.lang.FormPath, f.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
file = []byte("{}")
|
file = []byte("{}")
|
||||||
}
|
}
|
||||||
// Replace Jellyfin with emby on form
|
// Replace Jellyfin with emby on form
|
||||||
file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings))
|
file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings))
|
||||||
err = json.Unmarshal(file, &st.lang.Form)
|
err = json.Unmarshal(file, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERROR: Failed to read \"%s\": %s", st.lang.FormPath, err)
|
log.Printf("ERROR: Failed to read \"%s\": %s", st.lang.FormPath, err)
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err := loadJSON(st.lang.FormPath, &st.lang.Form)
|
} else {
|
||||||
|
err := loadJSON(filepath.Join(st.lang.FormPath, f.Name()), &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
strings := st.lang.Form["strings"].(map[string]interface{})
|
}
|
||||||
|
|
||||||
|
strings := data["strings"].(map[string]interface{})
|
||||||
validationStrings := strings["validationStrings"].(map[string]interface{})
|
validationStrings := strings["validationStrings"].(map[string]interface{})
|
||||||
vS, err := json.Marshal(validationStrings)
|
vS, err := json.Marshal(validationStrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
strings["validationStrings"] = string(vS)
|
strings["validationStrings"] = string(vS)
|
||||||
st.lang.Form["strings"] = strings
|
data["strings"] = strings
|
||||||
|
st.lang.Form[index] = data
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
ts/form.ts
20
ts/form.ts
@ -1,5 +1,5 @@
|
|||||||
import { Modal } from "./modules/modal.js";
|
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 {
|
interface formWindow extends Window {
|
||||||
validationStrings: pwValStrings;
|
validationStrings: pwValStrings;
|
||||||
@ -21,6 +21,24 @@ interface pwValStrings {
|
|||||||
[ type: string ]: pwValString;
|
[ 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 += `<a href="?lang=${code}" class="button input ~neutral field mb-half">${req.response[code]}</a>`;
|
||||||
|
}
|
||||||
|
list.innerHTML = innerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.modal = new Modal(document.getElementById("modal-success"));
|
window.modal = new Modal(document.getElementById("modal-success"));
|
||||||
declare var window: formWindow;
|
declare var window: formWindow;
|
||||||
|
|
||||||
|
8
views.go
8
views.go
@ -31,6 +31,12 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
|
|
||||||
func (app *appContext) InviteProxy(gc *gin.Context) {
|
func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||||
code := gc.Param("invCode")
|
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. */
|
/* 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 app.checkInvite(code, false, "") {
|
||||||
if _, ok := app.storage.invites[code]; ok {
|
if _, ok := app.storage.invites[code]; ok {
|
||||||
@ -49,7 +55,7 @@ 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),
|
||||||
"lang": app.storage.lang.Form["strings"],
|
"lang": app.storage.lang.Form[lang]["strings"],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
||||||
|
Loading…
Reference in New Issue
Block a user