snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
248
server/pkg/locale/load.go
Normal file
248
server/pkg/locale/load.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package locale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const serverApplication = "corteza-server"
|
||||
|
||||
var (
|
||||
defaultLanguage = language.English
|
||||
)
|
||||
|
||||
func loadConfigs(fsys fs.FS) (ll []*Language, err error) {
|
||||
var (
|
||||
sub fs.FS
|
||||
lang *Language
|
||||
configs = make([]string, 0)
|
||||
)
|
||||
|
||||
configs, err = fs.Glob(fsys, "*/config.yaml")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
if sub, err = fs.Sub(fsys, filepath.Dir(config)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lang, err = loadConfig(sub, filepath.Base(config)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lang.Tag = language.Make(filepath.Base(filepath.Dir(config)))
|
||||
lang.src = filepath.Dir(config)
|
||||
|
||||
ll = append(ll, lang)
|
||||
}
|
||||
|
||||
return ll, nil
|
||||
}
|
||||
|
||||
func loadConfig(fsys fs.FS, p string) (lang *Language, err error) {
|
||||
var f fs.File
|
||||
lang = &Language{
|
||||
Tag: language.Make(filepath.Base(filepath.Dir(p))),
|
||||
fs: fsys,
|
||||
}
|
||||
|
||||
f, err = fsys.Open(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read %s: %v", p, err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
if err = yaml.NewDecoder(f).Decode(&lang); err != nil {
|
||||
return nil, fmt.Errorf("could not decode %s: %v", p, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadTranslations(lang *Language) (err error) {
|
||||
lang.internal = make(internal)
|
||||
lang.external = make(external)
|
||||
|
||||
var (
|
||||
// this will help us collect structured keys from YAML files
|
||||
// for external translations
|
||||
auxExternal = make(map[string]map[string]map[string]interface{})
|
||||
)
|
||||
|
||||
err = fs.WalkDir(lang.fs, ".", func(p string, entry fs.DirEntry, err error) error {
|
||||
if err != nil || entry.IsDir() {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
f fs.File
|
||||
firstSep = strings.Index(p, string(filepath.Separator))
|
||||
)
|
||||
|
||||
if firstSep == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// From this point on, we're assuming we are at least 1 level deep
|
||||
// and the name of the 1st directory is the name of the application
|
||||
// that the content is for
|
||||
var (
|
||||
namespace string
|
||||
keyPath string
|
||||
|
||||
ext = path.Ext(p)
|
||||
appDir = p[:firstSep]
|
||||
subpath = p[len(appDir)+1:]
|
||||
nsDirIndex = strings.Index(subpath, string(filepath.Separator))
|
||||
nsDotIndex = strings.LastIndex(subpath, ".")
|
||||
|
||||
isYAML = ext == ".yaml" || ext == ".yml"
|
||||
isJSON = ext == ".json"
|
||||
)
|
||||
|
||||
if !isYAML && !isJSON {
|
||||
// unsupported format, skipping
|
||||
return nil
|
||||
}
|
||||
|
||||
if appDir == serverApplication && isJSON {
|
||||
return fmt.Errorf("expecting YAML translation files for server translations")
|
||||
}
|
||||
|
||||
// namespace is whatever we put on the 1st level - either filename or directory
|
||||
if nsDirIndex > 0 {
|
||||
namespace = subpath[:nsDirIndex]
|
||||
|
||||
keyPath = subpath[nsDirIndex+1:]
|
||||
keyPath = keyPath[:len(keyPath)-len(ext)]
|
||||
keyPath = strings.ReplaceAll(keyPath, string(filepath.Separator), ".")
|
||||
} else if nsDotIndex > 0 {
|
||||
namespace = subpath[:nsDotIndex]
|
||||
}
|
||||
|
||||
if f, err = lang.fs.Open(p); err != nil {
|
||||
return fmt.Errorf("could not open %s: %w", p, err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
if appDir == serverApplication {
|
||||
if lang.internal[namespace] == nil {
|
||||
lang.internal[namespace] = make(map[string]string)
|
||||
}
|
||||
|
||||
if err = procInternal(lang.internal[namespace], keyPath, f); err != nil {
|
||||
return fmt.Errorf("could not process %s: %v", p, err)
|
||||
}
|
||||
} else {
|
||||
if isJSON {
|
||||
if lang.external[appDir] != nil {
|
||||
return fmt.Errorf("only one JSON file per namespace supported")
|
||||
}
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
if _, err = io.Copy(buf, f); err != nil {
|
||||
return fmt.Errorf("could not copy buffer: %v", err)
|
||||
}
|
||||
|
||||
lang.external[appDir] = bytes.NewReader(buf.Bytes())
|
||||
} else {
|
||||
if auxExternal[appDir] == nil {
|
||||
auxExternal[appDir] = make(map[string]map[string]interface{})
|
||||
}
|
||||
if auxExternal[appDir][namespace] == nil {
|
||||
auxExternal[appDir][namespace] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if err = procExternal(auxExternal[appDir][namespace], keyPath, f); err != nil {
|
||||
return fmt.Errorf("could not process %s: %v", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// encode all external (for webapps, clientS) translations
|
||||
// into buffers that will be copied directly into responses
|
||||
for app, nss := range auxExternal {
|
||||
var (
|
||||
buf = &bytes.Buffer{}
|
||||
)
|
||||
|
||||
if err = json.NewEncoder(buf).Encode(nss); err != nil {
|
||||
return
|
||||
}
|
||||
lang.external[app] = bytes.NewReader(buf.Bytes())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *service) LoadResourceTranslations(ctx context.Context, tg language.Tag) (out map[string]map[string]*ResourceTranslation, err error) {
|
||||
return svc.s.TransformResource(ctx, tg)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var (
|
||||
aux = make(map[string]interface{})
|
||||
|
||||
// this allows us to do recursive calls to lambda
|
||||
flatten func(prefix string, sub map[string]interface{})
|
||||
)
|
||||
|
||||
if err = yaml.NewDecoder(f).Decode(&aux); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
flatten = func(prefix string, sub map[string]interface{}) {
|
||||
if len(prefix) > 0 {
|
||||
prefix += "."
|
||||
}
|
||||
|
||||
for k, v := range sub {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
ns[prefix+k] = val
|
||||
case map[string]interface{}:
|
||||
flatten(prefix+k, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flatten(prefix, aux)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func procExternal(kv map[string]interface{}, prefix string, f io.Reader) error {
|
||||
// get to the level we want to be:
|
||||
if len(prefix) > 0 {
|
||||
for _, p := range strings.Split(prefix, ".") {
|
||||
if kv[p] == nil {
|
||||
kv[p] = make(map[string]interface{})
|
||||
kv = kv[p].(map[string]interface{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return yaml.NewDecoder(f).Decode(&kv)
|
||||
}
|
||||
Reference in New Issue
Block a user