From 2c5c2b715e38cba001574abd37d76a0f07620fbe Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 03:11:15 +0000 Subject: [PATCH] fix(i18n): normalise underscore language tags Co-Authored-By: Virgil --- i18n_test.go | 12 ++++++++++++ language.go | 1 + language_test.go | 7 +++++++ localise.go | 12 ++++++++++-- localise_test.go | 1 + service.go | 1 + 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/i18n_test.go b/i18n_test.go index f9fd9b3..0b41012 100644 --- a/i18n_test.go +++ b/i18n_test.go @@ -75,6 +75,18 @@ func TestSetLanguage_Good(t *testing.T) { assert.Contains(t, CurrentLanguage(), "en") } +func TestSetLanguage_Good_UnderscoreTag(t *testing.T) { + svc, err := New() + require.NoError(t, err) + _ = Init() + SetDefault(svc) + + err = SetLanguage("fr_CA") + assert.NoError(t, err) + assert.True(t, len(CurrentLanguage()) >= 2) + assert.Equal(t, "fr", CurrentLanguage()[:2]) +} + func TestSetLanguage_Bad_Unsupported(t *testing.T) { svc, err := New() require.NoError(t, err) diff --git a/language.go b/language.go index a93a06f..2ba98b2 100644 --- a/language.go +++ b/language.go @@ -2,6 +2,7 @@ package i18n // GetPluralRule returns the plural rule for a language code. func GetPluralRule(lang string) PluralRule { + lang = normalizeLanguageTag(lang) if rule, ok := pluralRules[lang]; ok { return rule } diff --git a/language_test.go b/language_test.go index 54151fb..fe4537c 100644 --- a/language_test.go +++ b/language_test.go @@ -12,11 +12,13 @@ func TestGetPluralCategory(t *testing.T) { {"en", 0, PluralOther}, {"en", 1, PluralOne}, {"en", 2, PluralOther}, + {"en_US", 1, PluralOne}, // French (0 and 1 are singular) {"fr", 0, PluralOne}, {"fr", 1, PluralOne}, {"fr", 2, PluralOther}, + {"fr_CA", 2, PluralOther}, // Russian {"ru", 1, PluralOne}, @@ -88,6 +90,11 @@ func TestGetPluralRule(t *testing.T) { t.Error("Welsh-GB rule(2) should be PluralTwo") } + rule = GetPluralRule("en_US") + if rule(1) != PluralOne { + t.Error("English_US rule(1) should be PluralOne") + } + // Unknown falls back to English rule = GetPluralRule("xx-YY") if rule(1) != PluralOne { diff --git a/localise.go b/localise.go index e0533e4..d8c485c 100644 --- a/localise.go +++ b/localise.go @@ -57,6 +57,7 @@ func (g GrammaticalGender) String() string { // IsRTLLanguage returns true if the language code uses right-to-left text. func IsRTLLanguage(lang string) bool { + lang = normalizeLanguageTag(lang) if rtlLanguages[lang] { return true } @@ -110,8 +111,7 @@ func detectLanguage(supported []language.Tag) string { if langEnv == "" { return "" } - baseLang := core.Split(langEnv, ".")[0] - baseLang = core.Replace(baseLang, "_", "-") + baseLang := normalizeLanguageTag(core.Split(langEnv, ".")[0]) parsedLang, err := language.Parse(baseLang) if err != nil { return "" @@ -129,3 +129,11 @@ func detectLanguage(supported []language.Tag) string { } return "" } + +func normalizeLanguageTag(lang string) string { + lang = core.Trim(lang) + if lang == "" { + return "" + } + return core.Replace(lang, "_", "-") +} diff --git a/localise_test.go b/localise_test.go index cefd15c..0ebd191 100644 --- a/localise_test.go +++ b/localise_test.go @@ -88,6 +88,7 @@ func TestIsRTLLanguage_Good(t *testing.T) { }{ {"arabic", "ar", true}, {"arabic_sa", "ar-SA", true}, + {"arabic_sa_underscore", "ar_EG", true}, {"hebrew", "he", true}, {"farsi", "fa", true}, {"urdu", "ur", true}, diff --git a/service.go b/service.go index d47edd4..d7d1f78 100644 --- a/service.go +++ b/service.go @@ -231,6 +231,7 @@ func (s *Service) loadJSON(lang string, data []byte) error { func (s *Service) SetLanguage(lang string) error { s.mu.Lock() defer s.mu.Unlock() + lang = normalizeLanguageTag(lang) requestedLang, err := language.Parse(lang) if err != nil { return log.E("Service.SetLanguage", "invalid language tag: "+lang, err) -- 2.45.3