github.com/cloudfoundry/cli@v7.1.0+incompatible/util/ui/i18n.go (about) 1 package ui 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "strings" 8 "text/template" 9 10 "code.cloudfoundry.org/cli/i18n/resources" 11 log "github.com/sirupsen/logrus" 12 "golang.org/x/text/language" 13 ) 14 15 const ( 16 // assetPath is the path of the translation file inside the asset loader. 17 assetPath = "resources/%s.all.json" 18 // chineseBase is the language code for Chinese. 19 chineseBase = "zh" 20 // defaultLocale is the default locale used when one is not configured. 21 defaultLocale = "en-us" 22 // unspecifiedScript is what is returned by language#Script objects when the 23 // script cannot be determined. 24 unspecifiedScript = "Zzzz" 25 ) 26 27 type LocaleReader interface { 28 Locale() string 29 } 30 31 // TranslationEntry is the expected format of the translation file. 32 type TranslationEntry struct { 33 // ID is the original English string. 34 ID string `json:"id"` 35 // Translation is the translation of the ID. 36 Translation string `json:"translation"` 37 } 38 39 // TranslateFunc returns the translation of the string identified by 40 // translationID. 41 // 42 // If there is no translation for translationID, then the translationID is used 43 // as the translation. 44 type TranslateFunc func(translationID string, args ...interface{}) string 45 46 // GetTranslationFunc will return back a function that can be used to translate 47 // strings into the currently set locale. 48 func GetTranslationFunc(reader LocaleReader) (TranslateFunc, error) { 49 locale, err := determineLocale(reader) 50 if err != nil { 51 locale = defaultLocale 52 } 53 54 rawTranslation, err := loadAssetFromResources(locale) 55 if err != nil { 56 rawTranslation, err = loadAssetFromResources(defaultLocale) 57 if err != nil { 58 return nil, err 59 } 60 } 61 62 return generateTranslationFunc(rawTranslation) 63 } 64 65 // ParseLocale will return a locale formatted as "<language code>-<region 66 // code>" for all non-Chinese lanagues. For Chinese, it will return 67 // "zh-<script>", defaulting to "hant" if script is unspecified. 68 func ParseLocale(locale string) (string, error) { 69 lang, err := language.Parse(locale) 70 if err != nil { 71 return "", err 72 } 73 74 base, script, region := lang.Raw() 75 switch base.String() { 76 case chineseBase: 77 if script.String() == unspecifiedScript { 78 return "zh-hant", nil 79 } 80 return strings.ToLower(fmt.Sprintf("%s-%s", base, script)), nil 81 default: 82 return strings.ToLower(fmt.Sprintf("%s-%s", base, region)), nil 83 } 84 } 85 86 func determineLocale(reader LocaleReader) (string, error) { 87 locale := reader.Locale() 88 if locale == "" { 89 return defaultLocale, nil 90 } 91 92 return ParseLocale(locale) 93 } 94 95 func generateTranslationFunc(rawTranslation []byte) (TranslateFunc, error) { 96 var entries []TranslationEntry 97 err := json.Unmarshal(rawTranslation, &entries) 98 if err != nil { 99 return nil, err 100 } 101 102 translations := map[string]string{} 103 for _, entry := range entries { 104 translations[entry.ID] = entry.Translation 105 } 106 107 return func(translationID string, args ...interface{}) string { 108 translated := translations[translationID] 109 if translated == "" { 110 translated = translationID 111 } 112 113 var keys interface{} 114 if len(args) > 0 { 115 keys = args[0] 116 } 117 118 var buffer bytes.Buffer 119 formattedTemplate := template.Must(template.New("Display Text").Parse(translated)) 120 err := formattedTemplate.Execute(&buffer, keys) 121 if err != nil { 122 log.WithField("translationID", translationID).Errorln("executing template:", err) 123 } 124 125 return buffer.String() 126 }, nil 127 } 128 129 func loadAssetFromResources(locale string) ([]byte, error) { 130 assetName := fmt.Sprintf(assetPath, locale) 131 assetBytes, err := resources.Asset(assetName) 132 if err != nil { 133 err = fmt.Errorf("could not load asset '%s': %s", assetName, err.Error()) 134 } 135 136 return assetBytes, err 137 }