...

Source file src/k8s.io/kubectl/pkg/util/i18n/i18n.go

Documentation: k8s.io/kubectl/pkg/util/i18n

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package i18n
    18  
    19  import (
    20  	"archive/zip"
    21  	"bytes"
    22  	"embed"
    23  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"strings"
    27  	"sync"
    28  
    29  	"github.com/chai2010/gettext-go"
    30  
    31  	"k8s.io/klog/v2"
    32  )
    33  
    34  //go:embed translations
    35  var translations embed.FS
    36  
    37  var knownTranslations = map[string][]string{
    38  	"kubectl": {
    39  		"default",
    40  		"en_US",
    41  		"fr_FR",
    42  		"zh_CN",
    43  		"ja_JP",
    44  		"zh_TW",
    45  		"it_IT",
    46  		"de_DE",
    47  		"ko_KR",
    48  		"pt_BR",
    49  	},
    50  	// only used for unit tests.
    51  	"test": {
    52  		"default",
    53  		"en_US",
    54  	},
    55  }
    56  
    57  var (
    58  	lazyLoadTranslationsOnce sync.Once
    59  	LoadTranslationsFunc     = func() error {
    60  		return LoadTranslations("kubectl", nil)
    61  	}
    62  	translationsLoaded bool
    63  )
    64  
    65  // SetLoadTranslationsFunc sets the function called to lazy load translations.
    66  // It must be called in an init() func that occurs BEFORE any i18n.T() calls are made by any package. You can
    67  // accomplish this by creating a separate package containing your init() func, and then importing that package BEFORE
    68  // any other packages that call i18n.T().
    69  //
    70  // Example Usage:
    71  //
    72  //	package myi18n
    73  //
    74  //	import "k8s.io/kubectl/pkg/util/i18n"
    75  //
    76  //	func init() {
    77  //		if err := i18n.SetLoadTranslationsFunc(loadCustomTranslations); err != nil {
    78  //			panic(err)
    79  //		}
    80  //	}
    81  //
    82  //	func loadCustomTranslations() error {
    83  //		// Load your custom translations here...
    84  //	}
    85  //
    86  // And then in your main or root command package, import your custom package like this:
    87  //
    88  //	import (
    89  //		// Other imports that don't need i18n...
    90  //		_ "example.com/myapp/myi18n"
    91  //		// Other imports that do need i18n...
    92  //	)
    93  func SetLoadTranslationsFunc(f func() error) error {
    94  	if translationsLoaded {
    95  		return errors.New("translations have already been loaded")
    96  	}
    97  	LoadTranslationsFunc = func() error {
    98  		if err := f(); err != nil {
    99  			return err
   100  		}
   101  		translationsLoaded = true
   102  		return nil
   103  	}
   104  	return nil
   105  }
   106  
   107  func loadSystemLanguage() string {
   108  	// Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG
   109  	// Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
   110  	langStr := os.Getenv("LC_ALL")
   111  	if langStr == "" {
   112  		langStr = os.Getenv("LC_MESSAGES")
   113  	}
   114  	if langStr == "" {
   115  		langStr = os.Getenv("LANG")
   116  	}
   117  
   118  	if langStr == "" {
   119  		klog.V(3).Infof("Couldn't find the LC_ALL, LC_MESSAGES or LANG environment variables, defaulting to en_US")
   120  		return "default"
   121  	}
   122  	pieces := strings.Split(langStr, ".")
   123  	if len(pieces) != 2 {
   124  		klog.V(3).Infof("Unexpected system language (%s), defaulting to en_US", langStr)
   125  		return "default"
   126  	}
   127  	return pieces[0]
   128  }
   129  
   130  func findLanguage(root string, getLanguageFn func() string) string {
   131  	langStr := getLanguageFn()
   132  
   133  	translations := knownTranslations[root]
   134  	for ix := range translations {
   135  		if translations[ix] == langStr {
   136  			return langStr
   137  		}
   138  	}
   139  	klog.V(3).Infof("Couldn't find translations for %s, using default", langStr)
   140  	return "default"
   141  }
   142  
   143  // LoadTranslations loads translation files. getLanguageFn should return a language
   144  // string (e.g. 'en-US'). If getLanguageFn is nil, then the loadSystemLanguage function
   145  // is used, which uses the 'LANG' environment variable.
   146  func LoadTranslations(root string, getLanguageFn func() string) error {
   147  	if getLanguageFn == nil {
   148  		getLanguageFn = loadSystemLanguage
   149  	}
   150  
   151  	langStr := findLanguage(root, getLanguageFn)
   152  	translationFiles := []string{
   153  		fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.po", root, langStr),
   154  		fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.mo", root, langStr),
   155  	}
   156  
   157  	klog.V(3).Infof("Setting language to %s", langStr)
   158  	// TODO: list the directory and load all files.
   159  	buf := new(bytes.Buffer)
   160  	w := zip.NewWriter(buf)
   161  
   162  	// Make sure to check the error on Close.
   163  	for _, file := range translationFiles {
   164  		filename := "translations/" + file
   165  		f, err := w.Create(file)
   166  		if err != nil {
   167  			return err
   168  		}
   169  		data, err := translations.ReadFile(filename)
   170  		if err != nil {
   171  			return err
   172  		}
   173  		if _, err := f.Write(data); err != nil {
   174  			return nil
   175  		}
   176  	}
   177  	if err := w.Close(); err != nil {
   178  		return err
   179  	}
   180  	gettext.BindLocale(gettext.New("k8s", root+".zip", buf.Bytes()))
   181  	gettext.SetDomain("k8s")
   182  	gettext.SetLanguage(langStr)
   183  	translationsLoaded = true
   184  	return nil
   185  }
   186  
   187  func lazyLoadTranslations() {
   188  	lazyLoadTranslationsOnce.Do(func() {
   189  		if translationsLoaded {
   190  			return
   191  		}
   192  		if err := LoadTranslationsFunc(); err != nil {
   193  			klog.Warning("Failed to load translations")
   194  		}
   195  	})
   196  }
   197  
   198  // T translates a string, possibly substituting arguments into it along
   199  // the way. If len(args) is > 0, args1 is assumed to be the plural value
   200  // and plural translation is used.
   201  func T(defaultValue string, args ...int) string {
   202  	lazyLoadTranslations()
   203  	if len(args) == 0 {
   204  		return gettext.PGettext("", defaultValue)
   205  	}
   206  	return fmt.Sprintf(gettext.PNGettext("", defaultValue, defaultValue+".plural", args[0]),
   207  		args[0])
   208  }
   209  
   210  // Errorf produces an error with a translated error string.
   211  // Substitution is performed via the `T` function above, following
   212  // the same rules.
   213  func Errorf(defaultValue string, args ...int) error {
   214  	return errors.New(T(defaultValue, args...))
   215  }
   216  

View as plain text