Update pkg/locale to support resource translations
This commit is contained in:
parent
2a07d0435f
commit
4da147ec1a
@ -23,7 +23,7 @@ func upgradeRequest(ll *service, r *http.Request) *http.Request {
|
||||
|
||||
func detectLanguage(ll *service, r *http.Request) (tag language.Tag) {
|
||||
if ll.opt.DevelopmentMode {
|
||||
if err := ll.Reload(); err != nil {
|
||||
if err := ll.ReloadStatic(); err != nil {
|
||||
// when in development mode, refresh languages for every request
|
||||
ll.log.Error("failed to load locales", zap.Error(err))
|
||||
return
|
||||
|
||||
@ -2,6 +2,7 @@ package locale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -194,6 +195,15 @@ func loadTranslations(lang *Language) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *service) loadResourceTranslations(ctx context.Context, lang *Language, tg language.Tag) (err error) {
|
||||
lang.resources, err = svc.s.TransformResource(ctx, tg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// procInternal reads internal YAML translation files and converts it into
|
||||
// simple key/value structure
|
||||
func procInternal(ns map[string]string, prefix string, f io.Reader) (err error) {
|
||||
|
||||
@ -20,6 +20,9 @@ type (
|
||||
// application => namespace => buffered JSON docs
|
||||
external map[string]io.ReadSeeker
|
||||
|
||||
// resource => key => ResourceTranslation
|
||||
resources map[string]map[string]*ResourceTranslation
|
||||
|
||||
Language struct {
|
||||
l sync.RWMutex
|
||||
|
||||
@ -49,13 +52,15 @@ type (
|
||||
internal internal
|
||||
|
||||
// External translations (webapps)
|
||||
external external
|
||||
external external
|
||||
resources resources
|
||||
}
|
||||
|
||||
ErrorMetaNamespace struct{}
|
||||
ErrorMetaKey struct{}
|
||||
)
|
||||
|
||||
// t returns the translated string for internal content
|
||||
func (l *Language) t(ns, key string, rr ...string) string {
|
||||
l.l.RLock()
|
||||
defer l.l.RUnlock()
|
||||
@ -75,3 +80,38 @@ func (l *Language) t(ns, key string, rr ...string) string {
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// tr returns the translated string for resource translations
|
||||
func (l *Language) tr(ns, key string, rr ...string) string {
|
||||
l.l.RLock()
|
||||
defer l.l.RUnlock()
|
||||
|
||||
for r := 0; r < len(rr); r += 2 {
|
||||
rr[r] = fmt.Sprintf("{{%s}}", rr[r])
|
||||
}
|
||||
|
||||
rt, has := l.resources[ns][key]
|
||||
if has {
|
||||
return strings.NewReplacer(rr...).Replace(rt.Msg)
|
||||
}
|
||||
|
||||
if l.extends != nil {
|
||||
return l.extends.tr(ns, key, rr...)
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// resourceTranslations returns all resource translations for the specified resource
|
||||
func (l *Language) resourceTranslations(resource string) ResourceTranslationIndex {
|
||||
out := make(ResourceTranslationIndex)
|
||||
if l.resources == nil {
|
||||
return out
|
||||
}
|
||||
|
||||
for k, rt := range l.resources[resource] {
|
||||
out[k] = rt
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
33
pkg/locale/resource.go
Normal file
33
pkg/locale/resource.go
Normal file
@ -0,0 +1,33 @@
|
||||
package locale
|
||||
|
||||
import "golang.org/x/text/language"
|
||||
|
||||
type (
|
||||
ResourceTranslation struct {
|
||||
Resource string `json:"resource"`
|
||||
Lang string `json:"lang"`
|
||||
Key string `json:"key"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
ResourceTranslationIndex map[string]*ResourceTranslation
|
||||
|
||||
ResourceTranslationSet []*ResourceTranslation
|
||||
)
|
||||
|
||||
func ContentID(cID uint64, i int) uint64 {
|
||||
if cID == 0 && i > 0 {
|
||||
return uint64(i)
|
||||
}
|
||||
return cID
|
||||
}
|
||||
|
||||
func (rr ResourceTranslationSet) SetLanguage(tag language.Tag) {
|
||||
for _, r := range rr {
|
||||
r.Lang = tag.String()
|
||||
}
|
||||
}
|
||||
|
||||
func (rx ResourceTranslationIndex) FindByKey(k string) *ResourceTranslation {
|
||||
return rx[k]
|
||||
}
|
||||
@ -16,8 +16,26 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
resourceStore interface {
|
||||
TransformResource(context.Context, language.Tag) (map[string]map[string]*ResourceTranslation, error)
|
||||
}
|
||||
|
||||
Locale interface {
|
||||
T(ctx context.Context, ns, key string, rr ...string) string
|
||||
TFor(tag language.Tag, ns, key string, rr ...string) string
|
||||
Tags() []language.Tag
|
||||
}
|
||||
|
||||
Resource interface {
|
||||
TR(ctx context.Context, ns, key string, rr ...string) string
|
||||
TRFor(tag language.Tag, ns, key string, rr ...string) string
|
||||
Tags() []language.Tag
|
||||
ResourceTranslations(code language.Tag, resource string) ResourceTranslationIndex
|
||||
}
|
||||
|
||||
service struct {
|
||||
l sync.RWMutex
|
||||
s resourceStore
|
||||
|
||||
// logger facility for the locale service
|
||||
log *zap.Logger
|
||||
@ -75,7 +93,11 @@ func Service(log *zap.Logger, opt options.LocaleOpt) (*service, error) {
|
||||
svc.tags = append(svc.tags, tag)
|
||||
}
|
||||
|
||||
return svc, svc.Reload()
|
||||
return svc, svc.ReloadStatic()
|
||||
}
|
||||
|
||||
func (svc *service) BindStore(s resourceStore) {
|
||||
svc.s = s
|
||||
}
|
||||
|
||||
// Default language
|
||||
@ -93,9 +115,9 @@ func (svc *service) Tags() (tt []language.Tag) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reload all embedded (via github.com/cortezaproject/corteza-locale package)
|
||||
// and imported (from one or more paths found in LOCALE_PATH) translations
|
||||
func (svc *service) Reload() (err error) {
|
||||
// ReloadStatic all language configurations (as configured via path options) and
|
||||
// all translation files
|
||||
func (svc *service) ReloadStatic() (err error) {
|
||||
var (
|
||||
i int
|
||||
lang *Language
|
||||
@ -107,7 +129,7 @@ func (svc *service) Reload() (err error) {
|
||||
svc.l.RLock()
|
||||
defer svc.l.RUnlock()
|
||||
|
||||
svc.log.Info("reloading",
|
||||
svc.log.Info("reloading static",
|
||||
zap.Strings("path", svc.src),
|
||||
zap.Strings("tags", tagsToStrings(svc.tags)),
|
||||
)
|
||||
@ -203,6 +225,60 @@ func (svc *service) Reload() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReloadResourceTranslations all language configurations (as configured via path options) and
|
||||
// all translation files
|
||||
func (svc *service) ReloadResourceTranslations(ctx context.Context) (err error) {
|
||||
svc.l.RLock()
|
||||
defer svc.l.RUnlock()
|
||||
|
||||
svc.log.Info("reloading resource translations",
|
||||
zap.Strings("tags", tagsToStrings(svc.tags)),
|
||||
)
|
||||
|
||||
for i, tag := range svc.tags {
|
||||
lang, ok := svc.set[tag]
|
||||
if !ok {
|
||||
lang = &Language{
|
||||
Tag: tag,
|
||||
src: "store",
|
||||
}
|
||||
|
||||
svc.set[tag] = lang
|
||||
}
|
||||
|
||||
if err = svc.loadResourceTranslations(ctx, lang, lang.Tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svc.log.Info(
|
||||
"language loaded",
|
||||
zap.Stringer("tag", lang.Tag),
|
||||
zap.String("src", lang.src),
|
||||
zap.Stringer("extends", lang.Extends),
|
||||
)
|
||||
|
||||
if i == 0 && svc.def == nil {
|
||||
// set first one as default
|
||||
svc.def = lang
|
||||
}
|
||||
}
|
||||
|
||||
// Do another pass and link all extended languages
|
||||
for _, lang := range svc.set {
|
||||
if lang.Extends.IsRoot() {
|
||||
continue
|
||||
}
|
||||
|
||||
if svc.set[lang.Extends] == nil {
|
||||
return fmt.Errorf("could not extend langage %q from an unknown language %q", lang.Tag, lang.Extends)
|
||||
}
|
||||
|
||||
lang.extends = svc.set[lang.Extends]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns list of all languages
|
||||
func (svc *service) List() []*Language {
|
||||
svc.l.RLock()
|
||||
@ -304,6 +380,43 @@ func (svc *service) T(ctx context.Context, ns, key string, rr ...string) string
|
||||
return svc.t(GetLanguageFromContext(ctx), ns, key, rr...)
|
||||
}
|
||||
|
||||
// T returns translated key from namespaces using list of replacement pairs
|
||||
//
|
||||
// Language is specified
|
||||
func (svc *service) TFor(tag language.Tag, ns, key string, rr ...string) string {
|
||||
return svc.t(tag, ns, key, rr...)
|
||||
}
|
||||
|
||||
// TR returns translated key for resource using list of replacement pairs
|
||||
//
|
||||
// Language is picked from the context
|
||||
func (svc *service) TR(ctx context.Context, ns, key string, rr ...string) string {
|
||||
return svc.tr(GetLanguageFromContext(ctx), ns, key, rr...)
|
||||
}
|
||||
|
||||
// TRFor returns translated key for resource using list of replacement pairs
|
||||
//
|
||||
// Language is picked from the context
|
||||
func (svc *service) TRFor(tag language.Tag, ns, key string, rr ...string) string {
|
||||
return svc.tr(tag, ns, key, rr...)
|
||||
}
|
||||
|
||||
// ResourceTranslations returns all translations for the given language for the
|
||||
// given resource.
|
||||
//
|
||||
// The response is indexed by translation key for nicer lookups.
|
||||
func (svc *service) ResourceTranslations(code language.Tag, resource string) ResourceTranslationIndex {
|
||||
out := make(ResourceTranslationIndex)
|
||||
|
||||
if svc != nil && svc.set != nil {
|
||||
if l, has := svc.set[code]; has {
|
||||
return l.resourceTranslations(resource)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Finds language and uses it to translate the given key
|
||||
func (svc *service) t(code language.Tag, ns, key string, rr ...string) string {
|
||||
if svc != nil && svc.set != nil {
|
||||
@ -315,6 +428,17 @@ func (svc *service) t(code language.Tag, ns, key string, rr ...string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
// Finds language and uses it to translate the given key for resource
|
||||
func (svc *service) tr(code language.Tag, ns, key string, rr ...string) string {
|
||||
if svc != nil && svc.set != nil {
|
||||
if l, has := svc.set[code]; has {
|
||||
return l.tr(ns, key, rr...)
|
||||
}
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func hasTag(t language.Tag, tt []language.Tag) bool {
|
||||
for _, tag := range tt {
|
||||
if tag.String() == t.String() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user