feat(i18n): add base language grammar fallback
Some checks are pending
Test / test (push) Waiting to run
Security Scan / security (push) Successful in 12s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 04:37:45 +00:00
parent c5fbf6ee0c
commit 1c9cf442b6
2 changed files with 86 additions and 7 deletions

View file

@ -190,7 +190,7 @@ func Upper(s string) string {
}
func getVerbForm(lang, verb, form string) string {
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data == nil || data.Verbs == nil {
return ""
}
@ -207,7 +207,7 @@ func getVerbForm(lang, verb, form string) string {
}
func getWord(lang, word string) string {
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data == nil || data.Words == nil {
return ""
}
@ -215,7 +215,7 @@ func getWord(lang, word string) string {
}
func getPunct(lang, rule, defaultVal string) string {
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data == nil {
return defaultVal
}
@ -233,7 +233,7 @@ func getPunct(lang, rule, defaultVal string) string {
}
func getNounForm(lang, noun, form string) string {
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data == nil || data.Nouns == nil {
return ""
}
@ -477,7 +477,7 @@ func Article(word string) string {
func articleForCurrentLanguage(lowerWord, originalWord string) (string, bool) {
lang := currentLangForGrammar()
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data == nil {
return "", false
}
@ -768,7 +768,7 @@ func DefiniteArticle(word string) string {
return article
}
lang := currentLangForGrammar()
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data != nil && data.Articles.Definite != "" {
return data.Articles.Definite
}
@ -794,7 +794,7 @@ func DefinitePhrase(word string) string {
func definiteArticleForCurrentLanguage(lowerWord, originalWord string) (string, bool) {
lang := currentLangForGrammar()
data := GetGrammarData(lang)
data := grammarDataForLang(lang)
if data == nil {
return "", false
}
@ -807,6 +807,23 @@ func definiteArticleForCurrentLanguage(lowerWord, originalWord string) (string,
return "", false
}
func grammarDataForLang(lang string) *GrammarData {
if data := GetGrammarData(lang); data != nil {
return data
}
if base := baseLanguageTag(lang); base != "" {
return GetGrammarData(base)
}
return nil
}
func baseLanguageTag(lang string) string {
if idx := indexAny(lang, "-_"); idx > 0 {
return lang[:idx]
}
return ""
}
func definiteArticleFromGrammarForms(data *GrammarData, lowerWord, originalWord, lang string) (string, bool) {
if data == nil || data.Articles.Definite == "" {
return "", false

View file

@ -7,6 +7,16 @@ import (
"time"
)
type regionFallbackLoader struct{}
func (regionFallbackLoader) Languages() []string {
return []string{"en-GB"}
}
func (regionFallbackLoader) Load(lang string) (map[string]Message, *GrammarData, error) {
return map[string]Message{}, nil, nil
}
func TestPastTense(t *testing.T) {
// Ensure grammar data is loaded from embedded JSON
svc, err := New()
@ -933,6 +943,58 @@ func TestFrenchGrammarData(t *testing.T) {
}
}
func TestGrammarFallbackToBaseLanguageTag(t *testing.T) {
prevDefault := Default()
prevGrammar := GetGrammarData("en")
t.Cleanup(func() {
SetGrammarData("en", prevGrammar)
SetDefault(prevDefault)
})
SetGrammarData("en", &GrammarData{
Verbs: map[string]VerbForms{
"delete": {Past: "deleted", Gerund: "deleting"},
},
Nouns: map[string]NounForms{
"file": {One: "file", Other: "files"},
},
Articles: ArticleForms{
IndefiniteDefault: "a",
IndefiniteVowel: "an",
Definite: "the",
},
Punct: PunctuationRules{
LabelSuffix: ":",
ProgressSuffix: "...",
},
Words: map[string]string{
"status": "Status",
},
})
svc, err := NewWithLoader(regionFallbackLoader{})
if err != nil {
t.Fatalf("NewWithLoader() failed: %v", err)
}
SetDefault(svc)
if err := svc.SetLanguage("en-GB"); err != nil {
t.Fatalf("SetLanguage(en-GB) failed: %v", err)
}
if got := PastTense("delete"); got != "deleted" {
t.Fatalf("PastTense(delete) = %q, want %q", got, "deleted")
}
if got := Pluralize("file", 2); got != "files" {
t.Fatalf("Pluralize(file, 2) = %q, want %q", got, "files")
}
if got := Article("apple"); got != "an" {
t.Fatalf("Article(apple) = %q, want %q", got, "an")
}
if got := Label("status"); got != "Status:" {
t.Fatalf("Label(status) = %q, want %q", got, "Status:")
}
}
func TestTemplateFuncs(t *testing.T) {
funcs := TemplateFuncs()
expected := []string{