# locale

`import "github.com/matjam/bunyip/locale"`

Package locale translates a game's strings. It provides tables of messages by language with placeholders and plural forms, a fallback chain so a half-translated language falls back to a configured source rather than showing keys, and the plural rules of the common languages.

A Bundle holds one Table per language, loaded from JSON files a translator edits:

	{
	  "menu.play": "Play",
	  "hud.gold": "{n} gold",
	  "inv.arrows": {"one": "{n} arrow", "other": "{n} arrows"}
	}

To translate a string, get a language's Translator from the bundle and call T with a key and the values for its placeholders. A plural entry picks its form from the value named "n", or from the first number given. Keys missing from a language come from the fallbacks in order, and a key missing everywhere returns itself in brackets so it is visible and can be fixed. Right-to-left layout of the interface is the game's responsibility; the text itself shapes correctly through gfx. Bundles and tables have no internal locks. Finish loading them before sharing translators, or synchronize mutations with all readers.

## Types

<a id="Bundle"></a>

<a id="Bundle.Fallbacks"></a>

### Bundle

```go
type Bundle struct {

	// Fallbacks are tried in order for keys a language lacks; the
	// source language, usually "en", goes last.
	Fallbacks []string
	// contains filtered or unexported fields
}
```

Bundle holds every language's table and the fallback order.

<a id="NewBundle"></a>

#### NewBundle

```go
func NewBundle(fallbacks ...string) *Bundle
```

NewBundle makes an empty bundle with fallbacks, the source language last.

<a id="Bundle.Add"></a>

#### Bundle.Add

```go
func (b *Bundle) Add(t *Table)
```

Add puts a table in the bundle, replacing any for its language.

<a id="Bundle.For"></a>

#### Bundle.For

```go
func (b *Bundle) For(lang string) *Translator
```

For returns a translator for a language ("de", "pt-BR"): the language itself, then its base language without the region, then the bundle's fallbacks.

<a id="Bundle.Languages"></a>

#### Bundle.Languages

```go
func (b *Bundle) Languages() []string
```

Languages lists the loaded languages, sorted.

<a id="Bundle.Load"></a>

#### Bundle.Load

```go
func (b *Bundle) Load(lang string, data []byte) error
```

Load parses JSON for a language and adds it.

<a id="Bundle.Missing"></a>

#### Bundle.Missing

```go
func (b *Bundle) Missing(lang, source string) []string
```

Missing lists the keys a language lacks that the source language has, for a translator's to-do list.

<a id="Bundle.Table"></a>

#### Bundle.Table

```go
func (b *Bundle) Table(lang string) *Table
```

Table returns a language's table, nil when none is loaded.

<a id="Category"></a>

### Category

```go
type Category string
```

Category is a plural form a language distinguishes.

<a id="Zero"></a>

<a id="One"></a>

<a id="Two"></a>

<a id="Few"></a>

<a id="Many"></a>

<a id="Other"></a>

```go
const (
	Zero  Category = "zero"
	One   Category = "one"
	Two   Category = "two"
	Few   Category = "few"
	Many  Category = "many"
	Other Category = "other"
)
```

Plural-category names used in translation JSON. Which counts belong to a category depends on the language; Other is the fallback form.

<a id="Plural"></a>

#### Plural

```go
func Plural(lang string, n float64) Category
```

Plural returns the plural category of a count in a language, by the language's rules ("en", "fr", "ru", "ar", ...; a region suffix such as "pt-BR" is ignored except where it matters). Unknown languages use the one/other rule.

<a id="Table"></a>

<a id="Table.Lang"></a>

### Table

```go
type Table struct {
	Lang string // language tag used for plural rules and Bundle lookup
	// contains filtered or unexported fields
}
```

Table is one language's messages.

<a id="NewTable"></a>

#### NewTable

```go
func NewTable(lang string) *Table
```

NewTable makes an empty table for a language.

<a id="ParseTable"></a>

#### ParseTable

```go
func ParseTable(lang string, data []byte) (*Table, error)
```

ParseTable reads a language's messages from JSON: a string per key, or an object of plural forms ("one", "other", ...). Keys may be nested objects, which flatten with dots: {"menu": {"play": "Play"}} is "menu.play".

<a id="Table.Has"></a>

#### Table.Has

```go
func (t *Table) Has(key string) bool
```

Has reports whether the table has a key.

<a id="Table.Keys"></a>

#### Table.Keys

```go
func (t *Table) Keys() []string
```

Keys lists the table's keys, sorted, for checking a translation's coverage against the source language.

<a id="Table.Set"></a>

#### Table.Set

```go
func (t *Table) Set(key, message string)
```

Set adds or replaces a plain message.

<a id="Table.SetPlural"></a>

#### Table.SetPlural

```go
func (t *Table) SetPlural(key string, forms map[Category]string)
```

SetPlural adds or replaces a message with plural forms. Include Other as a fallback: it is not validated, and when both the selected form and Other are absent, lookup chooses an unspecified available form. The map is retained, not copied. A plain entry of the same key takes precedence until the table is replaced.

<a id="Translator"></a>

### Translator

```go
type Translator struct {
	// contains filtered or unexported fields
}
```

Translator translates for one language. Get one with Bundle.For and keep it; changing language means asking for another.

<a id="Translator.Lang"></a>

#### Translator.Lang

```go
func (t *Translator) Lang() string
```

Lang is the language the translator was made for.

<a id="Translator.N"></a>

#### Translator.N

```go
func (t *Translator) N(key string, n int) string
```

N is T for the common case of one count: T(key, "n", n).

<a id="Translator.T"></a>

#### Translator.T

```go
func (t *Translator) T(key string, args ...any) string
```

T translates a key, filling {name} placeholders from args given as alternating names and values ("n", 3, "who", "Ada"). A plural entry picks its form from the value named "n", or the first number given. A missing key returns "\[key]". Without a numeric argument a plural entry uses the category for 1. Unmatched placeholders stay as written; a trailing unpaired argument is ignored. Duplicate placeholder names use the last value.
