Translations
Translation files
Translations are JSON files, one per language. Each maps a message ID to its translation. A plain message is a string. A message with plural forms is an object with one entry per form:
{
"Welcome, {{.Name}}!": "Willkommen, {{.Name}}!",
"files_selected": {
"one": "{{.Count}} Datei ausgewählt",
"other": "{{.Count}} Dateien ausgewählt"
},
"button.save": "Speichern"
}Name each file after its language: de.json, pt_BR.json, zh_Hans.json.
A prefix before the language is allowed, as in app.de.json.
{{.Name}} is a placeholder filled from data you pass when you translate.
The syntax is Go’s text/template.
Load the files
Keep the files in a directory next to your code and embed it:
//go:embed translations
var translations embed.FSThen load them once at startup, before you build the UI:
if err := lang.AddTranslationsFS(translations, "translations"); err != nil {
bellui.LogError("Could not load translations", err)
}lang.AddTranslationsFS
loads every file in the directory. If one file fails, it loads the others and
returns the first error. To load a single file, use
AddTranslations
with a resource whose name ends in the language, such as fr.json, or
AddTranslationsForLocale
with the bytes and the locale.
Translate text
name := "Ada"
welcome := lang.L("Welcome, {{.Name}}!", map[string]any{"Name": name})
n := 3
selected := lang.N("files_selected", n, map[string]any{"Count": n})
save := lang.X("button.save", "Save")
gender := "female"
update := lang.LSelect("profile_updated", gender, map[string]any{"Name": name})| Function | Short name | Use it for |
|---|---|---|
lang.Localize | lang.L | Text whose ID is the English text itself. |
lang.LocalizePlural | lang.N | Text with plural forms, chosen by a count. |
lang.LocalizeKey | lang.X | Text looked up by a separate ID, with a fallback. |
lang.LocalizePluralKey | lang.XN | Plural text looked up by ID, with a fallback. |
Template data is the last argument: a map, or a struct with exported fields.
Plural forms. The count picks the form. Which forms a language has comes
from its CLDR plural rules (the Unicode project’s data on how each language
counts): English has one and other, Polish and Russian also have few
and many. Write the forms your language needs, and always include other.
The count only chooses the form. To show the number, put it in the template
data as well, as the example does with Count.
IDs or English text. With L and N the ID is the English text, so
the source reads naturally. L needs no English file. N without an English
file uses the ID for every count, so a count of 1 reads “1 files selected”.
Add an English file with the one form for it. With X and XN the ID
is a short key such as button.save, and you pass the English text as the
fallback. Keys stay stable when you reword the English.
Grammatical variants
Some sentences change with something other than a count, such as the
grammatical gender of a person. Write one message per variant, with the
variant name after a dot, plus an .other variant:
{
"profile_updated.female": "{{.Name}} updated her profile",
"profile_updated.male": "{{.Name}} updated his profile",
"profile_updated.other": "{{.Name}} updated their profile"
}lang.LSelect
picks the variant. An unknown selector uses .other. If that is missing too,
it returns the prefix itself.
lang.LSelectPlural
picks a variant and then a plural form inside it, so each variant is a
plural object.
When a translation is missing
Bellui chooses one language for the whole app. If your app has no file for that language, your messages are not found:
Lreturns the text you wrote, which is English.XandXNreturn the fallback you passed.NandLSelectreturn the message ID itself, such asfiles_selected.
Bellui also logs a “Translation failure” error for each missing message.
This happens whenever the user’s language is one of the 15 languages Bellui
itself is translated into but your app is not. A French user of an app with
only English and German files sees Bellui’s own text in French and your text
in English, or as IDs where you used N or LSelect. To avoid that, ship
a file for every language you support, and use XN with a fallback rather
than N with a key.
The full program:
// Translating an app's own strings: loading translation files, plain and
// templated strings, plural forms, keys and grammatical variants.
package main
import (
"embed"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/lang"
"github.com/belluikit/bellui/widget"
)
//go:embed translations
var translations embed.FS
func main() {
if err := lang.AddTranslationsFS(translations, "translations"); err != nil {
bellui.LogError("Could not load translations", err)
}
a := app.New()
w := a.NewWindow("Translations")
name := "Ada"
welcome := lang.L("Welcome, {{.Name}}!", map[string]any{"Name": name})
n := 3
selected := lang.N("files_selected", n, map[string]any{"Count": n})
save := lang.X("button.save", "Save")
gender := "female"
update := lang.LSelect("profile_updated", gender, map[string]any{"Name": name})
info := widget.NewLabel("System locale: " + lang.SystemLocale().String() +
", in use: " + lang.CurrentLocale().String())
w.SetContent(container.NewVBox(
widget.NewLabel(welcome),
widget.NewLabel(selected),
widget.NewButton(save, func() {}),
widget.NewLabel(update),
info,
))
bellui.ShowAndRun(w)
}