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  }