Skip to content
This repository has been archived by the owner on Aug 16, 2022. It is now read-only.

Commit

Permalink
feat: support 3rd party plugin translation (#109)
Browse files Browse the repository at this point in the history
* add i18n.String.WithDefault, rename i18n.String.Copy

* add id constructors to property

* make schema group fields nil

* change schema id pattern

* fix test

* refactor manifest translation

* support 3rd party plugin translation
  • Loading branch information
rot1024 committed Feb 9, 2022
1 parent 7002696 commit 67a618e
Show file tree
Hide file tree
Showing 36 changed files with 1,062 additions and 483 deletions.
26 changes: 0 additions & 26 deletions internal/infrastructure/fs/common.go
@@ -1,34 +1,8 @@
package fs

import (
"path/filepath"

"github.com/reearth/reearth-backend/pkg/id"
"github.com/reearth/reearth-backend/pkg/plugin/manifest"
"github.com/reearth/reearth-backend/pkg/rerror"
"github.com/spf13/afero"
)

const (
assetDir = "assets"
pluginDir = "plugins"
publishedDir = "published"
manifestFilePath = "reearth.yml"
)

func readManifest(fs afero.Fs, pid id.PluginID) (*manifest.Manifest, error) {
f, err := fs.Open(filepath.Join(pluginDir, pid.String(), manifestFilePath))
if err != nil {
return nil, rerror.ErrInternalBy(err)
}
defer func() {
_ = f.Close()
}()

m, err := manifest.Parse(f, nil)
if err != nil {
return nil, err
}

return m, nil
}
20 changes: 11 additions & 9 deletions internal/infrastructure/fs/file_test.go
Expand Up @@ -249,15 +249,17 @@ func TestGetAssetFileURL(t *testing.T) {
}

func mockFs() afero.Fs {
files := map[string]string{
"assets/xxx.txt": "hello",
"plugins/aaa~1.0.0/foo.js": "bar",
"published/s.json": "{}",
}

fs := afero.NewMemMapFs()
f, _ := fs.Create("assets/xxx.txt")
_, _ = f.WriteString("hello")
_ = f.Close()
f, _ = fs.Create("plugins/aaa~1.0.0/foo.js")
_, _ = f.WriteString("bar")
_ = f.Close()
f, _ = fs.Create("published/s.json")
_, _ = f.WriteString("{}")
_ = f.Close()
for name, content := range files {
f, _ := fs.Create(name)
_, _ = f.WriteString(content)
_ = f.Close()
}
return fs
}
63 changes: 62 additions & 1 deletion internal/infrastructure/fs/plugin.go
Expand Up @@ -3,10 +3,13 @@ package fs
import (
"context"
"errors"
"path/filepath"
"regexp"

"github.com/reearth/reearth-backend/internal/usecase/repo"
"github.com/reearth/reearth-backend/pkg/id"
"github.com/reearth/reearth-backend/pkg/plugin"
"github.com/reearth/reearth-backend/pkg/plugin/manifest"
"github.com/reearth/reearth-backend/pkg/rerror"
"github.com/spf13/afero"
)
Expand All @@ -22,7 +25,7 @@ func NewPlugin(fs afero.Fs) repo.Plugin {
}

func (r *pluginRepo) FindByID(ctx context.Context, pid id.PluginID, sids []id.SceneID) (*plugin.Plugin, error) {
m, err := readManifest(r.fs, pid)
m, err := readPluginManifest(r.fs, pid)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -54,3 +57,61 @@ func (r *pluginRepo) Save(ctx context.Context, p *plugin.Plugin) error {
func (r *pluginRepo) Remove(ctx context.Context, pid id.PluginID) error {
return rerror.ErrInternalBy(errors.New("read only"))
}

var translationFileNameRegexp = regexp.MustCompile(`reearth_([a-zA-Z]+(?:-[a-zA-Z]+)?).yml`)

func readPluginManifest(fs afero.Fs, pid id.PluginID) (*manifest.Manifest, error) {
base := filepath.Join(pluginDir, pid.String())
translationMap, err := readPluginTranslation(fs, base)
if err != nil {
return nil, err
}

f, err := fs.Open(filepath.Join(base, manifestFilePath))
if err != nil {
return nil, rerror.ErrInternalBy(err)
}
defer func() {
_ = f.Close()
}()

m, err := manifest.Parse(f, nil, translationMap.TranslatedRef())
if err != nil {
return nil, err
}

return m, nil
}

func readPluginTranslation(fs afero.Fs, base string) (manifest.TranslationMap, error) {
d, err := afero.ReadDir(fs, base)
if err != nil {
return nil, rerror.ErrInternalBy(err)
}

translationMap := manifest.TranslationMap{}
for _, e := range d {
if e.IsDir() {
continue
}
name := e.Name()
lang := translationFileNameRegexp.FindStringSubmatch(name)
if len(lang) == 0 {
continue
}
langfile, err := fs.Open(filepath.Join(base, name))
if err != nil {
return nil, rerror.ErrInternalBy(err)
}
defer func() {
_ = langfile.Close()
}()
t, err := manifest.ParseTranslation(langfile)
if err != nil {
return nil, err
}
translationMap[lang[1]] = t
}

return translationMap, nil
}
2 changes: 1 addition & 1 deletion internal/infrastructure/fs/plugin_repository.go
Expand Up @@ -26,5 +26,5 @@ func (r *pluginRepository) Data(ctx context.Context, id id.PluginID) (file.Itera
}

func (r *pluginRepository) Manifest(ctx context.Context, id id.PluginID) (*manifest.Manifest, error) {
return readManifest(r.fs, id)
return readPluginManifest(r.fs, id)
}
39 changes: 39 additions & 0 deletions internal/infrastructure/fs/plugin_test.go
@@ -0,0 +1,39 @@
package fs

import (
"context"
"testing"

"github.com/reearth/reearth-backend/pkg/i18n"
"github.com/reearth/reearth-backend/pkg/plugin"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)

func TestPlugin(t *testing.T) {
ctx := context.Background()
fs := NewPlugin(mockPluginFS())
p, err := fs.FindByID(ctx, plugin.MustID("testplugin~1.0.0"), nil)
assert.NoError(t, err)
assert.Equal(t, plugin.New().ID(plugin.MustID("testplugin~1.0.0")).Name(i18n.String{
"en": "testplugin",
"ja": "テストプラグイン",
"zh-CN": "测试插件",
}).MustBuild(), p)
}

func mockPluginFS() afero.Fs {
files := map[string]string{
"plugins/testplugin~1.0.0/reearth.yml": `{ "id": "testplugin", "version": "1.0.0", "name": "testplugin" }`,
"plugins/testplugin~1.0.0/reearth_ja.yml": `{ "name": "テストプラグイン" }`,
"plugins/testplugin~1.0.0/reearth_zh-CN.yml": `{ "name": "测试插件" }`,
}

fs := afero.NewMemMapFs()
for name, content := range files {
f, _ := fs.Create(name)
_, _ = f.WriteString(content)
_ = f.Close()
}
return fs
}
2 changes: 1 addition & 1 deletion internal/infrastructure/fs/property_schema.go
Expand Up @@ -22,7 +22,7 @@ func NewPropertySchema(fs afero.Fs) repo.PropertySchema {
}

func (r *propertySchema) FindByID(ctx context.Context, i id.PropertySchemaID) (*property.Schema, error) {
m, err := readManifest(r.fs, i.Plugin())
m, err := readPluginManifest(r.fs, i.Plugin())
if err != nil {
return nil, err
}
Expand Down
14 changes: 8 additions & 6 deletions pkg/builtin/main.go
Expand Up @@ -15,14 +15,16 @@ var pluginManifestJSON []byte
//go:embed manifest_ja.yml
var pluginManifestJSON_ja []byte

var pluginTranslationList = map[string]*manifest.TranslationRoot{"ja": manifest.MustParseTranslationFromBytes(pluginManifestJSON_ja)}
var pluginManifest = manifest.MergeManifestTranslation(manifest.MustParseSystemFromBytes(pluginManifestJSON, nil), pluginTranslationList)

// MUST NOT CHANGE
var PropertySchemaIDVisualizerCesium = property.MustSchemaID("reearth/cesium")
var pluginTranslationList = manifest.TranslationMap{
"ja": manifest.MustParseTranslationFromBytes(pluginManifestJSON_ja),
}
var pluginManifest = manifest.MustParseSystemFromBytes(pluginManifestJSON, nil, pluginTranslationList.TranslatedRef())

// MUST NOT CHANGE
var PropertySchemaIDInfobox = property.MustSchemaID("reearth/infobox")
var (
PropertySchemaIDVisualizerCesium = property.MustSchemaID("reearth/cesium")
PropertySchemaIDInfobox = property.MustSchemaID("reearth/infobox")
)

func GetPropertySchemaByVisualizer(v visualizer.Visualizer) *property.Schema {
for _, p := range pluginManifest.ExtensionSchema {
Expand Down
34 changes: 29 additions & 5 deletions pkg/i18n/string.go
@@ -1,12 +1,36 @@
package i18n

const DefaultLang = "en"

type String map[string]string // key should use BCP 47 representation

func StringFrom(s string) String {
if s == "" {
return String{}
}
return String{DefaultLang: s}
}

func (s String) WithDefault(d string) String {
if s == nil && d == "" {
return nil
}
return String{"en": s}

res := s.Clone()
if res == nil {
res = String{}
}
if d != "" {
res[DefaultLang] = d
}
return res
}

func (s String) WithDefaultRef(d *string) String {
if d == nil {
return s.Clone()
}
return s.WithDefault(*d)
}

func (s String) Translated(lang ...string) string {
Expand All @@ -21,8 +45,8 @@ func (s String) Translated(lang ...string) string {
return s.String()
}

func (s String) Copy() String {
if s == nil {
func (s String) Clone() String {
if len(s) == 0 {
return nil
}
s2 := make(String, len(s))
Expand All @@ -36,13 +60,13 @@ func (s String) String() string {
if s == nil {
return ""
}
return s["en"]
return s[DefaultLang]
}

func (s String) StringRef() *string {
if s == nil {
return nil
}
st := s["en"]
st := s[DefaultLang]
return &st
}

0 comments on commit 67a618e

Please sign in to comment.