iris/i18n/internal/catalog.go
2023-03-18 15:43:18 +02:00

151 lines
4.0 KiB
Go

package internal
import (
"fmt"
"text/template"
"github.com/kataras/iris/v12/context"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/message/catalog"
)
// 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.
//
// Note: we don't accept the Context here because Tr method and template func {{ tr }}
// have no direct access to it.
type MessageFunc func(langInput, langMatched, key string, args ...interface{}) string
// Catalog holds the locales and the variables message storage.
type Catalog struct {
builder *catalog.Builder
Locales []*Locale
}
// The Options of the Catalog and its Locales.
type Options struct {
// Left delimiter for template messages.
Left string
// Right delimeter for template messages.
Right string
// Enable strict mode.
Strict bool
// Optional functions for template messages per locale.
Funcs func(context.Locale) template.FuncMap
// Optional function to be called when no message was found.
DefaultMessageFunc MessageFunc
// Customize the overall behavior of the plurazation feature.
PluralFormDecoder PluralFormDecoder
}
// NewCatalog returns a new Catalog based on the registered languages and the loader options.
func NewCatalog(languages []language.Tag, opts Options) (*Catalog, error) { // ordered languages, the first should be the default one.
if len(languages) == 0 {
return nil, fmt.Errorf("catalog: empty languages")
}
if opts.Left == "" {
opts.Left = "{{"
}
if opts.Right == "" {
opts.Right = "}}"
}
if opts.PluralFormDecoder == nil {
opts.PluralFormDecoder = DefaultPluralFormDecoder
}
builder := catalog.NewBuilder(catalog.Fallback(languages[0]))
locales := make([]*Locale, 0, len(languages))
for idx, tag := range languages {
locale := &Locale{
tag: tag,
index: idx,
ID: tag.String(),
Options: opts,
Printer: message.NewPrinter(tag, message.Catalog(builder)),
Messages: make(map[string]Renderer),
}
locale.FuncMap = getFuncs(locale)
locales = append(locales, locale)
}
c := &Catalog{
builder: builder,
Locales: locales,
}
return c, nil
}
// Set sets a simple translation message.
func (c *Catalog) Set(tag language.Tag, key string, msgs ...catalog.Message) error {
// fmt.Printf("Catalog.Set[%s] %s:\n", tag.String(), key)
// for _, msg := range msgs {
// fmt.Printf("%#+v\n", msg)
// }
return c.builder.Set(tag, key, msgs...)
}
// Store stores the a map of values to the locale derives from the given "langIndex".
func (c *Catalog) Store(langIndex int, kv Map) error {
loc := c.getLocale(langIndex)
if loc == nil {
return fmt.Errorf("expected language index to be lower or equal than %d but got %d", len(c.Locales), langIndex)
}
return loc.Load(c, kv)
}
/* Localizer interface. */
// SetDefault changes the default language based on the "index".
// See `I18n#SetDefault` method for more.
func (c *Catalog) SetDefault(index int) bool {
if index < 0 {
index = 0
}
if maxIdx := len(c.Locales) - 1; index > maxIdx {
return false
}
// callers should protect with mutex if called at serve-time.
loc := c.Locales[index]
loc.index = 0
f := c.Locales[0]
c.Locales[0] = loc
f.index = index
c.Locales[index] = f
return true
}
// GetLocale returns a valid `Locale` based on the "index".
func (c *Catalog) GetLocale(index int) context.Locale {
return c.getLocale(index)
}
func (c *Catalog) getLocale(index int) *Locale {
if index < 0 {
index = 0
}
if maxIdx := len(c.Locales) - 1; index > maxIdx {
// panic("expected language index to be lower or equal than %d but got %d", maxIdx, langIndex)
return nil
}
loc := c.Locales[index]
return loc
}