1 // Copyright 2016 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
23 "golang.org/x/tools/go/loader"
27 // - merge information into existing files
28 // - handle different file formats (PO, XLIFF)
29 // - handle features (gender, plural)
30 // - message rewriting
32 var cmdExtract = &Command{
34 UsageLine: "extract <package>*",
35 Short: "extract strings to be translated from code",
38 func runExtract(cmd *Command, args []string) error {
43 conf := loader.Config{
44 Build: &build.Default,
45 ParserMode: parser.ParseComments,
48 // Use the initial packages from the command line.
49 args, err := conf.FromArgs(args, false)
54 // Load, parse and type-check the whole program.
55 iprog, err := conf.Load()
60 // print returns Go syntax for the specified node.
61 print := func(n ast.Node) string {
63 format.Node(&buf, conf.Fset, n)
67 var translations []Translation
69 for _, info := range iprog.InitialPackages() {
70 for _, f := range info.Files {
71 // Associate comments with nodes.
72 cmap := ast.NewCommentMap(iprog.Fset, f, f.Comments)
73 getComment := func(n ast.Node) string {
74 cs := cmap.Filter(n).Comments()
76 return strings.TrimSpace(cs[0].Text())
81 // Find function calls.
82 ast.Inspect(f, func(n ast.Node) bool {
83 call, ok := n.(*ast.CallExpr)
88 // Skip calls of functions other than
89 // (*message.Printer).{Sp,Fp,P}rintf.
90 sel, ok := call.Fun.(*ast.SelectorExpr)
94 meth := info.Selections[sel]
95 if meth == nil || meth.Kind() != types.MethodVal {
98 // TODO: remove cheap hack and check if the type either
99 // implements some interface or is specifically of type
100 // "golang.org/x/text/message".Printer.
101 m, ok := extractFuncs[path.Base(meth.Recv().String())]
106 // argn is the index of the format string.
107 argn, ok := m[meth.Obj().Name()]
108 if !ok || argn >= len(call.Args) {
112 // Skip calls with non-constant format string.
113 fmtstr := info.Types[call.Args[argn]].Value
114 if fmtstr == nil || fmtstr.Kind() != constant.String {
118 posn := conf.Fset.Position(call.Lparen)
119 filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
121 // TODO: identify the type of the format argument. If it is not
122 // a string, multiple keys may be defined.
125 // TODO: replace substitutions (%v) with a translator friendly
126 // notation. For instance:
127 // "%d files remaining" -> "{numFiles} files remaining", or
128 // "%d files remaining" -> "{arg1} files remaining"
129 // Alternatively, this could be done at a later stage.
130 msg := constant.StringVal(fmtstr)
132 // Construct a Translation unit.
135 Position: filepath.Join(info.Pkg.Path(), filepos),
136 Original: Text{Msg: msg},
137 ExtractedComment: getComment(call.Args[0]),
138 // TODO(fix): this doesn't get the before comment.
139 // Comment: getComment(call),
142 for i, arg := range call.Args[argn+1:] {
144 if v := info.Types[arg].Value; v != nil {
145 val = v.ExactString()
147 posn := conf.Fset.Position(arg.Pos())
148 filepos := fmt.Sprintf("%s:%d:%d", filepath.Base(posn.Filename), posn.Line, posn.Column)
149 c.Args = append(c.Args, Argument{
151 Type: info.Types[arg].Type.String(),
152 UnderlyingType: info.Types[arg].Type.Underlying().String(),
155 Comment: getComment(arg),
156 Position: filepath.Join(info.Pkg.Path(), filepos),
157 // TODO report whether it implements
158 // interfaces plural.Interface,
163 translations = append(translations, c)
169 data, err := json.MarshalIndent(translations, "", " ")
173 for _, tag := range getLangs() {
174 // TODO: merge with existing files, don't overwrite.
175 os.MkdirAll(*dir, 0744)
176 file := filepath.Join(*dir, fmt.Sprintf("gotext_%v.out.json", tag))
177 if err := ioutil.WriteFile(file, data, 0744); err != nil {
178 return fmt.Errorf("could not create file: %v", err)
184 // extractFuncs indicates the types and methods for which to extract strings,
185 // and which argument to extract.
186 // TODO: use the types in conf.Import("golang.org/x/text/message") to extract
187 // the correct instances.
188 var extractFuncs = map[string]map[string]int{
189 // TODO: Printer -> *golang.org/x/text/message.Printer