2021-01-23 14:04:30 +00:00
|
|
|
// i18n is a simple package that translates strings using a language map.
|
|
|
|
// It mimicks some functionality of the vue-i18n library so that the same JSON
|
2022-02-28 13:19:50 +00:00
|
|
|
// language map may be used in the JS frontend and the Go backend.
|
2020-12-19 10:55:52 +00:00
|
|
|
package i18n
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2021-01-23 14:04:30 +00:00
|
|
|
"errors"
|
2020-12-19 10:55:52 +00:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2021-01-23 14:04:30 +00:00
|
|
|
// I18n offers translation functions over a language map.
|
|
|
|
type I18n struct {
|
|
|
|
code string `json:"code"`
|
|
|
|
name string `json:"name"`
|
2020-12-19 10:55:52 +00:00
|
|
|
langMap map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
var reParam = regexp.MustCompile(`(?i)\{([a-z0-9-.]+)\}`)
|
|
|
|
|
|
|
|
// New returns an I18n instance.
|
2021-01-23 14:04:30 +00:00
|
|
|
func New(b []byte) (*I18n, error) {
|
2020-12-19 10:55:52 +00:00
|
|
|
var l map[string]string
|
|
|
|
if err := json.Unmarshal(b, &l); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-01-23 14:04:30 +00:00
|
|
|
|
|
|
|
code, ok := l["_.code"]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("missing _.code field in language file")
|
|
|
|
}
|
|
|
|
|
|
|
|
name, ok := l["_.name"]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("missing _.name field in language file")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &I18n{
|
2020-12-19 10:55:52 +00:00
|
|
|
langMap: l,
|
2021-01-23 14:04:30 +00:00
|
|
|
code: code,
|
|
|
|
name: name,
|
2020-12-19 10:55:52 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-01-26 16:29:27 +00:00
|
|
|
// Load loads a JSON language map into the instance overwriting
|
|
|
|
// existing keys that conflict.
|
|
|
|
func (i *I18n) Load(b []byte) error {
|
|
|
|
var l map[string]string
|
|
|
|
if err := json.Unmarshal(b, &l); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range l {
|
|
|
|
i.langMap[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-23 14:04:30 +00:00
|
|
|
// Name returns the canonical name of the language.
|
|
|
|
func (i *I18n) Name() string {
|
|
|
|
return i.name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Code returns the ISO code of the language.
|
|
|
|
func (i *I18n) Code() string {
|
|
|
|
return i.code
|
|
|
|
}
|
|
|
|
|
2020-12-19 10:55:52 +00:00
|
|
|
// JSON returns the languagemap as raw JSON.
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) JSON() []byte {
|
2020-12-19 10:55:52 +00:00
|
|
|
b, _ := json.Marshal(i.langMap)
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// T returns the translation for the given key similar to vue i18n's t().
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) T(key string) string {
|
2020-12-19 10:55:52 +00:00
|
|
|
s, ok := i.langMap[key]
|
|
|
|
if !ok {
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
return i.getSingular(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ts returns the translation for the given key similar to vue i18n's t()
|
2022-02-28 13:19:50 +00:00
|
|
|
// and substitutes the params in the given map in the translated value.
|
2020-12-19 10:55:52 +00:00
|
|
|
// In the language values, the substitutions are represented as: {key}
|
|
|
|
// The params and values are received as a pairs of succeeding strings.
|
|
|
|
// That is, the number of these arguments should be an even number.
|
2021-01-23 13:24:33 +00:00
|
|
|
// eg: Ts("globals.message.notFound",
|
2020-12-19 10:55:52 +00:00
|
|
|
// "name", "campaigns",
|
|
|
|
// "error", err)
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) Ts(key string, params ...string) string {
|
2020-12-19 10:55:52 +00:00
|
|
|
if len(params)%2 != 0 {
|
|
|
|
return key + `: Invalid arguments`
|
|
|
|
}
|
|
|
|
|
|
|
|
s, ok := i.langMap[key]
|
|
|
|
if !ok {
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
s = i.getSingular(s)
|
|
|
|
for n := 0; n < len(params); n += 2 {
|
|
|
|
// If there are {params} in the param values, substitute them.
|
|
|
|
val := i.subAllParams(params[n+1])
|
|
|
|
s = strings.ReplaceAll(s, `{`+params[n]+`}`, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tc returns the translation for the given key similar to vue i18n's tc().
|
|
|
|
// It expects the language string in the map to be of the form `Singular | Plural` and
|
|
|
|
// returns `Plural` if n > 1, or `Singular` otherwise.
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) Tc(key string, n int) string {
|
2020-12-19 10:55:52 +00:00
|
|
|
s, ok := i.langMap[key]
|
|
|
|
if !ok {
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
// Plural.
|
|
|
|
if n > 1 {
|
|
|
|
return i.getPlural(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
return i.getSingular(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getSingular returns the singular term from the vuei18n pipe separated value.
|
|
|
|
// singular term | plural term
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) getSingular(s string) string {
|
2020-12-19 10:55:52 +00:00
|
|
|
if !strings.Contains(s, "|") {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSpace(strings.Split(s, "|")[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
// getSingular returns the plural term from the vuei18n pipe separated value.
|
|
|
|
// singular term | plural term
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) getPlural(s string) string {
|
2020-12-19 10:55:52 +00:00
|
|
|
if !strings.Contains(s, "|") {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
chunks := strings.Split(s, "|")
|
|
|
|
if len(chunks) == 2 {
|
|
|
|
return strings.TrimSpace(chunks[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSpace(chunks[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
// subAllParams recursively resolves and replaces all {params} in a string.
|
2021-01-23 14:04:30 +00:00
|
|
|
func (i *I18n) subAllParams(s string) string {
|
2020-12-19 10:55:52 +00:00
|
|
|
if !strings.Contains(s, `{`) {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := reParam.FindAllStringSubmatch(s, -1)
|
|
|
|
if len(parts) < 1 {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range parts {
|
|
|
|
s = strings.ReplaceAll(s, p[0], i.T(p[1]))
|
|
|
|
}
|
|
|
|
|
|
|
|
return i.subAllParams(s)
|
|
|
|
}
|