From df534f72fad48f6b2167cf8ada0eb44dbe03c3cc Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Wed, 16 Mar 2022 16:58:42 -0400 Subject: [PATCH] i18n server-side strings --- core/database/lock.go | 110 -------------------------- core/database/readme.md | 0 core/database/templates/db-error.html | 2 +- core/i18n/localize.go | 21 ++++- core/osutil/command.go | 2 +- domain/auth/keycloak/endpoint.go | 15 ++-- domain/auth/ldap/endpoint.go | 17 ++-- domain/context.go | 1 + gui/public/i18n/en-US.json | 16 +++- model/user/user.go | 1 + server/middleware.go | 5 ++ 11 files changed, 59 insertions(+), 131 deletions(-) delete mode 100644 core/database/lock.go delete mode 100644 core/database/readme.md diff --git a/core/database/lock.go b/core/database/lock.go deleted file mode 100644 index 78ab17e2..00000000 --- a/core/database/lock.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2016 Documize Inc. . All rights reserved. -// -// This software (Documize Community Edition) is licensed under -// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html -// -// You can operate outside the AGPL restrictions by purchasing -// Documize Enterprise Edition and obtaining a commercial license -// by contacting . -// -// https://documize.com - -package database - -// import ( -// "crypto/rand" -// "time" - -// "github.com/documize/community/core/env" -// "github.com/jmoiron/sqlx" -// ) - -// // Lock will try to lock the database instance to the running process. -// // Uses a "random" delay as a por man's database cluster-aware process. -// // We skip delay if there are no scripts to process. -// func Lock(runtime *env.Runtime, scriptsToProcess int) (bool, error) { -// // Wait for random period of time. -// b := make([]byte, 2) -// _, err := rand.Read(b) -// if err != nil { -// return false, err -// } -// wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait - -// // Why delay if nothing to process? -// if scriptsToProcess > 0 { -// time.Sleep(wait) -// } - -// // Start transaction fotr lock process. -// tx, err := runtime.Db.Beginx() -// if err != nil { -// runtime.Log.Error("Database: unable to start transaction", err) -// return false, err -// } - -// // Lock the database. -// _, err = tx.Exec(runtime.StoreProvider.QueryStartLock()) -// if err != nil { -// runtime.Log.Error("Database: unable to lock tables", err) -// return false, err -// } - -// // Unlock the database at the end of this function. -// defer func() { -// _, err = tx.Exec(runtime.StoreProvider.QueryFinishLock()) -// if err != nil { -// runtime.Log.Error("Database: unable to unlock tables", err) -// } -// tx.Commit() -// }() - -// // Try to record this process as leader of database migration process. -// _, err = tx.Exec(runtime.StoreProvider.QueryInsertProcessID()) -// if err != nil { -// runtime.Log.Info("Database: marked as slave process awaiting upgrade") -// return false, nil -// } - -// // We are the leader! -// runtime.Log.Info("Database: marked as database upgrade process leader") -// return true, err -// } - -// // Unlock completes process that was started with Lock(). -// func Unlock(runtime *env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error { -// if amLeader { -// defer func() { -// doUnlock(runtime) -// }() - -// if tx != nil { -// if err == nil { -// tx.Commit() -// runtime.Log.Info("Database: is ready") -// return nil -// } -// tx.Rollback() -// } - -// runtime.Log.Error("Database: install/upgrade failed", err) - -// return err -// } - -// return nil // not the leader, so ignore errors -// } - -// // Helper method for defer function called from Unlock(). -// func doUnlock(runtime *env.Runtime) error { -// tx, err := runtime.Db.Beginx() -// if err != nil { -// return err -// } -// _, err = tx.Exec(runtime.StoreProvider.QueryDeleteProcessID()) -// if err != nil { -// return err -// } - -// return tx.Commit() -// } diff --git a/core/database/readme.md b/core/database/readme.md deleted file mode 100644 index e69de29b..00000000 diff --git a/core/database/templates/db-error.html b/core/database/templates/db-error.html index e8ca9a8f..8fee7b7d 100644 --- a/core/database/templates/db-error.html +++ b/core/database/templates/db-error.html @@ -110,7 +110,7 @@

Database Error

-

There seems to be a problem with the Documize database: {{.DBname}}

+

There seems to be a problem with the Documize Community database: {{.DBname}}

{{.Issue}}

diff --git a/core/i18n/localize.go b/core/i18n/localize.go index 27ccaa69..36be7303 100644 --- a/core/i18n/localize.go +++ b/core/i18n/localize.go @@ -4,12 +4,17 @@ import ( "embed" "encoding/json" "fmt" + "strings" "github.com/documize/community/core/asset" "github.com/pkg/errors" ) +const ( + DefaultLocale = "en-US" +) + var localeMap map[string]map[string]string // type translation struct { @@ -57,11 +62,15 @@ func Initialize(e embed.FS) (err error) { // Localize will returns string value for given key using specified locale). // e.g. locale = "en-US", key = "admin_billing" -func Localize(locale, key string) (s string) { +// +// Replacements are for replacing string placeholders ({1} {2} {3}) with +// replacement text. +// e.g. "This is {1} example" where replacements[0] will replace {1} +func Localize(locale string, key string, replacements ...string) (s string) { l, ok := localeMap[locale] if !ok { // fallback - l = localeMap["en-US"] + l = localeMap[DefaultLocale] } s, ok = l[key] @@ -70,5 +79,13 @@ func Localize(locale, key string) (s string) { s = fmt.Sprintf("!! %s !!", key) } + // placeholders are one-based: {1} {2} {3} + // replacements array is zero-based hence the +1 below + if len(replacements) > 0 { + for i := range replacements { + s = strings.Replace(s, fmt.Sprintf("{%d}", i+1), replacements[i], 1) + } + } + return } diff --git a/core/osutil/command.go b/core/osutil/command.go index b5fa59aa..88102d32 100644 --- a/core/osutil/command.go +++ b/core/osutil/command.go @@ -38,7 +38,7 @@ func CommandWithTimeout(command *exec.Cmd, timeout time.Duration) ([]byte, error select { case <-time.After(timeout): if err := command.Process.Kill(); err != nil { - fmt.Errorf("failed to kill: ", err) + fmt.Printf("failed to kill: %s", err.Error()) } <-done // prevent memory leak //fmt.Println("DEBUG timeout") diff --git a/domain/auth/keycloak/endpoint.go b/domain/auth/keycloak/endpoint.go index 95e68aee..b062481e 100644 --- a/domain/auth/keycloak/endpoint.go +++ b/domain/auth/keycloak/endpoint.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/documize/community/core/env" + "github.com/documize/community/core/i18n" "github.com/documize/community/core/response" "github.com/documize/community/core/secrets" "github.com/documize/community/core/streamutil" @@ -57,7 +58,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // Org contains raw auth provider config org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID) if err != nil { - result.Message = "Error: unable to get organization record" + result.Message = i18n.Localize(ctx.Locale, "server_err_org") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -66,7 +67,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // Exit if not using Keycloak if org.AuthProvider != ath.AuthProviderKeycloak { - result.Message = "Error: skipping user sync with Keycloak as it is not the configured option" + result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error1") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Info(result.Message) @@ -77,7 +78,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { c := ath.KeycloakConfig{} err = json.Unmarshal([]byte(org.AuthConfig), &c) if err != nil { - result.Message = "Error: unable read Keycloak configuration data" + result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error2") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -87,7 +88,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // User list from Keycloak kcUsers, err := Fetch(c) if err != nil { - result.Message = "Error: unable to fetch Keycloak users: " + err.Error() + result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error3", err.Error()) result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -97,7 +98,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // User list from Documize dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999) if err != nil { - result.Message = "Error: unable to fetch Documize users" + result.Message = i18n.Localize(ctx.Locale, "server_error_user") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -135,8 +136,8 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { } } - result.Message = fmt.Sprintf("Keycloak sync found %d users, %d new users added, %d users with missing data ignored", - len(kcUsers), len(insert), missing) + result.Message = i18n.Localize(ctx.Locale, "server_keycloak_summary", + fmt.Sprintf("%d", len(kcUsers)), fmt.Sprintf("%d", len(insert)), fmt.Sprintf("%d", missing)) response.WriteJSON(w, result) h.Runtime.Log.Info(result.Message) diff --git a/domain/auth/ldap/endpoint.go b/domain/auth/ldap/endpoint.go index 61e9227f..94c4078d 100644 --- a/domain/auth/ldap/endpoint.go +++ b/domain/auth/ldap/endpoint.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/documize/community/core/env" + "github.com/documize/community/core/i18n" "github.com/documize/community/core/response" "github.com/documize/community/core/secrets" "github.com/documize/community/core/streamutil" @@ -146,7 +147,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // Org contains raw auth provider config org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID) if err != nil { - result.Message = "Error: unable to get organization record" + result.Message = i18n.Localize(ctx.Locale, "server_error_org") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -155,7 +156,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // Exit if not using LDAP if org.AuthProvider != ath.AuthProviderLDAP { - result.Message = "Error: skipping user sync with LDAP as it is not the configured option" + result.Message = i18n.Localize(ctx.Locale, "server_ldap_error1") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Info(result.Message) @@ -166,7 +167,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { c := lm.LDAPConfig{} err = json.Unmarshal([]byte(org.AuthConfig), &c) if err != nil { - result.Message = "Error: unable read LDAP configuration data" + result.Message = i18n.Localize(ctx.Locale, "server_ldap_error2") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -176,7 +177,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // Get user list from LDAP. ldapUsers, err := fetchUsers(c) if err != nil { - result.Message = "Error: unable to fetch LDAP users: " + err.Error() + result.Message = i18n.Localize(ctx.Locale, "server_ldap_error3", err.Error()) result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -186,7 +187,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { // Get user list from Documize dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999) if err != nil { - result.Message = "Error: unable to fetch Documize users" + result.Message = i18n.Localize(ctx.Locale, "server_error_user") result.IsError = true response.WriteJSON(w, result) h.Runtime.Log.Error(result.Message, err) @@ -223,10 +224,8 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) { } result.IsError = false - result.Message = "Sync complete with LDAP server" - result.Message = fmt.Sprintf( - "LDAP sync found %d users, %d new users added, %d users with missing data ignored", - len(ldapUsers), len(insert), missing) + result.Message = i18n.Localize(ctx.Locale, "server_ldap_complete") + result.Message = i18n.Localize(ctx.Locale, "server_ldap_summary", fmt.Sprintf("%d", len(ldapUsers)), fmt.Sprintf("%d", len(insert)), fmt.Sprintf("%d", missing)) h.Runtime.Log.Info(result.Message) diff --git a/domain/context.go b/domain/context.go index 5ffef969..57c55bb0 100644 --- a/domain/context.go +++ b/domain/context.go @@ -44,6 +44,7 @@ type RequestContext struct { GlobalAdmin bool ViewUsers bool Subscription Subscription + Locale string } //GetAppURL returns full HTTP url for the app diff --git a/gui/public/i18n/en-US.json b/gui/public/i18n/en-US.json index 4e765d0a..3603f951 100644 --- a/gui/public/i18n/en-US.json +++ b/gui/public/i18n/en-US.json @@ -689,5 +689,19 @@ "404": "Oops! That page couldn't be found.", "404_explain": "Maybe the content you're looking for is no longer available?", "close_account": "Please close my Documize account.", - "third_party": "Documize Community utilizes open source libraries and components from third parties" + "third_party": "Documize Community utilizes open source libraries and components from third parties", + + "server_ldap_error1": "Error: skipping user sync with LDAP as it is not the configured option", + "server_ldap_error2": "Error: unable read LDAP configuration data", + "server_ldap_error3": "Error: unable to fetch LDAP users: {1}", + "server_ldap_complete": "Sync complete with LDAP server", + "server_ldap_summary": "LDAP sync found {1} users, {2} new users added, {3} users with missing data ignored", + + "server_keycloak_error1": "Error: skipping user sync with Keycloak as it is not the configured option", + "server_keycloak_error2": "Error: unable read Keycloak configuration data", + "server_keycloak_error3": "Error: unable to fetch Keycloak users: {1}", + "server_keycloak_summary": "Keycloak sync found {1} users, {2} new users added, {3} users with missing data ignored", + + "server_error_user": "Error: unable to fetch users", + "server_error_org": "Error: unable to get organization record" } diff --git a/model/user/user.go b/model/user/user.go index f20e5510..96337bc8 100644 --- a/model/user/user.go +++ b/model/user/user.go @@ -37,6 +37,7 @@ type User struct { Reset string `json:"-"` LastVersion string `json:"lastVersion"` Theme string `json:"theme"` + Locale string `json:"locale"` Accounts []account.Account `json:"accounts"` Groups []group.Record `json:"groups"` } diff --git a/server/middleware.go b/server/middleware.go index 58b6f948..80651629 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -22,6 +22,7 @@ import ( "time" "github.com/documize/community/core/env" + "github.com/documize/community/core/i18n" "github.com/documize/community/core/request" "github.com/documize/community/core/response" "github.com/documize/community/domain" @@ -191,6 +192,10 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http rc.GlobalAdmin = u.GlobalAdmin rc.ViewUsers = u.ViewUsers rc.Fullname = u.Fullname() + rc.Locale = u.Locale + if len(rc.Locale) == 0 { + u.Locale = i18n.DefaultLocale + } // We send back with every HTTP request/response cycle the latest // user state. This helps client-side applications to detect changes in