Files
honeyDueAPI/internal/i18n/i18n.go
treyt e26116e2cf Add webhook logging, pagination, middleware, migrations, and prod hardening
- 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>
2026-02-24 21:32:09 -06:00

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
}