Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract command improvement: collecting values from constants in other package files #355

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions goi18n/extract_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,41 @@
return nil
}

func resolveMessageFieldWithConsts(f *string, consts []*constObj) {
s := strings.Split(*f, unresolvedConstIdentifier)
if len(s) != 2 {
return
}

name, pkg := s[0], s[1]
for _, c := range consts {
if c.name == name && c.packageName == pkg {
*f = c.value
return
}
}
*f = name

Check warning on line 82 in goi18n/extract_command.go

View check run for this annotation

Codecov / codecov/patch

goi18n/extract_command.go#L82

Added line #L82 was not covered by tests
}

func resolveMessageWithConsts(m *i18n.Message, consts []*constObj) {
resolveMessageFieldWithConsts(&m.ID, consts)
resolveMessageFieldWithConsts(&m.Hash, consts)
resolveMessageFieldWithConsts(&m.Description, consts)
resolveMessageFieldWithConsts(&m.LeftDelim, consts)
resolveMessageFieldWithConsts(&m.RightDelim, consts)
resolveMessageFieldWithConsts(&m.Zero, consts)
resolveMessageFieldWithConsts(&m.One, consts)
resolveMessageFieldWithConsts(&m.Two, consts)
resolveMessageFieldWithConsts(&m.Few, consts)
resolveMessageFieldWithConsts(&m.Many, consts)
resolveMessageFieldWithConsts(&m.Other, consts)
}

func (ec *extractCommand) execute() error {
if len(ec.paths) == 0 {
ec.paths = []string{"."}
}
consts := []*constObj{}
messages := []*i18n.Message{}
for _, path := range ec.paths {
if err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
Expand All @@ -92,18 +123,24 @@
if err != nil {
return err
}
msgs, err := extractMessages(buf)
msgs, cnsts, err := extractMessages(buf)
if err != nil {
return err
}
messages = append(messages, msgs...)
consts = append(consts, cnsts...)
return nil
}); err != nil {
return err
}
}

messageTemplates := map[string]*i18n.MessageTemplate{}
for _, m := range messages {
// resolve message consts
resolveMessageWithConsts(m, consts)

// create template
if mt := i18n.NewMessageTemplate(m); mt != nil {
messageTemplates[m.ID] = mt
}
Expand All @@ -116,32 +153,84 @@
}

// extractMessages extracts messages from the bytes of a Go source file.
func extractMessages(buf []byte) ([]*i18n.Message, error) {
func extractMessages(buf []byte) ([]*i18n.Message, []*constObj, error) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", buf, parser.AllErrors)
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 160 in goi18n/extract_command.go

View check run for this annotation

Codecov / codecov/patch

goi18n/extract_command.go#L160

Added line #L160 was not covered by tests
}
extractor := newExtractor(file)
ast.Walk(extractor, file)
return extractor.messages, nil
return extractor.messages, extractor.consts, nil
}

func newExtractor(file *ast.File) *extractor {
return &extractor{i18nPackageName: i18nPackageName(file)}
return &extractor{
i18nPackageName: i18nPackageName(file),
packageName: file.Name.Name,
}
}

const unresolvedConstIdentifier = ":::unresolved:::"

type constObj struct {
name string
value string
packageName string
}

type extractor struct {
i18nPackageName string
packageName string
messages []*i18n.Message
consts []*constObj
}

func (e *extractor) Visit(node ast.Node) ast.Visitor {
e.extractMessages(node)
return e
}

func (e *extractor) extractConsts(node ast.GenDecl) {
if node.Tok != token.CONST {
return
}

for _, s := range node.Specs {
if vs, ok := s.(*ast.ValueSpec); ok {
for i, n := range vs.Names {
if len(vs.Values) <= i {
break

Check warning on line 203 in goi18n/extract_command.go

View check run for this annotation

Codecov / codecov/patch

goi18n/extract_command.go#L203

Added line #L203 was not covered by tests
}
bl, ok := vs.Values[i].(*ast.BasicLit)
if !ok {
break

Check warning on line 207 in goi18n/extract_command.go

View check run for this annotation

Codecov / codecov/patch

goi18n/extract_command.go#L207

Added line #L207 was not covered by tests
}

v, err := strconv.Unquote(bl.Value)
if err != nil {
continue

Check warning on line 212 in goi18n/extract_command.go

View check run for this annotation

Codecov / codecov/patch

goi18n/extract_command.go#L212

Added line #L212 was not covered by tests
}
if v == "iota" {
break

Check warning on line 215 in goi18n/extract_command.go

View check run for this annotation

Codecov / codecov/patch

goi18n/extract_command.go#L215

Added line #L215 was not covered by tests
}
e.consts = append(e.consts, &constObj{
name: n.Name,
value: v,
packageName: e.packageName,
})

}
}
}
}

func (e *extractor) extractMessages(node ast.Node) {
// Collect consts from all files to resolve nil objects recived after extract message
if gd, ok := node.(*ast.GenDecl); ok {
e.extractConsts(*gd)
}

cl, ok := node.(*ast.CompositeLit)
if !ok {
return
Expand Down Expand Up @@ -221,6 +310,11 @@
}
v, ok := extractStringLiteral(kve.Value)
if !ok {
if len(v) > 0 {
// v might be not empty if const cannot be r (placed other file) so we should try to resolve it later
data[key.Name] = v + unresolvedConstIdentifier + e.packageName
}

continue
}
data[key.Name] = v
Expand Down Expand Up @@ -260,7 +354,7 @@
return x + y, true
case *ast.Ident:
if v.Obj == nil {
return "", false
return v.Name, false
}
switch z := v.Obj.Decl.(type) {
case *ast.ValueSpec:
Expand Down
9 changes: 9 additions & 0 deletions goi18n/testdata/ids.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

const (
ID1 = "HelloPersonConst"
ID2 = "MyUnreadEmailsConst"
ID3 = "PersonUnreadEmailsConst"

MSG_HELLO = "Hello {{.Name}}"
)
49 changes: 49 additions & 0 deletions goi18n/testdata/some_enum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import "github.com/nicksnyder/go-i18n/v2/i18n"

type MessagList struct {
HelloPerson string
MyUnreadEmails string
PersonUnreadEmails string
}

func FillLocalizer(localizer *i18n.Localizer, unreadEmailCount, name string) *MessagList {
ml := &MessagList{}

ml.HelloPerson = localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: ID1,
Other: MSG_HELLO,
},
TemplateData: map[string]string{
"Name": name,
},
})

ml.MyUnreadEmails = localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: ID2,
Description: "The number of unread emails I have",
One: "I have {{.PluralCount}} unread email.",
Other: "I have {{.PluralCount}} unread emails.",
},
PluralCount: unreadEmailCount,
})

ml.PersonUnreadEmails = localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: ID3,
Description: "The number of unread emails a person has",
One: "{{.Name}} has {{.UnreadEmailCount}} unread email.",
Other: "{{.Name}} has {{.UnreadEmailCount}} unread emails.",
},
PluralCount: unreadEmailCount,
TemplateData: map[string]interface{}{
"Name": name,
"UnreadEmailCount": unreadEmailCount,
},
})

return ml
}
Loading