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