github.com/erda-project/erda-infra@v1.0.9/providers/component-protocol/protocol/translator/translator.go (about)

     1  // Copyright (c) 2021 Terminus, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // TODO switch to servicehub listener mechanism later, now this mechanism is missing.
    16  
    17  package translator
    18  
    19  import (
    20  	"bytes"
    21  	_ "embed" // embed
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"regexp"
    27  	"strings"
    28  
    29  	"github.com/recallsong/go-utils/reflectx"
    30  	"github.com/sirupsen/logrus"
    31  
    32  	cfg "github.com/erda-project/erda-infra/pkg/config"
    33  	"github.com/erda-project/erda-infra/providers/i18n"
    34  )
    35  
    36  // InternalI18nConfigs contains all protocl internal i18n configs.
    37  //
    38  //go:embed i18n-cp-internal.yaml
    39  var InternalI18nConfigs string
    40  
    41  // Tran is a translator.
    42  type Tran struct {
    43  	dic map[string]map[string]string
    44  }
    45  
    46  // NewInternalTranslator .
    47  func NewInternalTranslator() *Tran {
    48  	// make embed content as a temp file
    49  	f, _ := ioutil.TempFile(os.TempDir(), "*.yaml")
    50  	defer func() {
    51  		if err := f.Close(); err != nil {
    52  			logrus.Errorf("failed to close i18n config file, err: %v", err)
    53  		}
    54  	}()
    55  	if _, err := f.WriteString(InternalI18nConfigs); err != nil {
    56  		panic(fmt.Errorf("failed to write i18n config content to temp file, err: %v", err))
    57  	}
    58  	fPath, err := filepath.Abs(f.Name())
    59  	if err != nil {
    60  		panic(fmt.Errorf("failed to get abs path of temp i18n config file, err: %v", err))
    61  	}
    62  	defer func() {
    63  		if err := os.RemoveAll(fPath); err != nil {
    64  			logrus.Errorf("failed to remove temp i18n config file, err: %v", err)
    65  		}
    66  	}()
    67  
    68  	// load to dic
    69  	dic := make(map[string]map[string]string)
    70  	if err := loadToDic(fPath, dic); err != nil {
    71  		panic(err)
    72  	}
    73  	return &Tran{dic: dic}
    74  }
    75  
    76  // Get .
    77  func (t *Tran) Get(lang i18n.LanguageCodes, key, def string) string {
    78  	text := t.getText(lang, key)
    79  	if len(text) > 0 {
    80  		return text
    81  	}
    82  	return def
    83  }
    84  
    85  // Text .
    86  func (t *Tran) Text(lang i18n.LanguageCodes, key string) string {
    87  	text := t.getText(lang, key)
    88  	if len(text) > 0 {
    89  		return text
    90  	}
    91  	return key
    92  }
    93  
    94  // Sprintf .
    95  func (t *Tran) Sprintf(lang i18n.LanguageCodes, key string, args ...interface{}) string {
    96  	return fmt.Sprintf(t.escape(lang, key), args...)
    97  }
    98  
    99  func (t *Tran) getText(langs i18n.LanguageCodes, key string) string {
   100  	key = strings.ToLower(key)
   101  	for _, lang := range langs {
   102  		if t.dic != nil {
   103  			text := t.dic[lang.Code]
   104  			if text != nil {
   105  				if value, ok := text[key]; ok {
   106  					return value
   107  				}
   108  			}
   109  			text = t.dic[lang.RestrictedCode()]
   110  			if text != nil {
   111  				if value, ok := text[key]; ok {
   112  					return value
   113  				}
   114  			}
   115  		}
   116  	}
   117  	return ""
   118  }
   119  
   120  var regExp = regexp.MustCompile(`\$\{([^:}]*)(:[^}]*)?\}`)
   121  
   122  func (t *Tran) escape(lang i18n.LanguageCodes, text string) string {
   123  	contents := reflectx.StringToBytes(text)
   124  	params := regExp.FindAllSubmatch(contents, -1)
   125  	for _, param := range params {
   126  		if len(param) != 3 {
   127  			continue
   128  		}
   129  		var key, defval []byte = param[1], nil
   130  		if len(param[2]) > 0 {
   131  			defval = param[2][1:]
   132  		}
   133  		k := reflectx.BytesToString(key)
   134  		val := t.getText(lang, k)
   135  		if len(val) <= 0 {
   136  			val = strings.Trim(reflectx.BytesToString(defval), `"`)
   137  		}
   138  		contents = bytes.Replace(contents, param[0], reflectx.StringToBytes(val), 1)
   139  	}
   140  	return reflectx.BytesToString(contents)
   141  }
   142  
   143  func loadToDic(file string, dic map[string]map[string]string) error {
   144  	m := make(map[string]interface{})
   145  	err := cfg.LoadToMap(file, m)
   146  	if err != nil {
   147  		return fmt.Errorf("fail to load i18n file: %s", err)
   148  	}
   149  	for lang, v := range m {
   150  		text := dic[lang]
   151  		if text == nil {
   152  			text = make(map[string]string)
   153  			dic[lang] = text
   154  		}
   155  		switch m := v.(type) {
   156  		case map[string]string:
   157  			for k, v := range m {
   158  				text[strings.ToLower(k)] = fmt.Sprint(v)
   159  			}
   160  		case map[string]interface{}:
   161  			for k, v := range m {
   162  				text[strings.ToLower(k)] = fmt.Sprint(v)
   163  			}
   164  		case map[interface{}]interface{}:
   165  			for k, v := range m {
   166  				text[strings.ToLower(fmt.Sprint(k))] = fmt.Sprint(v)
   167  			}
   168  		default:
   169  			return fmt.Errorf("invalid i18n file format: %s", file)
   170  		}
   171  	}
   172  	return nil
   173  }