From 4ac62a107ce6148fa0dae6ebd8beee16e6d0b7eb Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 12 Jan 2021 00:11:40 +0000 Subject: [PATCH] Start adding translation support for admin --- config.go | 1 + html/admin.html | 166 ++++++++++++++++++++---------------------- lang/admin/README.md | 5 ++ lang/admin/en-us.json | 80 ++++++++++++++++++++ main.go | 1 + storage.go | 81 ++++++++++++--------- views.go | 26 ++++--- 7 files changed, 229 insertions(+), 131 deletions(-) create mode 100644 lang/admin/README.md create mode 100644 lang/admin/en-us.json diff --git a/config.go b/config.go index 2e72432..347bed4 100644 --- a/config.go +++ b/config.go @@ -84,6 +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") app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us") return nil diff --git a/html/admin.html b/html/admin.html index d86e90b..71f6b83 100644 --- a/html/admin.html +++ b/html/admin.html @@ -16,164 +16,152 @@ @@ -182,40 +170,40 @@
- Invites - Accounts - Settings + {{ .strings.invites }} + {{ .strings.accounts }} + {{ .strings.settings }}
- Logout - Theme + {{ .strings.logout }} + {{ .strings.theme }}
- Invites + {{ .strings.invites }}
- Create + {{ .strings.create }}
- +
- +
- +
-

Warning invites with infinite uses can be used abusively.

- +

{{ .strings.warning }} {{ .strings.inviteInfiniteUsesWarning }}

+
- +
- Create + {{ .strings.create }}
- Accounts + {{ .strings.accounts }}
- Add User - Modify Settings - Delete User + {{ .quantityStrings.addUser.singular }} + {{ .strings.modifySettings }} + {{ .quantityStrings.deleteUser.singular }}
- - - + + + @@ -276,15 +264,15 @@
- Settings + {{ .string.settings }}
- Save + {{ .strings.settingsSave }}
- - About - User profiles + + {{ .strings.aboutProgram }} + {{ .strings.userProfiles }}
diff --git a/lang/admin/README.md b/lang/admin/README.md new file mode 100644 index 0000000..5bb2bba --- /dev/null +++ b/lang/admin/README.md @@ -0,0 +1,5 @@ +##### admin page translation + +* [x] static page content +* [ ] Typescript: + * [x] accounts.ts diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json new file mode 100644 index 0000000..24862a1 --- /dev/null +++ b/lang/admin/en-us.json @@ -0,0 +1,80 @@ +{ + "meta": { + "name": "English (US)" + }, + "strings": { + "invites": "Invites", + "accounts": "Accounts", + "settings": "Settings", + "theme": "Theme", + "inviteDays": "Days", + "inviteHours": "Hours", + "inviteMinutes": "Minutes", + "inviteNumberOfUses": "Number of uses", + "warning": "Warning", + "inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively", + "inviteSendToEmail": "Send to", + "login": "Login", + "logout": "Logout", + "create": "Create", + "apply": "Apply", + "delete": "Delete", + "submit": "Submit", + "name": "Name", + "username": "Username", + "password": "Password", + "emailAddress": "Email Address", + "lastActiveTime": "Last Active", + "from": "From", + "user": "User", + "aboutProgram": "About", + "version": "Version", + "commitNoun": "Commit", + "newUser": "New User", + "profile": "Profile", + "modifySettings": "Modify Settings", + "modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.", + "applyHomescreenLayout": "Apply homescreen layout", + "sendDeleteNotificationEmail": "Send notification email", + "sendDeleteNotifiationExample": "Your account has been deleted.", + "settingsRestartRequired": "Restart needed", + "settingsRestartRequiredDescription": "A restart is necessary to apply some settings you changed. Restart now or later?", + "settingsApplyRestartLater": "Apply, restart later", + "settingsApplyRestartNow": "Apply & restart", + "settingsApplied": "Settings applied.", + "settingsRefreshPage": "Refresh the page in a few seconds", + "settingsRequiredOrRestartMessage": "Note: {*} indicates a required field, {R} indicates changes require a restart.", + "settingsSave": "Save", + "ombiUserDefaults": "Ombi user defaults", + "ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go", + "userProfiles": "User Profiles", + "userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.", + "userProfilesIsDefault": "Default", + "userProfilesLibraries": "Libraries", + "addProfile": "Add Profile", + "addProfileDescription": "Create a Jellyfin user and configure it, then select it below. When this profile is applied to an invite, new users will be created with the settings.", + "addProfileNameOf": "Profile Name", + "addProfileStoreHomescreenLayout": "Store homescreen layout" + }, + "variableStrings": { + "settingsRequiredOrRestartMessage": "Note: {*} indicates a required field, {R} indicates changes require a restart." + }, + "quantityStrings": { + "modifySettingsFor": { + "singular": "Modify Settings for {n} user", + "plural": "Modify Settings for {n} users" + }, + "deleteNUsers": { + "singular": "Delete {n} user", + "plural": "Delete {n} users" + }, + "addUser": { + "singular": "Add user", + "plural": "Add users" + }, + "deleteUser": { + "singular": "Delete User", + "plural": "Delete Users" + } + } +} diff --git a/main.go b/main.go index 5f5a8c7..06ace3d 100644 --- a/main.go +++ b/main.go @@ -514,6 +514,7 @@ func start(asDaemon, firstCall bool) { } } app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form") + app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin") err = app.storage.loadLang() if err != nil { app.info.Fatalf("Failed to load language files: %+v\n", err) diff --git a/storage.go b/storage.go index e5b9ea6..01182ef 100644 --- a/storage.go +++ b/storage.go @@ -21,9 +21,12 @@ type Storage struct { } type Lang struct { - chosenFormLang string - FormPath string - Form map[string]map[string]interface{} + chosenFormLang string + chosenAdminLang string + AdminPath string + Admin map[string]map[string]interface{} + FormPath string + Form map[string]map[string]interface{} } // timePattern: %Y-%m-%dT%H:%M:%S.%f @@ -60,46 +63,58 @@ func (st *Storage) storeInvites() error { } func (st *Storage) loadLang() error { - formFiles, err := ioutil.ReadDir(st.lang.FormPath) - st.lang.Form = map[string]map[string]interface{}{} + loadData := func(path string) (map[string]map[string]interface{}, error) { + files, err := ioutil.ReadDir(path) + out := map[string]map[string]interface{}{} + if err != nil { + return nil, err + } + for _, f := range files { + 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(path, 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", path, err) + return nil, err + } + } else { + err := loadJSON(filepath.Join(path, f.Name()), &data) + if err != nil { + return nil, err + } + } + out[index] = data + } + return out, nil + } + form, err := loadData(st.lang.FormPath) 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{}) + for index, lang := range form { + strings := lang["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 + lang["strings"] = strings + form[index] = lang } - return nil + st.lang.Form = form + admin, err := loadData(st.lang.AdminPath) + st.lang.Admin = admin + return err } func (st *Storage) loadEmails() error { diff --git a/views.go b/views.go index 50b038e..2a42d74 100644 --- a/views.go +++ b/views.go @@ -13,19 +13,27 @@ func gcHTML(gc *gin.Context, code int, file string, templ gin.H) { } func (app *appContext) AdminPage(gc *gin.Context) { + 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 + } emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool() notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool() ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false) gcHTML(gc, http.StatusOK, "admin.html", gin.H{ - "urlBase": app.URLBase, - "cssClass": app.cssClass, - "contactMessage": "", - "email_enabled": emailEnabled, - "notifications": notificationsEnabled, - "version": VERSION, - "commit": COMMIT, - "ombiEnabled": ombiEnabled, - "username": !app.config.Section("email").Key("no_username").MustBool(false), + "urlBase": app.URLBase, + "cssClass": app.cssClass, + "contactMessage": "", + "email_enabled": emailEnabled, + "notifications": notificationsEnabled, + "version": VERSION, + "commit": COMMIT, + "ombiEnabled": ombiEnabled, + "username": !app.config.Section("email").Key("no_username").MustBool(false), + "strings": app.storage.lang.Admin[lang]["strings"], + "quantityStrings": app.storage.lang.Admin[lang]["quantityStrings"], }) }
UsernameEmail AddressLast Active{{ .strings.username }}{{ .strings.emailAddress }}{{ .strings.lastActiveTime }}