diff --git a/message/extract/extract.go b/message/extract/extract.go index ce38e69..bb777e3 100644 --- a/message/extract/extract.go +++ b/message/extract/extract.go @@ -19,6 +19,7 @@ import ( "sync" "github.com/issue9/localeutil" + "golang.org/x/text/language" "golang.org/x/tools/go/packages" "github.com/issue9/localeutil/message" @@ -62,7 +63,7 @@ func Extract(ctx context.Context, o *Options) (*message.File, error) { slices.SortStableFunc(ex.msg, func(a, b message.Message) int { return cmp.Compare(a.Key, b.Key) }) - return &message.File{ID: o.Language, Messages: ex.msg}, nil + return &message.File{Languages: []language.Tag{o.Language}, Messages: ex.msg}, nil } func (ex *extractor) inspectDirs(ctx context.Context, dirs []string) error { diff --git a/message/extract/extract_test.go b/message/extract/extract_test.go index 896b7ff..c230ab3 100644 --- a/message/extract/extract_test.go +++ b/message/extract/extract_test.go @@ -40,7 +40,7 @@ func TestExtract(t *testing.T) { } l, err := Extract(context.Background(), o) a.NotError(err).NotNil(l). - Equal(l.ID.String(), "zh-CN") + Equal(l.Languages, []language.Tag{language.MustParse("zh-CN")}) m := l.Messages a.NotNil(m). diff --git a/message/message.go b/message/message.go index fabc6ea..7dfe5b5 100644 --- a/message/message.go +++ b/message/message.go @@ -20,9 +20,9 @@ import ( type ( // File 单个本地化语言组成的文件 File struct { - XMLName struct{} `xml:"language" json:"-" yaml:"-"` - ID language.Tag `xml:"id,attr" json:"id" yaml:"id"` // 如果用字符串,还需要处理大小写以及不同值表示同一个 language.Tag 对象的问题 - Messages []Message `xml:"message" json:"messages" yaml:"messages"` + XMLName struct{} `xml:"language" json:"-" yaml:"-"` + Languages []language.Tag `xml:"languages>language" json:"language" yaml:"language"` // 如果用字符串,还需要处理大小写以及不同值表示同一个 language.Tag 对象的问题 + Messages []Message `xml:"message" json:"messages" yaml:"messages"` } // Message 单条本地化内容 @@ -75,7 +75,7 @@ func (l *File) Join(l2 *File) { } } -// Merge 将 l.Messages 写入 dest 中的每个元素 +// Merge 将 l.Messages 写入 dest // // 这将会执行以下几个步骤: // @@ -84,18 +84,13 @@ func (l *File) Join(l2 *File) { // // 最终内容是 dest 为准。 // log 所有删除的记录都将通过此输出; -func (f *File) MergeTo(log LogFunc, dest []*File) { - for _, d := range dest { - f.mergeTo(log, d) - } -} - -func (f *File) mergeTo(log LogFunc, dest *File) { +// destFile 最终输出的文件名,该值仅在错误信息中; +func (f *File) MergeTo(log LogFunc, dest *File, destFile string) { // 删除只存在于 dest 而不存在于 l 的内容 dest.Messages = sliceutil.Delete(dest.Messages, func(dm Message, _ int) bool { exist := slices.IndexFunc(f.Messages, func(sm Message) bool { return sm.Key == dm.Key }) >= 0 if !exist { - log(localeutil.Phrase("the key %s of %s not found, will be deleted", strconv.Quote(dm.Key), dest.ID)) + log(localeutil.Phrase("the key %s of %s not found, will be deleted", strconv.Quote(dm.Key), destFile)) } return !exist }) @@ -120,16 +115,24 @@ func (f *File) Catalog(b *catalog.Builder) (err error) { msgs = append(msgs, mm) } msgs = append(msgs, catalog.String(msg.Message.Msg)) - err = b.Set(f.ID, msg.Key, msgs...) + for _, id := range f.Languages { + if err := b.Set(id, msg.Key, msgs...); err != nil { + return err + } + } case msg.Message.Select != nil: s := msg.Message.Select - err = b.Set(f.ID, msg.Key, plural.Selectf(s.Arg, s.Format, ex(s.Cases)...)) + for _, id := range f.Languages { + if err := b.Set(id, msg.Key, plural.Selectf(s.Arg, s.Format, ex(s.Cases)...)); err != nil { + return err + } + } case msg.Message.Msg != "": - err = b.SetString(f.ID, msg.Key, msg.Message.Msg) - } - - if err != nil { - return err + for _, id := range f.Languages { + if err := b.SetString(id, msg.Key, msg.Message.Msg); err != nil { + return err + } + } } } diff --git a/message/message_test.go b/message/message_test.go index dde8c55..a7a0333 100644 --- a/message/message_test.go +++ b/message/message_test.go @@ -19,12 +19,12 @@ func TestLanguage_Join(t *testing.T) { a := assert.New(t, false) src := &File{ - ID: language.SimplifiedChinese, - Messages: []Message{{Key: "src"}, {Key: "g", Message: Text{Msg: "src"}}}, + Languages: []language.Tag{language.SimplifiedChinese}, + Messages: []Message{{Key: "src"}, {Key: "g", Message: Text{Msg: "src"}}}, } l := &File{ - ID: language.SimplifiedChinese, - Messages: []Message{{Key: "l"}, {Key: "g", Message: Text{Msg: "l"}}}, + Languages: []language.Tag{language.SimplifiedChinese}, + Messages: []Message{{Key: "l"}, {Key: "g", Message: Text{Msg: "l"}}}, } l.Join(src) a.Length(l.Messages, 3) @@ -35,27 +35,27 @@ func TestLanguage_MergeTo(t *testing.T) { log := func(s localeutil.Stringer) {} dest := &File{ - ID: language.SimplifiedChinese, - Messages: []Message{{Key: "dest"}}, + Languages: []language.Tag{language.SimplifiedChinese}, + Messages: []Message{{Key: "dest"}}, } l := &File{ - ID: language.Afrikaans, - Messages: []Message{{Key: "l"}}, + Languages: []language.Tag{language.Afrikaans}, + Messages: []Message{{Key: "l"}}, } - l.MergeTo(log, []*File{dest}) - a.Equal(dest.ID, language.SimplifiedChinese). + l.MergeTo(log, dest, "dest.yaml") + a.Equal(dest.Languages, []language.Tag{language.SimplifiedChinese}). Length(dest.Messages, 1).Equal(dest.Messages[0].Key, "l"). Length(l.Messages, 1).Equal(l.Messages[0].Key, "l") dest = &File{ - ID: language.SimplifiedChinese, - Messages: []Message{{Key: "dest"}, {Key: "g"}}, + Languages: []language.Tag{language.SimplifiedChinese}, + Messages: []Message{{Key: "dest"}, {Key: "g"}}, } l = &File{ - ID: language.SimplifiedChinese, - Messages: []Message{{Key: "l"}, {Key: "g"}}, + Languages: []language.Tag{language.SimplifiedChinese}, + Messages: []Message{{Key: "l"}, {Key: "g"}}, } - l.MergeTo(log, []*File{dest}) + l.MergeTo(log, dest, "dest.yaml") a.Length(dest.Messages, 2). Length(l.Messages, 2).Equal(l.Messages[0].Key, "l").Equal(l.Messages[1].Key, "g") } @@ -65,7 +65,7 @@ func TestLanguage_Catalog(t *testing.T) { b := catalog.NewBuilder() l := &File{ - ID: language.SimplifiedChinese, + Languages: []language.Tag{language.SimplifiedChinese}, Messages: []Message{ {Key: "k1", Message: Text{Msg: "msg1"}}, {Key: "k2 %s", Message: Text{Msg: "msg-%s"}}, diff --git a/message/serialize/marshal.go b/message/serialize/marshal.go index dee9c80..9239289 100644 --- a/message/serialize/marshal.go +++ b/message/serialize/marshal.go @@ -8,7 +8,6 @@ import ( "cmp" "io/fs" "os" - "path/filepath" "slices" "github.com/issue9/localeutil/message" @@ -33,18 +32,3 @@ func SaveFile(l *message.File, path string, f MarshalFunc, mode fs.FileMode) err } return err } - -// SaveFiles 将 langs 按语言 ID 分类保存 -func SaveFiles(langs []*message.File, dir, ext string, f MarshalFunc, mode fs.FileMode) error { - if ext[0] != '.' { - ext = "." + ext - } - - for _, l := range langs { - path := filepath.Join(dir, l.ID.String()+ext) - if err := SaveFile(l, path, f, mode); err != nil { - return err - } - } - return nil -} diff --git a/message/serialize/testdata/cmn-hans.json b/message/serialize/testdata/cmn-hans.json index 0d1b517..28049e9 100644 --- a/message/serialize/testdata/cmn-hans.json +++ b/message/serialize/testdata/cmn-hans.json @@ -1,52 +1,78 @@ { - "id": "cmn-hans", - "messages": [ - { - "key": "k1", - "message": { - "msg": "msg1" - } - }, - { - "key": "k2", - "message": { - "select": { - "arg": 1, - "format": "%d", - "cases": [ - {"case": "=1", "value": "msg-1"}, - {"case": "=2", "value": "msg-%[1]d"}, - {"case": "=3", "value": "msg-3"}, - {"case": "other", "value": "msg-other"} - ] + "language": [ + "cmn-hans" + ], + "messages": [ + { + "key": "k1", + "message": { + "msg": "msg1" + } + }, + { + "key": "k2", + "message": { + "select": { + "arg": 1, + "format": "%d", + "cases": [ + { + "case": "=1", + "value": "msg-1" + }, + { + "case": "=2", + "value": "msg-%[1]d" + }, + { + "case": "=3", + "value": "msg-3" + }, + { + "case": "other", + "value": "msg-other" + } + ] + } + } + }, + { + "key": "k3", + "message": { + "msg": "${number}-${string}", + "vars": [ + { + "name": "number", + "arg": 2, + "format": "%d", + "cases": [ + { + "case": "=1", + "value": "1" + }, + { + "case": "other", + "value": "2" + } + ] + }, + { + "name": "string", + "arg": 1, + "format": "%d", + "cases": [ + { + "case": "=1", + "value": "一" + }, + { + "case": "other", + "value": "二" + } + ] + } + ] + } } - } - }, - { - "key": "k3", - "message": { - "msg": "${number}-${string}", - "vars": [ - { - "name": "number", - "arg": 2, - "format": "%d", - "cases": [ - {"case": "=1", "value": "1"}, - {"case": "other", "value": "2"} - ] - }, - { - "name": "string", - "arg": 1, - "format": "%d", - "cases": [ - {"case": "=1", "value": "一"}, - {"case": "other", "value": "二"} - ] - } - ] - } - } - ] -} \ No newline at end of file + ] +} diff --git a/message/serialize/testdata/cmn-hant.xml b/message/serialize/testdata/cmn-hant.xml index 9e3c83c..2d14601 100644 --- a/message/serialize/testdata/cmn-hant.xml +++ b/message/serialize/testdata/cmn-hant.xml @@ -1,4 +1,7 @@ - + + + cmn-hant + k1 diff --git a/message/serialize/unmarshal.go b/message/serialize/unmarshal.go index d9323fe..d0af9f3 100644 --- a/message/serialize/unmarshal.go +++ b/message/serialize/unmarshal.go @@ -44,8 +44,6 @@ func unmarshalFS(f func() ([]byte, error), u UnmarshalFunc) (*message.File, erro } // LoadGlob 批量加载文件 -// -// 相同 Language.ID 的项会合并。 func LoadGlob(s Search, glob string) ([]*message.File, error) { matches, err := filepath.Glob(glob) if err != nil { @@ -69,8 +67,6 @@ func LoadGlob(s Search, glob string) ([]*message.File, error) { } // LoadFSGlob 批量加载文件 -// -// 相同 Language.ID 的项会合并。 func LoadFSGlob(s Search, glob string, fsys ...fs.FS) ([]*message.File, error) { langs := make([]*message.File, 0, 10) for _, f := range fsys { diff --git a/message/serialize/unmarshal_test.go b/message/serialize/unmarshal_test.go index ed647b1..80e3502 100644 --- a/message/serialize/unmarshal_test.go +++ b/message/serialize/unmarshal_test.go @@ -11,8 +11,7 @@ import ( "testing" "github.com/issue9/assert/v4" - - "github.com/issue9/localeutil/message" + "golang.org/x/text/language" ) func TestLoad(t *testing.T) { @@ -20,12 +19,12 @@ func TestLoad(t *testing.T) { l, err := LoadFS(os.DirFS("./testdata"), "cmn-hans.json", json.Unmarshal) a.NotError(err).NotNil(l) - a.Equal(l.ID.String(), "cmn-Hans") + a.Equal(l.Languages, []language.Tag{language.MustParse("cmn-Hans")}) ls, err := LoadFSGlob(func(string) UnmarshalFunc { return json.Unmarshal }, "*.json", os.DirFS("./testdata")) a.NotError(err).Length(ls, 1) a.Length(ls, 1). - Equal(ls[0].ID.String(), "cmn-Hans") + Equal(ls[0].Languages, []language.Tag{language.MustParse("cmn-Hans")}) ls, err = LoadGlob(func(string) UnmarshalFunc { return xml.Unmarshal }, "./testdata/*.xml") a.NotError(err).Length(ls, 1) @@ -38,6 +37,4 @@ func TestSaveFile(t *testing.T) { a.NotError(err).NotNil(l1) l2, err := LoadFS(os.DirFS("./testdata"), "cmn-hant.xml", xml.Unmarshal) a.NotError(err).NotNil(l2) - - a.NotError(SaveFiles([]*message.File{l1, l2}, "./testdata/", ".out", json.Marshal, os.ModePerm)) }