package lclrimp import ( "encoding/json" "path" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mutil/fr" "github.com/nicksnyder/go-i18n/v2/i18n" "go.uber.org/zap" "golang.org/x/text/language" ) type Lang struct { bundle *i18n.Bundle localizer *i18n.Localizer } type Localizers = map[string]Lang type Localizer struct { logger mlogger.Logger l9rs Localizers support string serviceName string } type Config struct { Path string `yaml:"path"` Langs []string `yaml:"languages"` Support string `yaml:"support"` ServiceName string `yaml:"service_name"` } func loadBundleLocalization(logger mlogger.Logger, bundle *i18n.Bundle, localizationPath string) error { b, err := fr.ReadFile(logger, localizationPath) if err != nil { logger.Error("Failed to read localization", zap.Error(err), zap.String("localization_path", localizationPath)) return err } _, err = bundle.ParseMessageFileBytes(b, localizationPath) if err != nil { logger.Error("Failed to parse localization", zap.Error(err), zap.String("localization_path", localizationPath)) return err } return err } func loadLocalizations(logger mlogger.Logger, source string) (*i18n.Bundle, error) { bundle := i18n.NewBundle(language.English) // Register a json unmarshal function for i18n bundle. bundle.RegisterUnmarshalFunc("json", json.Unmarshal) // Load translations from json files for non-default languages. err := loadBundleLocalization(logger, bundle, source) if err != nil { // will not log error once again, just return nil return nil, err } return bundle, nil } func newLang(logger mlogger.Logger, language string, source string) (*Lang, error) { var lang Lang var err error lang.bundle, err = loadLocalizations(logger, source) if err != nil { logger.Error("Failed to install language bundle", zap.Error(err), zap.String("language", language), zap.String("source", source)) return nil, err } lang.localizer = i18n.NewLocalizer(lang.bundle, language) if lang.localizer != nil { logger.Info("Installed language bundle", zap.String("language", language), zap.String("source", source)) } else { logger.Error("Failed to install language bundle", zap.String("language", language), zap.String("source", source)) return nil, merrors.Internal("failed_to_load_localization") } return &lang, nil } func prepareLocalizers(logger mlogger.Logger, conf *Config) (Localizers, error) { localizers := make(Localizers) for _, lang := range conf.Langs { path := path.Join(conf.Path, lang+".json") l, err := newLang(logger, lang, path) if err != nil { logger.Error("Failed to load localization", zap.Error(err), zap.String("language", lang), zap.String("source", path)) return localizers, err } localizers[lang] = *l } return localizers, nil } func (loc *Localizer) LocalizeTemplate(id string, templateData, ctr any, lang string) (string, error) { lclzr, found := loc.l9rs[lang] if !found { loc.logger.Info("Language not found, falling back to en", zap.String("message_id", id), zap.String("language", lang)) lclzr = loc.l9rs["en"] } config := i18n.LocalizeConfig{ MessageID: id, TemplateData: templateData, PluralCount: ctr, } localized, err := lclzr.localizer.Localize(&config) if err != nil { loc.logger.Warn("Failed to localize string", zap.Error(err), zap.String("message_id", id), zap.String("language", lang)) } return localized, err } func (loc *Localizer) LocalizeString(id string, lang string) (string, error) { return loc.LocalizeTemplate(id, nil, nil, lang) } func (loc *Localizer) ServiceName() string { return loc.serviceName } func (loc *Localizer) SupportMail() string { return loc.support } // NewConnection creates a new database connection func CreateLocalizer(logger mlogger.Logger, config *Config) (*Localizer, error) { p := new(Localizer) p.logger = logger.Named("localizer") var err error p.l9rs, err = prepareLocalizers(p.logger, config) if err != nil { logger.Warn("Failed to create localizer", zap.Error(err)) return nil, err } p.serviceName = config.ServiceName p.support = config.Support logger.Info("Localizer is up", zap.String("service_name", p.serviceName), zap.String("support", p.support)) return p, nil }