diff --git a/lib/localization/localization.go b/lib/localization/localization.go index 6b8d2a6..02189bf 100644 --- a/lib/localization/localization.go +++ b/lib/localization/localization.go @@ -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 diff --git a/lib/localization/localization_test.go b/lib/localization/localization_test.go index 47442f1..006e76d 100644 --- a/lib/localization/localization_test.go +++ b/lib/localization/localization_test.go @@ -3,6 +3,7 @@ package localization import ( "encoding/json" "fmt" + "net/http/httptest" "sort" "testing" @@ -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) + } + }) + } +}