diff options
38 files changed, 375 insertions, 893 deletions
diff --git a/NOTICE.txt b/NOTICE.txt index 56a6567d4..1906ae6b2 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1967,217 +1967,6 @@ THE SOFTWARE. --- -This product contains a modified portion of 'jibber_jabber', a GoLang Library that can be used to detect an operating system's current language. - -* HOMEPAGE: - * https://github.com/cloudfoundry-attic/jibber_jabber - -* LICENSE: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2014 Pivotal - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ---- - This product contains a modified portion of 'manners', a package imaging providing basic image manipulation functions (resize, rotate, flip, crop, etc.) by Grigory Dryapak. * HOMEPAGE: diff --git a/api/api.go b/api/api.go index e62d34dcc..63a460dc5 100644 --- a/api/api.go +++ b/api/api.go @@ -10,7 +10,6 @@ import ( "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" - _ "github.com/cloudfoundry/jibber_jabber" _ "github.com/nicksnyder/go-i18n/i18n" ) diff --git a/api/apitestlib.go b/api/apitestlib.go index ab342c6b7..a685528d1 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -30,7 +30,7 @@ type TestHelper struct { func SetupEnterprise() *TestHelper { if Srv == nil { utils.LoadConfig("config.json") - utils.InitTranslations() + utils.InitTranslations(utils.Cfg.LocalizationSettings) utils.Cfg.TeamSettings.MaxUsersPerTeam = 50 utils.DisableDebugLogForTest() utils.License.Features.SetDefaults() @@ -50,7 +50,7 @@ func SetupEnterprise() *TestHelper { func Setup() *TestHelper { if Srv == nil { utils.LoadConfig("config.json") - utils.InitTranslations() + utils.InitTranslations(utils.Cfg.LocalizationSettings) utils.Cfg.TeamSettings.MaxUsersPerTeam = 50 utils.DisableDebugLogForTest() NewServer() diff --git a/api/user.go b/api/user.go index caf573463..4d4518824 100644 --- a/api/user.go +++ b/api/user.go @@ -239,6 +239,7 @@ func CreateUser(user *model.User) (*model.User, *model.AppError) { } user.MakeNonNil() + user.Locale = *utils.Cfg.LocalizationSettings.DefaultClientLocale if result := <-Srv.Store.User().Save(user); result.Err != nil { l4g.Error(utils.T("api.user.create_user.save.error"), result.Err) diff --git a/config/config.json b/config/config.json index 0c2faa3c5..582a7244c 100644 --- a/config/config.json +++ b/config/config.json @@ -155,5 +155,10 @@ "Enable": false, "Directory": "./data/", "EnableDaily": false + }, + "LocalizationSettings": { + "DefaultServerLocale": "en", + "DefaultClientLocale": "en", + "AvailableLocales": "en,es,fr,ja,pt-BR" } -} +}
\ No newline at end of file diff --git a/glide.lock b/glide.lock index e9450f1bb..b1bde2847 100644 --- a/glide.lock +++ b/glide.lock @@ -5,8 +5,6 @@ imports: version: e5dc62318d9bd58682f1dceb53a4b24e8253682f - name: github.com/braintree/manners version: 82a8879fc5fd0381fa8b2d8033b19bf255252088 -- name: github.com/cloudfoundry/jibber_jabber - version: bcc4c8345a21301bf47c032ff42dd1aae2fe3027 - name: github.com/dgryski/dgoogauth version: 67642ac6f9144f6610279e37e7be9af13f1cd668 - name: github.com/disintegration/imaging diff --git a/glide.yaml b/glide.yaml index 21ff7062d..81335ef38 100644 --- a/glide.yaml +++ b/glide.yaml @@ -3,7 +3,6 @@ import: - package: github.com/NYTimes/gziphandler - package: github.com/alecthomas/log4go - package: github.com/braintree/manners -- package: github.com/cloudfoundry/jibber_jabber - package: github.com/dgryski/dgoogauth - package: github.com/disintegration/imaging - package: github.com/go-gorp/gorp diff --git a/i18n/pt.json b/i18n/pt-BR.json index 0f752b26c..0f752b26c 100644 --- a/i18n/pt.json +++ b/i18n/pt-BR.json diff --git a/mattermost.go b/mattermost.go index 9929f5973..bebb55c3b 100644 --- a/mattermost.go +++ b/mattermost.go @@ -79,7 +79,6 @@ func main() { parseCmds() - utils.InitTranslations() if errstr := doLoadConfig(flagConfigFile); errstr != "" { l4g.Exit(utils.T("mattermost.unable_to_load_config"), errstr) return @@ -88,6 +87,7 @@ func main() { if flagRunCmds { utils.ConfigureCmdLineLog() } + utils.InitTranslations(utils.Cfg.LocalizationSettings) pwd, _ := os.Getwd() l4g.Info(utils.T("mattermost.current_version"), model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise) diff --git a/model/config.go b/model/config.go index 674a352f0..08b00b90f 100644 --- a/model/config.go +++ b/model/config.go @@ -204,20 +204,27 @@ type ComplianceSettings struct { EnableDaily *bool } +type LocalizationSettings struct { + DefaultServerLocale *string + DefaultClientLocale *string + AvailableLocales *string +} + type Config struct { - ServiceSettings ServiceSettings - TeamSettings TeamSettings - SqlSettings SqlSettings - LogSettings LogSettings - FileSettings FileSettings - EmailSettings EmailSettings - RateLimitSettings RateLimitSettings - PrivacySettings PrivacySettings - SupportSettings SupportSettings - GitLabSettings SSOSettings - GoogleSettings SSOSettings - LdapSettings LdapSettings - ComplianceSettings ComplianceSettings + ServiceSettings ServiceSettings + TeamSettings TeamSettings + SqlSettings SqlSettings + LogSettings LogSettings + FileSettings FileSettings + EmailSettings EmailSettings + RateLimitSettings RateLimitSettings + PrivacySettings PrivacySettings + SupportSettings SupportSettings + GitLabSettings SSOSettings + GoogleSettings SSOSettings + LdapSettings LdapSettings + ComplianceSettings ComplianceSettings + LocalizationSettings LocalizationSettings } func (o *Config) ToJson() string { @@ -513,6 +520,21 @@ func (o *Config) SetDefaults() { o.LdapSettings.NicknameAttribute = new(string) *o.LdapSettings.NicknameAttribute = "" } + + if o.LocalizationSettings.DefaultServerLocale == nil { + o.LocalizationSettings.DefaultServerLocale = new(string) + *o.LocalizationSettings.DefaultServerLocale = DEFAULT_LOCALE + } + + if o.LocalizationSettings.DefaultClientLocale == nil { + o.LocalizationSettings.DefaultClientLocale = new(string) + *o.LocalizationSettings.DefaultClientLocale = DEFAULT_LOCALE + } + + if o.LocalizationSettings.AvailableLocales == nil { + o.LocalizationSettings.AvailableLocales = new(string) + *o.LocalizationSettings.AvailableLocales = *o.LocalizationSettings.DefaultClientLocale + } } func (o *Config) IsValid() *AppError { diff --git a/model/user.go b/model/user.go index b7717c4ff..ab076894a 100644 --- a/model/user.go +++ b/model/user.go @@ -136,7 +136,6 @@ func (u *User) PreSave() { u.Username = strings.ToLower(u.Username) u.Email = strings.ToLower(u.Email) - u.Locale = strings.ToLower(u.Locale) u.CreateAt = GetMillis() u.UpdateAt = u.CreateAt @@ -166,7 +165,6 @@ func (u *User) PreSave() { func (u *User) PreUpdate() { u.Username = strings.ToLower(u.Username) u.Email = strings.ToLower(u.Email) - u.Locale = strings.ToLower(u.Locale) u.UpdateAt = GetMillis() if u.AuthData != nil && *u.AuthData == "" { diff --git a/store/sql_store_test.go b/store/sql_store_test.go index 474a68ac7..19ae2caae 100644 --- a/store/sql_store_test.go +++ b/store/sql_store_test.go @@ -16,7 +16,7 @@ var store Store func Setup() { if store == nil { utils.LoadConfig("config.json") - utils.InitTranslations() + utils.InitTranslations(utils.Cfg.LocalizationSettings) store = NewSqlStore() store.MarkSystemRanUnitTests() diff --git a/utils/config.go b/utils/config.go index 313b4e29c..a3969fc40 100644 --- a/utils/config.go +++ b/utils/config.go @@ -246,7 +246,8 @@ func getClientConfig(c *model.Config) map[string]string { props["WebsocketPort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketPort) props["WebsocketSecurePort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketSecurePort) - props["AllowCorsFrom"] = *c.ServiceSettings.AllowCorsFrom + props["DefaultClientLocale"] = *c.LocalizationSettings.DefaultClientLocale + props["AvailableLocales"] = *c.LocalizationSettings.AvailableLocales if IsLicensed { if *License.Features.CustomBrand { diff --git a/utils/config_test.go b/utils/config_test.go index 6f36b30c3..96ef49696 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -9,5 +9,5 @@ import ( func TestConfig(t *testing.T) { LoadConfig("config.json") - InitTranslations() + InitTranslations(Cfg.LocalizationSettings) } diff --git a/utils/i18n.go b/utils/i18n.go index 2503cd500..b3e10a831 100644 --- a/utils/i18n.go +++ b/utils/i18n.go @@ -7,15 +7,16 @@ import ( "strings" l4g "github.com/alecthomas/log4go" - "github.com/cloudfoundry/jibber_jabber" "github.com/mattermost/platform/model" "github.com/nicksnyder/go-i18n/i18n" ) var T i18n.TranslateFunc var locales map[string]string = make(map[string]string) +var settings model.LocalizationSettings -func InitTranslations() { +func InitTranslations(localizationSettings model.LocalizationSettings) { + settings = localizationSettings InitTranslationsWithDir("i18n") } @@ -34,14 +35,10 @@ func InitTranslationsWithDir(dir string) { } func GetTranslationsBySystemLocale() i18n.TranslateFunc { - locale := model.DEFAULT_LOCALE - if userLanguage, err := jibber_jabber.DetectLanguage(); err == nil { - if _, ok := locales[userLanguage]; ok { - locale = userLanguage - } else { - l4g.Error("Failed to load system translations for '%v' attempting to fall back to '%v'", locale, model.DEFAULT_LOCALE) - locale = model.DEFAULT_LOCALE - } + locale := *settings.DefaultServerLocale + if _, ok := locales[locale]; !ok { + l4g.Error("Failed to load system translations for '%v' attempting to fall back to '%v'", locale, model.DEFAULT_LOCALE) + locale = model.DEFAULT_LOCALE } if locales[locale] == "" { @@ -72,10 +69,20 @@ func SetTranslations(locale string) i18n.TranslateFunc { } func GetTranslationsAndLocale(w http.ResponseWriter, r *http.Request) (i18n.TranslateFunc, string) { + // This is for checking against locales like pt_BR or zn_CN + headerLocaleFull := strings.Split(r.Header.Get("Accept-Language"), ",")[0] + // This is for checking agains locales like en, es headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0] - if locales[headerLocale] != "" { + defaultLocale := *settings.DefaultClientLocale + if locales[headerLocaleFull] != "" { + translations := TfuncWithFallback(headerLocaleFull) + return translations, headerLocaleFull + } else if locales[headerLocale] != "" { translations := TfuncWithFallback(headerLocale) return translations, headerLocale + } else if locales[defaultLocale] != "" { + translations := TfuncWithFallback(defaultLocale) + return translations, headerLocale } translations := TfuncWithFallback(model.DEFAULT_LOCALE) @@ -89,7 +96,7 @@ func TfuncWithFallback(pref string) i18n.TranslateFunc { return translated } - t, _ := i18n.Tfunc("en") + t, _ := i18n.Tfunc(model.DEFAULT_LOCALE) return t(translationID, args...) } } diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/.travis.yml b/vendor/github.com/cloudfoundry/jibber_jabber/.travis.yml deleted file mode 100644 index b19c2e535..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: go -go: - - 1.2 -before_install: -- go get github.com/onsi/ginkgo/... -- go get github.com/onsi/gomega/... -- go install github.com/onsi/ginkgo/ginkgo -script: PATH=$PATH:$HOME/gopath/bin ginkgo -r . -branches: - only: - - master diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE b/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE deleted file mode 100644 index 915b20892..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2014 Pivotal - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/README.md b/vendor/github.com/cloudfoundry/jibber_jabber/README.md deleted file mode 100644 index d696eb6b6..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Jibber Jabber [![Build Status](https://travis-ci.org/cloudfoundry/jibber_jabber.svg?branch=master)](https://travis-ci.org/cloudfoundry/jibber_jabber) -Jibber Jabber is a GoLang Library that can be used to detect an operating system's current language. - -### OS Support - -OSX and Linux via the `LC_ALL` and `LANG` environment variables. These are standard variables that are used in ALL versions of UNIX for language detection. - -Windows via [GetUserDefaultLocaleName](http://msdn.microsoft.com/en-us/library/windows/desktop/dd318136.aspx) and [GetSystemDefaultLocaleName](http://msdn.microsoft.com/en-us/library/windows/desktop/dd318122.aspx) system calls. These calls are supported in Windows Vista and up. - -# Usage -Add the following line to your go `import`: - -``` - "github.com/cloudfoundry/jibber_jabber" -``` - -### DetectIETF -`DetectIETF` will return the current locale as a string. The format of the locale will be the [ISO 639](http://en.wikipedia.org/wiki/ISO_639) two-letter language code, a DASH, then an [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) two-letter country code. - -``` - userLocale, err := jibber_jabber.DetectIETF() - println("Locale:", userLocale) -``` - -### DetectLanguage -`DetectLanguage` will return the current languge as a string. The format will be the [ISO 639](http://en.wikipedia.org/wiki/ISO_639) two-letter language code. - -``` - userLanguage, err := jibber_jabber.DetectLanguage() - println("Language:", userLanguage) -``` - -### DetectTerritory -`DetectTerritory` will return the current locale territory as a string. The format will be the [ISO 3166](http://en.wikipedia.org/wiki/ISO_3166-1) two-letter country code. - -``` - localeTerritory, err := jibber_jabber.DetectTerritory() - println("Territory:", localeTerritory) -``` - -### Errors -All the Detect commands will return an error if they are unable to read the Locale from the system. - -For Windows, additional error information is provided due to the nature of the system call being used. diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/ci/scripts/windows-64-test.bat b/vendor/github.com/cloudfoundry/jibber_jabber/ci/scripts/windows-64-test.bat deleted file mode 100755 index b9a87bf7a..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/ci/scripts/windows-64-test.bat +++ /dev/null @@ -1,5 +0,0 @@ -git fetch -git checkout %GIT_COMMIT% - -SET GOPATH=%CD%\Godeps\_workspace;c:\Users\Administrator\go -c:\Go\bin\go test -v . diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go deleted file mode 100644 index 45d288ea8..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber.go +++ /dev/null @@ -1,22 +0,0 @@ -package jibber_jabber - -import ( - "strings" -) - -const ( - COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE = "Could not detect Language" -) - -func splitLocale(locale string) (string, string) { - formattedLocale := strings.Split(locale, ".")[0] - formattedLocale = strings.Replace(formattedLocale, "-", "_", -1) - - pieces := strings.Split(formattedLocale, "_") - language := pieces[0] - territory := "" - if len(pieces) > 1 { - territory = strings.Split(formattedLocale, "_")[1] - } - return language, territory -} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_suite_test.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_suite_test.go deleted file mode 100644 index 3da19c84b..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package jibber_jabber_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "testing" -) - -func TestJibberJabber(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Jibber Jabber Suite") -} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go deleted file mode 100644 index 374d76176..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build darwin freebsd linux netbsd openbsd - -package jibber_jabber - -import ( - "errors" - "os" - "strings" -) - -func getLangFromEnv() (locale string) { - locale = os.Getenv("LC_ALL") - if locale == "" { - locale = os.Getenv("LANG") - } - return -} - -func getUnixLocale() (unix_locale string, err error) { - unix_locale = getLangFromEnv() - if unix_locale == "" { - err = errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE) - } - - return -} - -func DetectIETF() (locale string, err error) { - unix_locale, err := getUnixLocale() - if err == nil { - language, territory := splitLocale(unix_locale) - locale = language - if territory != "" { - locale = strings.Join([]string{language, territory}, "-") - } - } - - return -} - -func DetectLanguage() (language string, err error) { - unix_locale, err := getUnixLocale() - if err == nil { - language, _ = splitLocale(unix_locale) - } - - return -} - -func DetectTerritory() (territory string, err error) { - unix_locale, err := getUnixLocale() - if err == nil { - _, territory = splitLocale(unix_locale) - } - - return -} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix_test.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix_test.go deleted file mode 100644 index a5e3074a2..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_unix_test.go +++ /dev/null @@ -1,104 +0,0 @@ -// +build darwin freebsd linux netbsd openbsd - -package jibber_jabber_test - -import ( - "os" - - . "github.com/cloudfoundry/jibber_jabber" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Unix", func() { - AfterEach(func() { - os.Setenv("LC_ALL", "") - os.Setenv("LANG", "en_US.UTF-8") - }) - - Describe("#DetectIETF", func() { - Context("Returns IETF encoded locale", func() { - It("should return the locale set to LC_ALL", func() { - os.Setenv("LC_ALL", "fr_FR.UTF-8") - result, _ := DetectIETF() - Ω(result).Should(Equal("fr-FR")) - }) - - It("should return the locale set to LANG if LC_ALL isn't set", func() { - os.Setenv("LANG", "fr_FR.UTF-8") - - result, _ := DetectIETF() - Ω(result).Should(Equal("fr-FR")) - }) - - It("should return an error if it cannot detect a locale", func() { - os.Setenv("LANG", "") - - _, err := DetectIETF() - Ω(err.Error()).Should(Equal(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE)) - }) - }) - - Context("when the locale is simply 'fr'", func() { - BeforeEach(func() { - os.Setenv("LANG", "fr") - }) - - It("should return the locale without a territory", func() { - language, err := DetectIETF() - Ω(err).ShouldNot(HaveOccurred()) - Ω(language).Should(Equal("fr")) - }) - }) - }) - - Describe("#DetectLanguage", func() { - Context("Returns encoded language", func() { - It("should return the language set to LC_ALL", func() { - os.Setenv("LC_ALL", "fr_FR.UTF-8") - result, _ := DetectLanguage() - Ω(result).Should(Equal("fr")) - }) - - It("should return the language set to LANG if LC_ALL isn't set", func() { - os.Setenv("LANG", "fr_FR.UTF-8") - - result, _ := DetectLanguage() - Ω(result).Should(Equal("fr")) - }) - - It("should return an error if it cannot detect a language", func() { - os.Setenv("LANG", "") - - _, err := DetectLanguage() - Ω(err.Error()).Should(Equal(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE)) - }) - }) - }) - - Describe("#DetectTerritory", func() { - Context("Returns encoded territory", func() { - It("should return the territory set to LC_ALL", func() { - os.Setenv("LC_ALL", "fr_FR.UTF-8") - result, _ := DetectTerritory() - Ω(result).Should(Equal("FR")) - }) - - It("should return the territory set to LANG if LC_ALL isn't set", func() { - os.Setenv("LANG", "fr_FR.UTF-8") - - result, _ := DetectTerritory() - Ω(result).Should(Equal("FR")) - }) - - It("should return an error if it cannot detect a territory", func() { - os.Setenv("LANG", "") - - _, err := DetectTerritory() - Ω(err.Error()).Should(Equal(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE)) - }) - }) - }) - -}) diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go deleted file mode 100644 index 1acd96c38..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows.go +++ /dev/null @@ -1,114 +0,0 @@ -// +build windows - -package jibber_jabber - -import ( - "errors" - "syscall" - "unsafe" -) - -const LOCALE_NAME_MAX_LENGTH uint32 = 85 - -var SUPPORTED_LOCALES = map[uintptr]string{ - 0x0407: "de-DE", - 0x0409: "en-US", - 0x0c0a: "es-ES", //or is it 0x040a - 0x040c: "fr-FR", - 0x0410: "it-IT", - 0x0411: "ja-JA", - 0x0412: "ko_KR", - 0x0416: "pt-BR", - //0x0419: "ru_RU", - Will add support for Russian when nicksnyder/go-i18n supports Russian - 0x0804: "zh-CN", - 0x0c04: "zh-HK", - 0x0404: "zh-TW", -} - -func getWindowsLocaleFrom(sysCall string) (locale string, err error) { - buffer := make([]uint16, LOCALE_NAME_MAX_LENGTH) - - dll := syscall.MustLoadDLL("kernel32") - proc := dll.MustFindProc(sysCall) - r, _, dllError := proc.Call(uintptr(unsafe.Pointer(&buffer[0])), uintptr(LOCALE_NAME_MAX_LENGTH)) - if r == 0 { - err = errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE + ":\n" + dllError.Error()) - return - } - - locale = syscall.UTF16ToString(buffer) - - return -} - -func getAllWindowsLocaleFrom(sysCall string) (string, error) { - dll, err := syscall.LoadDLL("kernel32") - if err != nil { - return "", errors.New("Could not find kernel32 dll") - } - - proc, err := dll.FindProc(sysCall) - if err != nil { - return "", err - } - - locale, _, dllError := proc.Call() - if locale == 0 { - return "", errors.New(COULD_NOT_DETECT_PACKAGE_ERROR_MESSAGE + ":\n" + dllError.Error()) - } - - return SUPPORTED_LOCALES[locale], nil -} - -func getWindowsLocale() (locale string, err error) { - dll, err := syscall.LoadDLL("kernel32") - if err != nil { - return "", errors.New("Could not find kernel32 dll") - } - - proc, err := dll.FindProc("GetVersion") - if err != nil { - return "", err - } - - v, _, _ := proc.Call() - windowsVersion := byte(v) - isVistaOrGreater := (windowsVersion >= 6) - - if isVistaOrGreater { - locale, err = getWindowsLocaleFrom("GetUserDefaultLocaleName") - if err != nil { - locale, err = getWindowsLocaleFrom("GetSystemDefaultLocaleName") - } - } else if !isVistaOrGreater { - locale, err = getAllWindowsLocaleFrom("GetUserDefaultLCID") - if err != nil { - locale, err = getAllWindowsLocaleFrom("GetSystemDefaultLCID") - } - } else { - panic(v) - } - return -} -func DetectIETF() (locale string, err error) { - locale, err = getWindowsLocale() - return -} - -func DetectLanguage() (language string, err error) { - windows_locale, err := getWindowsLocale() - if err == nil { - language, _ = splitLocale(windows_locale) - } - - return -} - -func DetectTerritory() (territory string, err error) { - windows_locale, err := getWindowsLocale() - if err == nil { - _, territory = splitLocale(windows_locale) - } - - return -} diff --git a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows_test.go b/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows_test.go deleted file mode 100644 index f325d981e..000000000 --- a/vendor/github.com/cloudfoundry/jibber_jabber/jibber_jabber_windows_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// +build windows - -package jibber_jabber_test - -import ( - "regexp" - - . "github.com/cloudfoundry/jibber_jabber" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -const ( - LOCALE_REGEXP = "^[a-z]{2}-[A-Z]{2}$" - LANGUAGE_REGEXP = "^[a-z]{2}$" - TERRITORY_REGEXP = "^[A-Z]{2}$" -) - -var _ = Describe("Windows", func() { - BeforeEach(func() { - locale, err := DetectIETF() - Ω(err).Should(BeNil()) - Ω(locale).ShouldNot(BeNil()) - Ω(locale).ShouldNot(Equal("")) - }) - - Describe("#DetectIETF", func() { - It("detects correct IETF locale", func() { - locale, _ := DetectIETF() - matched, _ := regexp.MatchString(LOCALE_REGEXP, locale) - Ω(matched).Should(BeTrue()) - }) - }) - - Describe("#DetectLanguage", func() { - It("detects correct Language", func() { - language, _ := DetectLanguage() - matched, _ := regexp.MatchString(LANGUAGE_REGEXP, language) - Ω(matched).Should(BeTrue()) - }) - }) - - Describe("#DetectTerritory", func() { - It("detects correct Territory", func() { - territory, _ := DetectTerritory() - matched, _ := regexp.MatchString(TERRITORY_REGEXP, territory) - Ω(matched).Should(BeTrue()) - }) - }) -}) diff --git a/web/web_test.go b/web/web_test.go index d4d9a5f26..0a9869e40 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -21,7 +21,7 @@ var URL string func Setup() { if api.Srv == nil { utils.LoadConfig("config.json") - utils.InitTranslations() + utils.InitTranslations(utils.Cfg.LocalizationSettings) api.NewServer() api.StartServer() api.InitApi() diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx index 91b51a9c2..0b264a9b3 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -392,8 +392,10 @@ export function newLocalizationSelected(locale) { translations: en }); } else { + const localeInfo = I18n.getLanguageInfo(locale) || I18n.getLanguageInfo(global.window.mm_config.DefaultClientLocale); + Client.getTranslations( - I18n.getLanguageInfo(locale).url, + localeInfo.url, (data, res) => { let translations = data; if (!data && res.text) { @@ -412,16 +414,11 @@ export function newLocalizationSelected(locale) { } } -export function loadBrowserLocale() { - let locale = (navigator.languages && navigator.languages.length > 0 ? navigator.languages[0] : - (navigator.language || navigator.userLanguage)).split('-')[0]; - - const user = UserStore.getCurrentUser(); - if (user) { - locale = user.locale || locale; - } +export function loadDefaultLocale() { + const defaultLocale = global.window.mm_config.DefaultClientLocale; + let locale = global.window.mm_user ? global.window.mm_user.locale || defaultLocale : defaultLocale; - if (!I18n.getLanguages()[locale]) { + if (!I18n.getLanguageInfo(locale)) { locale = 'en'; } return newLocalizationSelected(locale); diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx index cdb7e29d5..9548a7763 100644 --- a/webapp/components/admin_console/admin_sidebar.jsx +++ b/webapp/components/admin_console/admin_sidebar.jsx @@ -293,6 +293,15 @@ export default class AdminSidebar extends React.Component { } /> <AdminSidebarSection + name='localization' + title={ + <FormattedMessage + id='admin.sidebar.localization' + defaultMessage='Localization' + /> + } + /> + <AdminSidebarSection name='users_and_teams' title={ <FormattedMessage diff --git a/webapp/components/admin_console/localization_settings.jsx b/webapp/components/admin_console/localization_settings.jsx new file mode 100644 index 000000000..6876e0c36 --- /dev/null +++ b/webapp/components/admin_console/localization_settings.jsx @@ -0,0 +1,145 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import * as I18n from 'i18n/i18n.jsx'; + +import AdminSettings from './admin_settings.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from './settings_group.jsx'; +import DropdownSetting from './dropdown_setting.jsx'; +import MultiSelectSetting from './multiselect_settings.jsx'; + +export default class LocalizationSettings extends AdminSettings { + constructor(props) { + super(props); + + this.getConfigFromState = this.getConfigFromState.bind(this); + + this.renderSettings = this.renderSettings.bind(this); + this.canSave = this.canSave.bind(this); + + const locales = I18n.getAllLanguages(); + + this.state = Object.assign(this.state, { + hasErrors: false, + defaultServerLocale: props.config.LocalizationSettings.DefaultServerLocale, + defaultClientLocale: props.config.LocalizationSettings.DefaultClientLocale, + availableLocales: props.config.LocalizationSettings.AvailableLocales.split(','), + languages: Object.keys(locales).map((l) => { + return {value: locales[l].value, text: locales[l].name}; + }) + }); + } + + canSave() { + return this.state.availableLocales.join(',').indexOf(this.state.defaultClientLocale) !== -1; + } + + getConfigFromState(config) { + config.LocalizationSettings.DefaultServerLocale = this.state.defaultServerLocale; + config.LocalizationSettings.DefaultClientLocale = this.state.defaultClientLocale; + config.LocalizationSettings.AvailableLocales = this.state.availableLocales.join(','); + + return config; + } + + renderTitle() { + return ( + <h3> + <FormattedMessage + id='admin.general.title' + defaultMessage='General Settings' + /> + </h3> + ); + } + + renderSettings() { + return ( + <SettingsGroup + header={ + <FormattedMessage + id='admin.general.localization' + defaultMessage='Localization' + /> + } + > + <DropdownSetting + id='defaultServerLocale' + values={this.state.languages} + label={ + <FormattedMessage + id='admin.general.localization.serverLocaleTitle' + defaultMessage='Default Server Language:' + /> + } + value={this.state.defaultServerLocale} + onChange={this.handleChange} + helpText={ + <FormattedMessage + id='admin.general.localization.serverLocaleDescription' + defaultMessage='This setting sets the default language for the system messages and logs. (NEED SERVER RESTART)' + /> + } + /> + <DropdownSetting + id='defaultClientLocale' + values={this.state.languages} + label={ + <FormattedMessage + id='admin.general.localization.clientLocaleTitle' + defaultMessage='Default Client Language:' + /> + } + value={this.state.defaultClientLocale} + onChange={this.handleChange} + helpText={ + <FormattedMessage + id='admin.general.localization.clientLocaleDescription' + defaultMessage="This setting sets the Default language for newly created users and for pages where the user hasn't loggged in." + /> + } + /> + <MultiSelectSetting + id='availableLocales' + values={this.state.languages} + label={ + <FormattedMessage + id='admin.general.localization.availableLocalesTitle' + defaultMessage='Available Languages:' + /> + } + selected={this.state.availableLocales} + mustBePresent={this.state.defaultClientLocale} + onChange={this.handleChange} + helpText={ + <FormattedMessage + id='admin.general.localization.availableLocalesDescription' + defaultMessage='This setting determines the available languages that a user can set using the Account Settings.' + /> + } + noResultText={ + <FormattedMessage + id='admin.general.localization.availableLocalesNoResults' + defaultMessage='No results found' + /> + } + errorText={ + <FormattedMessage + id='admin.general.localization.availableLocalesError' + defaultMessage='There has to be at least one language available' + /> + } + notPresent={ + <FormattedMessage + id='admin.general.localization.availableLocalesNotPresent' + defaultMessage='The default client language must be included in the available list' + /> + } + /> + </SettingsGroup> + ); + } +}
\ No newline at end of file diff --git a/webapp/components/admin_console/multiselect_settings.jsx b/webapp/components/admin_console/multiselect_settings.jsx new file mode 100644 index 000000000..deba983de --- /dev/null +++ b/webapp/components/admin_console/multiselect_settings.jsx @@ -0,0 +1,80 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. +import React from 'react'; +import ReactSelect from 'react-select'; + +import Setting from './setting.jsx'; +import FormError from 'components/form_error.jsx'; + +export default class MultiSelectSetting extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.state = {error: false}; + } + + handleChange(newValue) { + const values = newValue.map((n) => { + return n.value; + }); + + if (!newValue || newValue.length === 0) { + this.setState({error: this.props.errorText}); + } else if (this.props.mustBePresent && values.join(',').indexOf(this.props.mustBePresent) === -1) { + this.setState({error: this.props.notPresent}); + } else { + this.props.onChange(this.props.id, values); + this.setState({error: false}); + } + } + + componentWillReceiveProps(newProps) { + if (newProps.mustBePresent && newProps.selected.join(',').indexOf(newProps.mustBePresent) === -1) { + this.setState({error: this.props.notPresent}); + } else { + this.setState({error: false}); + } + } + + render() { + return ( + <Setting + label={this.props.label} + inputId={this.props.id} + helpText={this.props.helpText} + > + <ReactSelect + id={this.props.id} + multi={true} + labelKey='text' + options={this.props.values} + joinValues={true} + disabled={this.props.disabled} + noResultsText={this.props.noResultText} + onChange={this.handleChange} + value={this.props.selected} + /> + <FormError error={this.state.error}/> + </Setting> + ); + } +} + +MultiSelectSetting.defaultProps = { + disabled: false +}; + +MultiSelectSetting.propTypes = { + id: React.PropTypes.string.isRequired, + values: React.PropTypes.array.isRequired, + label: React.PropTypes.node.isRequired, + selected: React.PropTypes.array.isRequired, + mustBePresent: React.PropTypes.string, + onChange: React.PropTypes.func.isRequired, + disabled: React.PropTypes.bool, + helpText: React.PropTypes.node, + noResultText: React.PropTypes.node, + errorText: React.PropTypes.node, + notPresent: React.PropTypes.node +};
\ No newline at end of file diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx index c96499392..abaa05bb5 100644 --- a/webapp/components/root.jsx +++ b/webapp/components/root.jsx @@ -6,6 +6,7 @@ import * as GlobalActions from 'actions/global_actions.jsx'; import LocalizationStore from 'stores/localization_store.jsx'; +import Client from 'utils/web_client.jsx'; import {IntlProvider} from 'react-intl'; @@ -41,7 +42,10 @@ export default class Root extends React.Component { FastClick.attach(document.body); } localizationChanged() { - this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()}); + const locale = LocalizationStore.getLocale(); + + Client.setAcceptLanguage(locale); + this.setState({locale, translations: LocalizationStore.getTranslations()}); } redirectIfNecessary(props) { @@ -67,7 +71,7 @@ export default class Root extends React.Component { LocalizationStore.addChangeListener(this.localizationChanged); // Get our localizaiton - GlobalActions.loadBrowserLocale(); + GlobalActions.loadDefaultLocale(); } componentWillUnmount() { LocalizationStore.removeChangeListener(this.localizationChanged); diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx index 16175d4de..98d4ed7da 100644 --- a/webapp/components/user_settings/user_settings_display.jsx +++ b/webapp/components/user_settings/user_settings_display.jsx @@ -641,7 +641,11 @@ export default class UserSettingsDisplay extends React.Component { ); } + const userLocale = this.props.user.locale; if (this.props.activeSection === 'languages') { + if (!I18n.isLanguageAvailable(userLocale)) { + this.props.user.locale = global.window.mm_config.DefaultClientLocale; + } languagesSection = ( <ManageLanguages user={this.props.user} @@ -652,7 +656,12 @@ export default class UserSettingsDisplay extends React.Component { /> ); } else { - var locale = I18n.getLanguageInfo(this.props.user.locale).name; + let locale; + if (I18n.isLanguageAvailable(userLocale)) { + locale = I18n.getLanguageInfo(userLocale).name; + } else { + locale = I18n.getLanguageInfo(global.window.mm_config.DefaultClientLocale).name; + } languagesSection = ( <SettingItemMin diff --git a/webapp/i18n/i18n.jsx b/webapp/i18n/i18n.jsx index 2214fd386..783cef975 100644 --- a/webapp/i18n/i18n.jsx +++ b/webapp/i18n/i18n.jsx @@ -4,7 +4,7 @@ const es = require('!!file?name=i18n/[name].[ext]!./es.json'); const fr = require('!!file?name=i18n/[name].[ext]!./fr.json'); const ja = require('!!file?name=i18n/[name].[ext]!./ja.json'); -const pt = require('!!file?name=i18n/[name].[ext]!./pt.json'); +const pt_BR = require('!!file?name=i18n/[name].[ext]!./pt-BR.json'); //eslint-disable-line camelcase import {addLocaleData} from 'react-intl'; import enLocaleData from 'react-intl/locale-data/en'; @@ -34,19 +34,47 @@ const languages = { name: '日本語 (Beta)', url: ja }, - pt: { - value: 'pt', + 'pt-BR': { + value: 'pt-BR', name: 'Portugues (Beta)', - url: pt + url: pt_BR } }; -export function getLanguages() { +let availableLanguages = null; + +function setAvailableLanguages() { + const available = global.window.mm_config.AvailableLocales.split(','); + + availableLanguages = {}; + + available.forEach((l) => { + if (languages[l]) { + availableLanguages[l] = languages[l]; + } + }); +} + +export function getAllLanguages() { return languages; } +export function getLanguages() { + if (!availableLanguages) { + setAvailableLanguages(); + } + return availableLanguages; +} + export function getLanguageInfo(locale) { - return languages[locale]; + if (!availableLanguages) { + setAvailableLanguages(); + } + return availableLanguages[locale]; +} + +export function isLanguageAvailable(locale) { + return !!availableLanguages[locale]; } export function safariFix(callback) { diff --git a/webapp/i18n/pt.json b/webapp/i18n/pt-BR.json index 7a5821e37..7a5821e37 100644 --- a/webapp/i18n/pt.json +++ b/webapp/i18n/pt-BR.json diff --git a/webapp/package.json b/webapp/package.json index 8e2063c84..2f61faeb9 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -27,8 +27,9 @@ "react-bootstrap": "0.29.3", "react-custom-scrollbars": "4.0.0-beta.1", "react-dom": "15.0.2", - "react-intl": "2.0.0-rc-1", + "react-intl": "2.1.2", "react-router": "2.4.0", + "react-select": "1.0.0-beta13", "react-textarea-autosize": "4.0.1", "superagent": "1.8.3", "twemoji": "2.0.5", diff --git a/webapp/root.jsx b/webapp/root.jsx index dc2df64ac..40cf351de 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -53,6 +53,7 @@ const ActionTypes = Constants.ActionTypes; import AdminConsole from 'components/admin_console/admin_console.jsx'; import SystemAnalytics from 'components/analytics/system_analytics.jsx'; import ConfigurationSettings from 'components/admin_console/configuration_settings.jsx'; +import LocalizationSettings from 'components/admin_console/localization_settings.jsx'; import UsersAndTeamsSettings from 'components/admin_console/users_and_teams_settings.jsx'; import PrivacySettings from 'components/admin_console/privacy_settings.jsx'; import LogSettings from 'components/admin_console/log_settings.jsx'; @@ -142,8 +143,8 @@ function preRenderSetup(callwhendone) { ); function afterIntl() { - I18n.doAddLocaleData(); $.when(d1).done(() => { + I18n.doAddLocaleData(); callwhendone(); }); } @@ -363,6 +364,10 @@ function renderRootComponent() { component={ConfigurationSettings} /> <Route + path='localization' + component={LocalizationSettings} + /> + <Route path='users_and_teams' component={UsersAndTeamsSettings} /> diff --git a/webapp/sass/styles.scss b/webapp/sass/styles.scss index 67e62d023..c42722652 100644 --- a/webapp/sass/styles.scss +++ b/webapp/sass/styles.scss @@ -9,6 +9,7 @@ @import '~perfect-scrollbar/dist/css/perfect-scrollbar.css'; @import '~font-awesome/css/font-awesome.css'; @import '~bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css'; +@import '~react-select/dist/react-select.css'; // styles.scss @import 'utils/module'; diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx index 855222d47..f57ecf1cd 100644 --- a/webapp/stores/user_store.jsx +++ b/webapp/stores/user_store.jsx @@ -4,6 +4,9 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import EventEmitter from 'events'; +import * as GlobalActions from 'actions/global_actions.jsx'; +import LocalizationStore from './localization_store.jsx'; + import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; @@ -100,6 +103,9 @@ class UserStoreClass extends EventEmitter { this.saveProfile(user); this.currentUserId = user.id; global.window.mm_current_user_id = this.currentUserId; + if (LocalizationStore.getLocale() !== user.locale) { + GlobalActions.newLocalizationSelected(user.locale); + } } getCurrentId() { |