github.com/ablease/cli@v6.37.1-0.20180613014814-3adbb7d7fb19+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  	"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  }