1/* 2Copyright 2016 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package i18n 18 19import ( 20 "archive/zip" 21 "bytes" 22 "errors" 23 "fmt" 24 "os" 25 "strings" 26 27 "k8s.io/kubectl/pkg/generated" 28 29 "github.com/chai2010/gettext-go/gettext" 30 "k8s.io/klog/v2" 31) 32 33var knownTranslations = map[string][]string{ 34 "kubectl": { 35 "default", 36 "en_US", 37 "fr_FR", 38 "zh_CN", 39 "ja_JP", 40 "zh_TW", 41 "it_IT", 42 "de_DE", 43 "ko_KR", 44 }, 45 // only used for unit tests. 46 "test": { 47 "default", 48 "en_US", 49 }, 50} 51 52func loadSystemLanguage() string { 53 // Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG 54 // Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html 55 langStr := os.Getenv("LC_ALL") 56 if langStr == "" { 57 langStr = os.Getenv("LC_MESSAGES") 58 } 59 if langStr == "" { 60 langStr = os.Getenv("LANG") 61 } 62 63 if langStr == "" { 64 klog.V(3).Infof("Couldn't find the LC_ALL, LC_MESSAGES or LANG environment variables, defaulting to en_US") 65 return "default" 66 } 67 pieces := strings.Split(langStr, ".") 68 if len(pieces) != 2 { 69 klog.V(3).Infof("Unexpected system language (%s), defaulting to en_US", langStr) 70 return "default" 71 } 72 return pieces[0] 73} 74 75func findLanguage(root string, getLanguageFn func() string) string { 76 langStr := getLanguageFn() 77 78 translations := knownTranslations[root] 79 for ix := range translations { 80 if translations[ix] == langStr { 81 return langStr 82 } 83 } 84 klog.V(3).Infof("Couldn't find translations for %s, using default", langStr) 85 return "default" 86} 87 88// LoadTranslations loads translation files. getLanguageFn should return a language 89// string (e.g. 'en-US'). If getLanguageFn is nil, then the loadSystemLanguage function 90// is used, which uses the 'LANG' environment variable. 91func LoadTranslations(root string, getLanguageFn func() string) error { 92 if getLanguageFn == nil { 93 getLanguageFn = loadSystemLanguage 94 } 95 96 langStr := findLanguage(root, getLanguageFn) 97 translationFiles := []string{ 98 fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.po", root, langStr), 99 fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.mo", root, langStr), 100 } 101 102 klog.V(3).Infof("Setting language to %s", langStr) 103 // TODO: list the directory and load all files. 104 buf := new(bytes.Buffer) 105 w := zip.NewWriter(buf) 106 107 // Make sure to check the error on Close. 108 for _, file := range translationFiles { 109 filename := "translations/" + file 110 f, err := w.Create(file) 111 if err != nil { 112 return err 113 } 114 data, err := generated.Asset(filename) 115 if err != nil { 116 return err 117 } 118 if _, err := f.Write(data); err != nil { 119 return nil 120 } 121 } 122 if err := w.Close(); err != nil { 123 return err 124 } 125 gettext.BindTextdomain("k8s", root+".zip", buf.Bytes()) 126 gettext.Textdomain("k8s") 127 gettext.SetLocale(langStr) 128 return nil 129} 130 131// T translates a string, possibly substituting arguments into it along 132// the way. If len(args) is > 0, args1 is assumed to be the plural value 133// and plural translation is used. 134func T(defaultValue string, args ...int) string { 135 if len(args) == 0 { 136 return gettext.PGettext("", defaultValue) 137 } 138 return fmt.Sprintf(gettext.PNGettext("", defaultValue, defaultValue+".plural", args[0]), 139 args[0]) 140} 141 142// Errorf produces an error with a translated error string. 143// Substitution is performed via the `T` function above, following 144// the same rules. 145func Errorf(defaultValue string, args ...int) error { 146 return errors.New(T(defaultValue, args...)) 147} 148