mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 09:52:49 +00:00
i18n: Fix bug where package-level variables are not translated.
Change i18n.T() to load translations if they have not yet been loaded. Added new integration tests to test help output translation.
This commit is contained in:
parent
3f823c0daa
commit
c0dea5e31a
@ -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