Merge pull request #4464 from sharifelgamal/localization-poc

Add ability to localize all strings output to console
pull/4548/head
Sharif Elgamal 2019-06-20 17:08:28 -07:00 committed by GitHub
commit 37f09da483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1239 additions and 99 deletions

39
cmd/extract/extract.go Normal file
View File

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

View File

@ -92,7 +92,7 @@ var RootCmd = &cobra.Command{
}
if enableUpdateNotification {
notify.MaybePrintUpdateTextFromGithub(os.Stderr)
notify.MaybePrintUpdateTextFromGithub()
}
},
}

View File

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

View File

@ -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()
}

7
go.mod
View File

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

20
go.sum
View File

@ -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=

View File

@ -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.

View File

@ -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=<override>
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)
}

View File

@ -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]

View File

@ -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")

View File

@ -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, &currentTranslations)
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)
}
}

View File

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

View File

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

View File

@ -0,0 +1,4 @@
{
"Holy cow I'm in a loop!": "Something else",
"This was a choice: %s": "Something"
}

View File

@ -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:

View File

@ -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())

View File

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

View File

@ -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()
}

View File

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

View File

@ -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": ""
}

View File

@ -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": ""
}