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>
This commit is contained in:
86
internal/i18n/i18n.go
Normal file
86
internal/i18n/i18n.go
Normal file
@@ -0,0 +1,86 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user