Files
honeyDueAPI/internal/i18n/i18n.go
Trey t c17e85c14e Add comprehensive i18n localization support
- Add go-i18n package for internationalization
- Create i18n middleware to extract Accept-Language header
- Add translation files for en, es, fr, de, pt languages
- Localize all handler error messages and responses
- Add language context to all API handlers

Supported languages: English, Spanish, French, German, Portuguese

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 02:01:47 -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"}
// 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
}