diff --git a/cmd/extract/extract.go b/cmd/extract/extract.go new file mode 100644 index 000000000000..f4ebf0a5d8b2 --- /dev/null +++ b/cmd/extract/extract.go @@ -0,0 +1,39 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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 file scans all of minikube's code and finds all strings that need to be able to be translated. +It uses the more generic extract.TranslatableStringd, and prints all the translations +into every json file it can find in pkg/minikube/translate/translations. + +Usage: from the root minikube directory, go run cmd/extract/extract.go +*/ + +package main + +import ( + "k8s.io/minikube/pkg/minikube/extract" +) + +func main() { + paths := []string{"cmd", "pkg"} + functions := []string{"translate.T"} + output := "pkg/minikube/translate/translations" + err := extract.TranslatableStrings(paths, functions, output) + + if err != nil { + panic(err) + } +} diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go index c8094aa03372..61e90be20d79 100644 --- a/cmd/minikube/cmd/root.go +++ b/cmd/minikube/cmd/root.go @@ -92,7 +92,7 @@ var RootCmd = &cobra.Command{ } if enableUpdateNotification { - notify.MaybePrintUpdateTextFromGithub(os.Stderr) + notify.MaybePrintUpdateTextFromGithub() } }, } diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 49b62518beeb..915b5d120a8b 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -229,7 +229,7 @@ func runStart(cmd *cobra.Command, args []string) { // Makes minikube node ip to bypass http(s) proxy. since it is local traffic. err = proxy.ExcludeIP(ip) if err != nil { - console.ErrStyle(console.FailureType, "Failed to set NO_PROXY Env. please Use `export NO_PROXY=$NO_PROXY,%s`.", ip) + console.ErrStyle(console.FailureType, "Failed to set NO_PROXY Env. Please use `export NO_PROXY=$NO_PROXY,%s`.", ip) } // Save IP to configuration file for subsequent use config.KubernetesConfig.NodeIP = ip diff --git a/cmd/minikube/main.go b/cmd/minikube/main.go index cfdf2847d4da..f50ed4831e01 100644 --- a/cmd/minikube/main.go +++ b/cmd/minikube/main.go @@ -25,6 +25,7 @@ import ( "k8s.io/minikube/pkg/minikube/console" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/machine" + "k8s.io/minikube/pkg/minikube/translate" _ "k8s.io/minikube/pkg/provision" ) @@ -40,6 +41,6 @@ func main() { } console.SetOutFile(os.Stdout) console.SetErrFile(os.Stderr) - console.DetermineLocale() + translate.DetermineLocale() cmd.Execute() } diff --git a/go.mod b/go.mod index 0768b42129ec..56bb77c28651 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/fatih/color v1.7.0 // indirect github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 // indirect github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e // indirect + github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/golang/glog v0.0.0-20141105023935-44145f04b68c github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 // indirect github.com/google/btree v1.0.0 // indirect @@ -85,10 +86,10 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c // indirect github.com/xeipuuv/gojsonschema v0.0.0-20160623135812-c539bca196be github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 + golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 golang.org/x/oauth2 v0.0.0-20190115181402-5dab4167f31c - golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 - golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 + golang.org/x/sync v0.0.0-20190423024810-112230192c58 + golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b golang.org/x/text v0.3.2 golang.org/x/time v0.0.0-20161028155119-f51c12702a4d // indirect gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect diff --git a/go.sum b/go.sum index 0632dc68dcac..b336602e112e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Parallels/docker-machine-parallels v1.3.0 h1:RG1fyf3v1GwXMCeHRiZkB4tL9phFZEv6ixcvRZ1raN8= github.com/Parallels/docker-machine-parallels v1.3.0/go.mod h1:HCOMm3Hulq/xuEVQMyZOuQlA+dSZpFY5kdCTZWjMVis= github.com/Sirupsen/logrus v0.0.0-20170822132746-89742aefa4b2 h1:k1A7eIeUk6rnX2yuagwljW/pDezkK8oSpvPumT9zdZY= @@ -38,6 +40,8 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e h1:ago6fNuQ6IhszPsXkeU7qRCyfsIX7L67WDybsAPkLl8= github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= +github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20141105023935-44145f04b68c h1:CbdkBQ1/PiAo0FYJhQGwASD8wrgNvTdf01g6+O9tNuA= github.com/golang/glog v0.0.0-20141105023935-44145f04b68c/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= @@ -125,6 +129,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.7 h1:wCBv/ZWBkVl/x3xvw4MAMXgjtYbzyNTcZXO5jpmVQuA= +github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.7/go.mod h1:JXS4+OKhbcwDoVTEj0sLFWL1vOwec2g/YBAxZ9owJqY= github.com/olekukonko/tablewriter v0.0.0-20160923125401-bdcc175572fd h1:nEatQ6JnwCT9iYD5uqYUiFqq8tJGX25to8KVKXqya7k= github.com/olekukonko/tablewriter v0.0.0-20160923125401-bdcc175572fd/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -194,12 +200,18 @@ github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097 h1:Ucx5I1l1+TWXvqFm github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097/go.mod h1:lFZSWRIpCfE/pt91hHBBpV6+x87YlCjsp+aIR2qCPPU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190115181402-5dab4167f31c h1:pcBdqVcrlT+A3i+tWsOROFONQyey9tisIQHI4xqVGLg= golang.org/x/oauth2 v0.0.0-20190115181402-5dab4167f31c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -208,18 +220,24 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FY golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g= golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c h1:97SnQk1GYRXJgvwZ8fadnxDOWfKvkNQHH3CtZntPSrM= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/pkg/minikube/console/console.go b/pkg/minikube/console/console.go index 182c62822eed..1c39859ffb1b 100644 --- a/pkg/minikube/console/console.go +++ b/pkg/minikube/console/console.go @@ -24,11 +24,10 @@ import ( "strconv" "strings" - "github.com/cloudfoundry-attic/jibber_jabber" "github.com/golang/glog" isatty "github.com/mattn/go-isatty" - "golang.org/x/text/language" "golang.org/x/text/message" + "k8s.io/minikube/pkg/minikube/translate" ) // By design, this package uses global references to language and output objects, in preference @@ -48,10 +47,6 @@ var ( outFile fdWriter // errFile is where Err* functions send output to. Set using SetErrFile() errFile fdWriter - // preferredLanguage is the default language messages will be output in - preferredLanguage = language.AmericanEnglish - // our default language - defaultLanguage = language.AmericanEnglish // useColor is whether or not color output should be used, updated by Set*Writer. useColor = false // OverrideEnv is the environment variable used to override color/emoji usage @@ -76,7 +71,7 @@ func OutStyle(style StyleEnum, format string, a ...interface{}) { // Out writes a basic formatted string to stdout func Out(format string, a ...interface{}) { - p := message.NewPrinter(preferredLanguage) + p := message.NewPrinter(translate.GetPreferredLanguage()) if outFile == nil { glog.Warningf("[unset outFile]: %s", fmt.Sprintf(format, a...)) return @@ -104,7 +99,7 @@ func ErrStyle(style StyleEnum, format string, a ...interface{}) { // Err writes a basic formatted string to stderr func Err(format string, a ...interface{}) { - p := message.NewPrinter(preferredLanguage) + p := message.NewPrinter(translate.GetPreferredLanguage()) if errFile == nil { glog.Errorf("[unset errFile]: %s", fmt.Sprintf(format, a...)) return @@ -140,30 +135,6 @@ func Failure(format string, a ...interface{}) { ErrStyle(FailureType, format, a...) } -// SetPreferredLanguageTag configures which language future messages should use. -func SetPreferredLanguageTag(l language.Tag) { - glog.Infof("Setting Language to %s ...", l) - preferredLanguage = l -} - -// SetPreferredLanguage configures which language future messages should use, based on a LANG string. -func SetPreferredLanguage(s string) error { - // "C" is commonly used to denote a neutral POSIX locale. See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02 - if s == "" || s == "C" { - SetPreferredLanguageTag(defaultLanguage) - return nil - } - // Handles "de_DE" or "de_DE.utf8" - // We don't process encodings, since Rob Pike invented utf8 and we're mostly stuck with it. - parts := strings.Split(s, ".") - l, err := language.Parse(parts[0]) - if err != nil { - return err - } - SetPreferredLanguageTag(l) - return nil -} - // SetOutFile configures which writer standard output goes to. func SetOutFile(w fdWriter) { glog.Infof("Setting OutFile to fd %d ...", w.Fd()) @@ -178,17 +149,6 @@ func SetErrFile(w fdWriter) { useColor = wantsColor(w.Fd()) } -func DetermineLocale() { - locale, err := jibber_jabber.DetectIETF() - if err != nil { - glog.Warningf("Getting system locale failed: %s", err) - locale = "" - } - if err := SetPreferredLanguage(locale); err != nil { - glog.Errorf("Unable to set preferred language: %v", err) - } -} - // wantsColor determines if the user might want colorized output. func wantsColor(fd uintptr) bool { // First process the environment: we allow users to force colors on or off. diff --git a/pkg/minikube/console/console_test.go b/pkg/minikube/console/console_test.go index 488e12e3e740..b1c3e86fec1c 100644 --- a/pkg/minikube/console/console_test.go +++ b/pkg/minikube/console/console_test.go @@ -17,7 +17,6 @@ limitations under the License. package console import ( - "bytes" "fmt" "os" "strconv" @@ -25,31 +24,13 @@ import ( "golang.org/x/text/language" "golang.org/x/text/message" + "k8s.io/minikube/pkg/minikube/tests" + "k8s.io/minikube/pkg/minikube/translate" ) -// fakeFile satisfies fdWriter -type fakeFile struct { - b bytes.Buffer -} - -func newFakeFile() *fakeFile { - return &fakeFile{} -} - -func (f *fakeFile) Fd() uintptr { - return uintptr(0) -} - -func (f *fakeFile) Write(p []byte) (int, error) { - return f.b.Write(p) -} -func (f *fakeFile) String() string { - return f.b.String() -} - func TestOutStyle(t *testing.T) { - var tests = []struct { + var testCases = []struct { style StyleEnum message string params []interface{} @@ -64,12 +45,12 @@ func TestOutStyle(t *testing.T) { {Issue, "http://i/%d", []interface{}{10000}, " ▪ http://i/10000\n", " - http://i/10000\n"}, {Usage, "raw: %s %s", []interface{}{"'%'", "%d"}, "💡 raw: '%' %d\n", "* raw: '%' %d\n"}, } - for _, tc := range tests { + for _, tc := range testCases { for _, override := range []bool{true, false} { t.Run(fmt.Sprintf("%s-override-%v", tc.message, override), func(t *testing.T) { // Set MINIKUBE_IN_STYLE= os.Setenv(OverrideEnv, strconv.FormatBool(override)) - f := newFakeFile() + f := tests.NewFakeFile() SetOutFile(f) OutStyle(tc.style, tc.message, tc.params...) got := f.String() @@ -93,21 +74,23 @@ func TestOut(t *testing.T) { t.Fatalf("setstring: %v", err) } - var tests = []struct { + var testCases = []struct { format string - lang language.Tag + lang string arg interface{} want string }{ {format: "xyz123", want: "xyz123"}, - {format: "Installing Kubernetes version %s ...", lang: language.Arabic, arg: "v1.13", want: "... v1.13 تثبيت Kubernetes الإصدار"}, - {format: "Installing Kubernetes version %s ...", lang: language.AmericanEnglish, arg: "v1.13", want: "Installing Kubernetes version v1.13 ..."}, + {format: "Installing Kubernetes version %s ...", lang: "ar", arg: "v1.13", want: "... v1.13 تثبيت Kubernetes الإصدار"}, + {format: "Installing Kubernetes version %s ...", lang: "en-us", arg: "v1.13", want: "Installing Kubernetes version v1.13 ..."}, {format: "Parameter encoding: %s", arg: "%s%%%d", want: "Parameter encoding: %s%%%d"}, } - for _, tc := range tests { + for _, tc := range testCases { t.Run(tc.format, func(t *testing.T) { - SetPreferredLanguageTag(tc.lang) - f := newFakeFile() + if err := translate.SetPreferredLanguage(tc.lang); err != nil { + t.Errorf("unexpected error: %q", err) + } + f := tests.NewFakeFile() SetOutFile(f) ErrLn("unrelated message") Out(tc.format, tc.arg) @@ -121,7 +104,7 @@ func TestOut(t *testing.T) { func TestErr(t *testing.T) { os.Setenv(OverrideEnv, "0") - f := newFakeFile() + f := tests.NewFakeFile() SetErrFile(f) Err("xyz123 %s\n", "%s%%%d") OutLn("unrelated message") @@ -135,7 +118,7 @@ func TestErr(t *testing.T) { func TestErrStyle(t *testing.T) { os.Setenv(OverrideEnv, "1") - f := newFakeFile() + f := tests.NewFakeFile() SetErrFile(f) ErrStyle(FatalType, "error: %s", "%s%%%d") got := f.String() @@ -159,14 +142,15 @@ func TestSetPreferredLanguage(t *testing.T) { for _, tc := range tests { t.Run(tc.input, func(t *testing.T) { // Set something so that we can assert change. - SetPreferredLanguageTag(language.Icelandic) - if err := SetPreferredLanguage(tc.input); err != nil { + if err := translate.SetPreferredLanguage("is"); err != nil { + t.Errorf("unexpected error: %q", err) + } + if err := translate.SetPreferredLanguage(tc.input); err != nil { t.Errorf("unexpected error: %q", err) } - // Just compare the bases ("en", "fr"), since I can't seem to refer directly to them want, _ := tc.want.Base() - got, _ := preferredLanguage.Base() + got, _ := translate.GetPreferredLanguage().Base() if got != want { t.Errorf("SetPreferredLanguage(%s) = %q, want %q", tc.input, got, want) } diff --git a/pkg/minikube/console/style.go b/pkg/minikube/console/style.go index ce45f559399e..245f18e6f89c 100644 --- a/pkg/minikube/console/style.go +++ b/pkg/minikube/console/style.go @@ -21,6 +21,7 @@ import ( "golang.org/x/text/message" "golang.org/x/text/number" + "k8s.io/minikube/pkg/minikube/translate" ) var ( @@ -131,12 +132,13 @@ func lowPrefix(s style) string { // Apply styling to a format string func applyStyle(style StyleEnum, useColor bool, format string, a ...interface{}) string { - p := message.NewPrinter(preferredLanguage) + p := message.NewPrinter(translate.GetPreferredLanguage()) for i, x := range a { if _, ok := x.(int); ok { a[i] = number.Decimal(x, number.NoSeparator()) } } + format = translate.T(format) out := p.Sprintf(format, a...) s, ok := styles[style] diff --git a/pkg/minikube/exit/exit.go b/pkg/minikube/exit/exit.go index 2327fb3d506c..b92367f381eb 100644 --- a/pkg/minikube/exit/exit.go +++ b/pkg/minikube/exit/exit.go @@ -99,7 +99,7 @@ func displayError(msg string, err error) { // use Warning because Error will display a duplicate message to stderr glog.Warningf(fmt.Sprintf("%s: %v", msg, err)) console.Err("\n") - console.Fatal(msg+": %v", err) + console.Fatal("%s: %v", msg, err) console.Err("\n") console.ErrStyle(console.Sad, "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:") console.ErrStyle(console.URL, "https://github.com/kubernetes/minikube/issues/new") diff --git a/pkg/minikube/extract/extract.go b/pkg/minikube/extract/extract.go new file mode 100644 index 000000000000..4005b729fb33 --- /dev/null +++ b/pkg/minikube/extract/extract.go @@ -0,0 +1,387 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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. +*/ + +package extract + +import ( + "encoding/json" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/golang-collections/collections/stack" + "github.com/pkg/errors" +) + +// blacklist is a list of strings to explicitly omit from translation files. +var blacklist = []string{ + "%s: %v", + "%s.%s=%s", + "%s/%d", + "%s=%s", + "%v", +} + +// state is a struct that represent the current state of the extraction process +type state struct { + // The list of functions to check for + funcs map[funcType]struct{} + + // A stack representation of funcs for easy iteration + fs *stack.Stack + + // The list of translatable strings, in map form for easy json marhsalling + translations map[string]interface{} + + // The function call we're currently checking for + currentFunc funcType + + // The function we're currently parsing + parentFunc funcType + + // The file we're currently checking + filename string + + // The package we're currently in + currentPackage string +} + +type funcType struct { + pack string // The package the function is in + name string // The name of the function +} + +// newExtractor initializes state for extraction +func newExtractor(functionsToCheck []string) (*state, error) { + funcs := make(map[funcType]struct{}) + fs := stack.New() + + for _, t := range functionsToCheck { + // Functions must be of the form "package.function" + t2 := strings.Split(t, ".") + if len(t2) < 2 { + return nil, errors.Wrap(nil, fmt.Sprintf("Invalid function string %s. Needs package name as well.", t)) + } + f := funcType{ + pack: t2[0], + name: t2[1], + } + funcs[f] = struct{}{} + fs.Push(f) + } + + return &state{ + funcs: funcs, + fs: fs, + translations: make(map[string]interface{}), + }, nil +} + +// SetParentFunc Sets the current parent function, along with package information +func setParentFunc(e *state, f string) { + e.parentFunc = funcType{ + pack: e.currentPackage, + name: f, + } +} + +// TranslatableStrings finds all strings to that need to be translated in paths and prints them out to all json files in output +func TranslatableStrings(paths []string, functions []string, output string) error { + cwd, err := os.Getwd() + if err != nil { + return errors.Wrap(err, "Getting current working directory") + } + + if strings.Contains(cwd, "cmd") { + fmt.Println("Run extract.go from the minikube root directory.") + os.Exit(1) + } + + e, err := newExtractor(functions) + + if err != nil { + return errors.Wrap(err, "Initializing") + } + + fmt.Println("Compiling translation strings...") + for e.fs.Len() > 0 { + f := e.fs.Pop().(funcType) + e.currentFunc = f + for _, root := range paths { + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if shouldCheckFile(path) { + e.filename = path + return inspectFile(e) + } + return nil + }) + + if err != nil { + return errors.Wrap(err, "Extracting strings") + } + } + } + + err = writeStringsToFiles(e, output) + + if err != nil { + return errors.Wrap(err, "Writing translation files") + } + + fmt.Println("Done!") + return nil +} + +func shouldCheckFile(path string) bool { + return strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "_test.go") +} + +// inspectFile goes through the given file line by line looking for translatable strings +func inspectFile(e *state) error { + fset := token.NewFileSet() + r, err := ioutil.ReadFile(e.filename) + if err != nil { + return err + } + file, err := parser.ParseFile(fset, "", r, parser.ParseComments) + if err != nil { + return err + } + + ast.Inspect(file, func(x ast.Node) bool { + if fi, ok := x.(*ast.File); ok { + e.currentPackage = fi.Name.String() + return true + } + + if fd, ok := x.(*ast.FuncDecl); ok { + setParentFunc(e, fd.Name.String()) + return true + } + + checkNode(x, e) + return true + }) + + return nil +} + +// checkNode checks each node to see if it's a function call +func checkNode(stmt ast.Node, e *state) { + // This line is a function call, that's what we care about + if expr, ok := stmt.(*ast.CallExpr); ok { + checkCallExpression(expr, e) + } +} + +// checkCallExpression takes a function call, and checks its arguments for strings +func checkCallExpression(s *ast.CallExpr, e *state) { + for _, arg := range s.Args { + // This argument is a function literal, check its body. + if fl, ok := arg.(*ast.FuncLit); ok { + for _, stmt := range fl.Body.List { + checkNode(stmt, e) + } + } + } + + var functionName string + var packageName string + + // SelectorExpr is a function call to a separate package + sf, ok := s.Fun.(*ast.SelectorExpr) + if ok { + // Parse out the package of the call + sfi, ok := sf.X.(*ast.Ident) + if !ok { + return + } + packageName = sfi.Name + functionName = sf.Sel.Name + } + + // Ident is an identifier, in this case it's a function call in the same package + id, ok := s.Fun.(*ast.Ident) + if ok { + functionName = id.Name + packageName = e.currentPackage + } + + // This is not a function call. + if len(functionName) == 0 { + return + } + + // This is not the correct function call, or it was called with no arguments. + if e.currentFunc.name != functionName || e.currentFunc.pack != packageName || len(s.Args) == 0 { + return + } + + matched := false + for _, arg := range s.Args { + // This argument is an identifier. + if i, ok := arg.(*ast.Ident); ok { + if checkIdentForStringValue(i, e) { + matched = true + break + } + } + + // This argument is a string. + if argString, ok := arg.(*ast.BasicLit); ok { + if addStringToList(argString.Value, e) { + matched = true + break + } + } + } + + if !matched { + addParentFuncToList(e) + } + +} + +// checkIdentForStringValye takes a identifier and sees if it's a variable assigned to a string +func checkIdentForStringValue(i *ast.Ident, e *state) bool { + // This identifier is nil + if i.Obj == nil { + return false + } + + as, ok := i.Obj.Decl.(*ast.AssignStmt) + + // This identifier wasn't assigned anything + if !ok { + return false + } + + rhs, ok := as.Rhs[0].(*ast.BasicLit) + + // This identifier was not assigned a string/basic value + if !ok { + return false + } + + if addStringToList(rhs.Value, e) { + return true + } + + return false +} + +// addStringToList takes a string, makes sure it's meant to be translated then adds it to the list if so +func addStringToList(s string, e *state) bool { + // Empty strings don't need translating + if len(s) <= 2 { + return false + } + + // Parse out quote marks + stringToTranslate := s[1 : len(s)-1] + + // Don't translate integers + if _, err := strconv.Atoi(stringToTranslate); err == nil { + return false + } + + // Don't translate URLs + if u, err := url.Parse(stringToTranslate); err == nil && u.Scheme != "" && u.Host != "" { + return false + } + + // Don't translate commands + if strings.HasPrefix(stringToTranslate, "sudo ") { + return false + } + + // Don't translate blacklisted strings + for _, b := range blacklist { + if b == stringToTranslate { + return false + } + } + + // Hooray, we can translate the string! + e.translations[stringToTranslate] = "" + return true +} + +// writeStringsToFiles writes translations to all translation files in output +func writeStringsToFiles(e *state, output string) error { + err := filepath.Walk(output, func(path string, info os.FileInfo, err error) error { + if err != nil { + return errors.Wrap(err, "accessing path") + } + if info.Mode().IsDir() { + return nil + } + if !strings.HasSuffix(path, ".json") { + return nil + } + fmt.Printf("Writing to %s\n", filepath.Base(path)) + var currentTranslations map[string]interface{} + f, err := ioutil.ReadFile(path) + if err != nil { + return errors.Wrap(err, "reading translation file") + } + err = json.Unmarshal(f, ¤tTranslations) + if err != nil { + return errors.Wrap(err, "unmarshalling current translations") + } + + // Make sure to not overwrite already translated strings + for k := range e.translations { + if _, ok := currentTranslations[k]; !ok { + currentTranslations[k] = "" + } + } + + // Remove translations from the file that are empty and were not extracted + for k, v := range currentTranslations { + if _, ok := e.translations[k]; !ok && len(v.(string)) == 0 { + delete(currentTranslations, k) + } + } + + c, err := json.MarshalIndent(currentTranslations, "", "\t") + if err != nil { + return errors.Wrap(err, "marshalling translations") + } + err = ioutil.WriteFile(path, c, info.Mode()) + if err != nil { + return errors.Wrap(err, "writing translation file") + } + return nil + }) + + return err +} + +// addParentFuncToList adds the current parent function to the list of functions to inspect more closely. +func addParentFuncToList(e *state) { + if _, ok := e.funcs[e.parentFunc]; !ok { + e.funcs[e.parentFunc] = struct{}{} + e.fs.Push(e.parentFunc) + } +} diff --git a/pkg/minikube/extract/extract_test.go b/pkg/minikube/extract/extract_test.go new file mode 100644 index 000000000000..135e430fea80 --- /dev/null +++ b/pkg/minikube/extract/extract_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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. +*/ + +package extract + +import ( + "encoding/json" + "io/ioutil" + "reflect" + "testing" +) + +func TestExtract(t *testing.T) { + // The file to scan + paths := []string{"testdata/sample_file.go"} + + // The function we care about + functions := []string{"extract.PrintToScreen"} + + // The directory where the sample translation file is in + output := "testdata/" + + expected := map[string]interface{}{ + "Hint: This is not a URL, come on.": "", + "Holy cow I'm in a loop!": "Something else", + "This is a variable with a string assigned": "", + "This was a choice: %s": "Something", + "Wow another string: %s": "", + } + + err := TranslatableStrings(paths, functions, output) + + if err != nil { + t.Fatalf("Error translating strings: %v", err) + } + + var got map[string]interface{} + f, err := ioutil.ReadFile("testdata/test.json") + if err != nil { + t.Fatalf("Reading json file: %s", err) + } + + err = json.Unmarshal(f, &got) + if err != nil { + t.Fatalf("Error unmarshalling json: %v", err) + } + + if !reflect.DeepEqual(expected, got) { + t.Fatalf("Translation JSON not equal: expected %v, got %v", expected, got) + } + +} diff --git a/pkg/minikube/extract/testdata/sample_file.go b/pkg/minikube/extract/testdata/sample_file.go new file mode 100644 index 000000000000..2f28cd0d38c3 --- /dev/null +++ b/pkg/minikube/extract/testdata/sample_file.go @@ -0,0 +1,64 @@ +/* +Copyright 2019 The Kubernetes Authors All rights reserved. + +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. +*/ + +package extract + +import "fmt" + +func DoSomeStuff() { + // Test with a URL + PrintToScreenNoInterface("http://kubernetes.io") + + // Test with something that Go thinks looks like a URL + PrintToScreenNoInterface("Hint: This is not a URL, come on.") + + // Try with an integer + PrintToScreenNoInterface("5") + + // Try with a sudo command + PrintToScreenNoInterface("sudo ls .") + + DoSomeOtherStuff(true, 4, "I think this should work") + + v := "This is a variable with a string assigned" + PrintToScreenNoInterface(v) +} + +func DoSomeOtherStuff(choice bool, i int, s string) { + + // Let's try an if statement + if choice { + PrintToScreen("This was a choice: %s", s) + } else if i > 5 { + PrintToScreen("Wow another string: %s", i) + } else { + // Also try a loop + for i > 10 { + PrintToScreenNoInterface("Holy cow I'm in a loop!") + i = i + 1 + } + } + +} + +func PrintToScreenNoInterface(s string) { + PrintToScreen(s, nil) +} + +// This will be the function we'll focus the extractor on +func PrintToScreen(s string, i interface{}) { + fmt.Printf(s, i) +} diff --git a/pkg/minikube/extract/testdata/test.json b/pkg/minikube/extract/testdata/test.json new file mode 100644 index 000000000000..c7a3cf31edd1 --- /dev/null +++ b/pkg/minikube/extract/testdata/test.json @@ -0,0 +1,4 @@ +{ + "Holy cow I'm in a loop!": "Something else", + "This was a choice: %s": "Something" +} \ No newline at end of file diff --git a/pkg/minikube/notify/notify.go b/pkg/minikube/notify/notify.go index dc94b3d1c490..d657786d602e 100644 --- a/pkg/minikube/notify/notify.go +++ b/pkg/minikube/notify/notify.go @@ -19,7 +19,6 @@ package notify import ( "encoding/json" "fmt" - "io" "io/ioutil" "net/http" "runtime" @@ -31,6 +30,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/viper" "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/console" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/version" ) @@ -43,12 +43,12 @@ var ( ) // MaybePrintUpdateTextFromGithub prints update text if needed, from github -func MaybePrintUpdateTextFromGithub(output io.Writer) { - MaybePrintUpdateText(output, constants.GithubMinikubeReleasesURL, lastUpdateCheckFilePath) +func MaybePrintUpdateTextFromGithub() { + MaybePrintUpdateText(constants.GithubMinikubeReleasesURL, lastUpdateCheckFilePath) } // MaybePrintUpdateText prints update text if needed -func MaybePrintUpdateText(output io.Writer, url string, lastUpdatePath string) { +func MaybePrintUpdateText(url string, lastUpdatePath string) { if !shouldCheckURLVersion(lastUpdatePath) { return } @@ -66,7 +66,7 @@ func MaybePrintUpdateText(output io.Writer, url string, lastUpdatePath string) { if err := writeTimeToFile(lastUpdateCheckFilePath, time.Now().UTC()); err != nil { glog.Errorf("write time failed: %v", err) } - fmt.Fprintf(output, `There is a newer version of minikube available (%s%s). Download it here: + console.ErrStyle(console.WarningType, `There is a newer version of minikube available (%s%s). Download it here: %s%s To disable this notification, run the following: diff --git a/pkg/minikube/notify/notify_test.go b/pkg/minikube/notify/notify_test.go index 62861c549a15..e78f6bf7b30a 100644 --- a/pkg/minikube/notify/notify_test.go +++ b/pkg/minikube/notify/notify_test.go @@ -17,7 +17,6 @@ limitations under the License. package notify import ( - "bytes" "encoding/json" "fmt" "net/http" @@ -30,6 +29,7 @@ import ( "github.com/blang/semver" "github.com/spf13/viper" "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/console" "k8s.io/minikube/pkg/minikube/tests" "k8s.io/minikube/pkg/version" ) @@ -150,7 +150,8 @@ func TestMaybePrintUpdateText(t *testing.T) { viper.Set(config.WantUpdateNotification, true) viper.Set(config.ReminderWaitPeriodInHours, 24) - var outputBuffer bytes.Buffer + outputBuffer := tests.NewFakeFile() + console.SetErrFile(outputBuffer) lastUpdateCheckFilePath := filepath.Join(tempDir, "last_update_check") // test that no update text is printed if the latest version is lower/equal to the current version @@ -161,7 +162,7 @@ func TestMaybePrintUpdateText(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - MaybePrintUpdateText(&outputBuffer, server.URL, lastUpdateCheckFilePath) + MaybePrintUpdateText(server.URL, lastUpdateCheckFilePath) if len(outputBuffer.String()) != 0 { t.Fatalf("Expected MaybePrintUpdateText to not output text as the current version is %s and version %s was served from URL but output was [%s]", version.GetVersion(), latestVersionFromURL, outputBuffer.String()) @@ -175,7 +176,7 @@ func TestMaybePrintUpdateText(t *testing.T) { server = httptest.NewServer(handler) defer server.Close() - MaybePrintUpdateText(&outputBuffer, server.URL, lastUpdateCheckFilePath) + MaybePrintUpdateText(server.URL, lastUpdateCheckFilePath) if len(outputBuffer.String()) == 0 { t.Fatalf("Expected MaybePrintUpdateText to output text as the current version is %s and version %s was served from URL but output was [%s]", version.GetVersion(), latestVersionFromURL, outputBuffer.String()) diff --git a/pkg/minikube/problem/problem.go b/pkg/minikube/problem/problem.go index ff126e7eacef..a20e09b12162 100644 --- a/pkg/minikube/problem/problem.go +++ b/pkg/minikube/problem/problem.go @@ -21,6 +21,7 @@ import ( "regexp" "k8s.io/minikube/pkg/minikube/console" + "k8s.io/minikube/pkg/minikube/translate" ) const issueBase = "https://github.com/kubernetes/minikube/issues" @@ -52,7 +53,7 @@ type match struct { // Display problem metadata to the console func (p *Problem) Display() { console.ErrStyle(console.FailureType, "Error: [%s] %v", p.ID, p.Err) - console.ErrStyle(console.Tip, "Advice: %s", p.Advice) + console.ErrStyle(console.Tip, "Advice: %s", translate.T(p.Advice)) if p.URL != "" { console.ErrStyle(console.Documentation, "Documentation: %s", p.URL) } diff --git a/pkg/minikube/tests/dir_utils.go b/pkg/minikube/tests/dir_utils.go index c85915ec329b..c1ee8bd63b61 100644 --- a/pkg/minikube/tests/dir_utils.go +++ b/pkg/minikube/tests/dir_utils.go @@ -17,6 +17,7 @@ limitations under the License. package tests import ( + "bytes" "io/ioutil" "log" "os" @@ -43,3 +44,23 @@ func MakeTempDir() string { os.Setenv(constants.MinikubeHome, tempDir) return constants.GetMinipath() } + +// FakeFile satisfies fdWriter +type FakeFile struct { + b bytes.Buffer +} + +func NewFakeFile() *FakeFile { + return &FakeFile{} +} + +func (f *FakeFile) Fd() uintptr { + return uintptr(0) +} + +func (f *FakeFile) Write(p []byte) (int, error) { + return f.b.Write(p) +} +func (f *FakeFile) String() string { + return f.b.String() +} diff --git a/pkg/minikube/translate/translate.go b/pkg/minikube/translate/translate.go new file mode 100644 index 000000000000..6dd262e40c00 --- /dev/null +++ b/pkg/minikube/translate/translate.go @@ -0,0 +1,118 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +package translate + +import ( + "encoding/json" + "io/ioutil" + "strings" + + "github.com/cloudfoundry-attic/jibber_jabber" + "github.com/golang/glog" + "golang.org/x/text/language" +) + +var ( + // preferredLanguage is the default language messages will be output in + preferredLanguage = language.AmericanEnglish + // our default language + defaultLanguage = language.AmericanEnglish + + // Translations is a translation map from strings that can be output to console + // to its translation in the user's system locale. + Translations map[string]interface{} +) + +// T translates the given string to the supplied language. +func T(s string) string { + if preferredLanguage == defaultLanguage { + return s + } + + if len(Translations) == 0 { + return s + } + + if t, ok := Translations[s]; ok { + if len(t.(string)) > 0 && t.(string) != " " { + return t.(string) + } + } + + return s +} + +// DetermineLocale finds the system locale and sets the preferred language for output appropriately. +func DetermineLocale() { + locale, err := jibber_jabber.DetectIETF() + if err != nil { + glog.Warningf("Getting system locale failed: %s", err) + locale = "" + } + err = SetPreferredLanguage(locale) + if err != nil { + glog.Warningf("Setting locale failed: %s", err) + preferredLanguage = defaultLanguage + } + + if preferredLanguage == defaultLanguage { + return + } + + // Load translations for preferred language into memory. + translationFile := "pkg/minikube/translate/translations/" + preferredLanguage.String() + ".json" + t, err := ioutil.ReadFile(translationFile) + if err != nil { + glog.Errorf("Failed to load transalation file for %s: %s", preferredLanguage.String(), err) + return + } + + err = json.Unmarshal(t, &Translations) + if err != nil { + glog.Errorf("Failed to populate translation map: %s", err) + } + +} + +// setPreferredLanguageTag configures which language future messages should use. +func setPreferredLanguageTag(l language.Tag) { + glog.Infof("Setting Language to %s ...", l) + preferredLanguage = l +} + +// SetPreferredLanguage configures which language future messages should use, based on a LANG string. +func SetPreferredLanguage(s string) error { + // "C" is commonly used to denote a neutral POSIX locale. See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02 + if s == "" || s == "C" { + setPreferredLanguageTag(defaultLanguage) + return nil + } + // Handles "de_DE" or "de_DE.utf8" + // We don't process encodings, since Rob Pike invented utf8 and we're mostly stuck with it. + parts := strings.Split(s, ".") + l, err := language.Parse(parts[0]) + if err != nil { + return err + } + setPreferredLanguageTag(l) + return nil +} + +// GetPreferredLanguage returns the preferred language tag. +func GetPreferredLanguage() language.Tag { + return preferredLanguage +} diff --git a/pkg/minikube/translate/translations/fr-FR.json b/pkg/minikube/translate/translations/fr-FR.json new file mode 100644 index 000000000000..f0135e1afac0 --- /dev/null +++ b/pkg/minikube/translate/translations/fr-FR.json @@ -0,0 +1,237 @@ +{ + "%q VM does not exist, nothing to stop": "", + "%q cluster does not exist": "", + "%q host does not exist, unable to show an IP": "", + "%q profile does not exist": "", + "%q stopped.": "", + "%s IP has been updated to point at %s": "", + "%s IP was already correctly configured for %s": "", + "%s has no available configuration options": "", + "%s is not responding properly: %v": "", + "%s is not yet a supported filesystem. We will try anyways!": "", + "%s was successfully configured": "", + "%s was successfully disabled": "", + "%s was successfully enabled": "", + "%s:%s is not running: %v": "", + "'none' driver does not support 'minikube docker-env' command": "", + "'none' driver does not support 'minikube mount' command": "", + "'none' driver does not support 'minikube ssh' command": "", + "Advice: %s": "", + "Alternatively, you may delete the existing VM using `minikube delete -p %s`": "", + "Cannot find directory %s for mount": "", + "Check that minikube is running and that you have specified the correct namespace (-n flag) if required.": "", + "Configuring environment for Kubernetes %s on %s %s": "Configurant l'environment pour Kubernetes %s sur %s %s", + "Configuring local host environment ...": "", + "Creating %s VM (CPUs=%d, Memory=%dMB, Disk=%dMB) ...": "Créant un VM %s (CPUs=%d, Mémoire=%dMB, Disque=%dMB)", + "Creating mount %s ...": "", + "Deleting %q from %s ...": "", + "Documentation: %s": "", + "Done! kubectl is now configured to use %q": "Fini! kubectl est maintenant configuré pour utiliser %s.", + "Download complete!": "", + "Downloading %s %s": "", + "Downloading Minikube ISO ...": "", + "ERROR creating `registry-creds-dpr` secret": "", + "ERROR creating `registry-creds-ecr` secret: %v": "", + "ERROR creating `registry-creds-gcr` secret: %v": "", + "Enabling dashboard ...": "", + "Error creating list template": "", + "Error creating minikube directory": "", + "Error creating status template": "", + "Error creating view template": "", + "Error executing list template": "", + "Error executing status template": "", + "Error executing template": "", + "Error executing view template": "", + "Error finding port for mount": "", + "Error getting IP": "", + "Error getting bootstrapper": "", + "Error getting client": "", + "Error getting client: %v": "", + "Error getting cluster": "", + "Error getting cluster bootstrapper": "", + "Error getting config": "", + "Error getting host": "", + "Error getting host status": "", + "Error getting machine logs": "", + "Error getting machine status": "", + "Error getting service status": "", + "Error getting service with namespace: %s and labels %s:%s: %v": "", + "Error getting the host IP address to use from within the VM": "", + "Error host driver ip status": "", + "Error killing mount process": "", + "Error loading api": "", + "Error opening service": "", + "Error reading %s: %v": "", + "Error restarting cluster": "", + "Error setting shell variables": "", + "Error starting cluster": "", + "Error starting mount": "", + "Error unsetting shell variables": "", + "Error writing mount pid": "", + "Error: [%s] %v": "", + "Exiting due to %s signal": "", + "Failed to cache ISO": "", + "Failed to cache and load images": "", + "Failed to cache binaries": "", + "Failed to cache images": "", + "Failed to check if machine exists": "", + "Failed to check main repository and mirrors for images for images": "", + "Failed to chown %s: %v": "", + "Failed to delete cluster": "", + "Failed to delete images": "", + "Failed to delete images from config": "", + "Failed to download kubectl": "", + "Failed to enable container runtime": "", + "Failed to generate config": "", + "Failed to get bootstrapper": "", + "Failed to get command runner": "", + "Failed to get driver URL": "", + "Failed to get image map": "", + "Failed to get machine client": "", + "Failed to get service URL: %v": "", + "Failed to kill mount process: %v": "", + "Failed to list cached images": "", + "Failed to remove profile": "", + "Failed to save config": "", + "Failed to set NO_PROXY Env. Please use `export NO_PROXY=$NO_PROXY,%s`.": "", + "Failed to setup certs": "", + "Failed to setup kubeconfig": "", + "Failed to update cluster": "", + "Failed to update config": "", + "Failed unmount: %v": "", + "Follow": "", + "For best results, install kubectl: https://kubernetes.io/docs/tasks/tools/install-kubectl/": "", + "For more information, see:": "", + "Found network options:": "", + "GID: %s": "", + "If the above advice does not help, please let us know: ": "", + "Ignoring --vm-driver=%s, as the existing %q VM was created using the %s driver.": "", + "IsEnabled failed": "", + "Kubernetes downgrade is not supported, will continue to use %v": "", + "Launching Kubernetes ... ": "Lançant Kubernetes ...", + "Launching proxy ...": "", + "MSize: %d": "", + "Mode: %o (%s)": "", + "Mount options:": "", + "Mounting host path %s into VM as %s ...": "", + "NOTE: This process must stay alive for the mount to be accessible ...": "", + "None of known repositories in your location is accessible. Use %s as fallback.": "", + "None of known repositories is accessible. Consider specifying an alternative image repository with --image-repository flag": "", + "Opening %s in your default browser...": "", + "Opening kubernetes service %s/%s in default browser...": "", + "Options: %s": "", + "Please enter a value:": "", + "Please specify the directory to be mounted: \n\tminikube mount \u003csource directory\u003e:\u003ctarget directory\u003e (example: \"/host-home:/vm-home\")": "", + "Powering off %q via SSH ...": "", + "Problems detected in %q:": "", + "Pulling images ...": "Extrayant les images ... ", + "Re-using the currently running %s VM for %q ...": "", + "Related issues:": "", + "Relaunching Kubernetes %s using %s ... ": "", + "Requested disk size (%dMB) is less than minimum of %dMB": "", + "Restarting existing %s VM for %q ...": "", + "Set failed": "", + "Setting profile failed": "", + "Skipped switching kubectl context for %s , because --keep-context": "", + "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:": "", + "Sorry, completion support is not yet implemented for %q": "", + "Sorry, the --gpu feature is currently only supported with --vm-driver=kvm2": "", + "Sorry, the --hidden feature is currently only supported with --vm-driver=kvm2": "", + "Sorry, the kubeadm.%s parameter is currently not supported by --extra-config": "", + "Stopping %q in %s ...": "", + "Successfully mounted %s to %s": "", + "Target directory %q must be an absolute path": "", + "The %q cluster has been deleted.": "", + "The 'none' driver provides limited isolation and may reduce system security and reliability.": "", + "The docker host is currently not running": "", + "The docker service is currently not active": "", + "The kvm driver is deprecated and support for it will be removed in a future release.\n\t\t\t\tPlease consider switching to the kvm2 driver, which is intended to replace the kvm driver.\n\t\t\t\tSee https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#kvm2-driver for more information.\n\t\t\t\tTo disable this message, run [minikube config set ShowDriverDeprecationNotification false]": "", + "The value passed to --format is invalid": "", + "The value passed to --format is invalid: %s": "", + "The vmwarefusion driver is deprecated and support for it will be removed in a future release.\n\t\t\t\tPlease consider switching to the new vmware unified driver, which is intended to replace the vmwarefusion driver.\n\t\t\t\tSee https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#vmware-unified-driver for more information.\n\t\t\t\tTo disable this message, run [minikube config set ShowDriverDeprecationNotification false]": "", + "The xhyve driver is deprecated and support for it will be removed in a future release.\nPlease consider switching to the hyperkit driver, which is intended to replace the xhyve driver.\nSee https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver for more information.\nTo disable this message, run [minikube config set ShowDriverDeprecationNotification false]": "", + "There is a newer version of minikube available (%s%s). Download it here:\n%s%s\n\nTo disable this notification, run the following:\nminikube config set WantUpdateNotification false\n": "", + "These changes will take effect upon a minikube delete and then a minikube start": "", + "This addon does not have an endpoint defined for the 'addons open' command.\nYou can add one by annotating a service with the label %s:%s": "", + "This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true": "", + "Tip: Use 'minikube start -p \u003cname\u003e' to create a new cluster, or 'minikube delete' to delete this one.": "", + "To connect to this cluster, use: kubectl --context=%s": "", + "To switch drivers, you may create a new VM using `minikube start -p \u003cname\u003e --vm-driver=%s`": "", + "To use kubectl or minikube commands as your own user, you may": "", + "Type: %s": "", + "UID: %s": "", + "Unable to bind flags": "", + "Unable to enable dashboard": "", + "Unable to fetch latest version info": "", + "Unable to get VM IP address": "", + "Unable to get runtime": "", + "Unable to kill mount process: %s": "", + "Unable to load cached images from config file.": "", + "Unable to load cached images: %v": "", + "Unable to load config: %v": "", + "Unable to parse %q: %v": "", + "Unable to pull images, which may be OK: %v": "", + "Unable to start VM": "", + "Unable to stop VM": "", + "Uninstalling Kubernetes %s using %s ...": "", + "Unmounting %s ...": "", + "Update server returned an empty list": "", + "Usage: minikube completion SHELL": "", + "Userspace file server is shutdown": "", + "Userspace file server: ": "", + "Verifying dashboard health ...": "", + "Verifying proxy health ...": "", + "Verifying:": "Vérifiant:", + "Version: %s": "", + "Wait failed": "", + "Wait failed: %v": "", + "Waiting for SSH access ...": "Attendant l'accès SSH ...", + "You appear to be using a proxy, but your NO_PROXY environment does not include the minikube IP (%s). Please see https://github.com/kubernetes/minikube/blob/master/docs/http_proxy.md for more details": "", + "You must specify a service name": "", + "addon '%s' is currently not enabled.\nTo enable this addon run:\nminikube addons enable %s": "", + "addon '%s' is not a valid addon packaged with minikube.\nTo see the list of available addons run:\nminikube addons list": "", + "addon list failed": "", + "api load": "", + "bash completion failed": "", + "checking main repository and mirrors for images": "", + "command runner": "", + "config view failed": "", + "disable failed": "", + "enable failed: %v": "", + "env %s": "", + "error creating clientset": "", + "error creating machine client": "", + "error getting driver": "", + "error parsing the input ip address for mount": "", + "error starting tunnel": "", + "failed to open browser: %v": "", + "kubectl and minikube configuration will be stored in %s": "", + "kubectl not found in PATH, but is required for the dashboard. Installation guide: https://kubernetes.io/docs/tasks/tools/install-kubectl/": "", + "kubectl proxy": "", + "logdir set failed": "", + "minikube %s on %s (%s)": "minikube %s sur %s (%s)", + "minikube is not running, so the service cannot be accessed": "", + "minikube profile was successfully set to %s": "", + "minikube will upgrade the local cluster from Kubernetes %s to %s": "", + "mount argument %q must be in form: \u003csource directory\u003e:\u003ctarget directory\u003e": "", + "mount failed": "", + "need to relocate them. For example, to overwrite your own settings:": "", + "opt %s": "", + "stat failed": "", + "unable to bind flags": "", + "unable to set logtostderr": "", + "unset failed": "", + "unsupported driver: %s": "", + "update config": "", + "usage: minikube addons configure ADDON_NAME": "", + "usage: minikube addons disable ADDON_NAME": "", + "usage: minikube addons enable ADDON_NAME": "", + "usage: minikube addons list": "", + "usage: minikube addons open ADDON_NAME": "", + "usage: minikube config set PROPERTY_NAME PROPERTY_VALUE": "", + "usage: minikube config unset PROPERTY_NAME": "", + "usage: minikube delete": "", + "usage: minikube profile [MINIKUBE_PROFILE_NAME]": "", + "using image repository %s": "", + "zsh completion failed": "" +} \ No newline at end of file diff --git a/pkg/minikube/translate/translations/zh-CN.json b/pkg/minikube/translate/translations/zh-CN.json new file mode 100644 index 000000000000..258e24b44e9f --- /dev/null +++ b/pkg/minikube/translate/translations/zh-CN.json @@ -0,0 +1,237 @@ +{ + "%q VM does not exist, nothing to stop": "", + "%q cluster does not exist": "", + "%q host does not exist, unable to show an IP": "", + "%q profile does not exist": "", + "%q stopped.": "", + "%s IP has been updated to point at %s": "", + "%s IP was already correctly configured for %s": "", + "%s has no available configuration options": "", + "%s is not responding properly: %v": "", + "%s is not yet a supported filesystem. We will try anyways!": "", + "%s was successfully configured": "", + "%s was successfully disabled": "", + "%s was successfully enabled": "", + "%s:%s is not running: %v": "", + "'none' driver does not support 'minikube docker-env' command": "", + "'none' driver does not support 'minikube mount' command": "", + "'none' driver does not support 'minikube ssh' command": "", + "Advice: %s": "", + "Alternatively, you may delete the existing VM using `minikube delete -p %s`": "", + "Cannot find directory %s for mount": "", + "Check that minikube is running and that you have specified the correct namespace (-n flag) if required.": "", + "Configuring environment for Kubernetes %s on %s %s": "开始为Kubernetes %s,%s %s 配置环境变量", + "Configuring local host environment ...": "", + "Creating %s VM (CPUs=%d, Memory=%dMB, Disk=%dMB) ...": "正在创建%s虚拟机(CPU =%d,内存=%dMB,磁盘=%dMB)...", + "Creating mount %s ...": "", + "Deleting %q from %s ...": "", + "Documentation: %s": "", + "Done! kubectl is now configured to use %q": "完成!kubectl已经配置至%q", + "Download complete!": "", + "Downloading %s %s": "", + "Downloading Minikube ISO ...": "", + "ERROR creating `registry-creds-dpr` secret": "", + "ERROR creating `registry-creds-ecr` secret: %v": "", + "ERROR creating `registry-creds-gcr` secret: %v": "", + "Enabling dashboard ...": "", + "Error creating list template": "", + "Error creating minikube directory": "", + "Error creating status template": "", + "Error creating view template": "", + "Error executing list template": "", + "Error executing status template": "", + "Error executing template": "", + "Error executing view template": "", + "Error finding port for mount": "", + "Error getting IP": "", + "Error getting bootstrapper": "", + "Error getting client": "", + "Error getting client: %v": "", + "Error getting cluster": "", + "Error getting cluster bootstrapper": "", + "Error getting config": "", + "Error getting host": "", + "Error getting host status": "", + "Error getting machine logs": "", + "Error getting machine status": "", + "Error getting service status": "", + "Error getting service with namespace: %s and labels %s:%s: %v": "", + "Error getting the host IP address to use from within the VM": "", + "Error host driver ip status": "", + "Error killing mount process": "", + "Error loading api": "", + "Error opening service": "", + "Error reading %s: %v": "", + "Error restarting cluster": "", + "Error setting shell variables": "", + "Error starting cluster": "", + "Error starting mount": "", + "Error unsetting shell variables": "", + "Error writing mount pid": "", + "Error: [%s] %v": "", + "Exiting due to %s signal": "", + "Failed to cache ISO": "", + "Failed to cache and load images": "", + "Failed to cache binaries": "", + "Failed to cache images": "", + "Failed to check if machine exists": "", + "Failed to check main repository and mirrors for images for images": "", + "Failed to chown %s: %v": "", + "Failed to delete cluster": "", + "Failed to delete images": "", + "Failed to delete images from config": "", + "Failed to download kubectl": "", + "Failed to enable container runtime": "", + "Failed to generate config": "", + "Failed to get bootstrapper": "", + "Failed to get command runner": "", + "Failed to get driver URL": "", + "Failed to get image map": "", + "Failed to get machine client": "", + "Failed to get service URL: %v": "", + "Failed to kill mount process: %v": "", + "Failed to list cached images": "", + "Failed to remove profile": "", + "Failed to save config": "", + "Failed to set NO_PROXY Env. Please use `export NO_PROXY=$NO_PROXY,%s`.": "", + "Failed to setup certs": "", + "Failed to setup kubeconfig": "", + "Failed to update cluster": "", + "Failed to update config": "", + "Failed unmount: %v": "", + "Follow": "", + "For best results, install kubectl: https://kubernetes.io/docs/tasks/tools/install-kubectl/": "", + "For more information, see:": "", + "Found network options:": "", + "GID: %s": "", + "If the above advice does not help, please let us know: ": "", + "Ignoring --vm-driver=%s, as the existing %q VM was created using the %s driver.": "", + "IsEnabled failed": "", + "Kubernetes downgrade is not supported, will continue to use %v": "", + "Launching Kubernetes ... ": "正在启动 Kubernetes ... ", + "Launching proxy ...": "", + "MSize: %d": "", + "Mode: %o (%s)": "", + "Mount options:": "", + "Mounting host path %s into VM as %s ...": "", + "NOTE: This process must stay alive for the mount to be accessible ...": "", + "None of known repositories in your location is accessible. Use %s as fallback.": "", + "None of known repositories is accessible. Consider specifying an alternative image repository with --image-repository flag": "", + "Opening %s in your default browser...": "", + "Opening kubernetes service %s/%s in default browser...": "", + "Options: %s": "", + "Please enter a value:": "", + "Please specify the directory to be mounted: \n\tminikube mount \u003csource directory\u003e:\u003ctarget directory\u003e (example: \"/host-home:/vm-home\")": "", + "Powering off %q via SSH ...": "", + "Problems detected in %q:": "", + "Pulling images ...": "拉取镜像 ...", + "Re-using the currently running %s VM for %q ...": "", + "Related issues:": "", + "Relaunching Kubernetes %s using %s ... ": "", + "Requested disk size (%dMB) is less than minimum of %dMB": "", + "Restarting existing %s VM for %q ...": "", + "Set failed": "", + "Setting profile failed": "", + "Skipped switching kubectl context for %s , because --keep-context": "", + "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:": "", + "Sorry, completion support is not yet implemented for %q": "", + "Sorry, the --gpu feature is currently only supported with --vm-driver=kvm2": "", + "Sorry, the --hidden feature is currently only supported with --vm-driver=kvm2": "", + "Sorry, the kubeadm.%s parameter is currently not supported by --extra-config": "", + "Stopping %q in %s ...": "", + "Successfully mounted %s to %s": "", + "Target directory %q must be an absolute path": "", + "The %q cluster has been deleted.": "", + "The 'none' driver provides limited isolation and may reduce system security and reliability.": "", + "The docker host is currently not running": "", + "The docker service is currently not active": "", + "The kvm driver is deprecated and support for it will be removed in a future release.\n\t\t\t\tPlease consider switching to the kvm2 driver, which is intended to replace the kvm driver.\n\t\t\t\tSee https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#kvm2-driver for more information.\n\t\t\t\tTo disable this message, run [minikube config set ShowDriverDeprecationNotification false]": "", + "The value passed to --format is invalid": "", + "The value passed to --format is invalid: %s": "", + "The vmwarefusion driver is deprecated and support for it will be removed in a future release.\n\t\t\t\tPlease consider switching to the new vmware unified driver, which is intended to replace the vmwarefusion driver.\n\t\t\t\tSee https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#vmware-unified-driver for more information.\n\t\t\t\tTo disable this message, run [minikube config set ShowDriverDeprecationNotification false]": "", + "The xhyve driver is deprecated and support for it will be removed in a future release.\nPlease consider switching to the hyperkit driver, which is intended to replace the xhyve driver.\nSee https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver for more information.\nTo disable this message, run [minikube config set ShowDriverDeprecationNotification false]": "", + "There is a newer version of minikube available (%s%s). Download it here:\n%s%s\n\nTo disable this notification, run the following:\nminikube config set WantUpdateNotification false\n": "", + "These changes will take effect upon a minikube delete and then a minikube start": "", + "This addon does not have an endpoint defined for the 'addons open' command.\nYou can add one by annotating a service with the label %s:%s": "", + "This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true": "", + "Tip: Use 'minikube start -p \u003cname\u003e' to create a new cluster, or 'minikube delete' to delete this one.": "", + "To connect to this cluster, use: kubectl --context=%s": "", + "To switch drivers, you may create a new VM using `minikube start -p \u003cname\u003e --vm-driver=%s`": "", + "To use kubectl or minikube commands as your own user, you may": "", + "Type: %s": "", + "UID: %s": "", + "Unable to bind flags": "", + "Unable to enable dashboard": "", + "Unable to fetch latest version info": "", + "Unable to get VM IP address": "", + "Unable to get runtime": "", + "Unable to kill mount process: %s": "", + "Unable to load cached images from config file.": "", + "Unable to load cached images: %v": "", + "Unable to load config: %v": "", + "Unable to parse %q: %v": "", + "Unable to pull images, which may be OK: %v": "", + "Unable to start VM": "", + "Unable to stop VM": "", + "Uninstalling Kubernetes %s using %s ...": "", + "Unmounting %s ...": "", + "Update server returned an empty list": "", + "Usage: minikube completion SHELL": "", + "Userspace file server is shutdown": "", + "Userspace file server: ": "", + "Verifying dashboard health ...": "", + "Verifying proxy health ...": "", + "Verifying:": "正在验证:", + "Version: %s": "", + "Wait failed": "", + "Wait failed: %v": "", + "Waiting for SSH access ...": "", + "You appear to be using a proxy, but your NO_PROXY environment does not include the minikube IP (%s). Please see https://github.com/kubernetes/minikube/blob/master/docs/http_proxy.md for more details": "", + "You must specify a service name": "", + "addon '%s' is currently not enabled.\nTo enable this addon run:\nminikube addons enable %s": "", + "addon '%s' is not a valid addon packaged with minikube.\nTo see the list of available addons run:\nminikube addons list": "", + "addon list failed": "", + "api load": "", + "bash completion failed": "", + "checking main repository and mirrors for images": "", + "command runner": "", + "config view failed": "", + "disable failed": "", + "enable failed: %v": "", + "env %s": "", + "error creating clientset": "", + "error creating machine client": "", + "error getting driver": "", + "error parsing the input ip address for mount": "", + "error starting tunnel": "", + "failed to open browser: %v": "", + "kubectl and minikube configuration will be stored in %s": "", + "kubectl not found in PATH, but is required for the dashboard. Installation guide: https://kubernetes.io/docs/tasks/tools/install-kubectl/": "", + "kubectl proxy": "", + "logdir set failed": "", + "minikube %s on %s (%s)": "您正在使用minikube %s, 运行平台:%s (%s)", + "minikube is not running, so the service cannot be accessed": "", + "minikube profile was successfully set to %s": "", + "minikube will upgrade the local cluster from Kubernetes %s to %s": "", + "mount argument %q must be in form: \u003csource directory\u003e:\u003ctarget directory\u003e": "", + "mount failed": "", + "need to relocate them. For example, to overwrite your own settings:": "", + "opt %s": "", + "stat failed": "", + "unable to bind flags": "", + "unable to set logtostderr": "", + "unset failed": "", + "unsupported driver: %s": "", + "update config": "", + "usage: minikube addons configure ADDON_NAME": "", + "usage: minikube addons disable ADDON_NAME": "", + "usage: minikube addons enable ADDON_NAME": "", + "usage: minikube addons list": "", + "usage: minikube addons open ADDON_NAME": "", + "usage: minikube config set PROPERTY_NAME PROPERTY_VALUE": "", + "usage: minikube config unset PROPERTY_NAME": "", + "usage: minikube delete": "", + "usage: minikube profile [MINIKUBE_PROFILE_NAME]": "", + "using image repository %s": "", + "zsh completion failed": "" +} \ No newline at end of file