lingua

package module
v0.0.0-...-e76ba11 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2025 License: MIT Imports: 14 Imported by: 0

README

Lingua

This is a simple translation library for golang. It uses yaml files to load translations. Files can be specific to a language or a combination of language and region.

Translations are simple key value pairs. The key is used to identify the translation and the value is the translated message. The value can contain placeholders that will be replaced with the values provided.

# Example translation line with a simple replacement.
# Calling this with "user" => "john" will result in "Welcome john!"
welcome.message: "Welcome :user!"

Usage

Lingua parses translation files from a filesystem(anything that implements afero.Fs). Files should follow the following naming convention to be recognized by lingua:

  • en.yaml (language only)
  • or en-US.yaml (language and region)

Empty files are allowed and will also be parsed. This can be useful for adding a new language and prefill it with the keys found by lingua extract.

// Parse translations from fs (this can be any filesystem that implements the afero.FS interface).
// This can be an embed.FS or a local filesystem.
// Adding a default language to use as fallback.
c, err := lingua.ContainerFromFs(afero.NewBasePathFs(afero.NewOsFs(), "./translations"), lingua.WithDefaultLanguage(lingua.MustParseLanguage("en")))
if err != nil {
    // Handle error...
}

// On each request set the preferred language in the context.
// Parse the language from a user request. This can be from a header or user settings, for example the http Accept-Language header.
ctx := context.Background()
ctx, err := lingua.WithLanguage(ctx, r.Header.Get("Accept-Language"))
if err != nil {
    // Handle error...
}

// Translate the message.
// If the user requested "en-US" but you only have "en" translations available, the translator will use the "en" translations.
msg := c.Message(ctx, "welcome.message", map[string]any{"user": "wvell"})
fmt.Println(msg) // prints: Welcome wvell!

Transformers

Transformers can be used to modify the replacement value before it is inserted into the translation message. There are 3 built-in transformers:

  • capitalize: Capitalizes the replacement value.
  • replace: Uses the placeholder to find a translation message. This is usefull for translating generic error messages for validation.
  • plural: Uses the replacement value to determine the plural form of the translation message.
Capitalize
# Calling this with "user" => "john" will result in "Welcome John!"
capitalize: "Welcome :user|capitalize"
Replace
user.email: "email address"
street: "street"

# Calling this with "field" => "user.email" will result in "Email address is a required field"
# Note the chaining of the transformers. The first transformer is applied to the replacement value and the second transformer is applied to the result of the first transformer.
required: ":field|replace|capitalize is a required field"
Plural
# Calling this with "count" => 0 will result in "no items"
# Calling this with "count" => 1 will result in "1 item"
# Calling this with "count" => 5 will result in "a few items"
# Calling this with "count" => 15 will result in "15 items"
items: ":count|plural(=0 {no items} =1 {1 item} =2-10 {a few items} other {# items})"

Message extraction

Users can use the lingua tool to extract translation keys from your go source files. This will collect every value of type github.com/SLASH2NL/lingua.Key from the src directory.

Install the tool by running:

$ go install github.com/SLASH2NL/lingua/cmd/lingua@latest
# Extract translation keys from go source files and write them to translation files in dst.
# If there are existing files they will be merged and only new keys will be added.
#
# Note: The tool will only write to language files that exist in the translation directory.
# If you want to add a new language add en empty yaml file like `$ touch en.yaml`.
lingua extract path_to_go_source_files path_to_translation_files

What does it extact?

  • const values const translation lingua.Key = "const.translation"
  • var values var translation lingua.Key = "var.translation"
  • function calls that provide a lingua.Key as argument myFunc("func.call") where myFunc is defined as func(msg lingua.Key)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithLanguage

func WithLanguage(ctx context.Context, raw string) context.Context

WithLanguage parses the given raw language and adds it to the ctx. If the language can not be parsed no language is added to the context. This will make Container.Message fallback to the default container language if set.

Types

type Container

type Container struct {
	// contains filtered or unexported fields
}

func ContainerFromFs

func ContainerFromFs(fs afero.Fs, opts ...ContainerOpt) (*Container, error)

ContainerFromFs reads all translation files that match the default FileMatcher from the fs.

func ContainerFromFsAndMatcher

func ContainerFromFsAndMatcher(fs afero.Fs, matcher FileMatcher, opts ...ContainerOpt) (*Container, error)

func Merge

func Merge(from *Container, to *Container, strategy MergeStrategy) *Container

Merge merges the messages from the from container to the to container based on the strategy.

func (*Container) Message

func (c *Container) Message(ctx context.Context, key Key, replacements map[string]any) string

func (*Container) Messages

func (c *Container) Messages(lang LanguageID) map[Key]*parser.Message

func (*Container) Raw

func (c *Container) Raw() map[LanguageID]map[string]string

Raw returns the raw messages from the container.

func (*Container) Scope

func (c *Container) Scope(ctx context.Context) *ScopedContainer

Scope returns a container type with the ctx embedded.

func (*Container) ScopedLanguage

func (c *Container) ScopedLanguage(ctx context.Context) LanguageID

ScopedLanguage returns the language in the context or falls back to no strict matches or the default lang. Returns and empty LanguageID{} if no language can be detected.

type ContainerOpt

type ContainerOpt func(c *Container)

func WithDefaultLanguage

func WithDefaultLanguage(lang LanguageID) ContainerOpt

type FileMatcher

type FileMatcher interface {
	IsMatch(name string) bool
	LanguageID(name string) (LanguageID, error)
}

FileMatcher is an interface that is used to check if a given file in a directory structure is a match, and should be parsed. It also provides a way to get the language ID from the file name.

type Key

type Key string

Key is a unique identifier for a translation message.

type LanguageID

type LanguageID struct {
	Language string
	Region   string
}

LanguageID holds the language and an optional region.

func FromCtx

func FromCtx(ctx context.Context) LanguageID

FromCtx returns the language from the ctx or an empty language if no language is set.

func MustParseLanguage

func MustParseLanguage(lang string) LanguageID

MustParseLanguage parses the language string into a LanguageID. If the language can not be parsed it will panic.

func ParseLanguage

func ParseLanguage(lang string) (LanguageID, error)

ParseLanguage parses the language string into a LanguageID.

func (LanguageID) Empty

func (l LanguageID) Empty() bool

func (LanguageID) Match

func (l LanguageID) Match(cmp LanguageID) (match bool, strongMatch bool)

func (LanguageID) String

func (l LanguageID) String() string

type MergeStrategy

type MergeStrategy int
const (
	// SkipAndClean will skip existing messages and remove the messages from to that are not in from.
	SkipAndClean MergeStrategy = iota
	// Skip will skip the merge for messages that already exist in to.
	Skip
	// Overwrite will overwrite the messages in to with the messages from from.
	Overwrite
	// OverWriteAndClean will overwrite the messages in to with the messages from from and remove the messages from to that are not in from.
	OverWriteAndClean
)

type RegexMatcher

type RegexMatcher struct {
	// contains filtered or unexported fields
}

RegexMatcher matches all files in the directory that match the regex. The first capture group is used to extract the language ID.

func NewRegexMatcher

func NewRegexMatcher(re *regexp.Regexp) *RegexMatcher

NewRegexMatcher creates a new RegexMatcher with the given regex.

func (*RegexMatcher) IsMatch

func (m *RegexMatcher) IsMatch(name string) bool

func (*RegexMatcher) LanguageID

func (m *RegexMatcher) LanguageID(name string) (LanguageID, error)

type ScopedContainer

type ScopedContainer struct {
	// contains filtered or unexported fields
}

func (*ScopedContainer) Message

func (s *ScopedContainer) Message(key Key, replacements map[string]any) string

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳