mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #114054 from brianpursley/kubectl-1327
i18n: Fix bug where package-level variables are not translated.
This commit is contained in:
commit
75b67bf0d3
@ -331,13 +331,6 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||||||
|
|
||||||
f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
|
f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
|
||||||
|
|
||||||
// Sending in 'nil' for the getLanguageFn() results in using
|
|
||||||
// the LANG environment variable.
|
|
||||||
//
|
|
||||||
// TODO: Consider adding a flag or file preference for setting
|
|
||||||
// the language, instead of just loading from the LANG env. variable.
|
|
||||||
i18n.LoadTranslations("kubectl", nil)
|
|
||||||
|
|
||||||
// Proxy command is incompatible with CommandHeaderRoundTripper, so
|
// Proxy command is incompatible with CommandHeaderRoundTripper, so
|
||||||
// clear the WrapConfigFn before running proxy command.
|
// clear the WrapConfigFn before running proxy command.
|
||||||
proxyCmd := proxy.NewCmdProxy(f, o.IOStreams)
|
proxyCmd := proxy.NewCmdProxy(f, o.IOStreams)
|
||||||
|
@ -24,8 +24,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/chai2010/gettext-go"
|
||||||
|
|
||||||
gettext "github.com/chai2010/gettext-go"
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,6 +54,56 @@ var knownTranslations = map[string][]string{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lazyLoadTranslationsOnce sync.Once
|
||||||
|
LoadTranslationsFunc = func() error {
|
||||||
|
return LoadTranslations("kubectl", nil)
|
||||||
|
}
|
||||||
|
translationsLoaded bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetLoadTranslationsFunc sets the function called to lazy load translations.
|
||||||
|
// It must be called in an init() func that occurs BEFORE any i18n.T() calls are made by any package. You can
|
||||||
|
// accomplish this by creating a separate package containing your init() func, and then importing that package BEFORE
|
||||||
|
// any other packages that call i18n.T().
|
||||||
|
//
|
||||||
|
// Example Usage:
|
||||||
|
//
|
||||||
|
// package myi18n
|
||||||
|
//
|
||||||
|
// import "k8s.io/kubectl/pkg/util/i18n"
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// if err := i18n.SetLoadTranslationsFunc(loadCustomTranslations); err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func loadCustomTranslations() error {
|
||||||
|
// // Load your custom translations here...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// And then in your main or root command package, import your custom package like this:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// // Other imports that don't need i18n...
|
||||||
|
// _ "example.com/myapp/myi18n"
|
||||||
|
// // Other imports that do need i18n...
|
||||||
|
// )
|
||||||
|
func SetLoadTranslationsFunc(f func() error) error {
|
||||||
|
if translationsLoaded {
|
||||||
|
return errors.New("translations have already been loaded")
|
||||||
|
}
|
||||||
|
LoadTranslationsFunc = func() error {
|
||||||
|
if err := f(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
translationsLoaded = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadSystemLanguage() string {
|
func loadSystemLanguage() string {
|
||||||
// Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG
|
// Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG
|
||||||
// Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
// Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
||||||
@ -128,13 +180,26 @@ func LoadTranslations(root string, getLanguageFn func() string) error {
|
|||||||
gettext.BindLocale(gettext.New("k8s", root+".zip", buf.Bytes()))
|
gettext.BindLocale(gettext.New("k8s", root+".zip", buf.Bytes()))
|
||||||
gettext.SetDomain("k8s")
|
gettext.SetDomain("k8s")
|
||||||
gettext.SetLanguage(langStr)
|
gettext.SetLanguage(langStr)
|
||||||
|
translationsLoaded = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lazyLoadTranslations() {
|
||||||
|
lazyLoadTranslationsOnce.Do(func() {
|
||||||
|
if translationsLoaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := LoadTranslationsFunc(); err != nil {
|
||||||
|
klog.Warning("Failed to load translations")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// T translates a string, possibly substituting arguments into it along
|
// T translates a string, possibly substituting arguments into it along
|
||||||
// the way. If len(args) is > 0, args1 is assumed to be the plural value
|
// the way. If len(args) is > 0, args1 is assumed to be the plural value
|
||||||
// and plural translation is used.
|
// and plural translation is used.
|
||||||
func T(defaultValue string, args ...int) string {
|
func T(defaultValue string, args ...int) string {
|
||||||
|
lazyLoadTranslations()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return gettext.PGettext("", defaultValue)
|
return gettext.PGettext("", defaultValue)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,10 @@ package i18n
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/chai2010/gettext-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var knownTestLocale = "en_US.UTF-8"
|
var knownTestLocale = "en_US.UTF-8"
|
||||||
@ -155,3 +158,131 @@ func TestTranslationUsingEnvVar(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetLazyLoading allows multiple tests to test translation lazy loading by resetting the state
|
||||||
|
func resetLazyLoading() {
|
||||||
|
translationsLoaded = false
|
||||||
|
lazyLoadTranslationsOnce = sync.Once{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLazyLoadTranslationFuncIsCalled(t *testing.T) {
|
||||||
|
resetLazyLoading()
|
||||||
|
|
||||||
|
timesCalled := 0
|
||||||
|
err := SetLoadTranslationsFunc(func() error {
|
||||||
|
timesCalled++
|
||||||
|
return LoadTranslations("test", func() string { return "en_US" })
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be false, but it was true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translation should succeed and use the lazy loaded translations
|
||||||
|
result := T("test_string")
|
||||||
|
if result != "baz" {
|
||||||
|
t.Errorf("expected: %s, saw: %s", "baz", result)
|
||||||
|
}
|
||||||
|
if timesCalled != 1 {
|
||||||
|
t.Errorf("expected LoadTranslationsFunc to have been called 1 time, but it was called %d times", timesCalled)
|
||||||
|
}
|
||||||
|
if !translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be true, but it was false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call T() again, and timesCalled should remain 1
|
||||||
|
T("test_string")
|
||||||
|
if timesCalled != 1 {
|
||||||
|
t.Errorf("expected LoadTranslationsFunc to have been called 1 time, but it was called %d times", timesCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLazyLoadTranslationFuncOnlyCalledIfTranslationsNotLoaded(t *testing.T) {
|
||||||
|
resetLazyLoading()
|
||||||
|
|
||||||
|
// Set a custom translations func
|
||||||
|
timesCalled := 0
|
||||||
|
err := SetLoadTranslationsFunc(func() error {
|
||||||
|
timesCalled++
|
||||||
|
return LoadTranslations("test", func() string { return "en_US" })
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be false, but it was true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly load translations before lazy loading can occur
|
||||||
|
err = LoadTranslations("test", func() string { return "default" })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be true, but it was false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translation should succeed, and use the explicitly loaded translations, not the lazy loaded ones
|
||||||
|
result := T("test_string")
|
||||||
|
if result != "foo" {
|
||||||
|
t.Errorf("expected: %s, saw: %s", "foo", result)
|
||||||
|
}
|
||||||
|
if timesCalled != 0 {
|
||||||
|
t.Errorf("expected LoadTranslationsFunc to have not been called, but it was called %d times", timesCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCustomLoadTranslationsFunc(t *testing.T) {
|
||||||
|
resetLazyLoading()
|
||||||
|
|
||||||
|
// Set a custom translations func that loads translations from a directory
|
||||||
|
err := SetLoadTranslationsFunc(func() error {
|
||||||
|
gettext.BindLocale(gettext.New("k8s", "./translations/test"))
|
||||||
|
gettext.SetDomain("k8s")
|
||||||
|
gettext.SetLanguage("en_US")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be false, but it was true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translation should succeed
|
||||||
|
result := T("test_string")
|
||||||
|
if result != "baz" {
|
||||||
|
t.Errorf("expected: %s, saw: %s", "baz", result)
|
||||||
|
}
|
||||||
|
if !translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be true, but it was false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCustomLoadTranslationsFuncAfterTranslationsLoadedShouldFail(t *testing.T) {
|
||||||
|
resetLazyLoading()
|
||||||
|
|
||||||
|
// Explicitly load translations
|
||||||
|
err := LoadTranslations("test", func() string { return "en_US" })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !translationsLoaded {
|
||||||
|
t.Errorf("expected translationsLoaded to be true, but it was false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should fail because translations have already been loaded, and the custom function should not be called.
|
||||||
|
timesCalled := 0
|
||||||
|
err = SetLoadTranslationsFunc(func() error {
|
||||||
|
timesCalled++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error, but it did not occur")
|
||||||
|
}
|
||||||
|
if timesCalled != 0 {
|
||||||
|
t.Errorf("expected LoadTranslationsFunc to have not been called, but it was called %d times", timesCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
52
test/cmd/help.sh
Normal file
52
test/cmd/help.sh
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright 2022 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
run_kubectl_help_tests() {
|
||||||
|
set -o nounset
|
||||||
|
set -o errexit
|
||||||
|
|
||||||
|
# The purpose of this test is to exercise the translation functionality in a simple way.
|
||||||
|
# If the strings used by this test are changed, they will need to be updated here so that this test will pass.
|
||||||
|
|
||||||
|
kube::test::if_has_string "$(kubectl help)" "Modify kubeconfig files"
|
||||||
|
kube::test::if_has_string "$(LANG=de_DE.UTF8 kubectl help)" "Verändere kubeconfig Dateien"
|
||||||
|
kube::test::if_has_string "$(LANG=en_US.UTF8 kubectl help)" "Modify kubeconfig files"
|
||||||
|
kube::test::if_has_string "$(LANG=fr_FR.UTF8 kubectl help)" "Modifier des fichiers kubeconfig"
|
||||||
|
kube::test::if_has_string "$(LANG=it_IT.UTF8 kubectl help)" "Modifica i file kubeconfig"
|
||||||
|
kube::test::if_has_string "$(LANG=ja_JP.UTF8 kubectl help)" "kubeconfigを変更する"
|
||||||
|
kube::test::if_has_string "$(LANG=ko_KR.UTF8 kubectl help)" "kubeconfig 파일을 수정합니다"
|
||||||
|
kube::test::if_has_string "$(LANG=pt_BR.UTF8 kubectl help)" "Edita o arquivo kubeconfig"
|
||||||
|
kube::test::if_has_string "$(LANG=zh_CN.UTF8 kubectl help)" "修改 kubeconfig 文件"
|
||||||
|
kube::test::if_has_string "$(LANG=zh_TW.UTF8 kubectl help)" "修改 kubeconfig 檔案"
|
||||||
|
|
||||||
|
kube::test::if_has_string "$(kubectl uncordon --help)" "Mark node as schedulable."
|
||||||
|
kube::test::if_has_string "$(LANG=de_DE.UTF-8 kubectl uncordon --help)" "Markiere Knoten als schedulable."
|
||||||
|
kube::test::if_has_string "$(LANG=en_US.UTF-8 kubectl uncordon --help)" "Mark node as schedulable."
|
||||||
|
kube::test::if_has_string "$(LANG=fr_FR.UTF-8 kubectl uncordon --help)" "Mark node as schedulable."
|
||||||
|
kube::test::if_has_string "$(LANG=it_IT.UTF-8 kubectl uncordon --help)" "Contrassegna il nodo come programmabile."
|
||||||
|
kube::test::if_has_string "$(LANG=ja_JP.UTF-8 kubectl uncordon --help)" "Mark node as schedulable."
|
||||||
|
kube::test::if_has_string "$(LANG=ko_KR.UTF-8 kubectl uncordon --help)" "Mark node as schedulable."
|
||||||
|
kube::test::if_has_string "$(LANG=pt_BR.UTF-8 kubectl uncordon --help)" "Remove a restrição de execução de workloads no node."
|
||||||
|
kube::test::if_has_string "$(LANG=zh_CN.UTF-8 kubectl uncordon --help)" "标记节点为可调度。"
|
||||||
|
kube::test::if_has_string "$(LANG=zh_TW.UTF-8 kubectl uncordon --help)" "Mark node as schedulable."
|
||||||
|
|
||||||
|
set +o nounset
|
||||||
|
set +o errexit
|
||||||
|
}
|
@ -45,6 +45,7 @@ source "${KUBE_ROOT}/test/cmd/events.sh"
|
|||||||
source "${KUBE_ROOT}/test/cmd/exec.sh"
|
source "${KUBE_ROOT}/test/cmd/exec.sh"
|
||||||
source "${KUBE_ROOT}/test/cmd/generic-resources.sh"
|
source "${KUBE_ROOT}/test/cmd/generic-resources.sh"
|
||||||
source "${KUBE_ROOT}/test/cmd/get.sh"
|
source "${KUBE_ROOT}/test/cmd/get.sh"
|
||||||
|
source "${KUBE_ROOT}/test/cmd/help.sh"
|
||||||
source "${KUBE_ROOT}/test/cmd/kubeconfig.sh"
|
source "${KUBE_ROOT}/test/cmd/kubeconfig.sh"
|
||||||
source "${KUBE_ROOT}/test/cmd/node-management.sh"
|
source "${KUBE_ROOT}/test/cmd/node-management.sh"
|
||||||
source "${KUBE_ROOT}/test/cmd/plugins.sh"
|
source "${KUBE_ROOT}/test/cmd/plugins.sh"
|
||||||
@ -555,6 +556,12 @@ runTests() {
|
|||||||
record_command run_kubectl_get_tests
|
record_command run_kubectl_get_tests
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
################
|
||||||
|
# Kubectl help #
|
||||||
|
################
|
||||||
|
|
||||||
|
record_command run_kubectl_help_tests
|
||||||
|
|
||||||
##################
|
##################
|
||||||
# Kubectl events #
|
# Kubectl events #
|
||||||
##################
|
##################
|
||||||
|
Loading…
Reference in New Issue
Block a user