Marcel van Lohuizen would like Nigel Tao to review this change.
cmd/gotext: improved extraction
- split extraction and translation data in
two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 431 insertions(+), 105 deletions(-)
diff --git a/cmd/gotext/examples/main.go b/cmd/gotext/examples/main.go
new file mode 100644
index 0000000..3c9b4d3
--- /dev/null
+++ b/cmd/gotext/examples/main.go
@@ -0,0 +1,68 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+)
+
+func main() {
+ p := message.NewPrinter(language.English)
+
+ p.Print("Hello world!")
+
+ p.Println("Hello", "world!")
+
+ person := "Sheila"
+ place := "Zürich"
+
+ p.Print("Hello ", person, " in ", place, "!")
+
+ // Greet a city.
+ p.Print("Hello city!")
+
+ city := "Amsterdam"
+ // Greet a city.
+ p.Printf("Hello %s!", city)
+
+ town := "Amsterdam"
+ // Greet a town.
+ p.Printf("Hello %s!",
+ town, // Town
+ )
+
+ // Person visiting a place.
+ p.Printf("%s is visiting %s!",
+ person, // The person of matter.
+ place, // Place the person is visiting
+ )
+
+ var pp = struct {
+ Person string // The person of matter. // TODO: get this comment.
+ Place string
+ }{
+ person, place,
+ }
+
+ // Same string twice. Drop this comment in favor of below.
+ p.Printf("%s is visiting %s!", // Person visiting a place.
+ pp.Person,
+ pp.Place, // Place the person is visiting
+ )
+
+ // Numeric literal
+ p.Printf("%d files remaining!", 2) // Person visiting a place.
+
+ const n = 2
+
+ // Numeric var
+ p.Printf("%d more files remaining!", n) // Person visiting a place.
+
+ type referralCode int
+
+ c := referralCode(5)
+ p.Printf("Use the following code for your discount: %d", c)
+}
diff --git a/cmd/gotext/examples/textdata/gotext_en.out.json b/cmd/gotext/examples/textdata/gotext_en.out.json
new file mode 100755
index 0000000..d3e87c6
--- /dev/null
+++ b/cmd/gotext/examples/textdata/gotext_en.out.json
@@ -0,0 +1,192 @@
+[
+ {
+ "key": [
+ "Hello %s!"
+ ],
+ "original": {
+ "msg": "Hello {City}!"
+ },
+ "translation": {},
+ "args": [
+ {
+ "id": "City",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "city",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:29:24"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:29:10"
+ },
+ {
+ "key": [
+ "Hello %s!"
+ ],
+ "original": {
+ "msg": "Hello {Town}!"
+ },
+ "translation": {},
+ "args": [
+ {
+ "id": "Town",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "town",
+ "comment": "Town",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:34:3"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:33:10"
+ },
+ {
+ "key": [
+ "%s is visiting %s!"
+ ],
+ "original": {
+ "msg": "{Person} is visiting {Place}!"
+ },
+ "translation": {},
+ "args": [
+ {
+ "id": "Person",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "person",
+ "comment": "The person of matter.",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:39:3"
+ },
+ {
+ "id": "Place",
+ "argNum": 2,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "place",
+ "comment": "Place the person is visiting",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:40:3"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:38:10"
+ },
+ {
+ "key": [
+ "%s is visiting %s!"
+ ],
+ "original": {
+ "msg": "{Person} is visiting {Place}!"
+ },
+ "translation": {},
+ "extractedComment": "Person visiting a place.",
+ "args": [
+ {
+ "id": "Person",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "pp.Person",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:52:3"
+ },
+ {
+ "id": "Place",
+ "argNum": 2,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "pp.Place",
+ "comment": "Place the person is visiting",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:53:3"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:51:10"
+ },
+ {
+ "key": [
+ "%d files remaining!"
+ ],
+ "original": {
+ "msg": "{2} files remaining!"
+ },
+ "translation": {},
+ "args": [
+ {
+ "id": "2",
+ "argNum": 1,
+ "format": [
+ "%d"
+ ],
+ "type": "int",
+ "underlyingType": "int",
+ "expr": "2",
+ "value": "2",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:57:34"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:57:10"
+ },
+ {
+ "key": [
+ "%d more files remaining!"
+ ],
+ "original": {
+ "msg": "{N} more files remaining!"
+ },
+ "translation": {},
+ "args": [
+ {
+ "id": "N",
+ "argNum": 1,
+ "format": [
+ "%d"
+ ],
+ "type": "int",
+ "underlyingType": "int",
+ "expr": "n",
+ "value": "2",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:62:39"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:62:10"
+ },
+ {
+ "key": [
+ "Use the following code for your discount: %d"
+ ],
+ "original": {
+ "msg": "Use the following code for your discount: {ReferralCode}"
+ },
+ "translation": {},
+ "args": [
+ {
+ "id": "ReferralCode",
+ "argNum": 1,
+ "format": [
+ "%d"
+ ],
+ "type": "golang.org/x/text/cmd/gotext/examples.referralCode",
+ "underlyingType": "int",
+ "expr": "c",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:67:59"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:67:10"
+ }
+]
\ No newline at end of file
diff --git a/cmd/gotext/extract.go b/cmd/gotext/extract.go
index 79a9b59..8035a85 100644
--- a/cmd/gotext/extract.go
+++ b/cmd/gotext/extract.go
@@ -13,13 +13,16 @@
"go/constant"
"go/format"
"go/parser"
+ "go/token"
"go/types"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
+ "unicode"
+ fmtparser "golang.org/x/text/internal/format"
"golang.org/x/tools/go/loader"
)
@@ -64,7 +67,7 @@
return buf.String()
}
- var translations []Translation
+ var messages []ExtractedMessage
for _, info := range iprog.InitialPackages() {
for _, f := range info.Files {
@@ -103,70 +106,131 @@
return true
}
+ fmtType, ok := m[meth.Obj().Name()]
+ if !ok {
+ return true
+ }
// argn is the index of the format string.
- argn, ok := m[meth.Obj().Name()]
- if !ok || argn >= len(call.Args) {
+ argn := fmtType.arg
+ if argn >= len(call.Args) {
return true
}
- // Skip calls with non-constant format string.
- fmtstr := info.Types[call.Args[argn]].Value
- if fmtstr == nil || fmtstr.Kind() != constant.String {
- return true
+ args := call.Args[fmtType.arg:]
+
+ tStr := func(e ast.Expr) (s string, ok bool) {
+ v := info.Types[e].Value
+ if v == nil || v.Kind() != constant.String {
+ return "", false
+ }
+ s = constant.StringVal(v)
+ // Only record strings with letters.
+ found := false
+ for _, r := range s {
+ if unicode.In(r, unicode.L) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return "", false
+ }
+ return s, true
}
- posn := conf.Fset.Position(call.Lparen)
- filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
-
- // TODO: identify the type of the format argument. If it is not
- // a string, multiple keys may be defined.
- var key []string
+ if !fmtType.format {
+ // Simple case: no formatting
+ for _, arg := range args {
+ if s, ok := tStr(arg); ok {
+ // TODO: verify that the string does not have
+ // substitutions.
+ messages = append(messages, ExtractedMessage{
+ Position: posString(conf, info, call.Lparen),
+ Message: s,
+ Comment: getComment(arg),
+ // TODO(fix): this doesn't get the before comment.
+ // Comment: getComment(call),
+ })
+ }
+ }
+ return true
+ }
// TODO: replace substitutions (%v) with a translator friendly
// notation. For instance:
// "%d files remaining" -> "{numFiles} files remaining", or
// "%d files remaining" -> "{arg1} files remaining"
// Alternatively, this could be done at a later stage.
- msg := constant.StringVal(fmtstr)
-
- // Construct a Translation unit.
- c := Translation{
- Key: key,
- Position: filepath.Join(info.Pkg.Path(), filepos),
- Original: Text{Msg: msg},
- ExtractedComment: getComment(call.Args[0]),
- // TODO(fix): this doesn't get the before comment.
- // Comment: getComment(call),
+ fmtMsg, ok := tStr(args[0])
+ if !ok {
+ // TODO: identify the type of the format argument. If it
+ // is not a string, multiple keys may be defined.
+ return true
}
-
- for i, arg := range call.Args[argn+1:] {
- var val string
+ key := []string{fmtMsg}
+ arguments := []Argument{}
+ simArgs := make([]interface{}, len(args))
+ for i, arg := range args[1:] {
+ expr := print(arg)
+ val := ""
if v := info.Types[arg].Value; v != nil {
val = v.ExactString()
+ simArgs[i] = val
+ switch arg.(type) {
+ case *ast.BinaryExpr, *ast.UnaryExpr:
+ expr = val
+ }
}
- posn := conf.Fset.Position(arg.Pos())
- filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
- c.Args = append(c.Args, Argument{
- ID: i + 1,
+ arguments = append(arguments, Argument{
+ ArgNum: i + 1,
Type: info.Types[arg].Type.String(),
UnderlyingType: info.Types[arg].Type.Underlying().String(),
- Expr: print(arg),
+ Expr: expr,
Value: val,
Comment: getComment(arg),
- Position: filepath.Join(info.Pkg.Path(), filepos),
+ Position: posString(conf, info, arg.Pos()),
// TODO report whether it implements
// interfaces plural.Interface,
// gender.Interface.
})
}
+ msg := ""
- translations = append(translations, c)
+ p := fmtparser.Parser{}
+ p.Reset(simArgs)
+ for p.SetFormat(fmtMsg); p.Scan(); {
+ switch p.Status {
+ case fmtparser.StatusText:
+ msg += p.Text()
+ case fmtparser.StatusSubstitution,
+ fmtparser.StatusBadWidthSubstitution,
+ fmtparser.StatusBadPrecSubstitution:
+ arg := arguments[p.ArgNum-1]
+ id := getID(&arg)
+ arguments[p.ArgNum-1].ID = id
+ // TODO: do we allow the same entry to be formatted
+ // differently within the same string, do we give
+ // a warning, or is this an error?
+ arguments[p.ArgNum-1].Format = append(arguments[p.ArgNum-1].Format, p.Text())
+ msg += fmt.Sprintf("{%s}", id)
+ }
+ }
+
+ messages = append(messages, ExtractedMessage{
+ Key: key,
+ Position: posString(conf, info, call.Lparen),
+ Message: msg,
+ Comment: getComment(call.Args[0]),
+ Args: arguments,
+ // TODO(fix): this doesn't get the before comment.
+ // Comment: getComment(call),
+ })
return true
})
}
}
- data, err := json.MarshalIndent(translations, "", " ")
+ data, err := json.MarshalIndent(messages, "", " ")
if err != nil {
return err
}
@@ -181,15 +245,47 @@
return nil
}
+func posString(conf loader.Config, info *loader.PackageInfo, pos token.Pos) string {
+ p := conf.Fset.Position(pos)
+ file := fmt.Sprintf("%s:%d:%d", filepath.Base(p.Filename), p.Line, p.Column)
+ return filepath.Join(info.Pkg.Path(), file)
+}
+
// extractFuncs indicates the types and methods for which to extract strings,
-// and which argument to extract.
+// and which argument to extract. A negative argument indicates
// TODO: use the types in conf.Import("golang.org/x/text/message") to extract
// the correct instances.
-var extractFuncs = map[string]map[string]int{
+var extractFuncs = map[string]map[string]extractType{
// TODO: Printer -> *golang.org/x/text/message.Printer
"message.Printer": {
- "Printf": 0,
- "Sprintf": 0,
- "Fprintf": 1,
+ "Printf": extractType{arg: 0, format: true},
+ "Sprintf": extractType{arg: 0, format: true},
+ "Fprintf": extractType{arg: 1, format: true},
+
+ "Lookup": extractType{arg: 0},
},
}
+
+type extractType struct {
+ // all indicates if the next arg is a formatted string or whether to
+ // concatenate all arguments
+ format bool
+ // arg indicates the position of the argument to extract. If all is
+ // positive, all arguments from this argument onwards needs to be extracted.
+ arg int
+}
+
+func getID(arg *Argument) string {
+ s := getLastComponent(arg.Expr)
+ s = strings.Replace(s, " ", "", -1)
+ // For small variable names, use user-defined types for more info.
+ if len(s) <= 2 && arg.UnderlyingType != arg.Type {
+ s = getLastComponent(arg.Type)
+ }
+ return strings.Title(s)
+}
+
+func getLastComponent(s string) string {
+ a := strings.Split(s, ".")
+ return a[len(a)-1]
+}
diff --git a/cmd/gotext/message.go b/cmd/gotext/message.go
index 67a622f..e703d22 100644
--- a/cmd/gotext/message.go
+++ b/cmd/gotext/message.go
@@ -13,67 +13,23 @@
// A translation may have multiple translations strings, or messages, depending
// on the feature values of the various arguments. For instance, consider
// a hypothetical translation from English to English, where the source defines
-// the format string "%d file(s) remaining". A completed translation, expressed
-// in JS, for this format string could look like:
-//
-// {
-// "Key": [
-// "\"%d files(s) remaining\""
-// ],
-// "Original": {
-// "Msg": "\"%d files(s) remaining\""
-// },
-// "Translation": {
-// "Select": {
-// "Feature": "plural",
-// "Arg": 1,
-// "Case": {
-// "one": { "Msg": "1 file remaining" },
-// "other": { "Msg": "%d files remaining" }
-// },
-// },
-// },
-// "Args": [
-// {
-// "ID": 2,
-// "Type": "int",
-// "UnderlyingType": "int",
-// "Expr": "nFiles",
-// "Comment": "number of files remaining",
-// "Position": "golang.org/x/text/cmd/gotext/demo.go:34:3"
-// }
-// ],
-// "Position": "golang.org/x/text/cmd/gotext/demo.go:33:10",
-// }
-//
-// Alternatively, the Translation section could be written as:
-//
-// "Translation": {
-// "Msg": "%d %[files]s remaining",
-// "Var": {
-// "files" : {
-// "Select": {
-// "Feature": "plural",
-// "Arg": 1,
-// "Case": {
-// "one": { "Msg": "file" },
-// "other": { "Msg": "files" }
-// }
-// }
-// }
-// }
-// }
+// the format string "%d file(s) remaining".
+// See the examples directory for examples of extracted messages.
-// A Translation describes a translation for a single language for a single
-// message.
-type Translation struct {
+// A ExtractedMessage describes a message to be translated.
+type ExtractedMessage struct {
// Key contains a list of identifiers for the message. If this list is empty
- // Original is used as the key.
- Key []string `json:"key,omitempty"`
- Original Text `json:"original"`
- Translation Text `json:"translation"`
- ExtractedComment string `json:"extractedComment,omitempty"`
- TranslatorComment string `json:"translatorComment,omitempty"`
+ // the message itself is used as the key.
+ Key []string `json:"key,omitempty"`
+ Message string `json:"message"`
+ Meaning string
+
+ Comment string `json:"comment,omitempty"`
+
+ // TODO: have a separate placeholder list, mapping placeholders
+ // to arguments or constant strings.
+ // TODO: default placeholder syntax is {foo}. Allow alternatives
+ // like `foo`.
Args []Argument `json:"args,omitempty"`
@@ -81,15 +37,29 @@
Position string `json:"position,omitempty"` // filePosition:line
}
+// A Translation gives a translation of a single message in a single language.
+type Translation struct {
+ // Key contains a list of identifiers for the message. If this list is empty
+ // Original is used as the key.
+ Key []string `json:"key,omitempty"`
+
+ Translation Text `json:"translation"`
+ Comment string `json:"comment,omitempty"`
+}
+
// An Argument contains information about the arguments passed to a message.
type Argument struct {
- ID interface{} `json:"id"` // An int for printf-style calls, but could be a string.
- Type string `json:"type"`
- UnderlyingType string `json:"underlyingType"`
- Expr string `json:"expr"`
- Value string `json:"value,omitempty"`
- Comment string `json:"comment,omitempty"`
- Position string `json:"position,omitempty"`
+ ID string `json:"id"` // An int for printf-style calls, but could be a string.
+ // Argument position for integer style calls.
+ ArgNum int `json:"argNum,omitempty"`
+ Format []string `json:"format,omitempty"`
+
+ Type string `json:"type"`
+ UnderlyingType string `json:"underlyingType"`
+ Expr string `json:"expr"`
+ Value string `json:"value,omitempty"`
+ Comment string `json:"comment,omitempty"`
+ Position string `json:"position,omitempty"`
// Features contains the features that are available for the implementation
// of this argument.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots beginning. Status page: https://farmer.golang.org/try?commit=50fa3864
Build is still in progress...
This change failed on linux-amd64:
See https://storage.googleapis.com/go-build-log/1e3f563b/linux-amd64_02d9b1d6.log
Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.
Marcel van Lohuizen uploaded patch set #2 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in
two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 431 insertions(+), 105 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots beginning. Status page: https://farmer.golang.org/try?commit=fb2b1378
Build is still in progress...
This change failed on linux-amd64:
See https://storage.googleapis.com/go-build-log/1e3f563b/linux-amd64_06356989.log
Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
7 of 7 TryBots failed:
Failed on linux-amd64: https://storage.googleapis.com/go-build-log/1e3f563b/linux-amd64_02d9b1d6.log
Failed on linux-386: https://storage.googleapis.com/go-build-log/1e3f563b/linux-386_45438d99.log
Failed on darwin-amd64-10_11: https://storage.googleapis.com/go-build-log/1e3f563b/darwin-amd64-10_11_38b680fb.log
Failed on freebsd-amd64-110: https://storage.googleapis.com/go-build-log/1e3f563b/freebsd-amd64-110_d3f8818a.log
Failed on openbsd-amd64-60: https://storage.googleapis.com/go-build-log/1e3f563b/openbsd-amd64-60_7adde58a.log
Failed on windows-386-2008: https://storage.googleapis.com/go-build-log/1e3f563b/windows-386-2008_d6b9ff67.log
Failed on windows-amd64-2016: https://storage.googleapis.com/go-build-log/1e3f563b/windows-amd64-2016_3f32abc4.log
Consult https://build.golang.org/ to see whether they are new failures.
Patch set 1:TryBot-Result -1
7 of 7 TryBots failed:
Failed on linux-amd64: https://storage.googleapis.com/go-build-log/1e3f563b/linux-amd64_06356989.log
Failed on linux-386: https://storage.googleapis.com/go-build-log/1e3f563b/linux-386_8322eb5e.log
Failed on darwin-amd64-10_11: https://storage.googleapis.com/go-build-log/1e3f563b/darwin-amd64-10_11_9ebfa8e8.log
Failed on freebsd-amd64-110: https://storage.googleapis.com/go-build-log/1e3f563b/freebsd-amd64-110_2a8a6771.log
Failed on windows-386-2008: https://storage.googleapis.com/go-build-log/1e3f563b/windows-386-2008_58637522.log
Failed on openbsd-amd64-60: https://storage.googleapis.com/go-build-log/1e3f563b/openbsd-amd64-60_aa1f4667.log
Failed on windows-amd64-2016: https://storage.googleapis.com/go-build-log/1e3f563b/windows-amd64-2016_9f6e97af.log
Consult https://build.golang.org/ to see whether they are new failures.
Patch set 2:TryBot-Result -1
Marcel van Lohuizen uploaded patch set #3 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in
two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 417 insertions(+), 105 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
Build is still in progress...
This change failed on darwin-amd64-10_11:
See https://storage.googleapis.com/go-build-log/1e3f563b/darwin-amd64-10_11_6e4e2943.log
Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
7 of 7 TryBots failed:
Failed on darwin-amd64-10_11: https://storage.googleapis.com/go-build-log/1e3f563b/darwin-amd64-10_11_6e4e2943.log
Failed on linux-amd64: https://storage.googleapis.com/go-build-log/1e3f563b/linux-amd64_94599b5b.log
Failed on linux-386: https://storage.googleapis.com/go-build-log/1e3f563b/linux-386_fef4471b.log
Failed on freebsd-amd64-110: https://storage.googleapis.com/go-build-log/1e3f563b/freebsd-amd64-110_6ae1464b.log
Failed on openbsd-amd64-60: https://storage.googleapis.com/go-build-log/1e3f563b/openbsd-amd64-60_61f3b7aa.log
Failed on windows-386-2008: https://storage.googleapis.com/go-build-log/1e3f563b/windows-386-2008_1eda862d.log
Failed on windows-amd64-2016: https://storage.googleapis.com/go-build-log/1e3f563b/windows-amd64-2016_f534aad1.log
Consult https://build.golang.org/ to see whether they are new failures.
Patch set 3:TryBot-Result -1
Build is still in progress...
This change failed on darwin-amd64-10_11:
See https://storage.googleapis.com/go-build-log/ecc86503/darwin-amd64-10_11_3e1f9aaf.log
Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
1 of 7 TryBots failed:
Failed on darwin-amd64-10_11: https://storage.googleapis.com/go-build-log/ecc86503/darwin-amd64-10_11_3e1f9aaf.log
Consult https://build.golang.org/ to see whether they are new failures.
Patch set 4:TryBot-Result -1
TryBots are happy.
Patch set 5:TryBot-Result +1
Marcel van Lohuizen uploaded patch set #6 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in
two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 417 insertions(+), 114 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots are happy.
Patch set 6:TryBot-Result +1
Marcel van Lohuizen uploaded patch set #7 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in
two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 409 insertions(+), 114 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots are happy.
Patch set 7:TryBot-Result +1
Marcel van Lohuizen uploaded patch set #8 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in
two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 409 insertions(+), 114 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots are happy.
Patch set 8:TryBot-Result +1
TryBots beginning. Status page: https://farmer.golang.org/try?commit=c5b68184
TryBots are happy.
Patch set 9:TryBot-Result +1
20 comments:
Patch Set #9, Line 9: - split extraction and translation data in
You can unbreak the line.
Patch Set #9, Line 12: - more intelligent name picking
Can you give an example?
File cmd/gotext/examples/main.go:
s/16/17/
Patch Set #9, Line 5: package main
Perhaps add a
// +build ignore
line before this, so that "go install golang.org/x/text/cmd/..." won't install this example program?
Patch Set #9, Line 15: p.Print("Hello world!")
Add "\n" to some of these strings?? (Just the ones passed to Print, not the ones passed to Println).
Even if the point of this example is the generated JSON file, it'd still be nice for "go run main.go"'s output to look readable.
Patch Set #9, Line 40: place, // Place the person is visiting
Adding a full stop would be consistent. Ditto below.
Patch Set #9, Line 43: var pp = struct {
I'd write "pp := etc" instead of "var pp = etc".
Patch Set #9, Line 50: // Same string twice. Drop this comment in favor of below.
"Drop this comment"?? Is that a TODO for you, or is this explaining the gotext algorithm? If the latter, perhaps something like "gotext will drop this comment in favor etc".
It's also not clear whether the the dropping of this comment is due to the same string being seen twice, or simply because there are multiple //-comments before and in this single Printf call. If they're unrelated features, then please don't mix them up in the one example call.
File cmd/gotext/examples/textdata/gotext_en.out.json:
Add a comment (probably in main.go, since JSON doesn't do comments) about how this file was generated??
Patch Set #9, Line 9: "Meaning": "",
Huh, why is "Meaning" capitalized?
Also, all the Meaning values here are blank. Why isn't the "omitempty" kicking in? Can we tweak examples/main.go to generate a non-blank Meaning?
Or if it's unused, should we just delete the Meaning field in message.go?
Patch Set #9, Line 129: simArgs := make([]interface{}, len(args))
Should len(args) be len(args)-1?
Alternatively, have "args = args[1:]" above this line instead of in the for loop on the next line.
Would that clean up some of the +1s and -1s below??
Patch Set #9, Line 142: ArgNum: i + 1,
You add +1 here...
Patch Set #9, Line 165: arg := arguments[p.ArgNum-1]
...but then offset that +1 with -1s here and below. Can this all just cancel out??
Patch Set #9, Line 182: // TODO(fix): this doesn't get the before comment.
Delete?? Or move the comment up two lines?
Patch Set #9, Line 212: // and which argument to extract. A negative argument indicates
Indicates what?
Patch Set #9, Line 227: // all indicates if the next arg is a formatted string or whether to
s/all/format/
Patch Set #9, Line 230: // arg indicates the position of the argument to extract. If all is
s/all/arg/ ??
s/positive/negative/ ??
Patch Set #9, Line 246: a := strings.Split(s, ".")
return s[1+strings.LastIndexByte(s, '.'):]
Patch Set #9, Line 260: found = true
return s, true
and delete the found variable.
Patch Set #9, Line 45: // Argument position for integer style calls.
Maybe add some words about whether that position is 0-based or 1-based? There's some +1s and -1s sprinkled throughout extract.go, and it's a little hard to follow the semantics.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
Marcel van Lohuizen uploaded patch set #10 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking, for instance:
- use variable name for placeholders
- if var is too short, use type name, if it differs
from the underlying type.
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 398 insertions(+), 113 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots beginning. Status page: https://farmer.golang.org/try?commit=eeb56983
19 comments:
Patch Set #9, Line 9: - split extraction and translation data in two types
You can unbreak the line.
s/16/17/
Done
Patch Set #9, Line 5: package main
Perhaps add a […]
This will prevent the example from working.
I will move this to a testdata directory down the road, which will fix the problem.
Patch Set #9, Line 15: p := message.NewPrinter(language.English)
Add "\n" to some of these strings?? (Just the ones passed to Print, not the ones passed to Println). […]
Done
Patch Set #9, Line 40: p.Printf("%s is visiting %s!",
Adding a full stop would be consistent. Ditto below.
Done
I'd write "pp := etc" instead of "var pp = etc".
Done, although at some point I will need to include var-style assignments as it is a different code path.
"Drop this comment"?? Is that a TODO for you, or is this explaining the gotext algorithm? If the lat […]
Done
File cmd/gotext/examples/textdata/gotext_en.out.json:
Add a comment (probably in main. […]
added go:generate line
Patch Set #9, Line 9: "args": [
Huh, why is "Meaning" capitalized? […]
ah, changed json tag for meaning later and forgot to rerun extract.
Meaning isn't used yet, but it is an important feature used by many formats to disambiguate otherwise identical strings.
Done
Patch Set #9, Line 129: args = args[1:]
Should len(args) be len(args)-1? […]
Doesn't affect the +1 and -1 below, but changed as suggested.
Patch Set #9, Line 142: arguments = append(arguments, Argument{
You add +1 here...
Yes, but trimming args doesn't affect that.
Patch Set #9, Line 165: fmtparser.StatusBadPrecSubstitution:
...but then offset that +1 with -1s here and below. […]
No. We retain the same argument numbers that are used in printf format directives (%[ArgNum]d).
Patch Set #9, Line 182: Comment: getComment(call.Args[0]),
Delete?? Or move the comment up two lines?
Done
Patch Set #9, Line 212: // and which argument to extract.
Indicates what?
Done
Patch Set #9, Line 227: // format indicates if the next arg is a formatted string or whether to
s/all/format/
Done
Patch Set #9, Line 230: // arg indicates the position of the argument to extract.
s/all/arg/ ?? […]
Done
return s[1+strings.LastIndexByte(s, '. […]
Done. Less clear this is correct (only because -1 + 1 = 0), but it is a lot faster.
Patch Set #9, Line 260: return "", false
return s, true […]
Done
Patch Set #9, Line 45: // Argument position for printf-style format strings. ArgNum corresponds to
Maybe add some words about whether that position is 0-based or 1-based? There's some +1s and -1s spr […]
Done
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots are happy.
Patch set 10:TryBot-Result +1
TryBots beginning. Status page: https://farmer.golang.org/try?commit=46a085bf
TryBots are happy.
Patch set 11:TryBot-Result +1
Patch set 11:Code-Review +2
1 comment:
File cmd/gotext/examples/main.go:
Patch Set #11, Line 24: p.Print("Hello ", person, " in ", place, "!")
Add a "\n".
Ditto below, a number of times.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
Marcel van Lohuizen uploaded patch set #12 to this change.
cmd/gotext: improved extraction
- split extraction and translation data in two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking, for instance:
- use variable name for placeholders
- if var is too short, use type name, if it differs
from the underlying type.
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 398 insertions(+), 113 deletions(-)
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots beginning. Status page: https://farmer.golang.org/try?commit=9365ddf3
Marcel van Lohuizen merged this change.
cmd/gotext: improved extraction
- split extraction and translation data in two types
- rewrite fmt strings into translator-readable format
- more intelligent name picking, for instance:
- use variable name for placeholders
- if var is too short, use type name, if it differs
from the underlying type.
Change-Id: I80ae9c165892491df6fcedc340d56a08269f47fe
Reviewed-on: https://go-review.googlesource.com/79237
Run-TryBot: Marcel van Lohuizen <mp...@golang.org>
Reviewed-by: Nigel Tao <nige...@golang.org>
---
A cmd/gotext/examples/main.go
A cmd/gotext/examples/textdata/gotext_en.out.json
M cmd/gotext/extract.go
M cmd/gotext/message.go
4 files changed, 398 insertions(+), 113 deletions(-)
diff --git a/cmd/gotext/examples/main.go b/cmd/gotext/examples/main.go
new file mode 100644
index 0000000..d71bbd6
--- /dev/null
+++ b/cmd/gotext/examples/main.go
@@ -0,0 +1,70 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+//go:generate gotext extract
+
+import (
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+)
+
+func main() {
+ p := message.NewPrinter(language.English)
+
+ p.Print("Hello world!\n")
+
+ p.Println("Hello", "world!")
+
+ person := "Sheila"
+ place := "Zürich"
+
+ p.Print("Hello ", person, " in ", place, "!\n")
+
+ // Greet a city.
+ p.Print("Hello city!\n")
+
+ city := "Amsterdam"
+ // Greet a city.
+ p.Printf("Hello %s!\n", city)
+
+ town := "Amsterdam"
+ // Greet a town.
+ p.Printf("Hello %s!\n",
+ town, // Town
+ )
+
+ // Person visiting a place.
+ p.Printf("%s is visiting %s!\n",
+ person, // The person of matter.
+ place, // Place the person is visiting.
+ )
+
+ pp := struct {
+ Person string // The person of matter. // TODO: get this comment.
+ Place string
+ }{
+ person, place,
+ }
+
+ // extract will drop this comment in favor of the one below.
+ p.Printf("%s is visiting %s!\n", // Person visiting a place.
+ pp.Person,
+ pp.Place, // Place the person is visiting.
+ )
+
+ // Numeric literal
+ p.Printf("%d files remaining!", 2)
+
+ const n = 2
+
+ // Numeric var
+ p.Printf("%d more files remaining!", n)
+
+ type referralCode int
+
+ c := referralCode(5)
+ p.Printf("Use the following code for your discount: %d\n", c)
+}
diff --git a/cmd/gotext/examples/textdata/gotext_en.out.json b/cmd/gotext/examples/textdata/gotext_en.out.json
new file mode 100755
index 0000000..61de664
--- /dev/null
+++ b/cmd/gotext/examples/textdata/gotext_en.out.json
@@ -0,0 +1,185 @@
+[
+ {
+ "key": [
+ "Hello %s!\n"
+ ],
+ "message": {
+ "msg": "Hello {City}!\n"
+ },
+ "args": [
+ {
+ "id": "City",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "city",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:31:26"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:31:10"
+ },
+ {
+ "key": [
+ "Hello %s!\n"
+ ],
+ "message": {
+ "msg": "Hello {Town}!\n"
+ },
+ "args": [
+ {
+ "id": "Town",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "town",
+ "comment": "Town",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:36:3"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:35:10"
+ },
+ {
+ "key": [
+ "%s is visiting %s!\n"
+ ],
+ "message": {
+ "msg": "{Person} is visiting {Place}!\n"
+ },
+ "args": [
+ {
+ "id": "Person",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "person",
+ "comment": "The person of matter.",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:41:3"
+ },
+ {
+ "id": "Place",
+ "argNum": 2,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "place",
+ "comment": "Place the person is visiting.",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:42:3"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:40:10"
+ },
+ {
+ "key": [
+ "%s is visiting %s!\n"
+ ],
+ "message": {
+ "msg": "{Person} is visiting {Place}!\n"
+ },
+ "comment": "Person visiting a place.",
+ "args": [
+ {
+ "id": "Person",
+ "argNum": 1,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "pp.Person",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:54:3"
+ },
+ {
+ "id": "Place",
+ "argNum": 2,
+ "format": [
+ "%s"
+ ],
+ "type": "string",
+ "underlyingType": "string",
+ "expr": "pp.Place",
+ "comment": "Place the person is visiting.",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:55:3"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:53:10"
+ },
+ {
+ "key": [
+ "%d files remaining!"
+ ],
+ "message": {
+ "msg": "{2} files remaining!"
+ },
+ "args": [
+ {
+ "id": "2",
+ "argNum": 1,
+ "format": [
+ "%d"
+ ],
+ "type": "int",
+ "underlyingType": "int",
+ "expr": "2",
+ "value": "2",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:59:34"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:59:10"
+ },
+ {
+ "key": [
+ "%d more files remaining!"
+ ],
+ "message": {
+ "msg": "{N} more files remaining!"
+ },
+ "args": [
+ {
+ "id": "N",
+ "argNum": 1,
+ "format": [
+ "%d"
+ ],
+ "type": "int",
+ "underlyingType": "int",
+ "expr": "n",
+ "value": "2",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:64:39"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:64:10"
+ },
+ {
+ "key": [
+ "Use the following code for your discount: %d\n"
+ ],
+ "message": {
+ "msg": "Use the following code for your discount: {ReferralCode}\n"
+ },
+ "args": [
+ {
+ "id": "ReferralCode",
+ "argNum": 1,
+ "format": [
+ "%d"
+ ],
+ "type": "golang.org/x/text/cmd/gotext/examples.referralCode",
+ "underlyingType": "int",
+ "expr": "c",
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:69:61"
+ }
+ ],
+ "position": "golang.org/x/text/cmd/gotext/examples/main.go:69:10"
+ }
+]
\ No newline at end of file
diff --git a/cmd/gotext/extract.go b/cmd/gotext/extract.go
index 79a9b59..88c7513 100644
--- a/cmd/gotext/extract.go
+++ b/cmd/gotext/extract.go
@@ -13,13 +13,16 @@
"go/constant"
"go/format"
"go/parser"
+ "go/token"
"go/types"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
+ "unicode"
+ fmtparser "golang.org/x/text/internal/format"
"golang.org/x/tools/go/loader"
)
@@ -64,7 +67,7 @@
return buf.String()
}
- var translations []Translation
+ var messages []Message
for _, info := range iprog.InitialPackages() {
for _, f := range info.Files {
@@ -103,70 +106,88 @@
return true
}
+ fmtType, ok := m[meth.Obj().Name()]
+ if !ok {
+ return true
+ }
// argn is the index of the format string.
- argn, ok := m[meth.Obj().Name()]
- if !ok || argn >= len(call.Args) {
+ argn := fmtType.arg
+ if argn >= len(call.Args) {
return true
}
- // Skip calls with non-constant format string.
- fmtstr := info.Types[call.Args[argn]].Value
- if fmtstr == nil || fmtstr.Kind() != constant.String {
+ args := call.Args[fmtType.arg:]
+
+ fmtMsg, ok := msgStr(info, args[0])
+ if !ok {
+ // TODO: identify the type of the format argument. If it
+ // is not a string, multiple keys may be defined.
return true
}
-
- posn := conf.Fset.Position(call.Lparen)
- filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
-
- // TODO: identify the type of the format argument. If it is not
- // a string, multiple keys may be defined.
- var key []string
-
- // TODO: replace substitutions (%v) with a translator friendly
- // notation. For instance:
- // "%d files remaining" -> "{numFiles} files remaining", or
- // "%d files remaining" -> "{arg1} files remaining"
- // Alternatively, this could be done at a later stage.
- msg := constant.StringVal(fmtstr)
-
- // Construct a Translation unit.
- c := Translation{
- Key: key,
- Position: filepath.Join(info.Pkg.Path(), filepos),
- Original: Text{Msg: msg},
- ExtractedComment: getComment(call.Args[0]),
- // TODO(fix): this doesn't get the before comment.
- // Comment: getComment(call),
- }
-
- for i, arg := range call.Args[argn+1:] {
- var val string
+ key := []string{fmtMsg}
+ arguments := []Argument{}
+ args = args[1:]
+ simArgs := make([]interface{}, len(args))
+ for i, arg := range args {
+ expr := print(arg)
+ val := ""
if v := info.Types[arg].Value; v != nil {
val = v.ExactString()
+ simArgs[i] = val
+ switch arg.(type) {
+ case *ast.BinaryExpr, *ast.UnaryExpr:
+ expr = val
+ }
}
- posn := conf.Fset.Position(arg.Pos())
- filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
- c.Args = append(c.Args, Argument{
- ID: i + 1,
+ arguments = append(arguments, Argument{
+ ArgNum: i + 1,
Type: info.Types[arg].Type.String(),
UnderlyingType: info.Types[arg].Type.Underlying().String(),
- Expr: print(arg),
+ Expr: expr,
Value: val,
Comment: getComment(arg),
- Position: filepath.Join(info.Pkg.Path(), filepos),
+ Position: posString(conf, info, arg.Pos()),
// TODO report whether it implements
// interfaces plural.Interface,
// gender.Interface.
})
}
+ msg := ""
- translations = append(translations, c)
+ p := fmtparser.Parser{}
+ p.Reset(simArgs)
+ for p.SetFormat(fmtMsg); p.Scan(); {
+ switch p.Status {
+ case fmtparser.StatusText:
+ msg += p.Text()
+ case fmtparser.StatusSubstitution,
+ fmtparser.StatusBadWidthSubstitution,
+ fmtparser.StatusBadPrecSubstitution:
+ arg := arguments[p.ArgNum-1]
+ id := getID(&arg)
+ arguments[p.ArgNum-1].ID = id
+ // TODO: do we allow the same entry to be formatted
+ // differently within the same string, do we give
+ // a warning, or is this an error?
+ arguments[p.ArgNum-1].Format = append(arguments[p.ArgNum-1].Format, p.Text())
+ msg += fmt.Sprintf("{%s}", id)
+ }
+ }
+
+ messages = append(messages, Message{
+ Key: key,
+ Position: posString(conf, info, call.Lparen),
+ Message: Text{Msg: msg},
+ // TODO(fix): this doesn't get the before comment.
+ Comment: getComment(call.Args[0]),
+ Args: arguments,
+ })
return true
})
}
}
- data, err := json.MarshalIndent(translations, "", " ")
+ data, err := json.MarshalIndent(messages, "", " ")
if err != nil {
return err
}
@@ -181,15 +202,60 @@
return nil
}
+func posString(conf loader.Config, info *loader.PackageInfo, pos token.Pos) string {
+ p := conf.Fset.Position(pos)
+ file := fmt.Sprintf("%s:%d:%d", filepath.Base(p.Filename), p.Line, p.Column)
+ return filepath.Join(info.Pkg.Path(), file)
+}
+
// extractFuncs indicates the types and methods for which to extract strings,
// and which argument to extract.
// TODO: use the types in conf.Import("golang.org/x/text/message") to extract
// the correct instances.
-var extractFuncs = map[string]map[string]int{
+var extractFuncs = map[string]map[string]extractType{
// TODO: Printer -> *golang.org/x/text/message.Printer
"message.Printer": {
- "Printf": 0,
- "Sprintf": 0,
- "Fprintf": 1,
+ "Printf": extractType{arg: 0, format: true},
+ "Sprintf": extractType{arg: 0, format: true},
+ "Fprintf": extractType{arg: 1, format: true},
+
+ "Lookup": extractType{arg: 0},
},
}
+
+type extractType struct {
+ // format indicates if the next arg is a formatted string or whether to
+ // concatenate all arguments
+ format bool
+ // arg indicates the position of the argument to extract.
+ arg int
+}
+
+func getID(arg *Argument) string {
+ s := getLastComponent(arg.Expr)
+ s = strings.Replace(s, " ", "", -1)
+ // For small variable names, use user-defined types for more info.
+ if len(s) <= 2 && arg.UnderlyingType != arg.Type {
+ s = getLastComponent(arg.Type)
+ }
+ return strings.Title(s)
+}
+
+func getLastComponent(s string) string {
+ return s[1+strings.LastIndexByte(s, '.'):]
+}
+
+func msgStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) {
+ v := info.Types[e].Value
+ if v == nil || v.Kind() != constant.String {
+ return "", false
+ }
+ s = constant.StringVal(v)
+ // Only record strings with letters.
+ for _, r := range s {
+ if unicode.In(r, unicode.L) {
+ return s, true
+ }
+ }
+ return "", false
+}
diff --git a/cmd/gotext/message.go b/cmd/gotext/message.go
index 67a622f..7344f8d 100644
--- a/cmd/gotext/message.go
+++ b/cmd/gotext/message.go
@@ -13,67 +13,25 @@
// A translation may have multiple translations strings, or messages, depending
// on the feature values of the various arguments. For instance, consider
// a hypothetical translation from English to English, where the source defines
-// the format string "%d file(s) remaining". A completed translation, expressed
-// in JS, for this format string could look like:
-//
-// {
-// "Key": [
-// "\"%d files(s) remaining\""
-// ],
-// "Original": {
-// "Msg": "\"%d files(s) remaining\""
-// },
-// "Translation": {
-// "Select": {
-// "Feature": "plural",
-// "Arg": 1,
-// "Case": {
-// "one": { "Msg": "1 file remaining" },
-// "other": { "Msg": "%d files remaining" }
-// },
-// },
-// },
-// "Args": [
-// {
-// "ID": 2,
-// "Type": "int",
-// "UnderlyingType": "int",
-// "Expr": "nFiles",
-// "Comment": "number of files remaining",
-// "Position": "golang.org/x/text/cmd/gotext/demo.go:34:3"
-// }
-// ],
-// "Position": "golang.org/x/text/cmd/gotext/demo.go:33:10",
-// }
-//
-// Alternatively, the Translation section could be written as:
-//
-// "Translation": {
-// "Msg": "%d %[files]s remaining",
-// "Var": {
-// "files" : {
-// "Select": {
-// "Feature": "plural",
-// "Arg": 1,
-// "Case": {
-// "one": { "Msg": "file" },
-// "other": { "Msg": "files" }
-// }
-// }
-// }
-// }
-// }
+// the format string "%d file(s) remaining".
+// See the examples directory for examples of extracted messages.
-// A Translation describes a translation for a single language for a single
-// message.
-type Translation struct {
+// A Message describes a message to be translated.
+type Message struct {
// Key contains a list of identifiers for the message. If this list is empty
- // Original is used as the key.
- Key []string `json:"key,omitempty"`
- Original Text `json:"original"`
- Translation Text `json:"translation"`
- ExtractedComment string `json:"extractedComment,omitempty"`
- TranslatorComment string `json:"translatorComment,omitempty"`
+ // the message itself is used as the key.
+ Key []string `json:"key,omitempty"`
+ Meaning string `json:"meaning,omitempty"`
+ Message Text `json:"message"`
+ Translation *Text `json:"translation,omitempty"`
+
+ Comment string `json:"comment,omitempty"`
+ TranslatorComment string `json:"translatorComment,omitempty"`
+
+ // TODO: have a separate placeholder list, mapping placeholders
+ // to arguments or constant strings.
+ // TODO: default placeholder syntax is {foo}. Allow alternatives
+ // like `foo`.
Args []Argument `json:"args,omitempty"`
@@ -83,13 +41,19 @@
// An Argument contains information about the arguments passed to a message.
type Argument struct {
- ID interface{} `json:"id"` // An int for printf-style calls, but could be a string.
- Type string `json:"type"`
- UnderlyingType string `json:"underlyingType"`
- Expr string `json:"expr"`
- Value string `json:"value,omitempty"`
- Comment string `json:"comment,omitempty"`
- Position string `json:"position,omitempty"`
+ ID string `json:"id"` // An int for printf-style calls, but could be a string.
+ // Argument position for printf-style format strings. ArgNum corresponds to
+ // the number that should be used for explicit argument indexes (e.g.
+ // "%[1]d").
+ ArgNum int `json:"argNum,omitempty"`
+ Format []string `json:"format,omitempty"`
+
+ Type string `json:"type"`
+ UnderlyingType string `json:"underlyingType"`
+ Expr string `json:"expr"`
+ Value string `json:"value,omitempty"`
+ Comment string `json:"comment,omitempty"`
+ Position string `json:"position,omitempty"`
// Features contains the features that are available for the implementation
// of this argument.
@@ -118,8 +82,8 @@
Example string `json:"example,omitempty"`
}
-// Type Select selects a Text based on the feature value associated with
-// a feature of a certain argument.
+// Select selects a Text based on the feature value associated with a feature of
+// a certain argument.
type Select struct {
Feature string `json:"feature"` // Name of variable or Feature type
Arg interface{} `json:"arg"` // The argument ID.
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
1 comment:
File cmd/gotext/examples/main.go:
Patch Set #11, Line 24: p.Print("Hello ", person, " in ", place, "!\n")
Add a "\n". […]
Done
To view, visit change 79237. To unsubscribe, or for help writing mail filters, visit settings.
TryBots are happy.
Patch set 12:TryBot-Result +1