Files
apocapoc-api/internal/i18n/i18n.go
T
david a2aa8b2a76 Add i18n support with English and Spanish translations
- Created i18n package with translator and middleware
- Added translation files for English (en.json) and Spanish (es.json)
- Updated all HTTP handlers to use i18n for error/success messages
- Added comprehensive test coverage for i18n (87%)
- Updated CI workflow to use Go 1.24
- All tests passing with 50.8% total coverage
2025-11-29 12:27:26 +01:00

123 lines
2.8 KiB
Go

package i18n
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
"golang.org/x/text/language"
)
//go:embed locales/en.json
var enTranslations []byte
//go:embed locales/es.json
var esTranslations []byte
type Translations struct {
Errors map[string]string `json:"errors"`
Success map[string]string `json:"success"`
Validation map[string]string `json:"validation"`
Emails map[string]string `json:"emails"`
}
type Translator struct {
translations map[language.Tag]Translations
matcher language.Matcher
}
func NewTranslator() (*Translator, error) {
var enTrans, esTrans Translations
if err := json.Unmarshal(enTranslations, &enTrans); err != nil {
return nil, fmt.Errorf("failed to load English translations: %w", err)
}
if err := json.Unmarshal(esTranslations, &esTrans); err != nil {
return nil, fmt.Errorf("failed to load Spanish translations: %w", err)
}
translations := map[language.Tag]Translations{
language.English: enTrans,
language.Spanish: esTrans,
}
matcher := language.NewMatcher([]language.Tag{
language.English,
language.Spanish,
})
return &Translator{
translations: translations,
matcher: matcher,
}, nil
}
func (t *Translator) GetLanguage(acceptLanguage string) language.Tag {
if acceptLanguage == "" {
return language.English
}
tags, _, err := language.ParseAcceptLanguage(acceptLanguage)
if err != nil || len(tags) == 0 {
return language.English
}
_, index, _ := t.matcher.Match(tags...)
supportedTags := []language.Tag{language.English, language.Spanish}
if index < len(supportedTags) {
return supportedTags[index]
}
return language.English
}
func (t *Translator) Translate(lang language.Tag, category, key string) string {
trans, ok := t.translations[lang]
if !ok {
trans = t.translations[language.English]
}
var categoryMap map[string]string
switch category {
case "errors":
categoryMap = trans.Errors
case "success":
categoryMap = trans.Success
case "validation":
categoryMap = trans.Validation
case "emails":
categoryMap = trans.Emails
default:
return key
}
if value, ok := categoryMap[key]; ok {
return value
}
return key
}
func (t *Translator) Error(lang language.Tag, key string) string {
return t.Translate(lang, "errors", key)
}
func (t *Translator) Success(lang language.Tag, key string) string {
return t.Translate(lang, "success", key)
}
func (t *Translator) Validation(lang language.Tag, key string) string {
return t.Translate(lang, "validation", key)
}
func (t *Translator) Email(lang language.Tag, key string) string {
return t.Translate(lang, "emails", key)
}
func (t *Translator) TranslateValidationError(lang language.Tag, field, validationKey string) string {
message := t.Validation(lang, validationKey)
return strings.ReplaceAll(message, field, field)
}