Skip to content

Add support for choosing best match translations#23

Open
wttw wants to merge 1 commit intofullpipe:mainfrom
wttw:accept-language
Open

Add support for choosing best match translations#23
wttw wants to merge 1 commit intofullpipe:mainfrom
wttw:accept-language

Conversation

@wttw
Copy link
Copy Markdown
Contributor

@wttw wttw commented May 29, 2025

This adds language.MatchStrings() support to Bundle.Translator.

If a bundle contains "en" and "es" translations then bundle.Translator("es-AR") will return the translator for "es", while `bundle.Translator("de", "da, en-gb;q=0.8, en;q=0.7") will return a translator for "en".

This makes handling translations for web apps easy, where there may be explicit languages set in cookies or url parameters, and client preferences set in the Accept-Languages header.

The main user-visible change is that Bundle.Translator() can accept any number of strings rather than one.

The main implementation change is adding an optional Languages() function to a MessageProvider, returning a list of languages that provider supports, and using that to create a language.Matcher for bundle.Translator to use.

Tests and README have been updated to match.

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := NewBundle(
WithDefaultLangFallback(language.Spanish),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests will fail if you remove default. And will detect germanTranslation as default. I think it is because how NewMatcher builds lang index.

"messages.en.yaml": {Data: []byte("msg_id: englishTranslation")},
"messages.de.yaml": {Data: []byte("msg_id: germanTranslation")},
"messages.fr.yaml": {Data: []byte("msg_id: frenchTranslation")},
}),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ignores "forced" fallbacks

if add WithLangFallback(language.Polish, language.English),
test case

		{
			"Forced fallback",
			"pl",
			"",
			"englishTranslation",
		},

will fail

@fullpipe
Copy link
Copy Markdown
Owner

Hello. I thought about something similar. But I want to keep this lib as straight and simple as possible. Lang detection is not the work of the Bundle.

Here my thoughts:

  1. I think may be it is better to move it to separate folder? ext or some thing like this.
msBundle := ext.NewMatchStringBundle(bundle)
tr := msBundle.Translator(cookie, acceptHeader)
  1. Is it valid usage?

In most systems lang has 3-4 sources.

  • lang in query string
  • lang in cookie
  • Accept-Language header
  • user entity field Lang

and lang detection is better to hide in middleware, so in you handler you make like

handler( w http.ResponseWriter, r *http.Request) {
  tr := bundle.Translator(ext.ContextLang(r.Context))
}
package ext

const LandContextKey = "lang"
func ContextLang(ctx context.Context) string {
  return ctx.Value(LandContextKey).(string)
}

or even

handler( w http.ResponseWriter, r *http.Request) {
  tr := ext.ContextTranslator(r.Context)
}

Also mention some problems in PR

@wttw
Copy link
Copy Markdown
Contributor Author

wttw commented Jun 4, 2025

Moving it to a separate package might work, though to be able to choose the best match against available languages it does need to have access to a list of the languages a Bundle has available.

I'm using this fork to build out an app at the moment, so I'll see what other issues I run across.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants