From 6be463b58657154adbc9d61e3e73ce6190505383 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 28 Nov 2025 21:24:28 +0100 Subject: [PATCH] langs/i18n: Prefer languageCode when picking translation file Fixes #14204 Closes #14217 Co-authored-by: Patrice Chalin --- langs/i18n/i18n.go | 6 ++++ langs/i18n/i18n_integration_test.go | 55 +++++++++++++++++++++++++++++ langs/i18n/translationProvider.go | 17 +++++++-- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/langs/i18n/i18n.go b/langs/i18n/i18n.go index db27fb997..3893e0a43 100644 --- a/langs/i18n/i18n.go +++ b/langs/i18n/i18n.go @@ -45,6 +45,12 @@ func NewTranslator(b *i18n.Bundle, cfg config.AllProvider, logger loggers.Logger return t } +// Lookup looks up the translate func for the given language. +func (t Translator) Lookup(lang string) (translateFunc, bool) { + f, ok := t.translateFuncs[lang] + return f, ok +} + // Func gets the translate func for the given language, or for the default // configured language if not found. func (t Translator) Func(lang string) translateFunc { diff --git a/langs/i18n/i18n_integration_test.go b/langs/i18n/i18n_integration_test.go index 781d74909..af06e7b6e 100644 --- a/langs/i18n/i18n_integration_test.go +++ b/langs/i18n/i18n_integration_test.go @@ -14,6 +14,7 @@ package i18n_test import ( + "strings" "testing" qt "github.com/frankban/quicktest" @@ -165,3 +166,57 @@ b = 'b translated' b.Assert(err, qt.IsNotNil) b.Assert(err.Error(), qt.Contains, "failed to load translations: reserved keys [description] mixed with unreserved keys [a b]: see the lang.Translate documentation for a list of reserved keys") } + +func TestI18nUseLanguageCodeWhenBothTranslationFilesArePresent(t *testing.T) { + t.Parallel() + + filesTemplate := ` +-- hugo.yaml -- +languages: + en: + languageCode: en-us +-- i18n/en.yml -- +hello: Greetings from en! +-- i18n/en-us.yml -- +hello: Greetings from en-us! +-- layouts/all.html -- +{{ T "hello" }} +` + + runTest := func(s string) { + b := hugolib.Test(t, s) + b.AssertFileContent("public/index.html", `Greetings from en-us!`) + } + + runTest(filesTemplate) + runTest(strings.ReplaceAll(filesTemplate, "languageCode: en-us", "languageCode: En-US")) + runTest(strings.ReplaceAll(filesTemplate, "-- i18n/en-us.yml --", "-- i18n/en-US.yml --")) +} + +func TestI18nUseLangWhenLanguageCodeFileIsMissing(t *testing.T) { + t.Parallel() + + filesTemplate := ` +-- hugo.yaml -- +languages: + en: + title: English + pt: + languageCode: pt-br +-- i18n/en.yml -- +hello: Greetings from en! +-- i18n/pt.yml -- +hello: Greetings from pt! +-- layouts/all.html -- +{{ T "hello" }} +` + + runTest := func(s string) { + b := hugolib.Test(t, s) + b.AssertFileContent("public/pt/index.html", `Greetings from pt!`) + } + + runTest(filesTemplate) + runTest(strings.ReplaceAll(filesTemplate, "pt:", "PT:")) + runTest(strings.ReplaceAll(filesTemplate, "-- i18n/pt.yml --", "-- i18n/pT.yml --")) +} diff --git a/langs/i18n/translationProvider.go b/langs/i18n/translationProvider.go index 227492794..490951f4a 100644 --- a/langs/i18n/translationProvider.go +++ b/langs/i18n/translationProvider.go @@ -78,7 +78,7 @@ func (tp *TranslationProvider) NewResource(dst *deps.Deps) error { tp.t = NewTranslator(bundle, dst.Conf, dst.Log) - dst.Translate = tp.t.Func(dst.Conf.Language().(*langs.Language).Lang) + dst.Translate = tp.getTranslateFunc(dst) return nil } @@ -128,10 +128,23 @@ func addTranslationFile(bundle *i18n.Bundle, r *source.File) error { // CloneResource sets the language func for the new language. func (tp *TranslationProvider) CloneResource(dst, src *deps.Deps) error { - dst.Translate = tp.t.Func(dst.Conf.Language().(*langs.Language).Lang) + dst.Translate = tp.getTranslateFunc(dst) return nil } +// getTranslateFunc returns the translation function for the language in Deps. +// We first try the language code (e.g. "en-US"), then the language key (e.g. "en"). +func (tp *TranslationProvider) getTranslateFunc(dst *deps.Deps) func(ctx context.Context, translationID string, templateData any) string { + l := dst.Conf.Language().(*langs.Language) + if lc := l.LanguageCode(); lc != "" { + if fn, ok := tp.t.Lookup(strings.ToLower(lc)); ok { + return fn + } + } + // Func will fall back to the default language if not found. + return tp.t.Func(l.Lang) +} + func errWithFileContext(inerr error, r *source.File) error { meta := r.FileInfo().Meta() realFilename := meta.Filename -- 2.39.5