Popularity
2.4
Growing
Activity
6.4
-
43
1
3

README

Spreak Test status [MIT license](LICENSE) PkgGoDev Go Report Card codecov MinVersion

Flexible translation and humanization library for Go, based on the concepts behind gettext. Requires Go 1.16+.

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 and [json](./examples/features/jhttptempl) 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 v41 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@latest

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(
        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 Localiser 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](examples/features/resolver/main.go). For special cases you can also implement your own [Loader](examples/features/loaders/main.go).

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](./examples) for more examples of using spreak.
  • Use it!

Package humanize PkgGoDev

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/](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](LICENSE) file for more info. The translations of the humanize packages are licensed under Django's BSD license.


*Note that all licence references and agreements mentioned in the spreak README section above are relevant to that project's source code only.