Switching language
By default an app uses the operating system’s language. To offer a language
setting inside the app, call
lang.SetLocale:
language := widget.NewSelect([]string{"System", "English", "Deutsch", "العربية"}, func(s string) {
switch s {
case "English":
lang.SetLocale("en-US")
case "Deutsch":
lang.SetLocale("de-DE")
case "العربية":
lang.SetLocale("ar-EG") // also switches the layout to right-to-left
default:
lang.SetLocale("") // back to the system locale
}
})SetLocale takes a BCP 47 locale such as "fr", "de-DE" or
"sr-Cyrl-RS". It changes translations, formats, sorting and text direction
together. An empty locale goes back to the system locale.
When the locale changes, Bellui lays out and repaints every window, as for a
theme change. Call it on the UI goroutine, for example from a widget
callback. The choice is not saved: store it in the app’s preferences and call
SetLocale again at startup.
Text that follows the language
Repainting is not the same as re-translating. A label made with
widget.NewLabel(lang.L("Cancel")) received a finished string when it was
built, and keeps it after a language change. For text that should change,
use one of these instead.
Widgets that take a binding. A localized binding is a
binding.String whose value is the translation. It translates again on
every language change:
// A label whose text is re-translated on every language change.
welcome := widget.NewLabelWithData(
binding.NewLocalizedString("Welcome, {{.Name}}!", map[string]any{"Name": "Ada"}),
)
// A plural that follows both the language and the count.
count := binding.NewInt()
_ = count.Set(1)
selected := widget.NewLabelWithData(
// With no template data, {{.Count}} is the live count.
binding.NewLocalizedPluralWithKey("files_selected", "{{.Count}} files selected", count),
)
more := widget.NewButton("+1", func() {
n, _ := count.Get()
_ = count.Set(n + 1)
})binding.NewLocalizedStringworks likelang.L, andNewLocalizedStringWithKeylikelang.X.binding.NewLocalizedPluralandNewLocalizedPluralWithKeytake the count as anIntbinding, so the text also changes with the count. When you pass no template data,{{.Count}}in the message is the current count.
A localized binding only listens for language changes while something is bound to it, so a binding you stop using does not leak.
Everything else. A button’s text, a window title or a menu item has no
binding. lang.BindText
calls a setter with the translation now and after every language change:
// Surfaces without data binding: a button label and the window title.
save := widget.NewButton("", func() {})
lang.BindText("button.save", save.SetText)
lang.BindText("Welcome, {{.Name}}!", w.SetTitle, map[string]any{"Name": "Ada"})BindText returns a function that stops the updates. Call it when the
widget is thrown away. For things that live as long as the app, such as the
main window’s title, you can ignore it.
For anything else that depends on the locale,
lang.AddLocaleChangeListener
registers a function to call after each change.
The full program:
// Switching the app's language while it runs, with text that re-translates
// itself.
package main
import (
"embed"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/data/binding"
"github.com/belluikit/bellui/lang"
"github.com/belluikit/bellui/widget"
)
//go:embed translations
var translations embed.FS
func main() {
_ = lang.AddTranslationsFS(translations, "translations")
a := app.New()
w := a.NewWindow("")
// A label whose text is re-translated on every language change.
welcome := widget.NewLabelWithData(
binding.NewLocalizedString("Welcome, {{.Name}}!", map[string]any{"Name": "Ada"}),
)
// A plural that follows both the language and the count.
count := binding.NewInt()
_ = count.Set(1)
selected := widget.NewLabelWithData(
// With no template data, {{.Count}} is the live count.
binding.NewLocalizedPluralWithKey("files_selected", "{{.Count}} files selected", count),
)
more := widget.NewButton("+1", func() {
n, _ := count.Get()
_ = count.Set(n + 1)
})
// Surfaces without data binding: a button label and the window title.
save := widget.NewButton("", func() {})
lang.BindText("button.save", save.SetText)
lang.BindText("Welcome, {{.Name}}!", w.SetTitle, map[string]any{"Name": "Ada"})
language := widget.NewSelect([]string{"System", "English", "Deutsch", "العربية"}, func(s string) {
switch s {
case "English":
lang.SetLocale("en-US")
case "Deutsch":
lang.SetLocale("de-DE")
case "العربية":
lang.SetLocale("ar-EG") // also switches the layout to right-to-left
default:
lang.SetLocale("") // back to the system locale
}
})
w.SetContent(container.NewVBox(language, welcome, container.NewHBox(selected, more), save))
bellui.ShowAndRun(w)
}