i18n

package module
v0.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2023 License: MIT Imports: 16 Imported by: 17

README

i18n (Go)

build status report card godocs donate on Stripe

Efficient and easy to use localization and internationalization support for Go.

Installation

The only requirement is the Go Programming Language.

$ go get github.com/kataras/i18n@latest

Examples

Getting started

Create a folder named ./locales and put some YAML, TOML, JSON or INI files.

│   main.go
└───locales
    ├───el-GR
    │       example.yml
    ├───en-US
    │       example.yml
    └───zh-CN
            example.yml

Now, put the key-values content for each locale, e.g. ./locales/en-US/example.yml

hi: "Hi %s"
#
# Templates are supported
# hi: "Hi {{ .Name }}
#
# Template functions are supported
# hi: "Hi {{sayHi .Name}}
# ./locales/el-GR/example.yaml
hi: "Γειά σου %s"
# ./locales/zh-CN/example.yaml
hi: 您好 %s

Some other possible filename formats...

  • page.en.yaml
  • home.cart.el-GR.json
  • /el/file.tml

The language code MUST be right before the file extension.

The Default I18n instance will try to load locale files from ./locales directory. Use the Tr package-level function to translate a text based on the given language code. Use the GetMessage function to translate a text based on the incoming http.Request. Use the Router function to wrap an http.Handler (i.e an http.ServeMux) to set the language based on path prefix such as /zh-CN/some-path and subdomains such as zh.domain.com without the requirement of different routes per language.

Let's take a look at the simplest usage of this package.

package main

import (
	"fmt"

	"github.com/kataras/i18n"
)

type user struct {
	Name string
	Age  int
}

func main() {
	// i18n.SetDefaultLanguage("en-US")

	// Fmt style.
	enText := i18n.Tr("en", "hi", "John Doe") // or "en-US"
	elText := i18n.Tr("el", "hi", "John Doe")
	zhText := i18n.Tr("zh", "hi", "John Doe")

	fmt.Println(enText)
	fmt.Println(elText)
	fmt.Println(zhText)

	// Templates style.
	templateData := user{
		Name: "John Doe",
		Age:  66,
	}

	enText = i18n.Tr("en-US", "intro", templateData) // or "en"
	elText = i18n.Tr("el-GR", "intro", templateData)
	zhText = i18n.Tr("zh-CN", "intro", templateData)

	fmt.Println(enText)
	fmt.Println(elText)
	fmt.Println(zhText)
}

Load specific languages over a new I18n instance. The default language is the first registered, in that case is the "en-US".

I18n, err := i18n.New(i18n.Glob("./locales/*/*"), "en-US", "el-GR", "zh-CN")

Load embedded files through a go-bindata package:

I18n, err := i18n.New(i18n.Assets(AssetNames, Asset), "en-US", "el-GR", "zh-CN")

Load embedded files through Go's embed directive (recommended):

//go:embed static/locales/*
var staticFS embed.FS

loader, err := i18n.FS(staticFS, "./static/locales/*/*.yml")
// [handle error...]
I18n, err := i18n.New(loader, "en-US", "el-GR", "zh-CN")

Load through a simple Go map:

m := i18n.LangMap{
    "en-US": i18n.Map{
        "buy":               `buy %d`,
        "cart.checkout":     `checkout - {{.Param}}`,
        "cart.after.thanks": `thanks`,
        //
        "JSONTemplateExample":  `value of {{.Value}}`,
        "TypeOf":               `type of %T`,
        "KeyOnlyOnDefaultLang": `value`,
        //
        "title": `Title`,
        "hi":    `Hi {{.Name}}`,
        "int":   `1`,
        "hello": `Hello %s`,
        //
        "welcome": `welcome`,
    },
    "el-GR": i18n.Map{
        "buy":               `αγοράστε %d`,
        "cart.checkout":     `ολοκλήρωση παραγγελίας - {{.Param}}`,
        "cart.after.thanks": `ευχαριστούμε`,
        //
        "JSONTemplateExample": `τιμή του {{.Value}}`,
        "TypeOf":              `τύπος %T`,
        //
        "title": `Τίτλος`,
        "hi":    `Γειά σου {{.Name}}`,
        "int":   `1`,
        //
        "welcome": `καλώς ήρθατε`,
    },
}

loader := i18n.KV(m)

i18N, err := i18n.New(loader, "en-US", "el-GR")

Template variables & functions

Using template variables & functions as values in your locale value entry via LoaderConfig.

We are going to use a 3rd-party package for plural and singular words. Note that this is only for english dictionary, but you can use the "current" Locale and make a map with dictionaries to pluralize words based on the given language.

Before we get started, install the necessary packages:

$ go get github.com/kataras/i18n@master
$ go get github.com/gertd/go-pluralize@master

Let's create two simple translation files for our example. The ./locales/en-US/welcome.yml and ./locales/el-GR/welcome.yml respectfully:

Dog: "dog"
HiDogs: Hi {{plural (tr "Dog") .count }}
Dog: "σκυλί"
HiDogs: Γειά {{plural (tr "Dog") .count }}

The tr template function is a builtin function registered per locale. It returns the key's translated value. E.g. on english file the tr "Dog" returns the Dog:'s value: "dog" and on greek file it returns "σκυλί". This function helps importing a key to another key to complete a sentence.

Now, create a main.go file and store the following contents:

package main

import (
    "fmt"
    "text/template"

    "github.com/kataras/i18n"
    "github.com/gertd/go-pluralize"
)

var pluralizeClient = pluralize.NewClient()

func getFuncs(current *i18n.Locale) template.FuncMap {
    return template.FuncMap{
        "plural": func(word string, count int) string {
            return pluralizeClient.Pluralize(word, count, true)
        },
    }
}

func main() {
    I18n, err := i18n.New(i18n.Glob("./locales/*/*", i18n.LoaderConfig{
        // Set custom functions per locale!
        Funcs: getFuncs,
    }), "en-US", "el-GR", "zh-CN")
    if err != nil {
        panic(err)
    }

    textEnglish := I18n.Tr("en", "HiDogs", map[string]interface{}{
        "count": 2,
    }) // prints "Hi 2 dogs".
    fmt.Println(textEnglish)

    textEnglishSingular := I18n.Tr("en", "HiDogs", map[string]interface{}{
        "count": 1,
    }) // prints "Hi 1 dog".
    fmt.Println(textEnglishSingular)

    textGreek := I18n.Tr("el", "HiDogs", map[string]interface{}{
        "count": 1,
    }) // prints "Γειά 1 σκυλί".
    fmt.Println(textGreek)
}

Use go run main.go to run our small Go program. The output should look like this:

Hi 2 dogs
Hi 1 dog
Γειά 1 σκυλί

HTTP

HTTP, automatically searches for url parameter, cookie, custom function and headers for the current user language.

mux := http.NewServeMux()

I18n.URLParameter = "lang" // i.e https://domain.com?lang=el
I18n.Cookie = "lang"
I18n.ExtractFunc = func(r *http.Request) string { /* custom logic */ }

mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    translated := I18n.GetMessage(r, "hi", "John Doe")
    fmt.Fprintf(w, "Text: %s", translated)
})

Prefer GetLocale if more than one GetMessage call.

mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    locale := I18n.GetLocale(r)
    translated := locale.GetMessage("hi", "John Doe")
    // [...some locale.GetMessage calls]
})

Optionally, identify the current language by subdomain or path prefix, e.g. en.domain.com and domain.com/en or domain.com/en-US and e.t.c.

I18n.Subdomain = true

http.ListenAndServe(":8080", I18n.Router(mux))

If the ContextKey field is not empty then the Router will set the current language.

I18n.ContextKey = "lang" 

mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    currentLang := r.Context().Value("lang").(string)
    fmt.Fprintf(w, "Language: %s", currentLang)
})

Set the translate function as a key on a HTML Template.

templates, _ := template.ParseGlob("./templates/*.html")

mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    // per-request.
    translateFunc := I18n.GetLocale(r).GetMessage

    templates.ExecuteTemplate(w, "index.html", map[string]interface{}{
        "tr": translateFunc,
    })

    // {{ call .tr "hi" "John Doe" }}
})

Global function with the language as its first input argument.

translateLangFunc := I18n.Tr
templates.Funcs(template.FuncMap{
    "tr": translateLangFunc,
})

// {{ tr "en" "hi" "John Doe" }}

For a more detailed technical documentation you can head over to our godocs. And for executable code you can always visit the _examples repository's subdirectory.

License

kataras/i18n is free and open-source software licensed under the MIT License.

Documentation

Overview

Package i18n provides internalization and localization features.

Index

Constants

This section is empty.

Variables

View Source
var DefaultLoaderConfig = LoaderConfig{
	Left:               "{{",
	Right:              "}}",
	Strict:             false,
	DefaultMessageFunc: nil,
	PluralFormDecoder:  internal.DefaultPluralFormDecoder,
	Funcs:              nil,
}

DefaultLoaderConfig represents the default loader configuration.

Functions

func GetMessage

func GetMessage(r *http.Request, format string, args ...interface{}) string

GetMessage is package-level function which calls the `Default.GetMessage` method.

See `I18n#GetMessage` method for more.

func Router

func Router(next http.Handler) http.Handler

Router is package-level function which calls the `Default.Router` method.

See `I18n#Router` method for more.

func SetDefaultLanguage

func SetDefaultLanguage(langCode string) bool

SetDefaultLanguage changes the default language of the `Default` `I18n` instance.

func Tr

func Tr(lang, format string, args ...interface{}) string

Tr is package-level function which calls the `Default.Tr` method.

See `I18n#Tr` method for more.

Types

type I18n

type I18n struct {

	// If not nil, this request's context key can be used to identify the current language.
	// The found language(in this case, by path or subdomain) will be also filled with the current language on `Router` method.
	ContextKey interface{}
	// DefaultMessageFunc is the field which can be used
	// to modify the behavior when a key or language was not found.
	// All language inputs fallback to the default locale if not matched.
	// This is why this one accepts both input and matched languages,
	// so the caller can be more expressful knowing those.
	//
	// Defaults to nil.
	DefaultMessageFunc MessageFunc
	// ExtractFunc is the type signature for declaring custom logic
	// to extract the language tag name.
	ExtractFunc func(*http.Request) string
	// If not empty, it is language identifier by url query.
	URLParameter string
	// If not empty, it is language identifier by cookie of this name.
	Cookie string
	// If true then a subdomain can be a language identifier too.
	Subdomain bool
	// If true then it will return empty string when translation for a a specific language's key was not found.
	// Defaults to false, fallback defaultLang:key will be used.
	Strict bool
	// contains filtered or unexported fields
}

I18n is the structure which keeps the i18n configuration and implements Localization and internationalization features.

var Default *I18n

Default keeps a package-level pre-loaded `I18n` instance. The default glob pattern is "./locales/*/*" which accepts folder structure as: - ./locales

  • el-GR
  • filename.yaml
  • filename.toml
  • filename.json
  • en-US
  • ...
  • zh-CN
  • ...
  • ...

The default language depends on the first lookup, please use the package-level `SetDefaultLanguage` to set a default language as you are not able to customize the language lists from here.

See `New` package-level function to declare a fresh new, customized, `I18n` instance.

func New

func New(loader Loader, languages ...string) (*I18n, error)

New returns a new `I18n` instance. It contains a `Router` wrapper to (local) redirect subdomains and path prefixes too.

The "languages" input parameter is optional and if not empty then only these languages will be used for translations and the rest (if any) will be skipped. the first parameter of "loader" which lookups for translations inside files.

func (*I18n) GetLocale

func (i *I18n) GetLocale(r *http.Request) *Locale

GetLocale returns the found locale of a request. It will return the first registered language if nothing else matched.

func (*I18n) GetMessage

func (i *I18n) GetMessage(r *http.Request, format string, args ...interface{}) (msg string)

GetMessage returns the localized text message for this "r" request based on the key "format". It returns an empty string if locale or format not found.

func (*I18n) Router

func (i *I18n) Router(next http.Handler) http.Handler

Router returns a new router wrapper. It compares the path prefix for translated language and local redirects the requested path with the selected (from the path) language to the router.

func (*I18n) SetDefault

func (i *I18n) SetDefault(langCode string) bool

SetDefault changes the default language. Please avoid using this method; the default behavior will accept the first language of the registered tags as the default one.

func (*I18n) Tr

func (i *I18n) Tr(lang, format string, args ...interface{}) (msg string)

Tr returns a translated message based on the "lang" language code and its key(format) with any optional arguments attached to it.

It returns an empty string if "lang" not matched, unless DefaultMessageFunc. It returns the default language's translation if "key" not matched, unless DefaultMessageFunc.

func (*I18n) TryMatchString

func (i *I18n) TryMatchString(s string) (language.Tag, int, bool)

TryMatchString will try to match the "s" with a registered language tag. It returns -1 as the language index and false if not found.

type LangMap added in v0.0.8

type LangMap = map[string]Map

LangMap key as language (e.g. "el-GR") and value as a map of key-value pairs (e.g. "hello": "Γειά").

type Loader

type Loader func(m *Matcher) (Localizer, error)

Loader accepts a `Matcher` and should return a `Localizer`. Functions that implement this type should load locale files.

func Assets

func Assets(assetNames func() []string, asset func(string) ([]byte, error), options ...LoaderConfig) Loader

Assets accepts a function that returns a list of filenames (physical or virtual), another a function that should return the contents of a specific file and any Loader options. Go-bindata usage. It returns a valid `Loader` which loads and maps the locale files.

See `Glob`, `Assets`, `New` and `LoaderConfig` too.

func FS added in v0.0.8

func FS(fileSystem fs.FS, pattern string, options ...LoaderConfig) (Loader, error)

FS is a virtual or local locale file system Loader. It accepts embed.FS or fs.FS or http.FileSystem. The "pattern" is a classic glob pattern.

See `Glob`, `Assets`, `New` and `LoaderConfig` too.

func Glob

func Glob(globPattern string, options ...LoaderConfig) Loader

Glob accepts a glob pattern (see: https://golang.org/pkg/path/filepath/#Glob) and loads the locale files based on any "options".

The "globPattern" input parameter is a glob pattern which the default loader should search and load for locale files.

See `New` and `LoaderConfig` too.

func KV added in v0.0.8

func KV(langMap LangMap, opts ...LoaderConfig) Loader

KV is a loader which accepts a map of language(key) and the available key-value pairs. Example Code:

m := LangMap{
	"en-US": Map{
		"hello": "Hello",
	},
	"el-GR": Map{
		"hello": "Γειά",
	},
}

loader := KV(m, i18n.DefaultLoaderConfig) I18n, err := New(loader) I18N.SetDefault("en-US")

type LoaderConfig

type LoaderConfig = internal.Options

LoaderConfig the configuration structure which contains some options about how the template loader should act.

See `Glob` and `Assets` package-level functions.

type Locale

type Locale = internal.Locale

Locale is the type which the `Localizer.GetLocale` method returns. It serves the translations based on "key" or format. See its `GetMessage`.

func GetLocale

func GetLocale(r *http.Request) *Locale

GetLocale is package-level function which calls the `Default.GetLocale` method.

See `I18n#GetLocale` method for more.

type Localizer

type Localizer interface {
	// GetLocale should return a valid `Locale` based on the language index.
	// It will always match the Loader.Matcher.Languages[index].
	// It may return the default language if nothing else matches based on custom localizer's criteria.
	GetLocale(index int) *Locale
}

Localizer is the interface which returned from a `Loader`. Types that implement this interface should be able to retrieve a `Locale` based on the language index.

type Map added in v0.0.5

type Map = map[string]interface{}

Map is just an alias of the map[string]interface{} type.

type Matcher

type Matcher struct {
	Languages []language.Tag
	// contains filtered or unexported fields
}

Matcher implements the languae.Matcher. It contains the original language Matcher and keeps an ordered list of the registered languages for further use (see `Loader` implementation).

func (*Matcher) Match

func (m *Matcher) Match(t ...language.Tag) (language.Tag, int, language.Confidence)

Match returns the best match for any of the given tags, along with a unique index associated with the returned tag and a confidence score.

func (*Matcher) MatchOrAdd

func (m *Matcher) MatchOrAdd(t language.Tag) (tag language.Tag, index int, conf language.Confidence)

MatchOrAdd acts like Match but it checks and adds a language tag, if not found, when the `Matcher.strict` field is true (when no tags are provided by the caller) and they should be dynamically added to the list.

func (*Matcher) ParseLanguageFiles

func (m *Matcher) ParseLanguageFiles(fileNames []string) (map[int][]string, error)

ParseLanguageFiles returns a map of language indexes and their associated files based on the "fileNames".

type MessageFunc added in v0.0.4

type MessageFunc = internal.MessageFunc

MessageFunc is the function type to modify the behavior when a key or language was not found. All language inputs fallback to the default locale if not matched. This is why this signature accepts both input and matched languages, so caller can provide better messages.

The first parameter is set to the client real input of the language, the second one is set to the matched language (default one if input wasn't matched) and the third and forth are the translation format/key and its optional arguments.

Directories

Path Synopsis
_examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL