From 4614931ae17c2407495571db44c765f66e66a929 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 06:26:29 +0000 Subject: [PATCH] fix(i18n): prefer locale override precedence Co-Authored-By: Virgil --- localise.go | 32 +++++++++++++++++--------------- localise_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/localise.go b/localise.go index d4ad110..ac481a3 100644 --- a/localise.go +++ b/localise.go @@ -114,33 +114,35 @@ func CurrentPluralCategory(n int) PluralCategory { } func detectLanguage(supported []language.Tag) string { - langEnv := os.Getenv("LANG") - if langEnv == "" { - langEnv = os.Getenv("LC_ALL") + for _, langEnv := range []string{os.Getenv("LC_ALL"), os.Getenv("LC_MESSAGES"), os.Getenv("LANG")} { if langEnv == "" { - langEnv = os.Getenv("LC_MESSAGES") + continue + } + if detected := detectLanguageFromEnv(langEnv, supported); detected != "" { + return detected } } - if langEnv == "" { + return "" +} + +func detectLanguageFromEnv(langEnv string, supported []language.Tag) string { + baseLang := normalizeLanguageTag(core.Split(langEnv, ".")[0]) + if baseLang == "" || len(supported) == 0 { return "" } - baseLang := normalizeLanguageTag(core.Split(langEnv, ".")[0]) parsedLang, err := language.Parse(baseLang) if err != nil { return "" } - if len(supported) == 0 { - return "" - } matcher := language.NewMatcher(supported) bestMatch, bestIndex, confidence := matcher.Match(parsedLang) - if confidence >= language.Low { - if bestIndex >= 0 && bestIndex < len(supported) { - return supported[bestIndex].String() - } - return bestMatch.String() + if confidence < language.Low { + return "" } - return "" + if bestIndex >= 0 && bestIndex < len(supported) { + return supported[bestIndex].String() + } + return bestMatch.String() } func normalizeLanguageTag(lang string) string { diff --git a/localise_test.go b/localise_test.go index ced2a26..3bcc640 100644 --- a/localise_test.go +++ b/localise_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/text/language" ) // --- Formality.String() --- @@ -249,6 +250,35 @@ func TestDetectLanguage_Good(t *testing.T) { assert.Equal(t, "", result, "should return empty with no supported languages") } +func TestDetectLanguage_PrefersLocaleOverrides(t *testing.T) { + t.Setenv("LANG", "en_US.UTF-8") + t.Setenv("LC_MESSAGES", "fr_FR.UTF-8") + t.Setenv("LC_ALL", "de_DE.UTF-8") + + supported := []language.Tag{ + language.AmericanEnglish, + language.French, + language.German, + } + + result := detectLanguage(supported) + assert.Equal(t, "de", result, "LC_ALL should win over LANG and LC_MESSAGES") +} + +func TestDetectLanguage_SkipsInvalidHigherPriorityLocale(t *testing.T) { + t.Setenv("LANG", "en_US.UTF-8") + t.Setenv("LC_MESSAGES", "fr_FR.UTF-8") + t.Setenv("LC_ALL", "not-a-locale") + + supported := []language.Tag{ + language.AmericanEnglish, + language.French, + } + + result := detectLanguage(supported) + assert.Equal(t, "fr", result, "invalid LC_ALL should not block a valid lower-priority locale") +} + // --- Mode.String() --- func TestMode_String_Good(t *testing.T) { -- 2.45.3