diff --git a/build.bat b/build.bat index dc16f3bd..5a92ca26 100644 --- a/build.bat +++ b/build.bat @@ -22,6 +22,8 @@ echo "Copying Ember pdfjs folder" robocopy /e /NFL /NDL /NJH gui\dist-prod\pdfjs edition\static\public\pdfjs echo "Copying Ember sections folder" robocopy /e /NFL /NDL /NJH gui\dist-prod\sections edition\static\public\sections +echo "Copying i18n folder" +robocopy /e /NFL /NDL /NJH gui\dist-prod\i18n edition\static\public\i18n copy gui\dist-prod\*.* edition\static copy gui\dist-prod\favicon.ico edition\static\public @@ -32,6 +34,10 @@ mkdir edition\static\mail copy domain\mail\*.html edition\static\mail copy core\database\templates\*.html edition\static +rd /s /q edition\static\i18n +mkdir edition\static\i18n +robocopy /e /NFL /NDL /NJH gui\dist-prod\i18n\*.json edition\static\i18n + rd /s /q edition\static\scripts mkdir edition\static\scripts mkdir edition\static\scripts\mysql diff --git a/build.sh b/build.sh index 92a86f72..3b9dc6dc 100755 --- a/build.sh +++ b/build.sh @@ -20,6 +20,7 @@ cp -r gui/dist-prod/prism edition/static/public/prism cp -r gui/dist-prod/sections edition/static/public/sections cp -r gui/dist-prod/tinymce edition/static/public/tinymce cp -r gui/dist-prod/pdfjs edition/static/public/pdfjs +cp -r gui/dist-prod/i18n edition/static/public/i18n cp gui/dist-prod/*.* edition/static cp gui/dist-prod/favicon.ico edition/static/public cp gui/dist-prod/manifest.json edition/static/public @@ -29,6 +30,10 @@ mkdir -p edition/static/mail cp domain/mail/*.html edition/static/mail cp core/database/templates/*.html edition/static +rm -rf edition/static/i18n +mkdir -p edition/static/i18n +cp -r gui/dist-prod/i18n/*.json edition/static/i18n + rm -rf edition/static/scripts mkdir -p edition/static/scripts mkdir -p edition/static/scripts/mysql diff --git a/core/api/convert/apidocumizecom/msword.go b/core/api/convert/apidocumizecom/msword.go index 924a9742..9a24a806 100644 --- a/core/api/convert/apidocumizecom/msword.go +++ b/core/api/convert/apidocumizecom/msword.go @@ -19,8 +19,8 @@ import ( "net/http" "path/filepath" + "context" api "github.com/documize/community/core/convapi" - "golang.org/x/net/context" ) // Msword type provides a peg to hang the Convert method on. diff --git a/core/api/convert/convert.go b/core/api/convert/convert.go index d38ec097..af0d9113 100644 --- a/core/api/convert/convert.go +++ b/core/api/convert/convert.go @@ -19,7 +19,7 @@ import ( "github.com/documize/community/core/api/plugins" api "github.com/documize/community/core/convapi" - "golang.org/x/net/context" + "context" ) // Convert provides the entry-point into the document conversion process. diff --git a/core/api/convert/documizeapi/documizeapi.go b/core/api/convert/documizeapi/documizeapi.go index e22311e2..6ea452b4 100644 --- a/core/api/convert/documizeapi/documizeapi.go +++ b/core/api/convert/documizeapi/documizeapi.go @@ -16,7 +16,7 @@ import ( api "github.com/documize/community/core/convapi" - "golang.org/x/net/context" + "context" ) // Convert provides the standard interface for conversion of a ".documizeapi" json document. diff --git a/core/api/convert/html/html.go b/core/api/convert/html/html.go index 054a2a6e..d198cf2f 100644 --- a/core/api/convert/html/html.go +++ b/core/api/convert/html/html.go @@ -16,9 +16,9 @@ import ( "fmt" "strings" + "context" api "github.com/documize/community/core/convapi" "github.com/documize/community/core/stringutil" - "golang.org/x/net/context" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) diff --git a/core/api/convert/md/md.go b/core/api/convert/md/md.go index eaad1bed..cd99a073 100644 --- a/core/api/convert/md/md.go +++ b/core/api/convert/md/md.go @@ -16,7 +16,7 @@ import ( "github.com/documize/blackfriday" - "golang.org/x/net/context" + "context" ) // Convert provides the standard interface for conversion of a Markdown document. diff --git a/core/api/plugins/glick_test.go b/core/api/plugins/glick_test.go deleted file mode 100644 index 6c0562b4..00000000 --- a/core/api/plugins/glick_test.go +++ /dev/null @@ -1,61 +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 plugins - -import ( - "os" - "testing" -) - -func TestSetup(t *testing.T) { - err := LibSetup() - if err == nil { - //t.Error("should error on non-existent config file") - //t.Fail() - } - ssc, err := Lib.Actions("Convert") - if err != nil { - t.Error(err) - } - - // TODO(Elliott) review for empty database - //if len(ssc) > 3 { - // t.Errorf("extra convert formats:%v", ssc) - //} - - /* this code leaves plugins still running */ - err = os.Chdir("../../..") - if err != nil { - t.Error(err) - } - - err = LibSetup() - if err != nil { - t.Error(err) - } - ssc, err = Lib.Actions("Convert") - if err != nil { - t.Error(err) - } - if len(ssc) == 0 { - t.Error("no extra convert formats (defined)") - } - err = os.Chdir("documize/api/plugins") - if err != nil { - t.Error(err) - } - - err = Lib.KillSubProcs() - if err != nil { - t.Error(err) - } -} 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/scripts/mysql/db_00034.sql b/core/database/scripts/mysql/db_00034.sql new file mode 100644 index 00000000..463f7d7e --- /dev/null +++ b/core/database/scripts/mysql/db_00034.sql @@ -0,0 +1,5 @@ +/* Community Edition */ + +-- Local aware. +ALTER TABLE dmz_org ADD COLUMN `c_locale` VARCHAR(20) NOT NULL DEFAULT 'en-US'; +ALTER TABLE dmz_user ADD COLUMN `c_locale` VARCHAR(20) NOT NULL DEFAULT 'en-US'; diff --git a/core/database/scripts/postgresql/db_00010.sql b/core/database/scripts/postgresql/db_00010.sql new file mode 100644 index 00000000..08c86e8a --- /dev/null +++ b/core/database/scripts/postgresql/db_00010.sql @@ -0,0 +1,5 @@ +/* Community Edition */ + +-- Local aware. +ALTER TABLE dmz_org ADD COLUMN c_locale VARCHAR(20) NOT NULL DEFAULT 'en-US'; +ALTER TABLE dmz_user ADD COLUMN c_locale VARCHAR(20) NOT NULL DEFAULT 'en-US'; diff --git a/core/database/scripts/sqlserver/db_00007.sql b/core/database/scripts/sqlserver/db_00007.sql new file mode 100644 index 00000000..7ca5e1dd --- /dev/null +++ b/core/database/scripts/sqlserver/db_00007.sql @@ -0,0 +1,5 @@ +/* Community edition */ + +-- Local aware. +ALTER TABLE dmz_org ADD c_locale NVARCHAR(20) NOT NULL DEFAULT 'en-US'; +ALTER TABLE dmz_user ADD c_locale NVARCHAR(20) NOT NULL DEFAULT 'en-US'; diff --git a/core/database/templates/db-error.html b/core/database/templates/db-error.html index 4272b0bd..8fee7b7d 100644 --- a/core/database/templates/db-error.html +++ b/core/database/templates/db-error.html @@ -14,7 +14,7 @@ html { -webkit-font-smoothing: antialiased; } - + body { font-family: 'Open Sans', sans-serif; background-color: #1b75bb; @@ -22,54 +22,54 @@ color: #ffffff; padding-top: 50px; } - + .container { max-width: 1200px; margin: 0 auto; text-align: center; } - + .logo { margin: 0 15px; } - + .content { margin: 0 15px; } - + .content > div { margin: 50px 0; } - + .content h1 { font-size: 24px; font-weight: 400; text-transform: uppercase; margin: 0 0 30px; } - + .content p { font-size: 18px; line-height: 28px; margin: 30px 0 0 0; } - + .content .image { text-align: center; } - + .clearfix { overflow: auto; zoom: 1; } - + .btn-main { border: 1px solid #ffffff; padding: 12px 20px; border-radius: 5px; margin-left: 25px; } - + @media (min-width: 768px) { body { margin-top: 100px; @@ -102,7 +102,7 @@
@@ -110,11 +110,11 @@

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}}

- \ No newline at end of file + diff --git a/core/database/templates/offline.html b/core/database/templates/offline.html index 942a3e20..b63862a1 100644 --- a/core/database/templates/offline.html +++ b/core/database/templates/offline.html @@ -12,7 +12,7 @@ html { -webkit-font-smoothing: antialiased; } - + body { font-family: Arial, Helvetica, sans-serif; background-color: #1b75bb; @@ -20,54 +20,54 @@ color: #ffffff; padding-top: 50px; } - + .container { max-width: 1200px; margin: 0 auto; text-align: center; } - + .logo { margin: 0 15px; } - + .content { margin: 0 15px; } - + .content> div { margin: 50px 0; } - + .content h1 { font-size: 36px; font-weight: bold; text-transform: uppercase; margin: 0 0 30px; } - + .content p { font-size: 18px; line-height: 28px; margin: 0; } - + .content .image { text-align: center; } - + .clearfix { overflow: auto; zoom: 1; } - + .btn-main { border: 1px solid #ffffff; padding: 12px 20px; border-radius: 5px; margin-left: 25px; } - + @media (min-width: 768px) { body { margin-top: 100px; @@ -105,7 +105,7 @@
@@ -123,4 +123,4 @@ - \ No newline at end of file + diff --git a/core/i18n/localize.go b/core/i18n/localize.go new file mode 100644 index 00000000..36be7303 --- /dev/null +++ b/core/i18n/localize.go @@ -0,0 +1,91 @@ +package i18n + +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 { +// Key string `json:"key"` +// Value string `json:"value"` +// } + +// SupportedLocales returns array of locales. +func SupportedLocales() (locales []string) { + locales = append(locales, "en-US") + + return +} + +// Intialize will load language files +func Initialize(e embed.FS) (err error) { + localeMap = make(map[string]map[string]string) + + locales := SupportedLocales() + + for i := range locales { + content, _, err := asset.FetchStatic(e, "i18n/"+locales[i]+".json") + if err != nil { + err = errors.Wrap(err, fmt.Sprintf("missing locale %s", locales[i])) + return err + } + + var payload interface{} + json.Unmarshal([]byte(content), &payload) + m := payload.(map[string]interface{}) + + // translations := []translation{} + + translations := make(map[string]string) + + for j := range m { + translations[j] = m[j].(string) + } + + localeMap[locales[i]] = translations + } + + return nil +} + +// Localize will returns string value for given key using specified locale). +// e.g. locale = "en-US", key = "admin_billing" +// +// 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[DefaultLocale] + } + + s, ok = l[key] + if !ok { + // missing translation key is echo'ed back + 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/docker-compose.yaml b/docker-compose.yaml index 7527bc94..6064d432 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,6 +1,6 @@ # This Docker Compose file will start up Documize with PostgreSQL. # -# Use 'documize-enterprise-linux-amd64' for Enterprise Edition (default). +# Use 'documize-community-plus-linux-amd64' for Community+ Edition (default). # Use 'documize-community-linux-amd64' for Community Edition. # # You can move between editions anytime without any data loss diff --git a/domain/auth/add.go b/domain/auth/add.go index 16c6a39f..bb6654cb 100644 --- a/domain/auth/add.go +++ b/domain/auth/add.go @@ -48,6 +48,7 @@ func AddExternalUser(ctx domain.RequestContext, rt *env.Runtime, store *store.St if addUser { userID = uniqueid.Generate() u.RefID = userID + u.Locale = ctx.OrgLocale err = store.User.Add(ctx, u) if err != nil { 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/backup/backup.go b/domain/backup/backup.go index e401fca1..d3c4bdc3 100644 --- a/domain/backup/backup.go +++ b/domain/backup/backup.go @@ -244,7 +244,7 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) { c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription, coalesce(c_authconfig,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS authconfig, c_maxtags AS maxtags, - c_theme AS theme, c_logo AS logo, c_created AS created, c_revised AS revised + c_theme AS theme, c_logo AS logo, c_locale as locale, c_created AS created, c_revised AS revised FROM dmz_org`+w) if err != nil { return @@ -308,7 +308,7 @@ func (b backerHandler) dmzUserAccount(files *[]backupItem) (err error) { err = b.Runtime.Db.Select(&u, `SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised FROM dmz_user u`+w) if err != nil { diff --git a/domain/backup/restore.go b/domain/backup/restore.go index 3e524d48..73a5edf5 100644 --- a/domain/backup/restore.go +++ b/domain/backup/restore.go @@ -370,14 +370,14 @@ func (r *restoreHandler) dmzOrg() (err error) { INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig, c_maxtags, c_verified, c_serial, c_sub, c_active, - c_theme, c_logo, c_created, c_revised) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), + c_theme, c_logo, c_locale, c_created, c_revised) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), org[i].RefID, org[i].Company, org[i].Title, org[i].Message, strings.ToLower(org[i].Domain), org[i].ConversionEndpoint, strings.ToLower(org[i].Email), org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig, org[i].MaxTags, r.Runtime.StoreProvider.IsTrue(), org[i].Serial, org[i].Subscription, org[i].Active, - org[i].Theme, org[i].Logo, + org[i].Theme, org[i].Logo, org[i].Locale, org[i].Created, org[i].Revised) if err != nil { r.Context.Transaction.Rollback() @@ -412,6 +412,7 @@ func (r *restoreHandler) dmzOrg() (err error) { org[0].Title = r.Spec.Org.Title org[0].Subscription = r.Spec.Org.Subscription org[0].Theme = r.Spec.Org.Theme + org[0].Locale = r.Spec.Org.Locale } _, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET @@ -425,7 +426,8 @@ func (r *restoreHandler) dmzOrg() (err error) { c_message=:message, c_title=:title, c_serial=:serial, - c_sub=:subscription + c_sub=:subscription, + c_locale=:locale, WHERE c_refid=:refid`, &org[0]) if err != nil { r.Context.Transaction.Rollback() @@ -1735,11 +1737,11 @@ func (r *restoreHandler) dmzUser() (err error) { _, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(` INSERT INTO dmz_user (c_refid, c_firstname, c_lastname, c_email, c_initials, c_globaladmin, - c_password, c_salt, c_reset, c_active, c_lastversion, c_created, c_revised) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), + c_password, c_salt, c_reset, c_active, c_lastversion, c_locale, c_created, c_revised) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), r.remapUser(u[i].RefID), u[i].Firstname, u[i].Lastname, strings.ToLower(u[i].Email), u[i].Initials, u[i].GlobalAdmin, u[i].Password, u[i].Salt, u[i].Reset, u[i].Active, - u[i].LastVersion, u[i].Created, u[i].Revised) + u[i].LastVersion, u[i].Locale, u[i].Created, u[i].Revised) if err != nil { r.Context.Transaction.Rollback() diff --git a/domain/context.go b/domain/context.go index 5ffef969..1f245886 100644 --- a/domain/context.go +++ b/domain/context.go @@ -44,6 +44,8 @@ type RequestContext struct { GlobalAdmin bool ViewUsers bool Subscription Subscription + Locale string + OrgLocale string } //GetAppURL returns full HTTP url for the app diff --git a/domain/mail/document-approver.html b/domain/mail/document-approver.html index 057524a8..748a8e1d 100644 --- a/domain/mail/document-approver.html +++ b/domain/mail/document-approver.html @@ -61,7 +61,7 @@ background-color: #f6f6f6; @@ -69,19 +69,14 @@ background-color: #f6f6f6;
- Document Approval Role Granted + {{.Subject}}
+ 1 - - -
-

You are requested to approve all changes to the following document:

+

{{.ActionText}}

{{.Document}}

{{.Inviter}}

-
- View document -
- Have any questions? Contact Us + {{.ClickHere}}
diff --git a/domain/mail/document.go b/domain/mail/document.go index b4ed677a..661142d2 100644 --- a/domain/mail/document.go +++ b/domain/mail/document.go @@ -16,6 +16,7 @@ package mail import ( "fmt" + "github.com/documize/community/core/i18n" "github.com/documize/community/domain/smtp" ) @@ -26,32 +27,32 @@ func (m *Mailer) DocumentApprover(recipient, inviterName, inviterEmail, url, doc // check inviter name if inviterName == "Hello You" || len(inviterName) == 0 { - inviterName = "Your colleague" + inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender") } em := smtp.EmailMessage{} - em.Subject = fmt.Sprintf("%s has granted you document approval", inviterName) + em.Subject = i18n.Localize(m.Context.Locale, "mail_template_approval", inviterName) em.ToEmail = recipient em.ToName = recipient em.ReplyTo = inviterEmail em.ReplyName = inviterName - if IsBlockedEmailDomain(em.ToEmail) { - return - } - parameters := struct { Subject string Inviter string URL string Document string SenderEmail string + ActionText string + ClickHere string }{ em.Subject, inviterName, url, document, m.Config.SenderEmail, + i18n.Localize(m.Context.Locale, "mail_template_approval_explain"), + i18n.Localize(m.Context.Locale, "mail_template_click_here"), } html, err := m.ParseTemplate("mail/document-approver.html", parameters) diff --git a/domain/mail/email.html b/domain/mail/email.html deleted file mode 100644 index 770af545..00000000 --- a/domain/mail/email.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - -Your Documize Community Invitation - - - - - - - - - - - - - -
-
- - - - - - - -
- {{.Inviter}} has invited you to use Documize Community -
- - - - - - - - - - - - - - - - -
- Documize Community provides easy access to all your Word documents so everyone can find and edit documents - no more network drives. -
- Your co-workers are using Documize right now. -
- Use your email address as your password ({{.Email}}). -
- Click here to access Documize -
- Have any questions? Contact Us -
-
-
-
- - - diff --git a/domain/mail/invite-existing-user.html b/domain/mail/invite-existing-user.html index 3bad4055..2deac2d8 100644 --- a/domain/mail/invite-existing-user.html +++ b/domain/mail/invite-existing-user.html @@ -59,25 +59,15 @@ background-color: #f6f6f6;
- {{.Inviter}} has invited you to their Documize Community account + {{.Subject}}
- - - - - -
- Documize Community provides secure and easy access to all your documentation so everyone can find and edit the same thing. -
- Click here to access Documize -
- Have any questions? Contact Us + {{.ClickHere}}
diff --git a/domain/mail/invite-new-user.html b/domain/mail/invite-new-user.html index cc79c2e1..c4119e35 100644 --- a/domain/mail/invite-new-user.html +++ b/domain/mail/invite-new-user.html @@ -59,35 +59,20 @@ background-color: #f6f6f6; - - + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -117,7 +117,7 @@  {{permission.name}} {{#if (eq permission.whoId session.user.id)}} -  (you) +  * {{/if}} {{/if}} @@ -144,14 +144,14 @@ {{ui/ui-button color=constants.Color.Green light=true icon=constants.Icon.Locked - label=constants.Label.Save onClick=(action "onSave")}} + label=(localize 'save') onClick=(action "onSave")}}
- {{.Inviter}} has invited you to Documize Community + {{.Subject}}
- - - - - - - - -
- Documize Community provides secure and easy access to all your documentation so everyone can find and edit the same thing. -
- Your co-workers are using Documize right now. -
- Your temporary password: {{.Password}} + {{.Password}}
- Click here to access Documize -
- Have any questions? Contact Us + {{.ClickHere}}
diff --git a/domain/mail/password-reset.html b/domain/mail/password-reset.html index e20f0a08..933d8882 100644 --- a/domain/mail/password-reset.html +++ b/domain/mail/password-reset.html @@ -61,30 +61,15 @@ background-color: #f6f6f6; - - - - + + + + + @@ -107,7 +107,7 @@ {{group.name}}{{#if (not-eq group user.groups.lastObject)}}, {{/if}} {{else}} - <no groups> + <{{localize 'group_none'}}> {{/each}} @@ -152,10 +152,10 @@ @@ -165,8 +165,8 @@
- Your Documize Community password reset request + {{.Subject}}
- - - - - - - - -
- Someone has requested to reset your Documize Community password. If this was you, then please click below to specify a new password. -
- If you did not request a password reset, please change your password and contact us. -
- Click here to reset your password -
- Have any questions? Contact Us + {{.ClickHere}}
diff --git a/domain/mail/share-space-existing-user.html b/domain/mail/share-space-existing-user.html index 6dba93a3..1cbe265e 100644 --- a/domain/mail/share-space-existing-user.html +++ b/domain/mail/share-space-existing-user.html @@ -61,7 +61,7 @@ background-color: #f6f6f6; @@ -75,12 +75,7 @@ background-color: #f6f6f6; - - -
- {{.Inviter}} has shared {{.Folder}} with you + {{.Subject}}
- Login to Documize -
- Have any questions? Contact Us + {{.ClickHere}}
diff --git a/domain/mail/share-space-new-user.html b/domain/mail/share-space-new-user.html index feb6b1fd..32a57cc2 100644 --- a/domain/mail/share-space-new-user.html +++ b/domain/mail/share-space-new-user.html @@ -61,7 +61,7 @@ background-color: #f6f6f6; @@ -74,19 +74,9 @@ background-color: #f6f6f6; - - - - - -
- {{.Inviter}} has shared {{.Folder}} with you on Documize Community + {{.Subject}}
- Documize Community provides secure and easy access to all your documentation so everyone can find and edit the same thing. -
- Go to Documize -
- Have any questions? Contact Documize + {{.ClickHere}}
diff --git a/domain/mail/space.go b/domain/mail/space.go index df3320e9..b1536418 100644 --- a/domain/mail/space.go +++ b/domain/mail/space.go @@ -14,6 +14,7 @@ package mail import ( "fmt" + "github.com/documize/community/core/i18n" "github.com/documize/community/domain/smtp" ) @@ -24,20 +25,16 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur // check inviter name if inviterName == "Hello You" || len(inviterName) == 0 { - inviterName = "Your colleague" + inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender") } em := smtp.EmailMessage{} - em.Subject = fmt.Sprintf("%s has shared %s with you", inviterName, folder) + em.Subject = i18n.Localize(m.Context.Locale, "mail_template_shared", inviterName, folder) em.ToEmail = recipient em.ToName = recipient em.ReplyTo = inviterEmail em.ReplyName = inviterName - if IsBlockedEmailDomain(em.ToEmail) { - return - } - parameters := struct { Subject string Inviter string @@ -45,6 +42,7 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur Folder string Intro string SenderEmail string + ClickHere string }{ em.Subject, inviterName, @@ -52,6 +50,7 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur folder, intro, m.Config.SenderEmail, + i18n.Localize(m.Context.Locale, "mail_template_click_here"), } html, err := m.ParseTemplate("mail/share-space-existing-user.html", parameters) @@ -77,20 +76,16 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp // check inviter name if inviterName == "Hello You" || len(inviterName) == 0 { - inviterName = "Your colleague" + inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender") } em := smtp.EmailMessage{} - em.Subject = fmt.Sprintf("%s has shared %s with you on Documize Community", inviterName, space) + em.Subject = i18n.Localize(m.Context.Locale, "mail_template_invited", inviterName, space) em.ToEmail = recipient em.ToName = recipient em.ReplyTo = inviterEmail em.ReplyName = inviterName - if IsBlockedEmailDomain(em.ToEmail) { - return - } - parameters := struct { Subject string Inviter string @@ -98,6 +93,7 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp Invitation string Folder string SenderEmail string + ClickHere string }{ em.Subject, inviterName, @@ -105,6 +101,7 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp invitationMessage, space, m.Config.SenderEmail, + i18n.Localize(m.Context.Locale, "mail_template_click_here"), } html, err := m.ParseTemplate("mail/share-space-new-user.html", parameters) diff --git a/domain/mail/user.go b/domain/mail/user.go index e5d14f57..470c1554 100644 --- a/domain/mail/user.go +++ b/domain/mail/user.go @@ -14,6 +14,7 @@ package mail import ( "fmt" + "github.com/documize/community/core/i18n" "github.com/documize/community/domain/smtp" ) @@ -24,20 +25,16 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna // check inviter name if inviterName == "Hello You" || len(inviterName) == 0 { - inviterName = "Your colleague" + inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender") } em := smtp.EmailMessage{} - em.Subject = fmt.Sprintf("%s has invited you to Documize Community", inviterName) + em.Subject = i18n.Localize(m.Context.Locale, "mail_template_user_invite", inviterName) em.ToEmail = recipient em.ToName = recipient em.ReplyTo = inviterEmail em.ReplyName = inviterName - if IsBlockedEmailDomain(em.ToEmail) { - return - } - parameters := struct { Subject string Inviter string @@ -45,13 +42,15 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna Username string Password string SenderEmail string + ClickHere string }{ em.Subject, inviterName, url, recipient, - password, + i18n.Localize(m.Context.Locale, "mail_template_password") + " " + password, m.Config.SenderEmail, + i18n.Localize(m.Context.Locale, "mail_template_click_here"), } html, err := m.ParseTemplate("mail/invite-new-user.html", parameters) @@ -77,30 +76,28 @@ func (m *Mailer) InviteExistingUser(recipient, inviterName, inviterEmail, url st // check inviter name if inviterName == "Hello You" || len(inviterName) == 0 { - inviterName = "Your colleague" + inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender") } em := smtp.EmailMessage{} - em.Subject = fmt.Sprintf("%s has invited you to their Documize Community account", inviterName) + em.Subject = i18n.Localize(m.Context.Locale, "mail_template_user_existing", inviterName) em.ToEmail = recipient em.ToName = recipient em.ReplyTo = inviterEmail em.ReplyName = inviterName - if IsBlockedEmailDomain(em.ToEmail) { - return - } - parameters := struct { Subject string Inviter string URL string SenderEmail string + ClickHere string }{ em.Subject, inviterName, url, m.Config.SenderEmail, + i18n.Localize(m.Context.Locale, "mail_template_click_here"), } html, err := m.ParseTemplate("mail/invite-existing-user.html", parameters) @@ -125,22 +122,20 @@ func (m *Mailer) PasswordReset(recipient, url string) { m.Initialize() em := smtp.EmailMessage{} - em.Subject = "Documize Community password reset request" + em.Subject = i18n.Localize(m.Context.Locale, "mail_template_reset_password") em.ToEmail = recipient em.ToName = recipient - if IsBlockedEmailDomain(em.ToEmail) { - return - } - parameters := struct { Subject string URL string SenderEmail string + ClickHere string }{ em.Subject, url, m.Config.SenderEmail, + i18n.Localize(m.Context.Locale, "mail_template_click_here"), } html, err := m.ParseTemplate("mail/password-reset.html", parameters) diff --git a/domain/meta/endpoint.go b/domain/meta/endpoint.go index 8cee98b0..8ddf0997 100644 --- a/domain/meta/endpoint.go +++ b/domain/meta/endpoint.go @@ -21,6 +21,7 @@ import ( "text/template" "github.com/documize/community/core/env" + "github.com/documize/community/core/i18n" "github.com/documize/community/core/response" "github.com/documize/community/core/stringutil" "github.com/documize/community/domain" @@ -67,6 +68,11 @@ func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) { data.ConversionEndpoint = org.ConversionEndpoint data.Storage = h.Runtime.StoreProvider.Type() data.Location = h.Runtime.Flags.Location // reserved + data.Locale = org.Locale + data.Locales = i18n.SupportedLocales() + if len(data.Locale) == 0 { + data.Locale = i18n.DefaultLocale + } // Is product setup complete? SMTP in this case. data.Configured = true @@ -281,5 +287,7 @@ func (h *Handler) writeLogo(w http.ResponseWriter, r *http.Request, logo []byte) } var defaultLogo = ` -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 ` + +// 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 diff --git a/domain/organization/store.go b/domain/organization/store.go index 3c3cf156..0734e215 100644 --- a/domain/organization/store.go +++ b/domain/organization/store.go @@ -52,7 +52,7 @@ func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Or c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, - c_maxtags AS maxtags, c_theme AS theme, c_created AS created, c_revised AS revised + c_maxtags AS maxtags, c_theme AS theme, c_locale as locale, c_created AS created, c_revised AS revised FROM dmz_org WHERE c_refid=?`), id) @@ -84,7 +84,7 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, - c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme + c_maxtags AS maxtags, c_theme AS theme, c_locale as locale, c_created AS created, c_revised AS revised, c_theme AS theme FROM dmz_org WHERE c_domain=? AND c_active=`+s.IsTrue()), subdomain) @@ -99,7 +99,7 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider, coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription, - c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme + c_maxtags AS maxtags, c_theme AS theme, c_locale as locale, c_created AS created, c_revised AS revised, c_theme AS theme FROM dmz_org WHERE c_domain='' AND c_active=`+s.IsTrue())) @@ -116,7 +116,7 @@ func (s Store) UpdateOrganization(ctx domain.RequestContext, org org.Organizatio _, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email, c_domain=:domain, - c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_theme=:theme, c_revised=:revised + c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_theme=:theme, c_locale=:locale, c_revised=:revised WHERE c_refid=:refid`, &org) diff --git a/domain/product.go b/domain/product.go index 58c78fac..f6c03e1f 100644 --- a/domain/product.go +++ b/domain/product.go @@ -88,30 +88,32 @@ type Product struct { // IsValid returns if subscription is valid using RequestContext. func (p *Product) IsValid(ctx RequestContext) bool { + return true + // Community edition is always valid. - if p.Edition == CommunityEdition { - return true - } + // if p.Edition == CommunityEdition { + // return true + // } // Empty means we cannot be valid. - if ctx.Subscription.IsEmpty() { - return false - } + // if ctx.Subscription.IsEmpty() { + // return false + // } // Enterprise edition is valid if system has loaded up user count by tenant. - if uc, ok := p.UserCount[ctx.OrgID]; ok { - // Enterprise edition is valid if subcription date is greater than now and we have enough users/seats. - if time.Now().UTC().Before(ctx.Subscription.End) && uc <= int(ctx.Subscription.Seats) { - return true - } - } else { - // First 10 is free for Enterprise edition. - if Seats1 == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) { - return true - } - } + // if uc, ok := p.UserCount[ctx.OrgID]; ok { + // // Enterprise edition is valid if subcription date is greater than now and we have enough users/seats. + // if time.Now().UTC().Before(ctx.Subscription.End) && uc <= int(ctx.Subscription.Seats) { + // return true + // } + // } else { + // // First 10 is free for Enterprise edition. + // if Seats1 == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) { + // return true + // } + // } - return false + // return false } // SubscriptionData holds encrypted data and is unpacked into Subscription. diff --git a/domain/section/github/auth.go b/domain/section/github/auth.go deleted file mode 100644 index 68c4a091..00000000 --- a/domain/section/github/auth.go +++ /dev/null @@ -1,116 +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 github - -import ( - "context" - "encoding/json" - "net/http" - "net/url" - "strings" - - "github.com/documize/community/core/env" - "github.com/documize/community/domain" - "github.com/documize/community/domain/section/provider" - gogithub "github.com/google/go-github/github" - "golang.org/x/oauth2" -) - -func clientID(ctx domain.RequestContext, s *domain.Store) string { - c, _ := s.Setting.Get(meta.ConfigHandle(), "clientID") - return c -} - -func clientSecret(ctx domain.RequestContext, s *domain.Store) string { - c, _ := s.Setting.Get(meta.ConfigHandle(), "clientSecret") - return c -} - -func authorizationCallbackURL(ctx domain.RequestContext, s *domain.Store) string { - // NOTE: URL value must have the path and query "/api/public/validate?section=github" - c, _ := s.Setting.Get(meta.ConfigHandle(), "authorizationCallbackURL") - return c -} - -func validateToken(ctx provider.Context, s *domain.Store, ptoken string) error { - // Github authorization check - authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{ - Username: clientID(ctx.Request, s), - Password: clientSecret(ctx.Request, s), - }).Client()) - _, _, err := authClient.Authorizations.Check(context.Background(), clientID(ctx.Request, s), ptoken) - return err -} - -func (*Provider) githubClient(config *githubConfig) *gogithub.Client { - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: config.Token}, - ) - tc := oauth2.NewClient(oauth2.NoContext, ts) - - return gogithub.NewClient(tc) -} - -// Callback is called by a browser redirect from Github, via the validation endpoint -func Callback(rt *env.Runtime, s *domain.Store, res http.ResponseWriter, req *http.Request) error { - ctx := domain.GetRequestContext(req) - - code := req.URL.Query().Get("code") - state := req.URL.Query().Get("state") - - ghurl := "https://github.com/login/oauth/access_token" - vals := "client_id=" + clientID(ctx, s) - vals += "&client_secret=" + clientSecret(ctx, s) - vals += "&code=" + code - vals += "&state=" + state - - req2, err := http.NewRequest("POST", ghurl+"?"+vals, strings.NewReader(vals)) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req2.Header.Set("Accept", "application/json") - - res2, err := http.DefaultClient.Do(req2) - if err != nil { - return err - } - - var gt githubCallbackT - - err = json.NewDecoder(res2.Body).Decode(>) - if err != nil { - return err - } - - err = res2.Body.Close() - if err != nil { - return err - } - - returl, err := url.QueryUnescape(state) - if err != nil { - return err - } - - up, err := url.Parse(returl) - if err != nil { - return err - } - - target := up.Scheme + "://" + up.Host + up.Path + "?mode=edit&code=" + gt.AccessToken - - http.Redirect(res, req, target, http.StatusTemporaryRedirect) - - return nil -} diff --git a/domain/section/github/commits.go b/domain/section/github/commits.go deleted file mode 100644 index 1a4861e3..00000000 --- a/domain/section/github/commits.go +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2016 Documize Inc. . All rights reserved. -// -// This software (Documize unity 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 github - -import ( - "context" - "fmt" - "html/template" - "sort" - "time" - - gogithub "github.com/google/go-github/github" -) - -const commitTimeFormat = "2006-01-02, 15:04" - -type githubCommit struct { - Owner string `json:"owner"` - Repo string `json:"repo"` - ShowRepo bool `json:"showRepo"` - Branch string `json:"branch"` - ShowBranch bool `json:"showBranch"` - Date string `json:"date"` - BinDate time.Time `json:"-"` // only used for sorting - ShowDate bool `json:"showDate"` - Login string `json:"login"` - Name string `json:"name"` - Avatar string `json:"avatar"` - Message string `json:"message"` - URL template.URL `json:"url"` -} - -type githubAuthorStats struct { - Author string `json:"author"` - Login string `json:"login"` - Avatar string `json:"avatar"` - CommitCount int `json:"commitCount"` - Repos []string `json:"repos"` - OpenIssues int `json:"openIssues"` - ClosedIssues int `json:"closedIssues"` -} - -// order commits in a way that makes sense of the table -type orderCommits []githubCommit - -func (s orderCommits) Len() int { return len(s) } -func (s orderCommits) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s orderCommits) Less(i, j int) bool { - if s[i].Repo == s[j].Repo { - if s[i].Branch == s[j].Branch { - if s[i].BinDate == s[j].BinDate { - return s[i].Name < s[j].Name - } - return s[i].BinDate.Before(s[j].BinDate) - } - return s[i].Branch < s[j].Branch - } - return s[i].Repo < s[j].Repo -} - -// sort stats in order that that should be presented. -type asToSort []githubAuthorStats - -func (s asToSort) Len() int { return len(s) } -func (s asToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s asToSort) Less(i, j int) bool { - return s[i].CommitCount > s[j].CommitCount -} - -// sort branches in order that that should be presented. -type branchByID []githubBranch - -func (s branchByID) Len() int { return len(s) } -func (s branchByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s branchByID) Less(i, j int) bool { - return s[i].ID < s[j].ID -} - -const tagCommitsData = "commitsData" - -func getCommits(client *gogithub.Client, config *githubConfig) ([]githubCommit, []githubAuthorStats, error) { - - if !config.ShowCommits { - return nil, nil, nil - } - - // first make sure we've got all the branches - for _, orb := range config.Lists { - if orb.Included { - - branches, _, err := client.Repositories.ListBranches(context.Background(), orb.Owner, orb.Repo, - &gogithub.ListOptions{PerPage: 100}) - if err == nil { - render := make([]githubBranch, len(branches)) - for kc, vb := range branches { - for _, existing := range config.Lists { - if orb.Owner == existing.Owner && orb.Repo == existing.Repo && orb.Name == *vb.Name { - goto found - } - } - render[kc] = githubBranch{ - Owner: orb.Owner, - Repo: orb.Repo, - Name: *vb.Name, - ID: fmt.Sprintf("%s:%s:%s", orb.Owner, orb.Repo, *vb.Name), - Included: true, - URL: "https://github.com/" + orb.Owner + "/" + orb.Repo + "/tree/" + *vb.Name, - } - found: - } - config.Lists = append(config.Lists, render...) - } - } - } - sort.Sort(branchByID(config.Lists)) - - config.UserNames = make(map[string]string) - - authorStats := make(map[string]githubAuthorStats) - - contribBranch := make(map[string]map[string]struct{}) - - overall := []githubCommit{} - - for _, orb := range config.Lists { - if orb.Included { - - opts := &gogithub.CommitsListOptions{ - SHA: orb.Name, - ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}} - - if config.SincePtr != nil { - opts.Since = *config.SincePtr - } - - guff, _, err := client.Repositories.ListCommits(context.Background(), orb.Owner, orb.Repo, opts) - - if err != nil { - fmt.Println(err) - return nil, nil, err - } - - thisBranch := fmt.Sprintf("%s:%s", orb.Repo, orb.Name) - - for _, v := range guff { - - var d, m, u string - var bd time.Time - if v.Commit != nil { - if v.Commit.Committer.Date != nil { - d = v.Commit.Committer.Date.Format(commitTimeFormat) - bd = *v.Commit.Committer.Date - } - if v.Commit.Message != nil { - m = *v.Commit.Message - } - } - - if v.HTMLURL != nil { - u = *v.HTMLURL - } - - // author commits - al, an, aa := "", "", githubGravatar - if v.Author != nil { - if v.Author.Login != nil { - al = *v.Author.Login - an = getUserName(client, config, al) - } - - if v.Author.AvatarURL != nil { - aa = *v.Author.AvatarURL - } - } - l := al // use author login - - overall = append(overall, githubCommit{ - Owner: orb.Owner, - Repo: orb.Repo, - Branch: orb.Name, - Name: an, - Login: l, - Message: m, - Date: d, - BinDate: bd, - Avatar: aa, - URL: template.URL(u), - }) - - if _, ok := contribBranch[l]; !ok { - contribBranch[l] = make(map[string]struct{}) - } - contribBranch[l][thisBranch] = struct{}{} - - cum := authorStats[l] - cum.Login = l - cum.Author = an - cum.Avatar = aa - cum.CommitCount++ - // TODO review, this code removed as too slow - //cmt, _, err := client.Repositories.GetCommit(orb.Owner, orb.Repo, *v.SHA) - //if err == nil { - // if cmt.Stats != nil { - // if cmt.Stats.Total != nil { - // cum.TotalChanges += (*cmt.Stats.Total) - // } - // } - //} - // - authorStats[l] = cum - } - } - } - - sort.Sort(orderCommits(overall)) - - for k := range overall { - overall[k].ShowRepo = true - overall[k].ShowBranch = true - overall[k].ShowDate = true - if k > 0 { - if overall[k].Repo == overall[k-1].Repo { - overall[k].ShowRepo = false - if overall[k].Branch == overall[k-1].Branch { - overall[k].ShowBranch = false - if overall[k].Date == overall[k-1].Date { - overall[k].ShowDate = false - } - } - } - } - } - - retStats := make([]githubAuthorStats, 0, len(authorStats)) - for _, v := range authorStats { - repos := contribBranch[v.Login] - v.Repos = make([]string, 0, len(repos)) - for r := range repos { - v.Repos = append(v.Repos, r) - } - sort.Strings(v.Repos) - retStats = append(retStats, v) - } - sort.Sort(asToSort(retStats)) - - return overall, retStats, nil - -} - -func refreshCommits(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) { - if !config.ShowCommits { - return nil - } - - gr.BranchCommits, gr.AuthorStats, err = getCommits(client, config) - if err != nil { - return err - } - - return nil -} - -func renderCommits(payload *githubRender, c *githubConfig) error { - if !c.ShowCommits { - return nil - } - - payload.CommitCount = 0 - for range payload.BranchCommits { - payload.CommitCount++ - } - payload.HasCommits = payload.CommitCount > 0 - - for i := range payload.Issues { - var author int - for a := range payload.AuthorStats { - if payload.AuthorStats[a].Login == payload.Issues[i].Name || - (payload.AuthorStats[a].Login == "" && payload.Issues[i].Name == unassignedIssue) { - author = a - goto found - } - } - // no Author found for issue, so create one - payload.AuthorStats = append(payload.AuthorStats, githubAuthorStats{ - Author: payload.Issues[i].Name, - Avatar: payload.Issues[i].Avatar, - }) - author = len(payload.AuthorStats) - 1 - found: - if payload.Issues[i].IsOpen { - payload.AuthorStats[author].OpenIssues++ - } else { - payload.AuthorStats[author].ClosedIssues++ - } - } - payload.HasAuthorStats = len(payload.AuthorStats) > 0 - sort.Sort(asToSort(payload.AuthorStats)) - - payload.NumContributors = len(payload.AuthorStats) - 1 - - return nil -} - -func init() { - reports[tagCommitsData] = report{refreshCommits, renderCommits, commitsTemplate} -} diff --git a/domain/section/github/commits_template.go b/domain/section/github/commits_template.go deleted file mode 100644 index 4bd33a92..00000000 --- a/domain/section/github/commits_template.go +++ /dev/null @@ -1,99 +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 github - -const commitsTemplate = ` -
- - - {{if .HasCommits}} - - - - - - - - - {{range $commit := .BranchCommits}} - - - - - {{end}} - -
Commits · {{len .BranchCommits}} commits -
- {{$commit.Message}} - {{$commit.Branch}} - -
- {{$commit.Date}} - @{{$commit.Name}} -
-
- {{end}} - -
-` diff --git a/domain/section/github/github.go b/domain/section/github/github.go deleted file mode 100644 index ac010daf..00000000 --- a/domain/section/github/github.go +++ /dev/null @@ -1,251 +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 github - -import ( - "bytes" - "encoding/json" - "errors" - "html/template" - "io/ioutil" - "net/http" - "strings" - - "github.com/documize/community/core/env" - "github.com/documize/community/domain" - "github.com/documize/community/domain/section/provider" - gogithub "github.com/google/go-github/github" -) - -// TODO find a smaller image than the one below -const githubGravatar = "https://i2.wp.com/assets-cdn.github.com/images/gravatars/gravatar-user-420.png" - -var meta provider.TypeMeta - -func init() { - meta = provider.TypeMeta{} - - meta.ID = "38c0e4c5-291c-415e-8a4d-262ee80ba5df" - meta.Title = "GitHub" - meta.Description = "Link code commits and issues" - meta.ContentType = "github" - meta.PageType = "tab" - meta.Callback = Callback -} - -// Provider represents GitHub -type Provider struct { - Runtime *env.Runtime - Store *domain.Store -} - -// Meta describes us. -func (*Provider) Meta() provider.TypeMeta { - return meta -} - -// Command to run the various functions required... -func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { - query := r.URL.Query() - method := query.Get("method") - - if len(method) == 0 { - msg := "missing method name" - provider.WriteMessage(w, "gitub", msg) - return - } - - if method == "config" { - var ret struct { - CID string `json:"clientID"` - URL string `json:"authorizationCallbackURL"` - } - ret.CID = clientID(ctx.Request, p.Store) - ret.URL = authorizationCallbackURL(ctx.Request, p.Store) - provider.WriteJSON(w, ret) - return - } - - defer r.Body.Close() // ignore error - body, err := ioutil.ReadAll(r.Body) - - if err != nil { - p.Runtime.Log.Error("bad body", errors.New("Missing body")) - provider.WriteMessage(w, "github", "bad body") - return - } - - if method == "saveSecret" { // secret Token update code - - // write the new one, direct from JS - if err = ctx.SaveSecrets(string(body), p.Store); err != nil { - p.Runtime.Log.Error("github settoken configuration", err) - provider.WriteError(w, "github", err) - return - } - provider.WriteEmpty(w) - return - - } - - // load the config from the client-side - config := githubConfig{} - err = json.Unmarshal(body, &config) - - if err != nil { - p.Runtime.Log.Error("github Command Unmarshal", err) - provider.WriteError(w, "github", err) - return - } - - config.Clean() - // always use DB version of the token - config.Token = ctx.GetSecrets("token", p.Store) // get the secret token in the database - - client := p.githubClient(&config) - - switch method { - - case "checkAuth": - - if len(config.Token) == 0 { - err = errors.New("empty github token") - } else { - err = validateToken(*ctx, p.Store, config.Token) - } - if err != nil { - // token now invalid, so wipe it - ctx.SaveSecrets("", p.Store) // ignore error, already in an error state - p.Runtime.Log.Error("github check token validation", err) - provider.WriteError(w, "github", err) - return - } - provider.WriteEmpty(w) - - default: - - if listFailed(p.Runtime, method, config, client, w) { - - gr := githubRender{} - for _, rep := range reports { - rep.refresh(&gr, &config, client) - } - provider.WriteJSON(w, &gr) - - } - - } -} - -// Refresh ... gets the latest version -func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) string { - var c = githubConfig{} - - err := json.Unmarshal([]byte(configJSON), &c) - if err != nil { - p.Runtime.Log.Error("github.Refresh unmarshal", err) - return "internal configuration error '" + err.Error() + "'" - } - - c.Clean() - c.Token = ctx.GetSecrets("token", p.Store) - - client := p.githubClient(&c) - - byts, err := json.Marshal(refreshReportData(&c, client)) - if err != nil { - p.Runtime.Log.Error("github.Refresh marshal", err) - return "internal configuration error '" + err.Error() + "'" - } - - return string(byts) - -} - -func refreshReportData(c *githubConfig, client *gogithub.Client) *githubRender { - var gr = githubRender{} - for _, rep := range reports { - rep.refresh(&gr, c, client) - } - return &gr -} - -// Render ... just returns the data given, suitably formatted -func (p *Provider) Render(ctx *provider.Context, config, data string) string { - var err error - - payload := githubRender{} - var c = githubConfig{} - - err = json.Unmarshal([]byte(config), &c) - if err != nil { - return "Please delete and recreate this Github section." - } - - c.Clean() - c.Token = ctx.GetSecrets("token", p.Store) - - data = strings.TrimSpace(data) - if len(data) == 0 { - p.Runtime.Log.Info("GitHub connector received empty data for rendering") - // TODO review why this error occurs & if it should be reported - seems to occur for new sections - // log.ErrorString(fmt.Sprintf("Rendered empty github JSON payload as '' for owner %s repos %#v", c.Owner, c.Lists)) - return "" - } - - err = json.Unmarshal([]byte(data), &payload) - if err != nil { - return "Please delete and recreate this Github section." - } - - payload.Config = c - payload.Limit = c.BranchLines - payload.List = c.Lists - - ret := "" - for _, repID := range c.ReportOrder { - - rep, ok := reports[repID] - if !ok { - msg := "github report not found for: " + repID - p.Runtime.Log.Info(msg) - return "Documize internal error: " + msg - } - - if err = rep.render(&payload, &c); err != nil { - p.Runtime.Log.Error("unable to render "+repID, err) - return "Documize internal github render " + repID + " error: " + err.Error() + "
" + data - } - - t := template.New("github") - t, err = t.Parse(rep.template) - if err != nil { - p.Runtime.Log.Error("github render template.Parse error:", err) - //for k, v := range strings.Split(rep.template, "\n") { - // fmt.Println("DEBUG", k+1, v) - //} - return "Documize internal github template.Parse error: " + err.Error() - } - - buffer := new(bytes.Buffer) - err = t.Execute(buffer, payload) - if err != nil { - p.Runtime.Log.Error("github render template.Execute error:", err) - return "Documize internal github template.Execute error: " + err.Error() - } - - ret += buffer.String() - - } - - return ret -} diff --git a/domain/section/github/issues.go b/domain/section/github/issues.go deleted file mode 100644 index b503f9d5..00000000 --- a/domain/section/github/issues.go +++ /dev/null @@ -1,239 +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 github - -import ( - "context" - "html/template" - "sort" - "time" - - gogithub "github.com/google/go-github/github" -) - -type githubIssue struct { - ID int `json:"id"` - Date string `json:"date"` - Updated string `json:"dated"` - Message string `json:"message"` - URL template.URL `json:"url"` - Name string `json:"name"` - Creator string `json:"creator"` - Avatar string `json:"avatar"` - Labels template.HTML `json:"labels"` - LabelNames []string `json:"labelNames"` - LabelColors []string `json:"labelColors"` - IsOpen bool `json:"isopen"` - Repo string `json:"repo"` - Private bool `json:"private"` - Milestone string `json:"milestone"` -} - -type githubSharedLabel struct { - Name string `json:"name"` - Count int `json:"count"` - Color string `json:"color"` - Repos template.HTML `json:"Repos"` -} - -// sort issues in order that that should be presented - by date updated. -type issuesToSort []githubIssue - -func (s issuesToSort) Len() int { return len(s) } -func (s issuesToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s issuesToSort) Less(i, j int) bool { - if s[i].Milestone != noMilestone && s[j].Milestone == noMilestone { - return true - } - if s[i].Milestone == noMilestone && s[j].Milestone != noMilestone { - return false - } - if s[i].Milestone != s[j].Milestone { - // TODO should this order be by milestone completion? - return s[i].Milestone < s[j].Milestone - } - if !s[i].IsOpen && s[j].IsOpen { - return true - } - if s[i].IsOpen && !s[j].IsOpen { - return false - } - // TODO this seems a very slow approach - iDate, _ := time.Parse(issuesTimeFormat, s[i].Updated) - jDate, _ := time.Parse(issuesTimeFormat, s[j].Updated) - return iDate.Before(jDate) -} - -// sort shared labels alphabetically -type sharedLabelsSort []githubSharedLabel - -func (s sharedLabelsSort) Len() int { return len(s) } -func (s sharedLabelsSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s sharedLabelsSort) Less(i, j int) bool { return s[i].Name < s[j].Name } - -const ( - tagIssuesData = "issuesData" - issuesTimeFormat = "January 2 2006, 15:04" - unassignedIssue = "(unassigned)" -) - -func init() { - reports[tagIssuesData] = report{refreshIssues, renderIssues, issuesTemplate} -} - -func wrapLabels(labels []gogithub.Label) (l string, labelNames []string, labelColors []string) { - labelNames = make([]string, 0, len(labels)) - labelColors = make([]string, 0, len(labels)) - for _, ll := range labels { - labelNames = append(labelNames, *ll.Name) - labelColors = append(labelColors, *ll.Color) - l += `` + *ll.Name + ` ` - } - return l, labelNames, labelColors -} - -func getIssues(client *gogithub.Client, config *githubConfig) ([]githubIssue, error) { - - if !config.ShowIssues { - return nil, nil - } - - ret := []githubIssue{} - - hadRepo := make(map[string]bool) - - for _, orb := range config.Lists { - if orb.Included { - - rName := orb.Owner + "/" + orb.Repo - - if !hadRepo[rName] { - - for _, state := range []string{"open", "closed"} { - - opts := &gogithub.IssueListByRepoOptions{ - Sort: "updated", - State: state, - ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}} - - if config.SincePtr != nil && state == "closed" /* we want all the open ones */ { - opts.Since = *config.SincePtr - } - - guff, _, err := client.Issues.ListByRepo(context.Background(), orb.Owner, orb.Repo, opts) - - if err != nil { - return ret, err - } - - for _, v := range guff { - n := unassignedIssue - av := githubGravatar - ptr := v.Assignee - if ptr != nil { - if ptr.Login != nil { - n = *ptr.Login - av = *ptr.AvatarURL - } - } - ms := noMilestone - if v.Milestone != nil { - if v.Milestone.Title != nil { - ms = *v.Milestone.Title - } - } - l, ln, lc := wrapLabels(v.Labels) - ret = append(ret, githubIssue{ - Name: n, - Creator: getUserName(client, config, *v.User.Login), - Avatar: av, - Message: *v.Title, - Date: v.CreatedAt.Format(issuesTimeFormat), - Updated: v.UpdatedAt.Format(issuesTimeFormat), - URL: template.URL(*v.HTMLURL), - Labels: template.HTML(l), - LabelNames: ln, - LabelColors: lc, - ID: *v.Number, - IsOpen: *v.State == "open", - Repo: repoName(rName), - Private: orb.Private, - Milestone: ms, - }) - } - } - } - hadRepo[rName] = true - } - - } - - sort.Sort(issuesToSort(ret)) - - return ret, nil - -} - -func refreshIssues(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) { - if !config.ShowIssues { - return nil - } - - gr.Issues, err = getIssues(client, config) - if err != nil { - return err - } - - gr.OpenIssues = 0 - gr.ClosedIssues = 0 - sharedLabels := make(map[string][]string) - sharedLabelColors := make(map[string]string) - for _, v := range gr.Issues { - if v.IsOpen { - gr.OpenIssues++ - } else { - gr.ClosedIssues++ - } - for i, lab := range v.LabelNames { - sharedLabels[lab] = append(sharedLabels[lab], v.Repo) - if _, exists := sharedLabelColors[lab]; !exists { // use the first one we see - sharedLabelColors[lab] = v.LabelColors[i] - } - } - } - gr.HasIssues = (gr.OpenIssues + gr.ClosedIssues) > 0 - - gr.SharedLabels = make([]githubSharedLabel, 0, len(sharedLabels)) // will usually be too big - for name, repos := range sharedLabels { - if len(repos) > 1 { - thisLab := githubSharedLabel{Name: name, Count: len(repos), Color: sharedLabelColors[name]} - show := "" - for i, r := range repos { - if i > 0 { - show += ", " - } - show += "" + r + "" - } - thisLab.Repos = template.HTML(show) - gr.SharedLabels = append(gr.SharedLabels, thisLab) - } - } - sort.Sort(sharedLabelsSort(gr.SharedLabels)) - gr.HasSharedLabels = len(gr.SharedLabels) > 0 - - return nil -} - -func renderIssues(payload *githubRender, c *githubConfig) error { - return nil -} diff --git a/domain/section/github/issues_template.go b/domain/section/github/issues_template.go deleted file mode 100644 index a33a757c..00000000 --- a/domain/section/github/issues_template.go +++ /dev/null @@ -1,68 +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 github - -const ( - openIsvg = ` - - - - - - ` - closedIsvg = ` - - - - - - ` - issuesTemplate = ` -
-{{if .HasIssues}} - - - - - - - - - - {{range $data := .Issues}} - - - - - {{end}} - -
- Issues · {{.ClosedIssues}} closed {{if eq 1 .ClosedIssues}}{{else}}issues{{end}} and {{.OpenIssues}} open - {{if eq 1 .OpenIssues}}issue{{else}}{{end}} -
- {{if $data.IsOpen}} - ` + openIsvg + ` - {{else}} - ` + closedIsvg + ` - {{end}} - {{$data.Message}} #{{$data.ID}} - {{$data.Labels}} - -
- {{$data.Milestone}} · - {{$data.Creator}} · {{$data.Date}} -
-
-{{end}} -
-` -) diff --git a/domain/section/github/lists.go b/domain/section/github/lists.go deleted file mode 100644 index 9f32c94f..00000000 --- a/domain/section/github/lists.go +++ /dev/null @@ -1,106 +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 github - -import ( - "context" - "fmt" - "net/http" - - "github.com/documize/community/core/env" - "github.com/documize/community/domain/section/provider" - gogithub "github.com/google/go-github/github" -) - -func listFailed(rt *env.Runtime, method string, config githubConfig, client *gogithub.Client, w http.ResponseWriter) (failed bool) { - switch method { // which list to choose? - - case "owners": - - me, _, err := client.Users.Get(context.Background(), "") - if err != nil { - rt.Log.Error("github get user details:", err) - provider.WriteError(w, "github", err) - return - } - - orgs, _, err := client.Organizations.List(context.Background(), "", nil) - if err != nil { - rt.Log.Error("github get user's organisations:", err) - provider.WriteError(w, "github", err) - return - } - - owners := make([]githubOwner, 1+len(orgs)) - owners[0] = githubOwner{ID: *me.Login, Name: *me.Login} - for ko, vo := range orgs { - id := 1 + ko - owners[id].ID = *vo.Login - owners[id].Name = *vo.Login - } - - owners = sortOwners(owners) - - provider.WriteJSON(w, owners) - - case "orgrepos": - - var render []githubBranch - if config.Owner != "" { - - me, _, err := client.Users.Get(context.Background(), "") - if err != nil { - rt.Log.Error("github get user details:", err) - provider.WriteError(w, "github", err) - return - } - - var repos []*gogithub.Repository - if config.Owner == *me.Login { - repos, _, err = client.Repositories.List(context.Background(), config.Owner, nil) - } else { - opt := &gogithub.RepositoryListByOrgOptions{ - ListOptions: gogithub.ListOptions{PerPage: 100}, - } - repos, _, err = client.Repositories.ListByOrg(context.Background(), config.Owner, opt) - } - if err != nil { - rt.Log.Error("github get user/org repositories:", err) - provider.WriteError(w, "github", err) - return - } - for _, vr := range repos { - render = append(render, - githubBranch{ - Name: "master", - ID: fmt.Sprintf("%s:%s", config.Owner, *vr.Name), - Owner: config.Owner, - Repo: *vr.Name, - Private: *vr.Private, - Included: false, - URL: *vr.HTMLURL, - }) - } - } - render = sortBranches(render) - - provider.WriteJSON(w, render) - - case "content": - - provider.WriteJSON(w, refreshReportData(&config, client)) - - default: - return true // failed to get a list - } - return -} diff --git a/domain/section/github/milestones.go b/domain/section/github/milestones.go deleted file mode 100644 index 311f352c..00000000 --- a/domain/section/github/milestones.go +++ /dev/null @@ -1,225 +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 github - -import ( - "context" - "fmt" - "html/template" - "sort" - - gogithub "github.com/google/go-github/github" -) - -type githubMilestone struct { - Repo string `json:"repo"` - Private bool `json:"private"` - Name string `json:"name"` - URL template.URL `json:"url"` - IsOpen bool `json:"isopen"` - OpenIssues int `json:"openIssues"` - ClosedIssues int `json:"closedIssues"` - CompleteMsg string `json:"completeMsg"` - DueDate string `json:"dueDate"` - UpdatedAt string `json:"updatedAt"` - Progress uint `json:"progress"` - IsMilestone bool `json:"isMilestone"` -} - -// sort milestones in order that that should be presented. - -type milestonesToSort []githubMilestone - -func (s milestonesToSort) Len() int { return len(s) } -func (s milestonesToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s milestonesToSort) Less(i, j int) bool { - if s[i].Repo < s[j].Repo { - return true - } - if s[i].Repo > s[j].Repo { - return false - } - if !s[i].IsOpen && s[j].IsOpen { - return true - } - if s[i].IsOpen && !s[j].IsOpen { - return false - } - if s[i].Name != noMilestone && s[j].Name == noMilestone { - return true - } - if s[i].Name == noMilestone && s[j].Name != noMilestone { - return false - } - if s[i].Progress == s[j].Progress { // order equal progress milestones - return s[i].Name < s[j].Name - } - return s[i].Progress >= s[j].Progress // put more complete milestones first -} - -const ( - tagMilestonesData = "milestonesData" - milestonesTimeFormat = "January 2 2006" - noMilestone = "no milestone" -) - -func init() { - reports[tagMilestonesData] = report{refreshMilestones, renderMilestones, milestonesTemplate} -} - -func getMilestones(client *gogithub.Client, config *githubConfig) ([]githubMilestone, error) { - - if !config.ShowMilestones { - return nil, nil - } - - ret := []githubMilestone{} - - hadRepo := make(map[string]bool) - - for _, orb := range config.Lists { - if orb.Included { - rName := orb.Owner + "/" + orb.Repo - - if !hadRepo[rName] { - - for _, state := range []string{"open", "closed"} { - - opts := &gogithub.MilestoneListOptions{ - Sort: "updated", - State: state, - ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}} - - guff, _, err := client.Issues.ListMilestones(context.Background(), orb.Owner, orb.Repo, opts) - - if err != nil { - return ret, err - } - - for _, v := range guff { - include := true - if state == "closed" { - if config.SincePtr != nil { - if (*config.SincePtr).After(*v.ClosedAt) { - include = false - } - } - } - if include { - dd := "no due date" - if v.DueOn != nil { - // TODO refactor to add message in red if the milestone is overdue - dd = "due " + (*v.DueOn).Format(milestonesTimeFormat) + "" - } - up := "" - if v.UpdatedAt != nil { - up = (*v.UpdatedAt).Format(milestonesTimeFormat) - } - - progress := float64(*v.ClosedIssues*100) / float64(*v.OpenIssues+*v.ClosedIssues) - - ret = append(ret, githubMilestone{ - Repo: repoName(rName), - Private: orb.Private, - Name: *v.Title, - URL: template.URL(fmt.Sprintf( - "https://github.com/%s/%s/milestone/%d", - orb.Owner, orb.Repo, *v.Number)), // *v.HTMLURL does not give the correct value - IsOpen: *v.State == "open", - OpenIssues: *v.OpenIssues, - ClosedIssues: *v.ClosedIssues, - CompleteMsg: fmt.Sprintf("%2.0f%%", progress), - DueDate: dd, - UpdatedAt: up, - Progress: uint(progress), - IsMilestone: true, - }) - } - } - - } - } - hadRepo[rName] = true - } - - } - - return ret, nil - -} - -func refreshMilestones(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) { - - if !config.ShowMilestones { - return nil - } - - gr.Milestones, err = getMilestones(client, config) - if err != nil { - return err - } - gr.OpenMS = 0 - gr.ClosedMS = 0 - for _, v := range gr.Milestones { - if v.IsOpen { - gr.OpenMS++ - } else { - gr.ClosedMS++ - } - } - gr.HasMilestones = (gr.OpenMS + gr.ClosedMS) > 0 - - return nil -} - -func renderMilestones(payload *githubRender, c *githubConfig) error { - - if !c.ShowMilestones { - return nil - } - - hadRepo := make(map[string]bool) - payload.RepoCount = 0 - for _, orb := range payload.List { - rName := orb.Owner + "/" + orb.Repo - if !hadRepo[rName] { - if orb.Included { - - payload.RepoCount++ - issuesOpen, issuesClosed := 0, 0 - for _, iss := range payload.Issues { - if iss.Repo == repoName(rName) { - if iss.Milestone == noMilestone { - if iss.IsOpen { - issuesOpen++ - } else { - issuesClosed++ - } - } - } - } - if issuesClosed+issuesOpen > 0 { - //payload.Milestones = append(payload.Milestones, githubMilestone{ - // Repo: orb.Repo, Private: orb.Private, Name: noMilestone, IsOpen: true, - // OpenIssues: issuesOpen, ClosedIssues: issuesClosed, URL: template.URL(orb.URL), - //}) - } - - hadRepo[rName] = true - } - } - } - - sort.Sort(milestonesToSort(payload.Milestones)) - - return nil -} diff --git a/domain/section/github/milestones_template.go b/domain/section/github/milestones_template.go deleted file mode 100644 index 9b194e50..00000000 --- a/domain/section/github/milestones_template.go +++ /dev/null @@ -1,75 +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 github - -const ( - rawMSsvg = `` - openMSsvg = ` - - - ` + rawMSsvg + ` - - - ` - closedMSsvg = ` - - - - ` - milestonesTemplate = ` -
- {{if .HasMilestones}} - - - - - - - - - - {{range $data := .Milestones}} - - - - - {{end}} - -
Milestones · {{.ClosedMS}} closed and {{.OpenMS}} open -
- {{if $data.IsMilestone}} - {{if $data.IsOpen}} - ` + openMSsvg + ` - {{else}} - ` + closedMSsvg + ` - {{end}} - {{end}} - {{$data.Name}} - · {{if $data.IsMilestone}} {{$data.DueDate}}{{end}} - - {{if $data.IsMilestone}} - {{$data.CompleteMsg}} complete - {{$data.OpenIssues}} open - {{$data.ClosedIssues}} closed - {{else}} - {{$data.OpenIssues}} open {{$data.ClosedIssues}} closed - {{end}} -
-
-
-
- {{end}} -
-` -) diff --git a/domain/section/github/model.go b/domain/section/github/model.go deleted file mode 100644 index e1dfae38..00000000 --- a/domain/section/github/model.go +++ /dev/null @@ -1,198 +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 github - -import ( - "context" - "sort" - "strings" - "time" - - gogithub "github.com/google/go-github/github" -) - -type githubRender struct { - Config githubConfig `json:"config"` - List []githubBranch `json:"list"` - RepoCount int `json:"repoCount"` - ShowList bool `json:"showList"` - ShowIssueNumbers bool `json:"showIssueNumbers"` - BranchCommits []githubCommit `json:"branchCommits"` - HasCommits bool `json:"hasCommits"` - CommitCount int `json:"commitCount"` - Issues []githubIssue `json:"issues"` - HasIssues bool `json:"hasIssues"` - SharedLabels []githubSharedLabel `json:"sharedLabels"` - HasSharedLabels bool `json:"hasSharedLabels"` - OpenIssues int `json:"openIssues"` - ClosedIssues int `json:"closedIssues"` - Limit int `json:"limit"` - Milestones []githubMilestone `json:"milestones"` - HasMilestones bool `json:"hasMilestones"` - OpenMS int `json:"openMS"` - ClosedMS int `json:"closedMS"` - OpenPRs int `json:"openPRs"` - ClosedPRs int `json:"closedPRs"` - AuthorStats []githubAuthorStats `json:"authorStats"` - HasAuthorStats bool `json:"hasAuthorStats"` - NumContributors int `json:"numContributors"` -} - -type report struct { - refresh func(*githubRender, *githubConfig, *gogithub.Client) error - render func(*githubRender, *githubConfig) error - template string -} - -var reports = make(map[string]report) - -type githubOwner struct { - ID string `json:"id"` - Name string `json:"name"` -} - -type githubBranch struct { - ID string `json:"id"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Name string `json:"name"` - Included bool `json:"included"` - URL string `json:"url"` - Color string `json:"color,omitempty"` - Comma bool `json:"comma"` - Private bool `json:"private"` -} - -type githubLabel struct { - ID string `json:"id"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Name string `json:"name"` - Included bool `json:"included"` - URL string `json:"url"` - Color string `json:"color,omitempty"` -} - -type githubConfig struct { - Token string `json:"-"` // NOTE very important that the secret Token is not leaked to the client side, so "-" - UserID string `json:"userId"` - PageID string `json:"pageId"` - Owner string `json:"owner_name"` - BranchSince string `json:"branchSince,omitempty"` - SincePtr *time.Time `json:"-"` - Since string `json:"-"` - BranchLines int `json:"branchLines,omitempty,string"` - OwnerInfo githubOwner `json:"owner"` - ClientID string `json:"clientId"` - CallbackURL string `json:"callbackUrl"` - Lists []githubBranch `json:"lists,omitempty"` - ReportOrder []string `json:"-"` - DateMessage string `json:"-"` - UserNames map[string]string `json:"UserNames"` - ShowMilestones bool `json:"showMilestones,omitempty"` - ShowIssues bool `json:"showIssues,omitempty"` - ShowCommits bool `json:"showCommits,omitempty"` -} - -func (c *githubConfig) Clean() { - c.Owner = c.OwnerInfo.Name - if len(c.BranchSince) >= len("yyyy/mm/dd hh:ss") { - var since time.Time - tt := []byte("yyyy-mm-ddThh:mm:00Z") - for _, i := range []int{0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15} { - tt[i] = c.BranchSince[i] - } - err := since.UnmarshalText(tt) - if err != nil { - } else { - c.SincePtr = &since - } - } - if c.SincePtr == nil { - c.DateMessage = " (the last 7 days)" - since := time.Now().AddDate(0, 0, -7) - c.SincePtr = &since - } else { - c.DateMessage = "" - } - c.Since = (*c.SincePtr).Format(issuesTimeFormat) - - c.ReportOrder = []string{tagSummaryData} - - if c.ShowMilestones { - c.ReportOrder = append(c.ReportOrder, tagMilestonesData) - } - if c.ShowIssues { - c.ReportOrder = append(c.ReportOrder, tagIssuesData) - } - if c.ShowCommits { - c.ReportOrder = append(c.ReportOrder, tagCommitsData) - } - - c.BranchLines = 100 // overide any existing value with maximum allowable in one call - - sort.Sort(branchesToSort(c.Lists)) // get the configured branches in a sensible order for display - - lastItem := 0 - for i := range c.Lists { - c.Lists[i].Comma = true - if c.Lists[i].Included { - lastItem = i - } - } - if lastItem < len(c.Lists) { - c.Lists[lastItem].Comma = false - } - - if c.UserNames == nil { - c.UserNames = make(map[string]string) - } - -} - -type githubCallbackT struct { - AccessToken string `json:"access_token"` -} - -func repoName(branchName string) string { - bits := strings.Split(branchName, "/") - if len(bits) != 2 { - return branchName + "?repo" - } - pieces := strings.Split(bits[1], ":") - if len(pieces) == 0 { - return branchName + "?repo:?branch" - } - return pieces[0] -} - -func getUserName(client *gogithub.Client, config *githubConfig, login string) (fullName string) { - an := login - if content, found := config.UserNames[login]; found { - if len(content) > 0 { - an = content - } - } else { - usr, _, err := client.Users.Get(context.Background(), login) - if err == nil { - if usr.Name != nil { - if len(*usr.Name) > 0 { - config.UserNames[login] = *usr.Name - an = *usr.Name - } - } - } else { - config.UserNames[login] = login // don't look again for a missing name - } - } - return an -} diff --git a/domain/section/github/sort.go b/domain/section/github/sort.go deleted file mode 100644 index ab5f88a9..00000000 --- a/domain/section/github/sort.go +++ /dev/null @@ -1,36 +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 github - -import "sort" - -// sort owners in order that that should be presented. -type ownersToSort []githubOwner - -func (s ownersToSort) Len() int { return len(s) } -func (s ownersToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s ownersToSort) Less(i, j int) bool { - return s[i].Name < s[j].Name -} - -func sortOwners(in []githubOwner) []githubOwner { - sts := ownersToSort(in) - sort.Sort(sts) - return []githubOwner(sts) -} - -// sort branches in order that that should be presented. -func sortBranches(in []githubBranch) []githubBranch { - sts := branchesToSort(in) - sort.Sort(sts) - return []githubBranch(sts) -} diff --git a/domain/section/github/summary.go b/domain/section/github/summary.go deleted file mode 100644 index 6388df6d..00000000 --- a/domain/section/github/summary.go +++ /dev/null @@ -1,40 +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 github - -import gogithub "github.com/google/go-github/github" - -const ( - tagSummaryData = "summaryData" -) - -// sort branches in order that they should be presented. - -type branchesToSort []githubBranch - -func (s branchesToSort) Len() int { return len(s) } -func (s branchesToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s branchesToSort) Less(i, j int) bool { - return s[i].URL < s[j].URL -} - -func init() { - reports[tagSummaryData] = report{refreshSummary, renderSummary, summaryTemplate} -} - -func refreshSummary(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) { - return nil -} - -func renderSummary(payload *githubRender, c *githubConfig) error { - return nil -} diff --git a/domain/section/github/summary_template.go b/domain/section/github/summary_template.go deleted file mode 100644 index e738a3da..00000000 --- a/domain/section/github/summary_template.go +++ /dev/null @@ -1,48 +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 github - -const summaryTemplate = ` -
-

Activity since {{.Config.Since}}{{.Config.DateMessage}} for {{.Config.Owner}} repository - {{range $data := .Config.Lists}} - {{if $data.Included}} - - {{$data.Repo}}{{if $data.Comma}},{{end}} - - {{end}} - {{end}} -

- - -
-` diff --git a/domain/setting/endpoint.go b/domain/setting/endpoint.go index 4148e173..969798cf 100644 --- a/domain/setting/endpoint.go +++ b/domain/setting/endpoint.go @@ -19,6 +19,7 @@ import ( "net/http" "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/core/streamutil" @@ -98,7 +99,7 @@ func (h *Handler) SetSMTP(w http.ResponseWriter, r *http.Request) { Message string `json:"message"` } - result.Message = "Email sent successfully!" + result.Message = i18n.Localize(ctx.Locale, "server_smtp_test") u, err := h.Store.User.Get(ctx, ctx.UserID) if err != nil { @@ -113,8 +114,8 @@ func (h *Handler) SetSMTP(w http.ResponseWriter, r *http.Request) { h.Runtime.Log.Infof("%v", cfg) dialer, err := smtp.Connect(cfg) em := smtp.EmailMessage{} - em.Subject = "Documize SMTP Test" - em.BodyHTML = "

This is a test email from Documize using current SMTP settings.

" + em.Subject = i18n.Localize(ctx.Locale, "server_smtp_test_subject") + em.BodyHTML = "

" + i18n.Localize(ctx.Locale, "server_smtp_test_body") + "

" em.ToEmail = u.Email em.ToName = u.Fullname() diff --git a/domain/setting/smtp.go b/domain/setting/smtp.go index 8803afac..66ca3222 100644 --- a/domain/setting/smtp.go +++ b/domain/setting/smtp.go @@ -36,7 +36,7 @@ func GetSMTPConfig(s *store.Store) (c smtp.Config) { c.SenderEmail, _ = s.Setting.Get("SMTP", "sender") c.SenderName, _ = s.Setting.Get("SMTP", "senderName") if c.SenderName == "" { - c.SenderName = "Documize" + c.SenderName = "Documize Community" } // anon auth? diff --git a/domain/space/space.go b/domain/space/space.go index 051fddb9..99aa3707 100644 --- a/domain/space/space.go +++ b/domain/space/space.go @@ -42,6 +42,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *s u.Password = secrets.GeneratePassword(requestedPassword, u.Salt) userID := uniqueid.Generate() u.RefID = userID + u.Locale = ctx.OrgLocale err = s.User.Add(ctx, u) if err != nil { diff --git a/domain/template/endpoint.go b/domain/template/endpoint.go index 892b832d..c8033c05 100644 --- a/domain/template/endpoint.go +++ b/domain/template/endpoint.go @@ -22,6 +22,7 @@ import ( "github.com/documize/community/core/env" "github.com/documize/community/core/event" + "github.com/documize/community/core/i18n" "github.com/documize/community/core/request" "github.com/documize/community/core/response" "github.com/documize/community/core/secrets" @@ -296,7 +297,7 @@ func (h *Handler) Use(w http.ResponseWriter, r *http.Request) { var d = doc.Document{} d.Name = docTitle d.Location = fmt.Sprintf("template-%s", templateID) - d.Excerpt = "Add detailed description for document..." + d.Excerpt = i18n.Localize(ctx.Locale, "description") d.Slug = stringutil.MakeSlug(d.Name) d.Tags = "" d.SpaceID = spaceID diff --git a/domain/user/endpoint.go b/domain/user/endpoint.go index a93f6b4a..de67f102 100644 --- a/domain/user/endpoint.go +++ b/domain/user/endpoint.go @@ -26,6 +26,7 @@ import ( "github.com/documize/community/core/event" "github.com/documize/community/core/request" "github.com/documize/community/core/response" + "github.com/documize/community/core/secrets" "github.com/documize/community/core/streamutil" "github.com/documize/community/core/stringutil" @@ -135,6 +136,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { if addUser { userID = uniqueid.Generate() userModel.RefID = userID + userModel.Locale = ctx.OrgLocale err = h.Store.User.Add(ctx, userModel) if err != nil { @@ -780,6 +782,7 @@ func (h *Handler) BulkImport(w http.ResponseWriter, r *http.Request) { userModel.Firstname = strings.TrimSpace(v[0]) userModel.Lastname = strings.TrimSpace(v[1]) userModel.Email = strings.ToLower(strings.TrimSpace(v[2])) + userModel.Locale = ctx.OrgLocale if len(userModel.Email) == 0 || len(userModel.Firstname) == 0 || len(userModel.Lastname) == 0 { h.Runtime.Log.Info(method + " missing firstname, lastname, or email") diff --git a/domain/user/store.go b/domain/user/store.go index 4aef43b9..f6bf7083 100644 --- a/domain/user/store.go +++ b/domain/user/store.go @@ -38,8 +38,8 @@ func (s Store) Add(ctx domain.RequestContext, u user.User) (err error) { u.Created = time.Now().UTC() u.Revised = time.Now().UTC() - _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user (c_refid, c_firstname, c_lastname, c_email, c_initials, c_password, c_salt, c_reset, c_lastversion, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), - u.RefID, u.Firstname, u.Lastname, strings.TrimSpace(strings.ToLower(u.Email)), u.Initials, u.Password, u.Salt, "", u.LastVersion, u.Created, u.Revised) + _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user (c_refid, c_firstname, c_lastname, c_email, c_initials, c_password, c_salt, c_reset, c_lastversion, c_locale, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), + u.RefID, u.Firstname, u.Lastname, strings.TrimSpace(strings.ToLower(u.Email)), u.Initials, u.Password, u.Salt, "", u.LastVersion, u.Locale, u.Created, u.Revised) if err != nil { err = errors.Wrap(err, "execute user insert") @@ -53,7 +53,7 @@ func (s Store) Get(ctx domain.RequestContext, id string) (u user.User, err error err = s.Runtime.Db.Get(&u, s.Bind(` SELECT id, c_refid AS refid, c_firstname AS firstname, c_lastname AS lastname, c_email AS email, c_initials AS initials, c_globaladmin AS globaladmin, c_password AS password, c_salt AS salt, c_reset AS reset, - c_lastversion AS lastversion, c_created AS created, c_revised AS revised + c_lastversion AS lastversion, c_locale as locale, c_created AS created, c_revised AS revised FROM dmz_user WHERE c_refid=?`), id) @@ -72,7 +72,7 @@ func (s Store) GetByDomain(ctx domain.RequestContext, domain, email string) (u u err = s.Runtime.Db.Get(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised FROM dmz_user u, dmz_user_account a, dmz_org o WHERE LOWER(u.c_email)=? AND u.c_refid=a.c_userid AND a.c_orgid=o.c_refid AND LOWER(o.c_domain)=?`), @@ -92,7 +92,7 @@ func (s Store) GetByEmail(ctx domain.RequestContext, email string) (u user.User, err = s.Runtime.Db.Get(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised FROM dmz_user u WHERE LOWER(u.c_email)=?`), @@ -110,7 +110,7 @@ func (s Store) GetByToken(ctx domain.RequestContext, token string) (u user.User, err = s.Runtime.Db.Get(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised FROM dmz_user u WHERE u.c_reset=?`), @@ -130,7 +130,7 @@ func (s Store) GetBySerial(ctx domain.RequestContext, serial string) (u user.Use err = s.Runtime.Db.Get(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised FROM dmz_user u WHERE u.c_salt=?`), @@ -151,7 +151,7 @@ func (s Store) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []use err = s.Runtime.Db.Select(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a @@ -176,7 +176,7 @@ func (s Store) GetSpaceUsers(ctx domain.RequestContext, spaceID string) (u []use err = s.Runtime.Db.Select(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a @@ -210,7 +210,7 @@ func (s Store) GetUsersForSpaces(ctx domain.RequestContext, spaces []string) (u SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a @@ -244,7 +244,7 @@ func (s Store) UpdateUser(ctx domain.RequestContext, u user.User) (err error) { u.Revised = time.Now().UTC() u.Email = strings.ToLower(u.Email) - _, err = ctx.Transaction.NamedExec("UPDATE dmz_user SET c_firstname=:firstname, c_lastname=:lastname, c_email=:email, c_revised=:revised, c_initials=:initials, c_lastversion=:lastversion WHERE c_refid=:refid", &u) + _, err = ctx.Transaction.NamedExec("UPDATE dmz_user SET c_firstname=:firstname, c_lastname=:lastname, c_email=:email, c_revised=:revised, c_initials=:initials, c_lastversion=:lastversion, c_locale=:locale WHERE c_refid=:refid", &u) if err != nil { err = errors.Wrap(err, fmt.Sprintf("execute user update %s", u.RefID)) } @@ -313,7 +313,7 @@ func (s Store) GetUsersForOrganization(ctx domain.RequestContext, filter string, err = s.Runtime.Db.Select(&u, s.Bind(`SELECT TOP(`+strconv.Itoa(limit)+`) u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a @@ -323,7 +323,7 @@ func (s Store) GetUsersForOrganization(ctx domain.RequestContext, filter string, err = s.Runtime.Db.Select(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a @@ -357,7 +357,7 @@ func (s Store) MatchUsers(ctx domain.RequestContext, text string, maxMatches int err = s.Runtime.Db.Select(&u, s.Bind(`SELECT TOP(`+strconv.Itoa(maxMatches)+`) u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a @@ -367,7 +367,7 @@ func (s Store) MatchUsers(ctx domain.RequestContext, text string, maxMatches int err = s.Runtime.Db.Select(&u, s.Bind(`SELECT u.id, u.c_refid AS refid, u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email, u.c_initials AS initials, u.c_globaladmin AS globaladmin, - u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, + u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale, u.c_created AS created, u.c_revised AS revised, a.c_active AS active, a.c_editor AS editor, a.c_admin AS admin, a.c_users AS viewusers, a.c_analytics AS analytics FROM dmz_user u, dmz_user_account a diff --git a/edition/community.go b/edition/community.go index cc70fca0..7812bb5e 100644 --- a/edition/community.go +++ b/edition/community.go @@ -18,6 +18,7 @@ import ( "os" "github.com/documize/community/core/env" + "github.com/documize/community/core/i18n" "github.com/documize/community/domain" "github.com/documize/community/domain/section" "github.com/documize/community/domain/store" @@ -38,10 +39,10 @@ func main() { // Specify the product edition. rt.Product = domain.Product{} - rt.Product.Major = "4" - rt.Product.Minor = "2" - rt.Product.Patch = "3" - rt.Product.Revision = "220214141054" + rt.Product.Major = "5" + rt.Product.Minor = "0" + rt.Product.Patch = "0" + rt.Product.Revision = "220318131033" rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch) rt.Product.Edition = domain.CommunityEdition rt.Product.Title = "Community" @@ -62,6 +63,12 @@ func main() { } rt.Log.Info("Configuration: " + rt.Flags.ConfigSource) + // i18n + err := i18n.Initialize(rt.Assets) + if err != nil { + rt.Log.Error("i18n", err) + } + // Start database init. boot.InitRuntime(&rt, &s) diff --git a/go.mod b/go.mod index ce017930..04523af2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/documize/community -go 1.17 +go 1.18 require ( github.com/BurntSushi/toml v0.3.1 @@ -15,8 +15,6 @@ require ( github.com/go-ldap/ldap/v3 v3.4.1 github.com/go-sql-driver/mysql v1.6.0 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect - github.com/golang/protobuf v1.4.0 // indirect - github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.0.0 // indirect github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.4 @@ -31,8 +29,6 @@ require ( github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 golang.org/x/net v0.0.0-20210614182718-04defd469f4e - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - google.golang.org/appengine v1.6.6 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc gopkg.in/cas.v2 v2.1.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect @@ -45,8 +41,7 @@ require ( github.com/fatih/structs v1.0.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect + github.com/google/go-cmp v0.4.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/trivago/tgo v1.0.1 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/protobuf v1.21.0 // indirect ) diff --git a/go.sum b/go.sum index fdba8ec8..2be45e43 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -35,20 +34,9 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -90,36 +78,18 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/cas.v2 v2.1.0 h1:sbYBMWtpanwLH75GAWjIp5JnON9wa3NodLZhouu0G9I= diff --git a/gui/.eslintrc.js b/gui/.eslintrc.js index 134bf586..f3117e9b 100644 --- a/gui/.eslintrc.js +++ b/gui/.eslintrc.js @@ -24,7 +24,11 @@ module.exports = { "ember/no-get": "off", "ember/no-jquery": "off", "ember/no-mixins": "off", - "ember/no-actions-hash": "off" + "ember/no-actions-hash": "off", + "ember/require-computed-macros": "off", + "ember/use-ember-data-rfc-395-imports": "off", + "ember/avoid-leaking-state-in-ember-objects": "off", + "ember/require-return-from-computed": "off" }, overrides: [ // node files diff --git a/gui/.watchmanconfig b/gui/.watchmanconfig index e23b9c2d..18194658 100644 --- a/gui/.watchmanconfig +++ b/gui/.watchmanconfig @@ -1,3 +1,3 @@ { - "ignore_dirs": ["tmp", "dist", "dist-prod", "tests", "node_modules"] + "ignore_dirs": ["tmp", "dist", "dist-prod", "tests", "node_modules", "public"] } diff --git a/gui/app/components/customize/auth-settings.js b/gui/app/components/customize/auth-settings.js index 638f45e9..72a9f830 100644 --- a/gui/app/components/customize/auth-settings.js +++ b/gui/app/components/customize/auth-settings.js @@ -23,6 +23,7 @@ import Component from '@ember/component'; export default Component.extend(ModalMixin, Notifier, { appMeta: service(), globalSvc: service('global'), + i18n: service(), isDocumizeProvider: computed('authProvider', function() { return this.get('authProvider') === this.get('constants').AuthProvider.Documize; @@ -90,7 +91,7 @@ export default Component.extend(ModalMixin, Notifier, { let provider = this.get('authProvider'); let constants = this.get('constants'); - this.set('ldapPreview', {isError: true, message: 'Unable to connect'}); + this.set('ldapPreview', {isError: true, message: this.i18n.localize('auth_ldap_preview_error')}); switch (provider) { case constants.AuthProvider.Documize: { @@ -176,7 +177,7 @@ export default Component.extend(ModalMixin, Notifier, { this.get('globalSvc').previewLDAP(config).then((preview) => { this.set('ldapPreview', preview); this.modalOpen("#ldap-preview-modal", {"show": true}); - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); }); }, @@ -314,7 +315,7 @@ export default Component.extend(ModalMixin, Notifier, { }); } - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); }); } } diff --git a/gui/app/components/customize/backup-restore.js b/gui/app/components/customize/backup-restore.js index fca96b28..06b461b2 100644 --- a/gui/app/components/customize/backup-restore.js +++ b/gui/app/components/customize/backup-restore.js @@ -18,16 +18,18 @@ import Component from '@ember/component'; export default Component.extend(Notifier, Modal, { appMeta: service(), router: service(), + i18n: service(), + browserSvc: service('browser'), - backupLabel: 'Backup', - backupSystemLabel: 'System Backup', + backupLabel: '', + backupSystemLabel: '', backupSpec: null, backupFilename: '', backupError: false, backupSuccess: false, backupRunning: false, restoreSpec: null, - restoreButtonLabel: 'Restore', + restoreButtonLabel: '', restoreUploadReady: false, confirmRestore: '', @@ -53,6 +55,10 @@ export default Component.extend(Notifier, Modal, { didInsertElement() { this._super(...arguments); + this.set('backupLabel', this.i18n.localize('backup')); + this.set('backupSystemLabel', this.i18n.localize('backup_system')); + this.set('restoreButtonLabel', this.i18n.localize('restore')) + $('#restore-file').on('change', function(){ var fileName = document.getElementById("restore-file").files[0].name; $(this).next('.custom-file-label').html(fileName); @@ -68,14 +74,14 @@ export default Component.extend(Notifier, Modal, { let spec = this.get('backupSpec'); this.get('onBackup')(spec).then((filename) => { - this.notifySuccess('Completed'); - this.set('backupLabel', 'Start Backup'); + this.notifySuccess(this.i18n.localize('completed')); + this.set('backupLabel', this.i18n.localize('backup_start')); this.set('backupSuccess', true); this.set('backupFilename', filename); this.set('backupRunning', false); }, ()=> { - this.notifyError('Failed'); - this.set('backupLabel', 'Run Backup'); + this.notifyError(this.i18n.localize('backup_failed')); + this.set('backupLabel', this.i18n.localize('backup_run')); this.set('backupFailed', true); this.set('backupRunning', false); }); @@ -133,7 +139,7 @@ export default Component.extend(Notifier, Modal, { } // start restore process - this.set('restoreButtonLabel', 'Please wait, restore running...'); + this.set('restoreButtonLabel', this.i18n.localize('restore_running')); this.set('restoreSuccess', false); this.set('restoreFailed', false); @@ -145,13 +151,13 @@ export default Component.extend(Notifier, Modal, { } this.get('onRestore')(spec, filedata).then(() => { - this.notifySuccess('Completed'); - this.set('backupLabel', 'Restore'); + this.notifySuccess(this.i18n.localize('completed')); + this.set('backupLabel', this.i18n.localize('restore')); this.set('restoreSuccess', true); this.get('router').transitionTo('auth.logout'); }, ()=> { - this.notifyError('Failed'); - this.set('restorbackupLabel', 'Restore'); + this.notifyError(this.i18n.localize('backup_failed')); + this.set('restorbackupLabel', this.i18n.localize('restore')); this.set('restoreFailed', true); }); }, diff --git a/gui/app/components/customize/general-settings.js b/gui/app/components/customize/general-settings.js index 40d12832..6c9d6ede 100644 --- a/gui/app/components/customize/general-settings.js +++ b/gui/app/components/customize/general-settings.js @@ -10,6 +10,7 @@ // https://documize.com import $ from 'jquery'; +import { A } from '@ember/array'; import { empty, and } from '@ember/object/computed'; import { isEmpty } from '@ember/utils'; import { set } from '@ember/object'; @@ -21,6 +22,7 @@ import Component from '@ember/component'; export default Component.extend(Notifier, { appMeta: service(), router: service(), + i18n: service(), maxTags: 3, domain: '', titleEmpty: empty('model.general.title'), @@ -29,12 +31,29 @@ export default Component.extend(Notifier, { hasTitleInputError: and('titleEmpty', 'titleError'), hasMessageInputError: and('messageEmpty', 'messageError'), hasConversionEndpointInputError: and('conversionEndpointEmpty', 'conversionEndpointError'), + locale: { name: '' }, + locales: A([]), + + init(...args) { + this._super(...args); + + let l = this.get('appMeta.locales'); + let t = A([]); + + l.forEach((locale) => { + t.pushObject({ name: locale }); + }); + + this.set('locales', t); + }, didReceiveAttrs() { this._super(...arguments); this.set('maxTags', this.get('model.general.maxTags')); this.set('domain', this.get('model.general.domain')); + + this.set('locale', this.locales.findBy('name', this.get('model.general.locale'))); }, didInsertElement() { @@ -68,7 +87,7 @@ export default Component.extend(Notifier, { }); this.on("queuecomplete", function () { - self.notifySuccess('Logo uploaded'); + self.notifySuccess(this.i18n.localize('saved')); }); this.on("error", function (error, msg) { @@ -148,6 +167,10 @@ export default Component.extend(Notifier, { }, actions: { + onSelectLocale(locale) { + this.set('model.general.locale', locale.name); + }, + change() { const selectEl = $('#maxTags')[0]; const selection = selectEl.selectedOptions[0].value; @@ -186,12 +209,11 @@ export default Component.extend(Notifier, { this.set('model.general.domain', this.get('domain').toLowerCase()); this.get('onUpdate')().then(() => { - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); set(this, 'titleError', false); set(this, 'messageError', false); set(this, 'conversionEndpointError', false); - if (domainChanged) { let router = this.get('router'); router.transitionTo('auth.login'); @@ -206,7 +228,7 @@ export default Component.extend(Notifier, { onDefaultLogo() { this.get('onDefaultLogo')(this.get('appMeta.orgId')); - this.notifySuccess('Using default logo'); + this.notifySuccess(this.i18n.localize('saved')); } } }); diff --git a/gui/app/components/customize/integration-settings.js b/gui/app/components/customize/integration-settings.js index 275588cb..7cc06601 100644 --- a/gui/app/components/customize/integration-settings.js +++ b/gui/app/components/customize/integration-settings.js @@ -61,7 +61,7 @@ export default Component.extend(Notifier, { this.get('orgSvc').saveGlobalSetting('SECTION-TRELLO', this.get('trelloCreds')); } - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); }); } } diff --git a/gui/app/components/customize/license-key.js b/gui/app/components/customize/license-key.js index 266dd432..897c4ebe 100644 --- a/gui/app/components/customize/license-key.js +++ b/gui/app/components/customize/license-key.js @@ -22,10 +22,13 @@ export default Component.extend(Notifier, Modals, { subscription: null, planCloud: false, planSelfhost: false, - comment: 'Nothing in particular -- just passing through. Please close my Documize account.', + comment: '', didReceiveAttrs() { this._super(...arguments); + + this.set('comment', this.i18n.localize('close_account')); + this.get('global').getSubscription().then((subs) => { this.set('subscription', subs); if (subs.plan === 'Installed') { @@ -41,7 +44,7 @@ export default Component.extend(Notifier, Modals, { actions: { saveLicense() { this.get('global').setLicense(this.get('license')).then(() => { - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); window.location.reload(); }); }, @@ -55,7 +58,7 @@ export default Component.extend(Notifier, Modals, { let comment = this.get('comment'); this.get('global').deactivate(comment).then(() => { - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); this.modalOpen("#deactivation-confirmation-modal", {"show": true}); }); } diff --git a/gui/app/components/customize/search-index.js b/gui/app/components/customize/search-index.js index 77d43c9b..e9a1761f 100644 --- a/gui/app/components/customize/search-index.js +++ b/gui/app/components/customize/search-index.js @@ -15,14 +15,21 @@ import Component from '@ember/component'; export default Component.extend(Notifier, { appMeta: service(), - buttonLabel: 'Rebuild', + i18n: service(), + buttonLabel: '', + + init() { + this._super(...arguments); + this.buttonLabel = this.i18n.localize('search_reindex_rebuild'); + }, actions: { reindex() { - this.set('buttonLabel', 'Running...'); - this.notifyInfo("Starting search re-index process"); + this.set('buttonLabel', this.i18n.localize('running')); + this.notifyInfo(this.i18n.localize('search_reindex_start')); this.get('reindex')(() => { - this.notifySuccess("Search re-indexing complete"); + this.notifySuccess(this.i18n.localize('search_reindex_finish')); + this.set('buttonLabel', this.i18n.localize('search_reindex_rebuild')); }); } } diff --git a/gui/app/components/customize/smtp-settings.js b/gui/app/components/customize/smtp-settings.js index fcf2f7e3..0cc7c697 100644 --- a/gui/app/components/customize/smtp-settings.js +++ b/gui/app/components/customize/smtp-settings.js @@ -17,14 +17,20 @@ import Component from '@ember/component'; export default Component.extend(Notifier, { appMeta: service(), + i18n: service(), + SMTPHostEmptyError: empty('model.smtp.host'), SMTPPortEmptyError: empty('model.smtp.port'), SMTPSenderEmptyError: empty('model.smtp.sender'), senderNameError: empty('model.smtp.senderName'), - buttonText: 'Save & Test', testSMTP: null, + init() { + this._super(...arguments); + this.buttonText = this.i18n.localize('smtp_save_test'); + }, + actions: { saveSMTP() { if (this.get('SMTPHostEmptyError')) { @@ -50,11 +56,11 @@ export default Component.extend(Notifier, { }, ); - this.set('buttonText', 'Please wait...'); - this.notifyInfo('Sending test email to you'); + this.set('buttonText', this.i18n.localize('please_test')); + this.notifyInfo(this.i18n.localize('smtp_sent_test_email')); this.get('saveSMTP')().then((result) => { - this.set('buttonText', 'Save & Test'); + this.set('buttonText', this.i18n.localize('smtp_save_test')); this.set('testSMTP', result); this.set('appMeta.configured', true); diff --git a/gui/app/components/customize/space-admin.js b/gui/app/components/customize/space-admin.js index d1fca565..b1de1325 100644 --- a/gui/app/components/customize/space-admin.js +++ b/gui/app/components/customize/space-admin.js @@ -20,6 +20,7 @@ export default Component.extend(Notifier, Modals, { spaceSvc: service('folder'), browserSvc: service('browser'), documentSvc: service('document'), + i18n: service(), spaces: null, label: computed('model', function() { @@ -75,7 +76,7 @@ export default Component.extend(Notifier, Modals, { this.set('deleteSpace.id', ''); this.set('deleteSpace.name', ''); this.loadData(); - this.notifySuccess('Deleted'); + this.notifySuccess(this.i18n.localize('deleted')); }); }, @@ -86,17 +87,17 @@ export default Component.extend(Notifier, Modals, { filterType: 'space', }; - this.notifyInfo('Export running...'); + this.notifyInfo(this.i18n.localize('space_admin_export_running')); this.get('documentSvc').export(spec).then((htmlExport) => { - this.get('browserSvc').downloadFile(htmlExport, 'documize.html'); - this.notifySuccess('Export completed'); + this.get('browserSvc').downloadFile(htmlExport, 'documize-community.html'); + this.notifySuccess(this.i18n.localize('completed')); }); }, onOwner(spaceId) { this.get('spaceSvc').grantOwnerPermission(spaceId).then(() => { /* jshint ignore:line */ - this.notifySuccess('Added as owner'); + this.notifySuccess(this.i18n.localize('completed')); }); } } diff --git a/gui/app/components/customize/user-admin.js b/gui/app/components/customize/user-admin.js index a1682c15..3ffc3506 100644 --- a/gui/app/components/customize/user-admin.js +++ b/gui/app/components/customize/user-admin.js @@ -15,10 +15,12 @@ import ModalMixin from '../../mixins/modal'; import Notifier from '../../mixins/notifier'; import stringUtil from '../../utils/string'; import Component from '@ember/component'; +import { inject as service } from '@ember/service'; export default Component.extend(AuthProvider, ModalMixin, Notifier, { bulkUsers: '', newUser: null, + i18n: service(), init() { this._super(...arguments); @@ -53,7 +55,7 @@ export default Component.extend(AuthProvider, ModalMixin, Notifier, { this.get('onAddUser')(user).then(() => { this.set('newUser', { firstname: '', lastname: '', email: '', active: true }); - this.notifySuccess('Added user'); + this.notifySuccess(this.i18n.localize('added')); }); this.modalClose("#add-user-modal"); @@ -68,7 +70,7 @@ export default Component.extend(AuthProvider, ModalMixin, Notifier, { this.get('onAddUsers')(this.get('bulkUsers')).then(() => { this.set('bulkUsers', ''); - this.notifySuccess('Added users'); + this.notifySuccess(this.i18n.localize('added')); }); this.modalClose("#add-user-modal"); diff --git a/gui/app/components/customize/user-groups.js b/gui/app/components/customize/user-groups.js index f1136e8f..e8064847 100644 --- a/gui/app/components/customize/user-groups.js +++ b/gui/app/components/customize/user-groups.js @@ -174,7 +174,7 @@ export default Component.extend(AuthProvider, ModalMixin, { }, onSearch() { - debounce(this, function() { this.loadGroupInfo(); }, 450); + debounce(this, this.loadGroupInfo, 450); }, onLeaveGroup(userId) { diff --git a/gui/app/components/customize/user-list.js b/gui/app/components/customize/user-list.js index 4ef034b2..3455756d 100644 --- a/gui/app/components/customize/user-list.js +++ b/gui/app/components/customize/user-list.js @@ -20,6 +20,8 @@ import Component from '@ember/component'; export default Component.extend(AuthProvider, ModalMixin, Notifier, { groupSvc: service('group'), + i18n: service(), + editUser: null, deleteUser: null, filter: '', @@ -183,7 +185,7 @@ export default Component.extend(AuthProvider, ModalMixin, Notifier, { let cb = this.get('onDelete'); cb(this.get('deleteUser.id')); - this.notifySuccess("Deleted user"); + this.notifySuccess(this.i18n.localize('deleted')); return true; }, @@ -203,7 +205,7 @@ export default Component.extend(AuthProvider, ModalMixin, Notifier, { this.set('selectedUsers', []); this.set('hasSelectedUsers', false); - this.notifySuccess("Deleted selected users"); + this.notifySuccess(this.i18n.localize('deleted')); this.modalClose('#admin-user-delete-modal'); }, @@ -222,6 +224,8 @@ export default Component.extend(AuthProvider, ModalMixin, Notifier, { }) this.set('groups', groups); + if (_.isNull(groups)) return; + this.modalOpen("#group-member-modal", {"show": true}); }, diff --git a/gui/app/components/document/document-toolbar.js b/gui/app/components/document/document-toolbar.js index 8280ddca..90b5cea2 100644 --- a/gui/app/components/document/document-toolbar.js +++ b/gui/app/components/document/document-toolbar.js @@ -25,6 +25,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, { pinned: service(), browserSvc: service('browser'), documentSvc: service('document'), + i18n: service(), showRevisions: computed('permissions', 'document.protection', function() { if (!this.get('session.authenticated')) return false; if (!this.get('session.viewUsers')) return false; @@ -218,7 +219,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, { this.get('documentSvc').export(spec).then((htmlExport) => { this.get('browserSvc').downloadFile(htmlExport, this.get('document.slug') + '.html'); - this.notifySuccess('Exported'); + this.notifySuccess(this.i18n.localize('exported')); }); } } diff --git a/gui/app/components/document/page-heading.js b/gui/app/components/document/page-heading.js index 2682a96b..94fd6c41 100644 --- a/gui/app/components/document/page-heading.js +++ b/gui/app/components/document/page-heading.js @@ -23,6 +23,7 @@ export default Component.extend(Notifier, ModalMixin, { searchService: service('search'), router: service(), appMeta: service(), + i18n: service(), deleteChildren: false, blockTitle: "", blockExcerpt: "", @@ -79,13 +80,12 @@ export default Component.extend(Notifier, ModalMixin, { this._super(...arguments); let pageId = this.get('page.id'); - let url = window.location.protocol + '//' + this.get('appMeta.appHost') + - this.get('router').generate('document.index', {queryParams: {currentPageId: pageId}}); + let url = window.location.protocol + '//' + this.get('appMeta.appHost') + this.get('router').generate('document.index', {queryParams: {currentPageId: pageId}}); let self = this; let clip = new ClipboardJS('#page-copy-link-' + pageId, { text: function() { - self.notifySuccess('Link copied to clipboard'); + self.notifySuccess(this.i18n.localize('copied')); return url; } }); diff --git a/gui/app/components/document/section-attachment.js b/gui/app/components/document/section-attachment.js index 87b548b3..53c3bc8e 100644 --- a/gui/app/components/document/section-attachment.js +++ b/gui/app/components/document/section-attachment.js @@ -18,17 +18,24 @@ import Component from '@ember/component'; export default Component.extend(Modals, Notifier, { appMeta: service(), session: service(), + i18n: service(), editMode: false, downloadQuery: '', uploadId: computed('page', function () { let page = this.get('page'); return `page-uploader-${page.id}`; }), - uploadLabel: 'Upload Attachments', + uploadLabel: '', + + init(...args) { + this._super(...args); + + this.uploadLabel = this.i18n.localize('upload_attachment'); + }, didReceiveAttrs() { this._super(...arguments); - + // For authenticated users we send server auth token. let qry = ''; if (this.get('session.hasSecureToken')) { @@ -36,12 +43,12 @@ export default Component.extend(Modals, Notifier, { } else if (this.get('session.authenticated')) { qry = '?token=' + this.get('session.authToken'); } - this.set('downloadQuery', qry); + this.set('downloadQuery', qry); }, didRender() { this._super(...arguments); - + // For authenticated users we send server auth token. let qry = ''; if (this.get('session.hasSecureToken')) { @@ -49,7 +56,7 @@ export default Component.extend(Modals, Notifier, { } else if (this.get('session.authenticated')) { qry = '?token=' + this.get('session.authToken'); } - this.set('downloadQuery', qry); + this.set('downloadQuery', qry); // We don't setup uploader if not edit mode. if (!this.get('editMode')) { @@ -95,7 +102,7 @@ export default Component.extend(Modals, Notifier, { }); this.on("queuecomplete", function () { - self.notifySuccess('Uploaded file'); + self.notifySuccess(this.i18n.localize('uploaded')); self.get('onAttachmentUpload')(); }); @@ -120,7 +127,7 @@ export default Component.extend(Modals, Notifier, { actions: { onDelete(attachment) { - this.notifySuccess('File deleted'); + this.notifySuccess(this.i18n.localize('deleted')); this.get('onAttachmentDelete')(attachment.id); } } diff --git a/gui/app/components/document/sidebar-attachment.js b/gui/app/components/document/sidebar-attachment.js index 2d9de920..fd792f08 100644 --- a/gui/app/components/document/sidebar-attachment.js +++ b/gui/app/components/document/sidebar-attachment.js @@ -22,6 +22,7 @@ export default Component.extend(Modals, Notifier, { browserSvc: service('browser'), appMeta: service(), session: service(), + i18n: service(), hasAttachments: notEmpty('files'), canEdit: computed('permissions.{documentApprove,documentEdit}', 'document.protection', function() { // Check to see if specific scenarios prevent us from changing doc level attachments. @@ -86,7 +87,7 @@ export default Component.extend(Modals, Notifier, { }); this.on("queuecomplete", function () { - self.notifySuccess('Uploaded file'); + self.notifySuccess(this.i18n.localize('uploaded')); self.getAttachments(); }); @@ -115,7 +116,7 @@ export default Component.extend(Modals, Notifier, { actions: { onDelete(attachment) { this.get('documentService').deleteAttachment(this.get('document.id'), attachment.id).then(() => { - this.notifySuccess('File deleted'); + this.notifySuccess(this.i18n.localize('deleted')); this.getAttachments(); }); } diff --git a/gui/app/components/folder/documents-list.js b/gui/app/components/folder/documents-list.js index 67f29594..4e26758f 100644 --- a/gui/app/components/folder/documents-list.js +++ b/gui/app/components/folder/documents-list.js @@ -16,10 +16,11 @@ import Component from '@ember/component'; export default Component.extend({ localStorage: service(), + i18n: service(), showDeleteDialog: false, showMoveDialog: false, selectedDocuments: A([]), - selectedCaption: 'document', + selectedCaption: '', viewDensity: "1", showAdd: computed('permissions.documentAdd', 'documents', function() { @@ -35,6 +36,11 @@ export default Component.extend({ return _.isEmpty(this.get('categoryFilter')) && this.get('documents').length == this.get("numDocuments"); }), + init() { + this._super(...arguments); + this.selectedCaption = this.i18n.localize('document'); + }, + didReceiveAttrs() { this._super(...arguments); @@ -162,7 +168,7 @@ export default Component.extend({ list = _.without(list, documentId); } - this.set('selectedCaption', list.length > 1 ? 'documents' : 'document'); + this.set('selectedCaption', list.length > 1 ? this.i18n.localize('document') : this.i18n.localize('documents')); this.set('selectedDocuments', A(list)); }, diff --git a/gui/app/components/folder/settings-blocks.js b/gui/app/components/folder/settings-blocks.js index d2b28a15..7d10a6b1 100644 --- a/gui/app/components/folder/settings-blocks.js +++ b/gui/app/components/folder/settings-blocks.js @@ -19,6 +19,7 @@ export default Component.extend(AuthMixin, Notifier, { router: service(), spaceSvc: service('folder'), sectionSvc: service('section'), + i18n: service(), showDeleteDialog: false, deleteBlockId: '', @@ -54,7 +55,7 @@ export default Component.extend(AuthMixin, Notifier, { this.get('sectionSvc').deleteBlock(id).then(() => { this.set('deleteBlockId', ''); - this.notifySuccess('Deleted'); + this.notifySuccess(this.i18n.localize('deleted')); this.get('sectionSvc').getSpaceBlocks(this.get('space.id')).then((blocks) => { this.set('blocks', blocks); diff --git a/gui/app/components/folder/settings-category.js b/gui/app/components/folder/settings-category.js index c540c775..d705296c 100644 --- a/gui/app/components/folder/settings-category.js +++ b/gui/app/components/folder/settings-category.js @@ -22,6 +22,7 @@ export default Component.extend(ModalMixin, Notifer, { categorySvc: service('category'), appMeta: service(), store: service(), + i18n: service(), editId: '', editName: '', editDefault: false, @@ -120,7 +121,7 @@ export default Component.extend(ModalMixin, Notifer, { this.get('categorySvc').add(c).then(() => { this.load(); - this.notifySuccess('Category added'); + this.notifySuccess(this.i18n.localize('added')); }); }, diff --git a/gui/app/components/folder/settings-general.js b/gui/app/components/folder/settings-general.js index 0eeef891..e347da53 100644 --- a/gui/app/components/folder/settings-general.js +++ b/gui/app/components/folder/settings-general.js @@ -24,6 +24,7 @@ export default Component.extend(AuthMixin, Notifier, { spaceSvc: service('folder'), iconSvc: service('icon'), localStorage: service('localStorage'), + i18n: service(), isSpaceAdmin: computed('permissions', function() { return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage'); }), @@ -120,9 +121,9 @@ export default Component.extend(AuthMixin, Notifier, { let folder = this.get('space'); let spaceTypeOptions = A([]); - spaceTypeOptions.pushObject({id: constants.SpaceType.Private, label: 'Private - viewable only by me'}); - spaceTypeOptions.pushObject({id: constants.SpaceType.Protected, label: 'Protected - access is restricted to selected users'}); - spaceTypeOptions.pushObject({id: constants.SpaceType.Public, label: 'Public - can be seen by everyone'}); + spaceTypeOptions.pushObject({id: constants.SpaceType.Private, label: this.i18n.localize('personal_explain')}); + spaceTypeOptions.pushObject({id: constants.SpaceType.Protected, label: this.i18n.localize('protected_explain')}); + spaceTypeOptions.pushObject({id: constants.SpaceType.Public, label: this.i18n.localize('public_explain')}); this.set('spaceTypeOptions', spaceTypeOptions); this.set('spaceType', spaceTypeOptions.findBy('id', folder.get('spaceType'))); @@ -131,7 +132,7 @@ export default Component.extend(AuthMixin, Notifier, { if (this.get('allowLikes')) { this.set('likes', folder.get('likes')); } else { - this.set('likes', 'Did this help you?'); + this.set('likes', this.i18n.localize('likes_prompt')); } this.set('spaceName', this.get('space.name')); @@ -184,7 +185,7 @@ export default Component.extend(AuthMixin, Notifier, { space.set('labelId', this.get('spaceLabel')); this.get('spaceSvc').save(space).then(() => { - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); }); } } diff --git a/gui/app/components/folder/settings-permissions.js b/gui/app/components/folder/settings-permissions.js index 22939bf4..10607ac8 100644 --- a/gui/app/components/folder/settings-permissions.js +++ b/gui/app/components/folder/settings-permissions.js @@ -27,6 +27,7 @@ export default Component.extend(Notifier, Modals, AuthProvider, { router: service(), appMeta: service(), store: service(), + i18n: service(), spacePermissions: null, users: null, searchText: '', @@ -129,7 +130,7 @@ export default Component.extend(Notifier, Modals, AuthProvider, { }, getDefaultInvitationMessage() { - return "Hey there, I am sharing the " + this.get('folder.name') + " space (in " + this.get("appMeta.title") + ") with you so we can both collaborate on documents."; + return this.i18n.localize('space_invite_message', this.get('folder.name'), this.get("appMeta.title")); }, matchUsers(s) { @@ -181,7 +182,7 @@ export default Component.extend(Notifier, Modals, AuthProvider, { onSave() { if (!this.get('isSpaceAdmin')) return; - let message = this.getDefaultInvitationMessage(); + let message = this.getDefaultInvitationMessage(); let permissions = this.get('spacePermissions'); let folder = this.get('folder'); let payload = { Message: message, Permissions: permissions }; @@ -216,7 +217,7 @@ export default Component.extend(Notifier, Modals, AuthProvider, { } this.get('spaceSvc').savePermissions(folder.get('id'), payload).then(() => { - this.notifySuccess('Saved'); + this.notifySuccess(this.i18n.localize('saved')); this.get('onRefresh')(); }); }, @@ -263,7 +264,7 @@ export default Component.extend(Notifier, Modals, AuthProvider, { return; } - var result = { + var result = { Message: message, Recipients: [] }; @@ -284,7 +285,7 @@ export default Component.extend(Notifier, Modals, AuthProvider, { this.set('inviteEmail', ''); this.get('spaceSvc').share(this.get('folder.id'), result).then(() => { - this.notifySuccess('Invites sent'); + this.notifySuccess(this.i18n.localize('sent')); $('#space-invite-email').removeClass('is-invalid'); this.modalClose("#space-invite-user-modal"); this.load(); diff --git a/gui/app/components/folder/space-toolbar.js b/gui/app/components/folder/space-toolbar.js index 571b9885..8aef45d7 100644 --- a/gui/app/components/folder/space-toolbar.js +++ b/gui/app/components/folder/space-toolbar.js @@ -27,6 +27,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, { session: service(), appMeta: service(), pinned: service(), + i18n: service(), spaceName: '', copyTemplate: true, copyPermission: true, @@ -252,7 +253,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, { let status = this.get('importStatus'); let documents = this.get('importedDocuments'); - status.pushObject(`Converting ${filename}...`); + status.pushObject(this.i18n.localize('import_convert', filename)); documents.push(filename); this.set('importStatus', status); @@ -263,7 +264,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, { let status = this.get('importStatus'); let documents = this.get('importedDocuments'); - status.pushObject(`Successfully converted ${filename}`); + status.pushObject(this.i18n.localize('import_success', filename)); documents.pop(filename); this.set('importStatus', status); @@ -301,7 +302,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, { this.get('documentSvc').export(spec).then((htmlExport) => { this.get('browserSvc').downloadFile(htmlExport, this.get('space.slug') + '.html'); - this.notifySuccess('Exported'); + this.notifySuccess(this.i18n.localize('exported')); }); this.modalClose("#space-export-modal"); diff --git a/gui/app/components/onboard/share-folder.js b/gui/app/components/onboard/share-folder.js index 9243614f..4f1c1022 100644 --- a/gui/app/components/onboard/share-folder.js +++ b/gui/app/components/onboard/share-folder.js @@ -20,7 +20,9 @@ export default Component.extend({ slug: "", processing: false, - didRender() { + didRender(...args) { + this._super(...args); + let self = this; $("#stage-1-firstname").focus(); @@ -97,8 +99,6 @@ export default Component.extend({ if ($("#stage-2-password-confirm").val() !== $("#stage-2-password").val()) { $("#stage-2-password").addClass("is-invalid"); $("#stage-2-password-confirm").addClass("is-invalid"); - // $(".mismatch").show(); - // $(".password-status").attr("src", "/assets/img/onboard/lock-red.png"); return; } @@ -115,7 +115,6 @@ export default Component.extend({ self.set('processing', true); $(".stage-3").fadeIn(); - // $("#spinner-1").show(); var payload = '{ "password": "' + $("#stage-2-password").val() + '", "serial": "' + self.serial + '", "firstname": "' + $("#stage-1-firstname").val() + '", "lastname": "' + $("#stage-1-lastname").val() + '" }'; var password = $("#stage-2-password").val(); @@ -126,11 +125,6 @@ export default Component.extend({ self.get('session').authenticate('authenticator:documize', creds).then(() => { window.location.href = '//' + window.location.host + '/s/' + self.folderId + "/" + self.slug; }); - - // var credentials = encodingUtil.Base64.encode(netUtil.getSubdomain() + ":" + user.email + ":" + password); - // self.session.sso(credentials).then(function() { - // window.location.href = 's/' + self.folderId + "/" + self.slug; - // }); }, function() { window.location.href = "/"; }); diff --git a/gui/app/components/search/search-results.js b/gui/app/components/search/search-results.js index 8e44df3d..be3b364b 100644 --- a/gui/app/components/search/search-results.js +++ b/gui/app/components/search/search-results.js @@ -15,13 +15,13 @@ import Component from '@ember/component'; export default Component.extend({ localStorage: service('localStorage'), + i18n: service(), resultPhrase: '', searchQuery: computed('keywords', function() { return encodeURIComponent(this.get('keywords')); }), - // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects sortBy: { - name: true, + name: true, created: false, updated: false, asc: true, @@ -33,15 +33,15 @@ export default Component.extend({ let docs = this.get('results'); let duped = []; - let phrase = 'Nothing found'; + let phrase = this.i18n.localize('nothing_found'); if (docs.length > 0) { duped = _.uniqBy(docs, function(item) { return item.get('documentId'); }); - let references = docs.length === 1 ? "reference" : "references"; - let docLabel = duped.length === 1 ? "document" : "documents"; + let references = docs.length === 1 ? this.i18n.localize('reference') : this.i18n.localize('references'); + let docLabel = duped.length === 1 ? this.i18n.localize('document') : this.i18n.localize('documents'); let i = docs.length; let j = duped.length; phrase = `${i} ${references} in ${j} ${docLabel}`; @@ -68,19 +68,19 @@ export default Component.extend({ if (_.isNull(docs)) return; - if (sortBy.name) { + if (sortBy.name) { docs = docs.sortBy('document'); ls.storeSessionItem('search.sortBy', 'name'); } - if (sortBy.created) { + if (sortBy.created) { docs = docs.sortBy('created'); ls.storeSessionItem('search.sortBy', 'created'); } - if (sortBy.updated) { + if (sortBy.updated) { docs = docs.sortBy('revised'); ls.storeSessionItem('search.sortBy', 'updated'); } - if (sortBy.desc) { + if (sortBy.desc) { docs = docs.reverseObjects(); ls.storeSessionItem('search.sortOrder', 'desc'); } else { diff --git a/gui/app/components/section/airtable/type-editor.js b/gui/app/components/section/airtable/type-editor.js index 00855a12..eaf47a4e 100644 --- a/gui/app/components/section/airtable/type-editor.js +++ b/gui/app/components/section/airtable/type-editor.js @@ -15,6 +15,7 @@ export default Component.extend({ data: "", didReceiveAttrs() { + this._super(); this.set("data", this.get("meta.rawBody")); }, diff --git a/gui/app/components/section/base-editor-inline.js b/gui/app/components/section/base-editor-inline.js index 218e2322..6fb390b7 100644 --- a/gui/app/components/section/base-editor-inline.js +++ b/gui/app/components/section/base-editor-inline.js @@ -34,7 +34,7 @@ export default Component.extend(Modals, Notifier, { pageTitle: '', didReceiveAttrs() { - this._super(...arguments); + this._super(); this.set('pageTitle', this.get('page.title')); }, diff --git a/gui/app/components/section/base-editor.js b/gui/app/components/section/base-editor.js index 77b5ccab..524ea425 100644 --- a/gui/app/components/section/base-editor.js +++ b/gui/app/components/section/base-editor.js @@ -21,7 +21,9 @@ export default Component.extend(ModalMixin, { hasNameError: empty('page.title'), hasDescError: empty('page.excerpt'), - didRender() { + didRender(...args) { + this._super(...args); + let self = this; Mousetrap.bind('esc', function () { self.send('onCancel'); diff --git a/gui/app/components/section/code/type-editor.js b/gui/app/components/section/code/type-editor.js index 3cbe2037..5c8b883a 100644 --- a/gui/app/components/section/code/type-editor.js +++ b/gui/app/components/section/code/type-editor.js @@ -67,7 +67,8 @@ export default Component.extend({ } }, - didInsertElement() { + didInsertElement(...args) { + this._super(...args); var editor = CodeMirror.fromTextArea(document.getElementById(this.get('editorId')), { theme: "material", lineNumbers: true, @@ -91,7 +92,8 @@ export default Component.extend({ this.set('codeEditor', editor); }, - willDestroyElement() { + willDestroyElement(...args) { + this._super(...args); let editor = this.get('codeEditor'); if (!_.isNull(editor)) { diff --git a/gui/app/components/section/flowchart/type-editor.js b/gui/app/components/section/flowchart/type-editor.js index afb899f0..6aaccf79 100644 --- a/gui/app/components/section/flowchart/type-editor.js +++ b/gui/app/components/section/flowchart/type-editor.js @@ -17,13 +17,14 @@ import Component from '@ember/component'; export default Component.extend({ appMeta: service(), sectionSvc: service('section'), + i18n: service(), isDirty: false, waiting: false, diagram: '', diagramXML: '', title: '', readyToSave: false, - previewButtonCaption: 'Preview', + previewButtonCaption: '', flowCallback: null, editorId: computed('page', function () { let page = this.get('page'); @@ -40,6 +41,8 @@ export default Component.extend({ didInsertElement() { this._super(...arguments); + this.previewButtonCaption = this.i18n.localize('preview'); + schedule('afterRender', () => { this.setupEditor(); }); @@ -125,7 +128,7 @@ export default Component.extend({ action: 'export', format: 'xmlpng', xml: this.get('diagramXML'), - spin: 'Updating' + spin: this.i18n.localize('updating') } ), '*'); }, diff --git a/gui/app/components/section/frame/type-editor.js b/gui/app/components/section/frame/type-editor.js index 00855a12..3001804c 100644 --- a/gui/app/components/section/frame/type-editor.js +++ b/gui/app/components/section/frame/type-editor.js @@ -15,6 +15,8 @@ export default Component.extend({ data: "", didReceiveAttrs() { + this._super(); + this.set("data", this.get("meta.rawBody")); }, diff --git a/gui/app/components/section/gemini/type-editor.js b/gui/app/components/section/gemini/type-editor.js index 428d838f..55965fce 100644 --- a/gui/app/components/section/gemini/type-editor.js +++ b/gui/app/components/section/gemini/type-editor.js @@ -30,6 +30,8 @@ export default Component.extend(SectionMixin, { }, didReceiveAttrs() { + this._super(); + let config = {}; try { diff --git a/gui/app/components/section/markdown/type-editor.js b/gui/app/components/section/markdown/type-editor.js index c36c8aaf..1dfb2f74 100644 --- a/gui/app/components/section/markdown/type-editor.js +++ b/gui/app/components/section/markdown/type-editor.js @@ -36,11 +36,13 @@ export default Component.extend({ this.set('pageBody', body); }, - didInsertElement() { + didInsertElement(...args) { + this._super(...args); this.attachEditor(); }, - willDestroyElement() { + willDestroyElement(...args) { + this._super(...args); let editor = this.get('codeEditor'); if (this.get('editMode')) { diff --git a/gui/app/components/section/papertrail/type-editor.js b/gui/app/components/section/papertrail/type-editor.js index d37cbdcc..61c6dc3d 100644 --- a/gui/app/components/section/papertrail/type-editor.js +++ b/gui/app/components/section/papertrail/type-editor.js @@ -28,6 +28,7 @@ export default Component.extend(SectionMixin, NotifierMixin, { }, didReceiveAttrs() { + this._super(); let config = {}; try { diff --git a/gui/app/components/section/pdf/type-editor.js b/gui/app/components/section/pdf/type-editor.js index a89ae1cf..4293e099 100644 --- a/gui/app/components/section/pdf/type-editor.js +++ b/gui/app/components/section/pdf/type-editor.js @@ -28,6 +28,8 @@ export default Component.extend({ }, didReceiveAttrs() { + this._super(); + let pdfOption = {}; try { @@ -46,10 +48,10 @@ export default Component.extend({ this.set('pdfOption', pdfOption); this.setPDF(); }, - + didUpdateAttrs() { this._super(...arguments); - this.setPDF(); + this.setPDF(); }, setPDF() { @@ -60,7 +62,7 @@ export default Component.extend({ if (!_.isArray(files)) return; for (let i=0; i < files.length; i++) { - if (_.endsWith(files[i].get('extension'), 'pdf') && + if (_.endsWith(files[i].get('extension'), 'pdf') && files[i].get('pageId') === this.get('page.id')) { this.set('pdfName', files[i].get('filename')); this.set('pdfOption.fileId', files[i].get('id')); diff --git a/gui/app/components/section/plantuml/type-editor.js b/gui/app/components/section/plantuml/type-editor.js index 71964f40..295c3ed1 100644 --- a/gui/app/components/section/plantuml/type-editor.js +++ b/gui/app/components/section/plantuml/type-editor.js @@ -17,11 +17,12 @@ import Component from '@ember/component'; export default Component.extend({ appMeta: service(), sectionSvc: service('section'), + i18n: service(), isDirty: false, waiting: false, diagramText: '', diagramPreview: null, - previewButtonCaption: 'Preview', + previewButtonCaption: '', editorId: computed('page', function () { let page = this.get('page'); return `plantuml-editor-${page.id}`; @@ -34,9 +35,14 @@ export default Component.extend({ return _.isEmpty(this.get('diagramText')); }), + init(...args) { + this._super(...args); + this.previewButtonCaption = this.i18n.localize('preview'); + }, + generatePreview() { this.set('waiting', true); - this.set('previewButtonCaption', 'Generating preview...'); + this.set('previewButtonCaption', this.i18n.localize('preview_wait')); let self = this; let data = { data: this.get('diagramText') }; @@ -45,11 +51,11 @@ export default Component.extend({ this.get('sectionSvc').fetch(this.get('page'), 'preview', data).then(function (response) { self.set('diagramPreview', response.data); self.set('waiting', false); - self.set('previewButtonCaption', 'Preview'); + self.set('previewButtonCaption', this.i18n.localize('preview')); }, function (reason) { // eslint-disable-line no-unused-vars self.set('diagramPreview', null); self.set('waiting', false); - self.set('previewButtonCaption', 'Preview'); + self.set('previewButtonCaption', this.i18n.localize('preview')); }); }); }, diff --git a/gui/app/components/section/trello/type-editor.js b/gui/app/components/section/trello/type-editor.js index 2455f30f..f1858e32 100644 --- a/gui/app/components/section/trello/type-editor.js +++ b/gui/app/components/section/trello/type-editor.js @@ -47,6 +47,8 @@ export default Component.extend(SectionMixin, NotifierMixin, { }, didReceiveAttrs() { + this._super(); + let page = this.get('page'); let config = {}; let self = this; diff --git a/gui/app/components/setup/first-run.js b/gui/app/components/setup/first-run.js deleted file mode 100644 index 8739194d..00000000 --- a/gui/app/components/setup/first-run.js +++ /dev/null @@ -1,34 +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 - -import { inject as service } from '@ember/service'; -import Modals from '../../mixins/modal'; -import Component from '@ember/component'; - -export default Component.extend(Modals, { - localStorage: service(), - - didInsertElement() { - this._super(...arguments); - - let firstRun = this.get('localStorage').isFirstRun(); - - if (firstRun) { - this.modalOpen('#first-run-modal', {'show': true}); - } - }, - - actions: { - onCloseModal() { - this.modalClose('#first-run-modal'); - } - } -}); diff --git a/gui/app/components/user/user-profile.js b/gui/app/components/user/user-profile.js index b9437f12..cc1a8185 100644 --- a/gui/app/components/user/user-profile.js +++ b/gui/app/components/user/user-profile.js @@ -10,6 +10,7 @@ // https://documize.com import $ from 'jquery'; +import { A } from '@ember/array'; import { empty } from '@ember/object/computed'; import { computed, set } from '@ember/object'; import { isPresent, isEqual, isEmpty } from '@ember/utils'; @@ -46,13 +47,34 @@ export default Component.extend(AuthProvider, { return ''; } }), + locale: { name: '' }, + locales: null, init() { this._super(...arguments); this.password = { password: "", confirmation: "" }; + + let l = this.get('appMeta.locales'); + let t = A([]); + + l.forEach((locale) => { + t.pushObject( {name: locale} ); + }); + + this.set('locales', t); + }, + + didReceiveAttrs() { + this._super(...arguments); + + this.set('locale', this.locales.findBy('name', this.get('model.locale'))); }, actions: { + onSelectLocale(locale) { + this.set('model.locale', locale.name); + }, + save() { let password = this.get('password.password'); let confirmation = this.get('password.confirmation'); @@ -87,10 +109,5 @@ export default Component.extend(AuthProvider, { set(this, 'password.confirmation', ''); }); } - - // onThemeChange(theme) { - // this.get('appMeta').setTheme(theme); - // this.set('model.theme', theme); - // } } }); diff --git a/gui/app/constants/constants.js b/gui/app/constants/constants.js index cc56eac4..17298509 100644 --- a/gui/app/constants/constants.js +++ b/gui/app/constants/constants.js @@ -68,10 +68,6 @@ let constants = EmberObject.extend({ None: 0, Lock: 1, Review: 2, - - NoneLabel: 'Changes permitted without approval', - LockLabel: 'Locked, changes not permitted', - ReviewLabel: 'Changes require approval before publication' }, // Document @@ -80,10 +76,6 @@ let constants = EmberObject.extend({ Anybody: 1, Majority: 2, Unanimous: 3, - - AnybodyLabel: 'Approval required from any approver', - MajorityLabel: 'Majority approval required from approvers', - UnanimousLabel: 'Unanimous approval required from all approvers' }, // Section @@ -115,10 +107,6 @@ let constants = EmberObject.extend({ Draft: 0, Live: 1, Archived: 2, - - DraftLabel: 'Draft', - LiveLabel: 'Live', - ArchivedLabel: 'Archived', }, // Document Version -- document.groupId links different versions of documents together @@ -318,51 +306,6 @@ let constants = EmberObject.extend({ Yellow: 'yellow', Gray: 'gray' }, - - Label: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects - Add: 'Add', - Activate: "Activate", - Approve: 'Approve', - Authenticate: 'Authenticate', - Cancel: 'Cancel', - Close: 'Close', - Copy: 'Copy', - Delete: 'Delete', - Duplicate: 'Duplicate', - Edit: 'Edit', - Export: 'Export', - File: 'File', - Import: 'Import', - Insert: 'Insert', - Invite: 'Invite', - Join: 'Join', - Leave: 'Leave', - Login: 'Login', - Move: 'Move', - Next: 'Next', - OK: 'OK', - Preview: 'Preview', - Print: 'Print', - Publish: 'Publish', - Reject: 'Reject', - Remove: 'Remove', - Reply: 'Reply', - Reset: 'Reset', - Restore: 'Restore', - Request: 'Request', - Save: 'Save', - Search: 'Search', - Send: 'Send', - Share: 'Share', - SignIn: 'Sign In', - Sort: 'Sort', - Space: 'Space', - Spaces: 'Spaces', - Unassigned: 'Unassigned', - Update: 'Update', - Upload: 'Upload', - Version: 'Version' - } }); export default { constants }; diff --git a/gui/app/helpers/localize.js b/gui/app/helpers/localize.js new file mode 100644 index 00000000..2a825a91 --- /dev/null +++ b/gui/app/helpers/localize.js @@ -0,0 +1,17 @@ +// Copyright 2022 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 +// +// https://www.documize.com + +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; + +export default Helper.extend({ + i18n: service(), + + compute([key, ...rest]) { + return this.i18n.localize(key, ...rest); + } +}); diff --git a/gui/app/initializers/i18n.js b/gui/app/initializers/i18n.js new file mode 100644 index 00000000..cd9f5b59 --- /dev/null +++ b/gui/app/initializers/i18n.js @@ -0,0 +1,19 @@ +// Copyright 2022 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 +// +// https://www.documize.com + +export function initialize(application) { + application.inject('route', 'i18n', 'service:i18n'); + application.inject('controller', 'i18n', 'service:i18n'); + application.inject('component', 'i18n', 'service:i18n'); + application.inject('model', 'i18n', 'service:i18n'); +} + +export default { + name: 'i18n', + after: "application", + initialize: initialize +}; diff --git a/gui/app/models/document-activity.js b/gui/app/models/document-activity.js index 5e3b9347..f6c5ec69 100644 --- a/gui/app/models/document-activity.js +++ b/gui/app/models/document-activity.js @@ -31,37 +31,37 @@ export default Model.extend({ switch (this.get('activityType')) { case constants.UserActivityType.Created: - label = 'Added'; + label = this.i18n.localize('added'); break; case constants.UserActivityType.Read: - label = 'Viewed'; + label = this.i18n.localize('viewed'); break; case constants.UserActivityType.Edited: - label = 'Edited'; + label = this.i18n.localize('edited'); break; case constants.UserActivityType.Deleted: - label = 'Deleted'; + label = this.i18n.localize('deleted'); break; case constants.UserActivityType.Archived: - label = 'Archived'; + label = this.i18n.localize('archived'); break; case constants.UserActivityType.Approved: - label = 'Approved'; + label = this.i18n.localize('approved'); break; case constants.UserActivityType.Reverted: - label = 'Reverted'; + label = this.i18n.localize('reverted'); break; case constants.UserActivityType.PublishedTemplate: - label = 'Published Template'; + label = this.i18n.localize('template_published'); break; case constants.UserActivityType.PublishedBlock: - label = 'Published Block'; + label = this.i18n.localize('block_published'); break; case constants.UserActivityType.Rejected: - label = 'Rejected'; + label = this.i18n.localize('rejected'); break; case constants.UserActivityType.Published: - label = 'Published'; + label = this.i18n.localize('published'); break; default: break; diff --git a/gui/app/models/document.js b/gui/app/models/document.js index 03d93406..82015d05 100644 --- a/gui/app/models/document.js +++ b/gui/app/models/document.js @@ -57,13 +57,15 @@ export default Model.extend({ lifecycleLabel: computed('lifecycle', function () { let constants = this.get('constants'); + let i18n = this.get('i18n'); + switch (this.get('lifecycle')) { case constants.Lifecycle.Draft: - return constants.Lifecycle.DraftLabel; + return i18n.localize('draft'); case constants.Lifecycle.Live: - return constants.Lifecycle.LiveLabel; + return i18n.localize('live'); case constants.Lifecycle.Archived: - return constants.Lifecycle.ArchivedLabel; + return i18n.localize('archived'); } return ''; diff --git a/gui/app/models/organization.js b/gui/app/models/organization.js index 77888908..e326d801 100644 --- a/gui/app/models/organization.js +++ b/gui/app/models/organization.js @@ -21,6 +21,7 @@ export default Model.extend({ allowAnonymousAccess: attr('boolean', { defaultValue: false }), maxTags: attr('number', {defaultValue: 3}), theme: attr('string'), + locale: attr('string', { defaultValue: "en-US" }), created: attr(), revised: attr() }); diff --git a/gui/app/models/template.js b/gui/app/models/template.js index 5e170e6c..01deaeea 100644 --- a/gui/app/models/template.js +++ b/gui/app/models/template.js @@ -53,13 +53,15 @@ export default Model.extend({ lifecycleLabel: computed('lifecycle', function () { let constants = this.get('constants'); + let i18n = this.get('i18n'); + switch (this.get('lifecycle')) { case constants.Lifecycle.Draft: - return constants.Lifecycle.DraftLabel; + return i18n.localize('draft'); case constants.Lifecycle.Live: - return constants.Lifecycle.LiveLabel; + return i18n.localize('live'); case constants.Lifecycle.Archived: - return constants.Lifecycle.ArchivedLabel; + return i18n.localize('archived'); } return ''; diff --git a/gui/app/models/user.js b/gui/app/models/user.js index 548a0aed..e1c53610 100644 --- a/gui/app/models/user.js +++ b/gui/app/models/user.js @@ -30,6 +30,7 @@ export default Model.extend({ theme: attr('string'), created: attr(), revised: attr(), + locale: attr('string', { defaultValue: "en-US" }), fullname: computed('firstname', 'lastname', function () { return `${this.get('firstname')} ${this.get('lastname')}`; diff --git a/gui/app/pods/auth/cas/template.hbs b/gui/app/pods/auth/cas/template.hbs index c61afd04..85965cf6 100644 --- a/gui/app/pods/auth/cas/template.hbs +++ b/gui/app/pods/auth/cas/template.hbs @@ -1,12 +1,12 @@ {{#if (is-equal model.mode "login")}}
-

Authenticating with CAS...

+

{{localize 'login_cas'}}

{{/if}} {{#if (is-equal model.mode "reject")}}
-

CAS authentication failure

+

{{localize 'login_cas_error'}}

{{/if}} diff --git a/gui/app/pods/auth/forgot/template.hbs b/gui/app/pods/auth/forgot/template.hbs index eb97bb25..1cc7732a 100644 --- a/gui/app/pods/auth/forgot/template.hbs +++ b/gui/app/pods/auth/forgot/template.hbs @@ -1,8 +1,7 @@
SpacesVisibleAnalyticsAdminActive{{localize 'permission_spaces'}}{{localize 'permission_visible'}}{{localize 'permission_analytics'}}{{localize 'permission_admin'}}{{localize 'permission_active'}}
{{#ui/ui-toolbar dark=false light=true raised=true large=false bordered=true}} - {{ui/ui-toolbar-icon icon=constants.Icon.Edit color=constants.Color.Gray tooltip="Edit user" onClick=(action "onShowEdit" user.id)}} - {{ui/ui-toolbar-icon icon=constants.Icon.AddUser color=constants.Color.Gray tooltip="Assign user groups" onClick=(action "onShowGroupsModal" user.id)}} + {{ui/ui-toolbar-icon icon=constants.Icon.Edit color=constants.Color.Gray tooltip=(localize 'edit') onClick=(action "onShowEdit" user.id)}} + {{ui/ui-toolbar-icon icon=constants.Icon.AddUser color=constants.Color.Gray tooltip=(localize 'user_assign_group') onClick=(action "onShowGroupsModal" user.id)}} {{#unless user.me}} - {{ui/ui-toolbar-icon icon=constants.Icon.Delete color=constants.Color.Red tooltip="Delete user" onClick=(action "onShowDelete" user.id)}} + {{ui/ui-toolbar-icon icon=constants.Icon.Delete color=constants.Color.Red tooltip=(localize 'delete') onClick=(action "onShowDelete" user.id)}} {{/unless}} {{/ui/ui-toolbar}}
-{{#ui/ui-dialog title="Delete User" confirmCaption="Delete" buttonColor=constants.Color.Red show=showDeleteDialog onAction=(action "onDelete")}} -

Are you sure you want to delete {{deleteUser.fullname}}?

+{{#ui/ui-dialog title="Delete User" confirmCaption=(localize 'delete') buttonColor=constants.Color.Red show=showDeleteDialog onAction=(action "onDelete")}} +

{{localize 'user_delete_confirm' deleteUser.fullname}}

{{/ui/ui-dialog}} @@ -213,14 +213,14 @@ diff --git a/gui/app/templates/components/document/add-section.hbs b/gui/app/templates/components/document/add-section.hbs index 4c9bd1db..40aafcd9 100644 --- a/gui/app/templates/components/document/add-section.hbs +++ b/gui/app/templates/components/document/add-section.hbs @@ -1,13 +1,13 @@
SpacesDocuments{{localize 'spaces'}}{{localize 'documents'}}
ViewManageOwnerCreateEditDeleteMoveCopyTemplatesApprovalDraftsVersions{{localize 'space_permission_view'}}{{localize 'space_permission_manage'}}{{localize 'space_permission_owner'}}{{localize 'space_permission_doc_create'}}{{localize 'space_permission_doc_edit'}}{{localize 'space_permission_doc_delete'}}{{localize 'space_permission_doc_move'}}{{localize 'space_permission_doc_copy'}}{{localize 'space_permission_doc_template'}}{{localize 'space_permission_doc_approval'}}{{localize 'space_permission_doc_draft'}}{{localize 'space_permission_doc_version'}}