feat(04-02): load namespaced translations from embedded plugins
- Walk HasLang FS for lang/<locale>/<group>.yaml and flatten nested keys - Publish one phrasebook.Translator before Boot with en/en defaults - Hello plugin resolves en/pl vendor.plugin::group.key after Activate Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/phrasebook"
|
||||
)
|
||||
|
||||
// Plugin is the required descriptor every compiled plugin implements.
|
||||
@@ -116,6 +117,9 @@ func activate(registered []Plugin, app *backpack.App, ids []string) ([]Plugin, e
|
||||
return nil, fmt.Errorf("party: register %s: %w", p.ID(), err)
|
||||
}
|
||||
}
|
||||
if err := phrasebook.Activate(app, ordered); err != nil {
|
||||
return nil, fmt.Errorf("party: lang: %w", err)
|
||||
}
|
||||
for _, p := range ordered {
|
||||
if err := p.Boot(app); err != nil {
|
||||
return nil, fmt.Errorf("party: boot %s: %w", p.ID(), err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package party
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"git.golem15.com/golem15/summercms/bonfire"
|
||||
"git.golem15.com/golem15/summercms/compass"
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/phrasebook"
|
||||
)
|
||||
|
||||
type tracePlugin struct {
|
||||
@@ -394,3 +396,62 @@ func TestActivateDiscoversHasConfigAndHasCommands(t *testing.T) {
|
||||
t.Fatal("plain plugin must not satisfy HasConfig")
|
||||
}
|
||||
}
|
||||
|
||||
type langPlugin struct {
|
||||
id string
|
||||
fsys fs.FS
|
||||
got *string
|
||||
}
|
||||
|
||||
func (p *langPlugin) ID() string { return p.id }
|
||||
func (p *langPlugin) Requires() []string { return nil }
|
||||
func (p *langPlugin) Register(*backpack.App) error { return nil }
|
||||
func (p *langPlugin) LangFS() fs.FS { return p.fsys }
|
||||
|
||||
func (p *langPlugin) Boot(app *backpack.App) error {
|
||||
if p.got == nil {
|
||||
return nil
|
||||
}
|
||||
tr, ok := app.Lookup[*phrasebook.Translator]()
|
||||
if !ok {
|
||||
return fmt.Errorf("translator missing at boot")
|
||||
}
|
||||
*p.got = tr.GetIn("en", p.id+"::lang.greeting", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestActivatePublishesTranslatorBeforeBoot(t *testing.T) {
|
||||
var got string
|
||||
p := &langPlugin{
|
||||
id: "golem15.hello",
|
||||
got: &got,
|
||||
fsys: fstest.MapFS{
|
||||
"lang/en/lang.yaml": {Data: []byte("greeting: Hello\n")},
|
||||
},
|
||||
}
|
||||
app := backpack.New(&compass.Config{})
|
||||
if _, err := activate([]Plugin{p}, app, []string{"golem15.hello"}); err != nil {
|
||||
t.Fatalf("activate: %v", err)
|
||||
}
|
||||
if got != "Hello" {
|
||||
t.Fatalf("boot lookup = %q, want Hello (translator must be published before Boot)", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivateMalformedLangFailsWithPluginContext(t *testing.T) {
|
||||
p := &langPlugin{
|
||||
id: "golem15.hello",
|
||||
fsys: fstest.MapFS{
|
||||
"lang/en.yaml": {Data: []byte("greeting: Hello\n")},
|
||||
},
|
||||
}
|
||||
app := backpack.New(&compass.Config{})
|
||||
_, err := activate([]Plugin{p}, app, []string{"golem15.hello"})
|
||||
if err == nil {
|
||||
t.Fatal("expected malformed catalog error")
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, "golem15.hello") || !strings.Contains(msg, "malformed path") {
|
||||
t.Fatalf("error %q should name the plugin and path problem", msg)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user