- Webhook event logging repo and subscription webhook idempotency - Pagination helper (echohelpers) with cursor/offset support - Request ID and structured logging middleware - Push client improvements (FCM HTTP v1, better error handling) - Task model version column, business constraint migrations, targeted indexes - Expanded categorization chain tests - Email service and config hardening - CI workflow updates, .gitignore additions, .env.example updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
87 lines
2.3 KiB
Go
87 lines
2.3 KiB
Go
package i18n
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
|
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
//go:embed translations/*.json
|
|
var translationFS embed.FS
|
|
|
|
// Bundle is the global i18n bundle
|
|
var Bundle *i18n.Bundle
|
|
|
|
// SupportedLanguages lists all supported language codes
|
|
var SupportedLanguages = []string{"en", "es", "fr", "de", "pt", "it", "ja", "ko", "nl", "zh"}
|
|
|
|
// DefaultLanguage is the fallback language
|
|
const DefaultLanguage = "en"
|
|
|
|
// Init initializes the i18n bundle with embedded translation files
|
|
func Init() error {
|
|
Bundle = i18n.NewBundle(language.English)
|
|
Bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
|
|
|
|
// Load all translation files
|
|
for _, lang := range SupportedLanguages {
|
|
path := "translations/" + lang + ".json"
|
|
data, err := translationFS.ReadFile(path)
|
|
if err != nil {
|
|
log.Warn().Str("language", lang).Err(err).Msg("Failed to load translation file")
|
|
continue
|
|
}
|
|
Bundle.MustParseMessageFileBytes(data, path)
|
|
log.Info().Str("language", lang).Msg("Loaded translation file")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewLocalizer creates a new localizer for the given language tags
|
|
func NewLocalizer(langs ...string) *i18n.Localizer {
|
|
return i18n.NewLocalizer(Bundle, langs...)
|
|
}
|
|
|
|
// T translates a message ID with optional template data
|
|
func T(localizer *i18n.Localizer, messageID string, templateData map[string]interface{}) string {
|
|
if localizer == nil {
|
|
localizer = NewLocalizer(DefaultLanguage)
|
|
}
|
|
|
|
msg, err := localizer.Localize(&i18n.LocalizeConfig{
|
|
MessageID: messageID,
|
|
TemplateData: templateData,
|
|
})
|
|
if err != nil {
|
|
// Fallback to message ID if translation not found
|
|
log.Debug().Str("message_id", messageID).Err(err).Msg("Translation not found")
|
|
return messageID
|
|
}
|
|
return msg
|
|
}
|
|
|
|
// TSimple translates a message ID without template data
|
|
func TSimple(localizer *i18n.Localizer, messageID string) string {
|
|
return T(localizer, messageID, nil)
|
|
}
|
|
|
|
// MustT translates a message ID or panics
|
|
func MustT(localizer *i18n.Localizer, messageID string, templateData map[string]interface{}) string {
|
|
if localizer == nil {
|
|
localizer = NewLocalizer(DefaultLanguage)
|
|
}
|
|
|
|
msg, err := localizer.Localize(&i18n.LocalizeConfig{
|
|
MessageID: messageID,
|
|
TemplateData: templateData,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return msg
|
|
}
|