fix: respect Accept-Language quality factors in language detection (#1380)
The Accept-Language header parsing was not correctly handling quality factors. When a browser sends "en-GB,de-DE;q=0.5", the expected behavior is to prefer English (q=1.0 by default) over German (q=0.5). The fix uses golang.org/x/text/language.ParseAcceptLanguage to properly parse and sort language preferences by quality factor. It also adds base language fallbacks (e.g., "en" for "en-GB") to ensure regional variants match their parent languages when no exact match exists. Fixes #1022 Signed-off-by: majiayu000 <1835304752@qq.com>
This commit is contained in:
parent
cee7871ef8
commit
71147b4857
2 changed files with 60 additions and 1 deletions
|
|
@ -81,7 +81,28 @@ func (ls *LocalizationService) GetLocalizerFromRequest(r *http.Request) *i18n.Lo
|
||||||
return i18n.NewLocalizer(bundle, "en")
|
return i18n.NewLocalizer(bundle, "en")
|
||||||
}
|
}
|
||||||
acceptLanguage := r.Header.Get("Accept-Language")
|
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
|
// SimpleLocalizer wraps i18n.Localizer with a more convenient API
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package localization
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http/httptest"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue