Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion lib/localization/localization.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,28 @@ func (ls *LocalizationService) GetLocalizerFromRequest(r *http.Request) *i18n.Lo
return i18n.NewLocalizer(bundle, "en")
}
acceptLanguage := r.Header.Get("Accept-Language")
return i18n.NewLocalizer(ls.bundle, acceptLanguage, "en")

// Parse Accept-Language header to properly handle quality factors
// The language.ParseAcceptLanguage function returns tags sorted by quality
tags, _, err := language.ParseAcceptLanguage(acceptLanguage)
if err != nil || len(tags) == 0 {
return i18n.NewLocalizer(ls.bundle, "en")
}

// Convert parsed tags to strings for the localizer
// We include both the full tag and base language to ensure proper matching
langs := make([]string, 0, len(tags)*2+1)
for _, tag := range tags {
langs = append(langs, tag.String())
// Also add base language (e.g., "en" for "en-GB") to help matching
base, _ := tag.Base()
if base.String() != tag.String() {
langs = append(langs, base.String())
}
}
langs = append(langs, "en") // Always include English as fallback

return i18n.NewLocalizer(ls.bundle, langs...)
}

// SimpleLocalizer wraps i18n.Localizer with a more convenient API
Expand Down
38 changes: 38 additions & 0 deletions lib/localization/localization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package localization
import (
"encoding/json"
"fmt"
"net/http/httptest"
"sort"
"testing"

Expand Down Expand Up @@ -138,3 +139,40 @@ func TestComprehensiveTranslations(t *testing.T) {
})
}
}

func TestAcceptLanguageQualityFactors(t *testing.T) {
service := NewLocalizationService()

testCases := []struct {
name string
acceptLanguage string
expectedLang string
}{
{"simple_en", "en", "en"},
{"simple_de", "de", "de"},
{"en_GB_with_lower_priority_de", "en-GB,de-DE;q=0.5", "en"},
{"en_GB_only", "en-GB", "en"},
{"de_with_lower_priority_en", "de,en;q=0.5", "de"},
{"de_DE_with_lower_priority_en", "de-DE,en;q=0.5", "de"},
{"fr_with_lower_priority_de", "fr,de;q=0.5", "fr"},
{"zh_CN_regional", "zh-CN", "zh-CN"},
{"zh_TW_regional", "zh-TW", "zh-TW"},
{"pt_BR_regional", "pt-BR", "pt-BR"},
{"complex_header", "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,de;q=0.5", "fr"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("Accept-Language", tc.acceptLanguage)

localizer := service.GetLocalizerFromRequest(req)
sl := &SimpleLocalizer{Localizer: localizer}

gotLang := sl.GetLang()
if gotLang != tc.expectedLang {
t.Errorf("Accept-Language %q: expected %s, got %s", tc.acceptLanguage, tc.expectedLang, gotLang)
}
})
}
}
Loading