i18n

package module
v0.0.0-...-964ae52 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2025 License: BSD-2-Clause Imports: 16 Imported by: 21

README

i18n GoDoc

The API is entirely developer centric and not build around any exchange format like json, toml or xml. This is intentional and based on our observation that most projects are not translated anymore in a classical way. With the rising of various AI tools and their reached level of quality, there is "no" need anymore for an average app to get translated by professionals. This is mostly driven by the customer and the market expectations that developers should just "start over" using those tools and weired translations are fixed only on demand if they are found.

Thus, this API uses the type system to remove redundant declaration and notation work from the developer and offloads it to the compiler, where possible without code generation. We expect that translation fixes and additional languages are added later at runtime and if not already written by the developer, it will be added by end users using some sort of user interface without the help of any developer or professional translator.

Note also that the implementation is optimized for speed and therefore brings its own parser and parameter notation so that evaluation can be as fast as possible which is not true for most other implementations. We care especially for a lock free lookup of quasi constant strings, which is the most common thing in our Apps. Also, any value string reference is always int-sized, and thus it is half the size of a regular string (fat) pointer. This also guarantees a real O(1) index lookup for any key in a localized bundle. Most other implementations use string keys, which must always be hashed and be found in a hashtable (collisions, equals etc.) - our approach is magnitudes more efficient than that. We also cache the pre-parsed variable strings and optimize (re-)allocation where possible.

There is also a convenient string-only [Bundle.Resolve] support to tunnel string resource handles through arbitrary string. Even though this looks slow, it still provides acceptable performance.

This library was made for https://github.com/worldiety/nago but can be used freely in other projects.

Documentation

Overview

Package i18n provides an internationalization and localization system.

Index

Examples

Constants

This section is empty.

Variables

View Source
var Default = &Resources{}

Functions

func FormatFloat

func FormatFloat(tag language.Tag, v float64, decimals int, unit string) string

FormatFloat converts the given float using conventional rounding and appends or prepends the given postfix based on the given locale.

func ParseFloat

func ParseFloat(tag language.Tag, text string) (prefix string, value float64, suffix string)

ParseFloat parses the given text by removing any unusual chars (this breaks scientific notations). It is intended to parse human notations like 1,234.42 $ or 1.234,42 EURO etc based on different locales.

Types

type Attr

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

func Int

func Int(name string, value int) Attr

func Plural

func Plural(quantity float64) Attr

func String

func String(name, value string) Attr

func (Attr) String

func (a Attr) String() string

type Bundle

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

Bundle contains the localized resources with index accessors for all available resource types. A bundle may be mutated by its owner Resources. A bundle may be used in high performance situations, because all access patterns are done without locks or map hash calculations. Note, that only adding new values and updating existing values are allowed. Removing is not supported and would fall into the category of use-after-free errors. It is guaranteed that all Bundles of the same Resources parent, can address and process the identical set of Handles.

func (*Bundle) Bundle

func (b *Bundle) Bundle() *Bundle

Bundle returns itself so that it confirms to the Bundler interface itself.

func (*Bundle) Flush

func (b *Bundle) Flush()

func (*Bundle) MessageByKey

func (b *Bundle) MessageByKey(key Key) Message

MessageByKey returns the raw and unparsed Message. There is no fallthrough logic applied. Thus, if not defined in this bundle, a Message with i18n.MessageUndefined is returned.

func (*Bundle) MessageTypeByKey

func (b *Bundle) MessageTypeByKey(key Key) MessageType

MessageTypeByKey lookup if the kind within this bundle. It does not fallthrough or checks otherwise for consistency. If the key is not contained in this Bundle the MessageUndefined value is returned.

func (*Bundle) MustString

func (b *Bundle) MustString(id StrHnd) string

func (*Bundle) Parent

func (b *Bundle) Parent() *Resources

Parent returns the enclosing Resources.

func (*Bundle) QuantityString

func (b *Bundle) QuantityString(id QStrHnd, quantity float64, args ...Attr) (string, bool)

QuantityString picks the best quantity fit or falls through sibling bundles. Note that this is not entirely correct, because we would need to know how the float is formatted (e.g. as 1.0 or just as 1). However, besides this special case, we are still better than gettext or Android (e.g. as 1 Gopher vs 1.5 Gophers vs 1.00 Gophers (which we can't detect)).

func (*Bundle) QuantityStringLiterals

func (b *Bundle) QuantityStringLiterals(id QStrHnd) (Quantities, bool)

QuantityStringLiterals returns the raw literal, if available. There is no fallthrough.

func (*Bundle) Resolve

func (b *Bundle) Resolve(text string, args ...Attr) string

Resolve tries to localize the given text. If a string starts with @ it tries to interpret it as @<handle>. If you want to use the literal @<handle> escape it with a double @@. If no such handle is found, it falls through to the key-based translation. If this bundle does not contain anything, it tries to fallthrough other languages. If that fails, it just returns the raw literal.

func (*Bundle) String

func (b *Bundle) String(id StrHnd) (string, bool)

String returns a static plain localized string or falls through sibling bundles.

func (*Bundle) StringLiteral

func (b *Bundle) StringLiteral(id StrHnd) (string, bool)

StringLiteral returns the raw literal, if available. There is no fallthrough.

func (*Bundle) Tag

func (b *Bundle) Tag() language.Tag

func (*Bundle) Update

func (b *Bundle) Update(msg Message) error

Update validates and updates the related message data values for the according localization. Note, that this will switch the bundle implementation into mutation mode, so after your mutation you may want to Bundle.Flush to optimize performance.

func (*Bundle) VarString

func (b *Bundle) VarString(id VarStrHnd, args ...Attr) (string, bool)

VarString uses an internally pre-parsed template and applies the given attributes on it or falls through sibling bundles.

func (*Bundle) VarStringLiteral

func (b *Bundle) VarStringLiteral(id StrHnd) (string, bool)

VarStringLiteral returns the raw literal, if available. There is no fallthrough.

type Bundler

type Bundler interface {
	Bundle() *Bundle
}

type Cloneable

type Cloneable[T any] interface {
	Clone() T
}

type Key

type Key string

A Key is usually an artificial string identifier which can be looked up to a translated resource using a bundle instance for a specific language tag. It is recommended to use lower-snake-case-dot notation starting with the location of the string (e.g. my_screen.sub_system.some_category.some_text). It is best-practice to separate the component location using a dot which allows modelling a hierarchy to better understand the domain semantic when translating.

However, as a fallback, you may use an arbitrary default text as a key together with StringKey to allow a backwards compatible code transition. But that is not recommended in general.

func (Key) Directories

func (k Key) Directories() []string

Directories inspects the key to return the elements of the key. If the key looks like a StringKey it returns a single element containing just the key as-is. Otherwise, sections are split using a dot and the last element is omitted.

func (Key) StringKey

func (k Key) StringKey() bool

StringKey tries to detect if this string looks like a string key. A StringKey represents the default translation instead of a hierarchical key. Depending on the context, using StringKey may be considered as bad practice. To classify as a conventional Key it must be all lowercase and contains at least one dot.

Example
fmt.Println(Key("hello world").StringKey())
fmt.Println(Key("hello.world").StringKey())
fmt.Println(Key("subdomain.screen.panel.text").StringKey())
Output:

true
false
false

type Message

type Message struct {
	Key        Key         `json:"key,omitempty"`
	Kind       MessageType `json:"kind,omitempty"`
	Value      string      `json:"value,omitempty"`     // either a string (MessageString) or a template (MessageVarString)
	Quantities Quantities  `json:"quantities,omitzero"` // valid if MessageQuantities
}

func (Message) Identity

func (m Message) Identity() Key

func (Message) String

func (m Message) String() string

func (Message) Valid

func (m Message) Valid() bool

type MessageType

type MessageType int8
const (
	MessageUndefined MessageType = iota
	MessageString
	MessageVarString
	MessageQuantities
)

type Option

type Option interface {
	// contains filtered or unexported methods
}

func LocalizationHint

func LocalizationHint(description string) Option

LocalizationHint describes the string key in the current context. Where does the string occur? Which screen? In what situation is the user? What is important?

func LocalizationVarHint

func LocalizationVarHint(name string, description string) Option

LocalizationVarHint describes a named string for interpolation and gives the translator more context and description about the named interpolation variable.

type QStrHnd

type QStrHnd int32

func MustQuantityString

func MustQuantityString(key Key, values QValues, opts ...Option) QStrHnd

MustQuantityString adds the given key and the localized values with variables and quantity variants to the Default Resources instance. It panics if the same key was already added or if the template is unparseable.

func (QStrHnd) Get

func (s QStrHnd) Get(b Bundler, quantity float64, attr ...Attr) string

func (QStrHnd) String

func (s QStrHnd) String() string

String returns something like @1234. See also StrHnd.String.

type QValues

type QValues map[language.Tag]Quantities

QValues makes the map declaration more convenient.

type Quantities

type Quantities struct {
	// Zero is the content of the message for the CLDR plural form "zero".
	Zero string `json:"zero,omitempty"`

	// One is the content of the message for the CLDR plural form "one".
	One string `json:"one,omitempty"`

	// Two is the content of the message for the CLDR plural form "two".
	Two string `json:"two,omitempty"`

	// Few is the content of the message for the CLDR plural form "few".
	Few string `json:"few,omitempty"`

	// Many is the content of the message for the CLDR plural form "many".
	Many string `json:"many,omitempty"`

	// Other is the content of the message for the CLDR plural form "other".
	Other string `json:"other,omitempty"`
}

Quantities holds localized message strings for the different plural categories defined by the CLDR (Common Locale Data Repository).

Not all categories are used in every language. The struct defines all possible fields to support languages with complex plural rules (e.g., Slavic or Arabic), even though simpler languages only use a subset.

In English and German only the categories "one" and "other" are actually matched:

  • English: "one" is used for exactly 1 item (e.g., "1 apple"), "other" is used for all other counts (e.g., "0 apples", "2 apples").
  • German: "one" is used for exactly 1 item (e.g., "1 Apfel"), "other" is used for all other counts (e.g., "0 Äpfel", "2 Äpfel").

Categories "zero", "two", "few", and "many" are not matched in English or German, but may be required in other languages (e.g., Arabic, Russian, Polish).

func (Quantities) IsZero

func (q Quantities) IsZero() bool

func (Quantities) String

func (q Quantities) String() string

type Resources

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

Resources contains the finally compiled and validated resources and also any pending and not yet flushed changes. This allows Bundle instances to behave as singletons in the context of their Resources parent and makes their usage easier.

func (*Resources) AddLanguage

func (r *Resources) AddLanguage(tag language.Tag) (*Bundle, bool)

AddLanguage ensures that at least an empty bundle with the given language is matchable.

func (*Resources) AddQuantityString

func (r *Resources) AddQuantityString(key Key, values QValues, opts ...Option) (QStrHnd, error)

AddQuantityString either adds the given string key or returns os.ErrExist and the handle of the key. Use Resources.Flush after mutation to fixate the returned handles and remove any mutex locks for read accesses.

func (*Resources) AddString

func (r *Resources) AddString(key Key, values Values, opts ...Option) (StrHnd, error)

AddString either adds the given string key or returns os.ErrExist and the handle of the key. Use Resources.Flush after mutation to fixate the returned handles and remove any mutex locks for read accesses.

func (*Resources) AddVarString

func (r *Resources) AddVarString(key Key, values Values, opts ...Option) (VarStrHnd, error)

AddVarString either adds the given string key or returns os.ErrExist and the handle of the key. Use Resources.Flush after mutation to fixate the returned handles and remove any mutex locks for read accesses.

func (*Resources) All

func (r *Resources) All() iter.Seq2[language.Tag, *Bundle]

All returns all bundles in stable language tag order.

func (*Resources) AllKeys

func (r *Resources) AllKeys() iter.Seq[Key]

func (*Resources) Bundle

func (r *Resources) Bundle(tag language.Tag) (*Bundle, bool)

Bundle returns the exact bundle. See also Resources.MatchBundle.

func (*Resources) Clone

func (r *Resources) Clone() *Resources

Clone returns a new snapshot of the instance and all of its contained bundles.

func (*Resources) Flush

func (r *Resources) Flush()

Flush does not affect consistency, but improves performance by copying all underlying data into a read-only state which requires no mutex locks for read access. Note that any mutation will remove the lock-free access until the Resources are flushed again.

func (*Resources) Hint

func (r *Resources) Hint(key Key) string

func (*Resources) MatchBundle

func (r *Resources) MatchBundle(tag language.Tag) (*Bundle, bool)

MatchBundle returns the best matching bundle for the given language tag. This takes languages, countries and the fallback priorities of languages into account to calculate the best fit. If there is at least one language configured, it will always return that tag as a fallback. See also Resources.Bundle to get always an exact localized bundle.

func (*Resources) MatchQuantityString

func (r *Resources) MatchQuantityString(tag language.Tag, hnd QStrHnd, quantity float64, args ...Attr) (string, bool)

func (*Resources) MatchString

func (r *Resources) MatchString(tag language.Tag, hnd StrHnd) (string, bool)

MatchString finds the best bundle match and resolves the localized string. If the best match does not contain the string, it falls through.

func (*Resources) MatchTag

func (r *Resources) MatchTag(tag language.Tag) (language.Tag, bool)

MatchTag tries to match the given tag to all available languages and picks the best suited language or one of the fallback languages. Returns false if no match can be found, e.g. if no languages are available.

func (*Resources) MatchVarString

func (r *Resources) MatchVarString(tag language.Tag, hnd VarStrHnd, args ...Attr) (string, bool)

MatchVarString uses an internally pre-parsed template and applies the given attributes on it.

func (*Resources) MessageType

func (r *Resources) MessageType(key Key) MessageType

MessageType returns the expected type for the given key. If the key is declared at least in a single bundle, it will return the configured type. Otherwise, returns MessageUndefined.

func (*Resources) MustMatchBundle

func (r *Resources) MustMatchBundle(tag language.Tag) *Bundle

func (*Resources) Priorities

func (r *Resources) Priorities() iter.Seq[language.Tag]

Priorities returns the declared priority order. The first tag is the last resort.

func (*Resources) SetPriorities

func (r *Resources) SetPriorities(tags ...language.Tag)

SetPriorities updates the matching fallback priority of the given language. The lowest priority is the last fallback. The higher the priority, the more specific it becomes in the matching order. As default, the first tag is the last resort fallback.

func (*Resources) SortedKeys

func (r *Resources) SortedKeys() []Key

SortedKeys returns a snapshot of all ascending sorted keys

func (*Resources) StringKey

func (r *Resources) StringKey(key Key) StrHnd

StringKey either returns the handle if the key is already known or it adds a new english string with the key as the string value. Consider using Resources.AddString instead, because it makes the key and value difference explicit and obvious. However, there are situations where the non-constant int handles are not applicable, e.g. when translating struct field tag values.

func (*Resources) Tags

func (r *Resources) Tags() []language.Tag

Tags returns the alphabetically sorted slice of tags

func (*Resources) VarHints

func (r *Resources) VarHints(key Key) iter.Seq[VarHint]

type StrHnd

type StrHnd int32

func MustEnglish

func MustEnglish(key Key, text string, opts ...Option) StrHnd

MustEnglish helps to keep distraction on prototyping lower without sacrificing functionality. See also MustString.

func MustGerman

func MustGerman(key Key, text string, opts ...Option) StrHnd

MustGerman helps to keep distraction on prototyping lower without sacrificing functionality. See also MustString.

func MustString

func MustString(key Key, values Values, opts ...Option) StrHnd

MustString adds the given key and the localized values to the Default Resources instance. It panics if the same key was already added.

func StringKey

func StringKey(key string) StrHnd

StringKey either uses the key to find a translation or just uses the values as-is. See Resources.StringKey.

func (StrHnd) Get

func (s StrHnd) Get(b Bundler) string

func (StrHnd) String

func (s StrHnd) String() string

String returns an encoded handle like @1234. This representation is cached when created through Resources and therefore allocation free. Unknown handles may allocate their representation. This string may be used to transport localized strings through legacy or standard string code. Use Bundle.Resolve to convert it into the actual string.

type Template

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

Template represents a parametrized string which can be interpolated.

func ParseTemplate

func ParseTemplate(text string) (Template, error)

ParseTemplate supports the following syntax:

  • "some string without variables"
  • "hello {name} nice to meet you\nbest regards { sender \t}"

This syntax is a minimal subset of the ICU MessageFormat and eventually we will support more of it in the future.

func (Template) Execute

func (t Template) Execute(args ...Attr) string

type Values

type Values map[language.Tag]string

Values makes the map declaration more convenient.

type VarHint

type VarHint struct {
	Name        string
	Description string
}

type VarStrHnd

type VarStrHnd int32

func MustVarEnglish

func MustVarEnglish(key Key, text string, opts ...Option) VarStrHnd

MustVarEnglish helps to keep distraction on prototyping lower without sacrificing functionality. See also MustVarString.

func MustVarGerman

func MustVarGerman(key Key, text string, opts ...Option) VarStrHnd

MustVarGerman helps to keep distraction on prototyping lower without sacrificing functionality. See also MustVarString.

func MustVarString

func MustVarString(key Key, values Values, opts ...Option) VarStrHnd

MustVarString adds the given key and the localized values with variables to the Default Resources instance. It panics if the same key was already added or if the template is unparseable.

func (VarStrHnd) Get

func (s VarStrHnd) Get(b Bundler, attr ...Attr) string

func (VarStrHnd) String

func (s VarStrHnd) String() string

String returns something like @1234. See also StrHnd.String.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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