Go gettext bindings based on purego.
go-gettext provides simple bindings to the GNUgettext internationalization system based on purego. Thanks to purego, this means it requires neither a full reimplementation of gettext and the associated complexities, nor does it require CGo.
You can add go-gettext to your Go project by running the following:
$ go get github.com/pojntfx/go-gettext/...@latestPlease note that a gettext library (usually named libintl) needs to be installed on your system. On Linux this is almost always the case, but on Windows you might want to ship the relevant DLL manually, while macOS requires that you install it with Homebrew or MacPorts. Only Linux is a tested platform at this time.
TL;DR: Extract strings, initialize the i18n system, then call
i18n.Local
Just like in any gettext-based project, you'll start by extracting strings from your source code. For Go, this works:
find . -name '*.go' | xgettext --language=C++ --keyword=_ --keyword=i18n.Local --keyword=Local --omit-header -o default.pot --files-from=Alternatively, if you're using the L shorthand instead of i18n.Local:
find . -name '*.go' | xgettext --language=C++ --keyword=_ --keyword=L --omit-header -o default.pot --files-from=The resulting .pot file can then be translated. The standard gettext toolchain can now be used; for a full example (including building and installing the .mo files), see pojntfx/sessions/po.
Next, in an init function or elsewhere, import and setup go-gettext:
import "github.com/pojntfx/go-gettext/pkg/i18n"
const (
gettextPackage = "sessions"
localeDir = "/usr/share/locale"
)
func init() {
if err := i18n.InitI18n(gettextPackage, localeDir); err != nil {
panic(err)
}
}Adjust gettextPackage and localeDir to match your local environment. If you're using Meson, see pojntfx/senbara/senbara-gtk/src/config.go.in for an example of how to get those dynamically. Since go-gettext uses the system gettext library, using go:embed is a bit harder than usual; one (somewhat hacky) solution is to embed the generated .mo files and extract them to a temporary directory at runtime like this:
Expand section
//go:embed *
var FS embed.FS
// ...
i18t, err := os.MkdirTemp("", "")
if err != nil {
panic(err)
}
defer os.RemoveAll(i18t)
if err := fs.WalkDir(po.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if err := os.MkdirAll(filepath.Join(i18t, path), os.ModePerm); err != nil {
return err
}
return nil
}
src, err := po.FS.Open(path)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(filepath.Join(i18t, path))
if err != nil {
return err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return err
}
return nil
}); err != nil {
panic(err)
}
i18n.InitI18n("default", i18t)If you're looking for a pure Go library that has support for go:embed out of the box, we recommend leonelquinteros/gotext.
Now that everything is set up, getting a localized string is as easy as calling i18n.Local:
i18n.Local("Session finished")Alternatively you can also use the L shorthand like so:
import . "github.com/pojntfx/go-gettext/pkg/i18n"
L("Session finished")The translated string for "Session finished" should be returned by i18n.Local or L, e.g. "Sitzung beendet" in German.
🚀 That's it! We hope go-gettext helps you with internationalizing your app.
- jwijenbergh/purego allows us to call functions from
gettextwithout the need for CGo. - jwijenbergh/puregotk is what is commonly used with this library, and was very helpful for learning how to use purego.
- diamondburned/gotk4 was the inspiration for how the
InitI18nfunction should work. - GNU gettext is the most commonly used implementation of gettext and what go-gettext is usually used with.
- leonelquinteros/gotext is a great, pure Go gettext reimplementation.
To contribute, please use the GitHub flow and follow our Code of Conduct.
go-gettext (c) 2025 Felicitas Pojtinger and contributors
SPDX-License-Identifier: Apache-2.0