65 lines
2.0 KiB
Go
65 lines
2.0 KiB
Go
package i18n
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// weightedTag is one entry of a parsed Accept-Language header.
|
|
type weightedTag struct {
|
|
primary string // "fr" out of "fr-FR"
|
|
q float64
|
|
}
|
|
|
|
// parseAcceptLanguage turns a raw "Accept-Language" header value (RFC
|
|
// 9110 §12.5.4 — e.g. "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7") into its
|
|
// tags' primary subtags, ordered by descending quality weight. A
|
|
// malformed segment is skipped rather than aborting the whole parse —
|
|
// browsers have been known to send odd values, and the worst case of
|
|
// ignoring one bad segment is falling back to Default, never a panic
|
|
// or a rejected request.
|
|
func parseAcceptLanguage(header string) []weightedTag {
|
|
var tags []weightedTag
|
|
for _, part := range strings.Split(header, ",") {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" {
|
|
continue
|
|
}
|
|
tag, qStr, hasQ := strings.Cut(part, ";")
|
|
tag = strings.TrimSpace(tag)
|
|
if tag == "" || tag == "*" {
|
|
continue
|
|
}
|
|
q := 1.0
|
|
if hasQ {
|
|
qStr = strings.TrimSpace(qStr)
|
|
if v, ok := strings.CutPrefix(qStr, "q="); ok {
|
|
if parsed, err := strconv.ParseFloat(strings.TrimSpace(v), 64); err == nil {
|
|
q = parsed
|
|
}
|
|
}
|
|
}
|
|
primary, _, _ := strings.Cut(tag, "-")
|
|
tags = append(tags, weightedTag{primary: strings.ToLower(primary), q: q})
|
|
}
|
|
sort.SliceStable(tags, func(i, j int) bool { return tags[i].q > tags[j].q })
|
|
return tags
|
|
}
|
|
|
|
// NegotiateAcceptLanguage picks the best Supported locale for a raw
|
|
// "Accept-Language" header value, falling back to Default if nothing
|
|
// in the header is one enkly ships a catalog for (or the header is
|
|
// empty / unparseable). Callers only reach for this when there's no
|
|
// stronger signal available — see the gateway's localeFrom, which
|
|
// tries the enkly_lang cookie first and only falls through to this
|
|
// for a visitor who has never touched the language switcher.
|
|
func NegotiateAcceptLanguage(header string) Locale {
|
|
for _, tag := range parseAcceptLanguage(header) {
|
|
if IsSupported(tag.primary) {
|
|
return Locale(tag.primary)
|
|
}
|
|
}
|
|
return Default
|
|
}
|