github.com/wfusion/gofusion@v1.1.14/i18n/i18n.go (about)

     1  package i18n
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/BurntSushi/toml"
     7  	"github.com/nicksnyder/go-i18n/v2/i18n"
     8  	"github.com/pkg/errors"
     9  	"github.com/spf13/cast"
    10  	"golang.org/x/text/language"
    11  	"gopkg.in/yaml.v3"
    12  
    13  	"github.com/wfusion/gofusion/common/utils"
    14  	"github.com/wfusion/gofusion/common/utils/clone"
    15  	"github.com/wfusion/gofusion/common/utils/serialize/json"
    16  )
    17  
    18  var (
    19  	Bundle *bundle[int]
    20  
    21  	locker               sync.RWMutex
    22  	defaultLang          = language.Chinese
    23  	defaultErrorMessages = map[language.Tag]string{
    24  		language.Chinese: "系统错误,请稍后重试",
    25  		language.English: "System error! Please try again later",
    26  	}
    27  )
    28  
    29  type bundle[T comparable] struct {
    30  	dup    *utils.Set[T]
    31  	bundle *i18n.Bundle
    32  
    33  	vars  map[T][]string
    34  	mutex sync.RWMutex
    35  }
    36  
    37  func NewBundle[T comparable](lang language.Tag) Localizable[T] {
    38  	b := &bundle[T]{
    39  		dup:    utils.NewSet[T](),
    40  		bundle: i18n.NewBundle(lang),
    41  		vars:   make(map[T][]string),
    42  	}
    43  
    44  	b.bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
    45  	b.bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
    46  	b.bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
    47  	b.bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
    48  
    49  	return b
    50  }
    51  
    52  type addMessagesOption struct {
    53  	vars []string
    54  }
    55  
    56  func Var(vars ...string) utils.OptionFunc[addMessagesOption] {
    57  	return func(o *addMessagesOption) {
    58  		o.vars = vars
    59  	}
    60  }
    61  
    62  func (i *bundle[T]) AddMessages(code T, trans map[language.Tag]*Message, opts ...utils.OptionExtender) Localizable[T] {
    63  	i.checkDuplicated(code, trans)
    64  
    65  	i.mutex.Lock()
    66  	defer i.mutex.Unlock()
    67  
    68  	o := utils.ApplyOptions[addMessagesOption](opts...)
    69  	id := cast.ToString(code)
    70  	i.vars[code] = o.vars
    71  	for lang, msg := range trans {
    72  		i.bundle.MustAddMessages(lang, &i18n.Message{
    73  			ID:          id,
    74  			Hash:        msg.Hash,
    75  			Description: msg.Description,
    76  			LeftDelim:   msg.LeftDelim,
    77  			RightDelim:  msg.RightDelim,
    78  			Zero:        msg.Zero,
    79  			One:         msg.One,
    80  			Two:         msg.Two,
    81  			Few:         msg.Few,
    82  			Many:        msg.Many,
    83  			Other:       msg.Other,
    84  		})
    85  	}
    86  	return i
    87  }
    88  
    89  func (i *bundle[T]) checkDuplicated(code T, trans map[language.Tag]*Message) {
    90  	if !i.dup.Contains(code) {
    91  		i.dup.Insert(code)
    92  		return
    93  	}
    94  	if trans == nil {
    95  		panic(errors.Errorf("%+v %s code translation is empty", code, cast.ToString(code)))
    96  	}
    97  
    98  	// panic if duplicated
    99  	var (
   100  		cfg                = &i18n.LocalizeConfig{MessageID: cast.ToString(code)}
   101  		existMsgEn         = i18n.NewLocalizer(i.bundle, language.English.String()).MustLocalize(cfg)
   102  		existMsgCn         = i18n.NewLocalizer(i.bundle, language.Chinese.String()).MustLocalize(cfg)
   103  		dupMsgCn, dupMsgEn string
   104  	)
   105  	if dupMsg, ok := trans[language.Chinese]; ok {
   106  		dupMsgCn = dupMsg.Other
   107  	}
   108  	if dupMsg, ok := trans[language.English]; ok {
   109  		dupMsgEn = dupMsg.Other
   110  	}
   111  
   112  	panic(errors.Errorf("%s(%s)(%+v)(%v) is duplicated with %s(%s)",
   113  		dupMsgCn, dupMsgEn, code, cast.ToString(code), existMsgCn, existMsgEn))
   114  }
   115  
   116  type localizeOption struct {
   117  	lang         language.Tag
   118  	langs        []string
   119  	pluralCount  any
   120  	templateData map[string]any
   121  }
   122  
   123  func Param(data map[string]any) utils.OptionFunc[localizeOption] {
   124  	return func(o *localizeOption) {
   125  		o.templateData = data
   126  	}
   127  }
   128  
   129  func Plural(pluralCount any) utils.OptionFunc[localizeOption] {
   130  	return func(o *localizeOption) {
   131  		o.pluralCount = pluralCount
   132  	}
   133  }
   134  
   135  func Lang(lang language.Tag) utils.OptionFunc[localizeOption] {
   136  	return func(o *localizeOption) {
   137  		o.lang = lang
   138  	}
   139  }
   140  
   141  func Langs(langs []string) utils.OptionFunc[localizeOption] {
   142  	return func(o *localizeOption) {
   143  		if len(langs) > 0 {
   144  			o.lang, _ = language.Parse(langs[0])
   145  		}
   146  		o.langs = clone.SliceComparable(langs)
   147  	}
   148  }
   149  
   150  func (i *bundle[T]) Localize(code T, opts ...utils.OptionExtender) (message string) {
   151  	option := utils.ApplyOptions[localizeOption](opts...)
   152  	if option.templateData == nil && len(i.vars) > 0 {
   153  		option.templateData = make(map[string]any, len(i.vars))
   154  	}
   155  
   156  	// TODO: Access the third-party internationalization platform to obtain text
   157  	cfg := &i18n.LocalizeConfig{
   158  		MessageID:    cast.ToString(code),
   159  		TemplateData: option.templateData,
   160  		PluralCount:  option.pluralCount,
   161  	}
   162  
   163  	i.mutex.RLock()
   164  	defer i.mutex.RUnlock()
   165  	// Assign an empty string to a variable to avoid rendering < no value > data
   166  	for _, v := range i.vars[code] {
   167  		if _, ok := option.templateData[v]; !ok {
   168  			option.templateData[v] = ""
   169  		}
   170  	}
   171  	message, err := i18n.NewLocalizer(i.bundle, option.langs...).Localize(cfg)
   172  	if err == nil {
   173  		return
   174  	}
   175  	message, ok := defaultErrorMessages[option.lang]
   176  	if ok {
   177  		return
   178  	}
   179  
   180  	return defaultErrorMessages[defaultLang]
   181  }