Awesome
Spreak
Flexible translation and humanization library for Go, based on the concepts behind gettext. Requires Go 1.19+.
Why another library?
There are already many good libraries for Go, which allow localizing an application. However, I always came to a point where I was dissatisfied. Either they use a self defined format, which could not be edited with common tools. Some libraries only support one language at a time or are using a lot of mutexes. And no tool could easily extract the strings to be translated. I wanted to solve these problems for myself, and so spreak was born.
Features
- Built-in support for
po
,mo
andjson
files - Direct support for humanization of Go data structures
- Support for
fs.FS
(e.g.embed
) - Goroutine-safe and lock free through immutability
- Powerful extractor for strings to simplify the localization process (with support for templates)
- Support for gettext and CLDR v44 plural rules.
- Support of bilingual and monolingual formats
Usage
Using spreak is easy. First, use go get to install the latest version of the library.
go get -u github.com/vorlif/spreak
After that, spreak offers you a comprehensive interface to load and query your translations.
package main
import (
"fmt"
"golang.org/x/text/language"
"github.com/vorlif/spreak"
"github.com/vorlif/spreak/localize"
)
func main() {
// Create a bundle that loads the translations for the required languages.
// Typically, you only need one bundle in an application.
bundle, err := spreak.NewBundle(
// Set the language used in the program code/templates
spreak.WithSourceLanguage(language.English),
// Set the path from which the translations should be loaded
spreak.WithDomainPath(spreak.NoDomain, "./locale"),
// Specify the languages you want to load
spreak.WithLanguage(language.German, language.Spanish, language.Chinese),
)
if err != nil {
panic(err)
}
// Create a Localizer to select the language to translate.
t := spreak.NewLocalizer(bundle, language.Spanish)
// Translate
fmt.Println(t.Get("Hello world"))
fmt.Println(t.NGetf("I have %d dog", "I have %d dogs", 2, 2))
fmt.Println(t.Localize(GetPlanet()))
// Output:
// Hola Mundo
// Tengo 2 perros
// No conozco ningún planeta
}
func GetPlanet() *localize.Message {
return &localize.Message{
Singular: "I do not know any planet",
Plural: "I do not know any planets",
}
}
Extract strings
Strings for the translations can be extracted via the command line program xspreak. Use a pre-built binary or install it from source:
go install github.com/vorlif/xspreak@latest
Tests installation with:
xspreak --help
xspreak extracts the strings from the program code and creates a template which can be used for new translations. Before you extract your strings, you have to decide on a format.
It can either create a .pot
(PO Templates) file in po format or a .json
file.
If you are not sure which format to use, I would recommend you to use po
format,
because it is supported by almost all translation programs, which makes your life and the life of your translators much
easier.
With -D
you specify the path to your source code and with -p
the one where the translation template should be saved.
# for .pot
xspreak -D path/to/source/ -p path/to/source/locale
# for .json
xspreak -D path/to/source/ -p path/to/source/locale -f json
This creates a new .pot
or .json
file representing the translation template.
Translate
po files
The generated POT files can be easily imported by most translation tools. If you are dealing with Po files for the first time, I recommend the application Poedit for a quick start.
After translation .po
or .mo
files are generated, which are used by spreak for looking up translations.
Attention, do not translate the .pot
file directly, as this is only a template.
json files
You can open and edit the JSON files with any text editor.
The extracted template always uses only the plural categories one
and other
.
To create a template with the plural categories suitable for your language, you can use xspreak.
xspreak merge -i locale/template.json -o locale/de.json -l de
Update translation files
:warning: It would be wise before making any move to keep a backup.
When you add, edit or delete text in your program code, you should also update the translation files. To achieve this, you must first update the template:
# for .pot
xspreak -D path/to/source/ -p path/to/source/locale
# for .json
xspreak -D path/to/source/ -p path/to/source/locale -f json
This creates a new .pot
or .json
file representing the new translation template.
po files
For PO files, almost every translation tool offers the possibility to update the files from a POT file.
With Poedit you can do it via Translation -> Update from POT file
.
If you use the gettext utilities, you can use msgmerge -U es.po template.pot
.
For all other tools, it is worth taking a look at the documentation.
json files
For JSON files, xspreak offers a simple way to update the translation files.
xspreak merge -i locale/template.json -o locale/de.json -l de
This copies new keys and existing translations from template.json
to de.json
and deletes keys from de.json
that are not present in template.json
.
Structure translations
How you structure the files with the translations is up to you.
Assuming you load the domain "helloworld"
from the path "./locale"
and the language language.Spanish
spreak.WithDomainPath("helloworld", "./locale"),
spreak.WithLanguage(language.Spanish),
Then spreak searches for the following files by default
./locale/es/helloworld.po
./locale/helloworld/es.po
./locale/es.po
./locale/LC_MESSAGES/es/helloworld.po
Where es
is an example from the list [es-ES, es_ES, spa, es]
and the file extension .po
is an example from the
list [po, mo, json]
.
If you don't like this behavior, you can implement your own Resolver
.
For special cases you can also implement your own Loader
.
How to use in tests?
Just create a Bundle
without options.
This will never return an error and can be used to create Localizer
which then simply return the input.
bundle, _ := spreak.NewBundle()
t := spreak.NewLocalizer(bundle, language.English)
What's next
- Read what you can extract with xspreak
- Take a look in the examples folder for more examples of using spreak.
- Use it!
Package humanize
The humanize
package provides a collection of functions to convert Go data structures into a human-readable format.
It was widely adapted from the Django project and also uses the Django translations.
It should therefore be noted that the translations are under
the Django's 3-clause BSD license.
To use the humanize
package, you first need to load the languages you want to use.
You can find a list of all supported languages under humanize/locale/
package main
import (
"fmt"
"time"
"golang.org/x/text/language"
"github.com/vorlif/spreak/humanize"
"github.com/vorlif/spreak/humanize/locale/ar"
"github.com/vorlif/spreak/humanize/locale/es"
"github.com/vorlif/spreak/humanize/locale/zhHans"
)
func main() {
// Load the translations for the desired languages
collection := humanize.MustNew(
humanize.WithLocale(es.New(), ar.New(), zhHans.New()),
)
// Create a humanizer.
// A humanizer features a collection of humanize functions for a language.
h := collection.CreateHumanizer(language.English)
// Uses the functions...
fmt.Println(h.Intword(1_000_000_000))
// Output: 1.0 billion
fmt.Println(h.NaturalDay(time.Now()))
// Output: today
t := time.Now().Add(5 * time.Minute)
fmt.Println(h.NaturalTime(t))
// Output: 5 minutes from now
d := -80 * time.Hour
fmt.Println(h.TimeSince(d))
// Output: 3 days, 8 hours
// ... for different languages
h = collection.CreateHumanizer(language.Spanish)
fmt.Println(h.TimeSince(d))
// Output: 3 días, 8 horas
}
A collection of all functions and further examples can be found in the documentation.
Add translations
If you would like to add a translation or add a new language, do not do so in this repository. The translations in this repository are automatically generated from the Django translations and additions should also be made there. Use the following link to do so: https://www.transifex.com/django/django/. For all non-translation related errors, this repository must be used.
License
spreak is available under the MIT license. See the LICENSE file for more info.
The translations of the humanize
packages are licensed
under Django's BSD license.