Files
enkly-ai/pkg/i18n/negotiate.go
enkly 79ce8dc4c0
Some checks failed
Go CI Pipeline / build_and_test (push) Failing after 22m0s
Init Project
2026-10-30 18:06:55 +01:00

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
}