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 "embed" 23 "errors" 24 "fmt" 25 "os" 26 "strings" 27 28 "github.com/chai2010/gettext-go/gettext" 29 "k8s.io/klog/v2" 30) 31 32//go:embed translations 33var translations embed.FS 34 35var knownTranslations = map[string][]string{ 36 "kubectl": { 37 "default", 38 "en_US", 39 "fr_FR", 40 "zh_CN", 41 "ja_JP", 42 "zh_TW", 43 "it_IT", 44 "de_DE", 45 "ko_KR", 46 "pt_BR", 47 }, 48 // only used for unit tests. 49 "test": { 50 "default", 51 "en_US", 52 }, 53} 54 55func loadSystemLanguage() string { 56 // Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG 57 // Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html 58 langStr := os.Getenv("LC_ALL") 59 if langStr == "" { 60 langStr = os.Getenv("LC_MESSAGES") 61 } 62 if langStr == "" { 63 langStr = os.Getenv("LANG") 64 } 65 66 if langStr == "" { 67 klog.V(3).Infof("Couldn't find the LC_ALL, LC_MESSAGES or LANG environment variables, defaulting to en_US") 68 return "default" 69 } 70 pieces := strings.Split(langStr, ".") 71 if len(pieces) != 2 { 72 klog.V(3).Infof("Unexpected system language (%s), defaulting to en_US", langStr) 73 return "default" 74 } 75 return pieces[0] 76} 77 78func findLanguage(root string, getLanguageFn func() string) string { 79 langStr := getLanguageFn() 80 81 translations := knownTranslations[root] 82 for ix := range translations { 83 if translations[ix] == langStr { 84 return langStr 85 } 86 } 87 klog.V(3).Infof("Couldn't find translations for %s, using default", langStr) 88 return "default" 89} 90 91// LoadTranslations loads translation files. getLanguageFn should return a language 92// string (e.g. 'en-US'). If getLanguageFn is nil, then the loadSystemLanguage function 93// is used, which uses the 'LANG' environment variable. 94func LoadTranslations(root string, getLanguageFn func() string) error { 95 if getLanguageFn == nil { 96 getLanguageFn = loadSystemLanguage 97 } 98 99 langStr := findLanguage(root, getLanguageFn) 100 translationFiles := []string{ 101 fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.po", root, langStr), 102 fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.mo", root, langStr), 103 } 104 105 klog.V(3).Infof("Setting language to %s", langStr) 106 // TODO: list the directory and load all files. 107 buf := new(bytes.Buffer) 108 w := zip.NewWriter(buf) 109 110 // Make sure to check the error on Close. 111 for _, file := range translationFiles { 112 filename := "translations/" + file 113 f, err := w.Create(file) 114 if err != nil { 115 return err 116 } 117 data, err := translations.ReadFile(filename) 118 if err != nil { 119 return err 120 } 121 if _, err := f.Write(data); err != nil { 122 return nil 123 } 124 } 125 if err := w.Close(); err != nil { 126 return err 127 } 128 gettext.BindTextdomain("k8s", root+".zip", buf.Bytes()) 129 gettext.Textdomain("k8s") 130 gettext.SetLocale(langStr) 131 return nil 132} 133 134// T translates a string, possibly substituting arguments into it along 135// the way. If len(args) is > 0, args1 is assumed to be the plural value 136// and plural translation is used. 137func T(defaultValue string, args ...int) string { 138 if len(args) == 0 { 139 return gettext.PGettext("", defaultValue) 140 } 141 return fmt.Sprintf(gettext.PNGettext("", defaultValue, defaultValue+".plural", args[0]), 142 args[0]) 143} 144 145// Errorf produces an error with a translated error string. 146// Substitution is performed via the `T` function above, following 147// the same rules. 148func Errorf(defaultValue string, args ...int) error { 149 return errors.New(T(defaultValue, args...)) 150} 151