package main import ( "flag" "fmt" "go/ast" "go/parser" "go/token" "io/ioutil" "log" "os" "path" "strconv" "strings" ) var ( debug = flag.Bool("debug", false, "enable debug mode and print AST") dirName = flag.String("in", "", "input dir: /path/to/go/pkg") outputDir = flag.String("out", "", "output dir: /path/to/i18n/files") domain = flag.String("domain", "default", "Domain") currentDomain = "default" fset *token.FileSet domainFiles map[string]*os.File currentFile string ) func main() { flag.Parse() currentDomain = *domain // Init logger log.SetFlags(0) // Init domain files domainFiles = make(map[string]*os.File) // Check if dir name parameter is valid log.Println(*dirName) f, err := os.Stat(*dirName) if err != nil { log.Fatal(err) } // Process file or dir if f.IsDir() { ParseDir(*dirName) } else { parseFile(*dirName) } } func getDomainFile(domain string) *os.File { // Return existent when available if f, ok := domainFiles[domain]; ok { return f } // If the file doesn't exist, create it. filePath := path.Join(*outputDir, domain+".po") f, err := os.OpenFile(filePath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } domainFiles[domain] = f writePoHeader(f) return f } func writePoHeader(f *os.File) { h := `msgid "" msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: \n" "X-Generator: xgotext\n" ` f.Write([]byte(h)) } func write(dom, msgid string) { f := getDomainFile(dom) f.Write([]byte("\nmsgid " + msgid)) f.Write([]byte("\nmsgstr \"\"")) f.Write([]byte("\n")) } func writePlural(dom, msgid, msgidPlural string) { f := getDomainFile(dom) f.Write([]byte("\nmsgid " + msgid)) f.Write([]byte("\nmsgid_plural " + msgidPlural)) f.Write([]byte("\nmsgstr[0] \"\"")) f.Write([]byte("\nmsgstr[1] \"\"")) f.Write([]byte("\n")) } func writeContext(dom, ctx string) { f := getDomainFile(dom) f.Write([]byte("\nmsgctxt " + ctx)) } func writeComments(dom, file, call string) { f := getDomainFile(dom) f.Write([]byte("\n#: " + file)) f.Write([]byte("\n#. " + call)) } func GetAllFiles(dirPth string) (files []string, err error) { var dirs []string dir, err := ioutil.ReadDir(dirPth) if err != nil { return nil, err } PthSep := string(os.PathSeparator) //suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写 for _, fi := range dir { if fi.IsDir() { // 目录, 递归遍历 dirs = append(dirs, dirPth+PthSep+fi.Name()) GetAllFiles(dirPth + PthSep + fi.Name()) } else { // 过滤指定格式 ok := strings.HasSuffix(fi.Name(), ".go") if ok { files = append(files, dirPth+PthSep+fi.Name()) } } } // 读取子目录下文件 for _, table := range dirs { temp, _ := GetAllFiles(table) for _, temp1 := range temp { files = append(files, temp1) } } return files, nil } func ParseDir(dirName string) error { files, err := GetAllFiles(dirName) if err != nil { return err } for _, fn := range files { parseFile(fn) } return nil } func parseDir(dirName string) error { fset := token.NewFileSet() pkgs, err := parser.ParseDir(fset, dirName, nil, parser.AllErrors) if err != nil { log.Fatal(err) } for _, pkg := range pkgs { for fn := range pkg.Files { fmt.Println(fn) //parseFile(fn) } } return nil } func parseFile(fileName string) error { // Remember current file to write comments on .po file currentFile = fileName // Parse AST fset = token.NewFileSet() node, err := parser.ParseFile(fset, fileName, nil, parser.AllErrors) if err != nil { log.Fatal(err) fmt.Println("Error: ", err) return err } //Debug mode if *debug { ast.Print(fset, node) } ast.Inspect(node, inspectFile) return nil } func inspectFile(n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: inspectCallExpr(x) } return true } func inspectCallExpr(n *ast.CallExpr) { if se, ok := n.Fun.(*ast.SelectorExpr); ok { switch se.Sel.String() { case "T": parseGet(n) case "GetN": parseGetN(n) case "GetD": parseGetD(n) case "GetND": parseGetND(n) case "GetC": parseGetC(n) case "GetNC": parseGetNC(n) case "GetDC": parseGetDC(n) case "GetNDC": parseGetNDC(n) case "SetDomain": parseSetDomain(n) } } } func parseGet(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 0 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit.Kind == token.STRING { writeComments(currentDomain, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) write(currentDomain, lit.Value) } } } } func parseGetN(call *ast.CallExpr) { if call.Args == nil || len(call.Args) < 3 { return } if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok1 := call.Args[1].(*ast.BasicLit); ok1 { if lit.Kind == token.STRING && lit1.Kind == token.STRING { switch x := call.Args[2].(type) { case *ast.BasicLit: if x.Kind != token.INT { return } case *ast.Ident: if x.Obj.Kind != ast.Var && x.Obj.Kind != ast.Con { return } default: return } writeComments(currentDomain, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) writePlural(currentDomain, lit.Value, lit1.Value) } } } } func parseGetD(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 1 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok := call.Args[1].(*ast.BasicLit); ok { if lit.Kind == token.STRING && lit1.Kind == token.STRING { dom, err := strconv.Unquote(lit.Value) if err != nil { log.Fatal(err) } writeComments(dom, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) write(dom, lit1.Value) } } } } } func parseGetND(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 2 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok := call.Args[1].(*ast.BasicLit); ok { if lit2, ok := call.Args[2].(*ast.BasicLit); ok { if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit2.Kind == token.STRING { dom, err := strconv.Unquote(lit.Value) if err != nil { log.Fatal(err) } writeComments(dom, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) writePlural(dom, lit1.Value, lit2.Value) } } } } } } func parseGetC(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 1 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok := call.Args[1].(*ast.BasicLit); ok { if lit.Kind == token.STRING && lit1.Kind == token.STRING { writeComments(currentDomain, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) writeContext(currentDomain, lit1.Value) write(currentDomain, lit.Value) } } } } } func parseGetNC(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 3 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok := call.Args[1].(*ast.BasicLit); ok { if lit3, ok := call.Args[3].(*ast.BasicLit); ok { if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit3.Kind == token.STRING { writeComments(currentDomain, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) writeContext(currentDomain, lit3.Value) writePlural(currentDomain, lit.Value, lit1.Value) } } } } } } func parseGetDC(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 2 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok := call.Args[1].(*ast.BasicLit); ok { if lit2, ok := call.Args[2].(*ast.BasicLit); ok { if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit2.Kind == token.STRING { dom, err := strconv.Unquote(lit.Value) if err != nil { log.Fatal(err) } writeComments(dom, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) writeContext(dom, lit2.Value) write(dom, lit1.Value) } } } } } } func parseGetNDC(call *ast.CallExpr) { if call.Args != nil && len(call.Args) > 4 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit1, ok := call.Args[1].(*ast.BasicLit); ok { if lit2, ok := call.Args[2].(*ast.BasicLit); ok { if lit4, ok := call.Args[4].(*ast.BasicLit); ok { if lit.Kind == token.STRING && lit1.Kind == token.STRING && lit2.Kind == token.STRING && lit4.Kind == token.STRING { dom, err := strconv.Unquote(lit.Value) if err != nil { log.Fatal(err) } writeComments(dom, fmt.Sprintf("%s:%d", fset.Position(call.Lparen).Filename, fset.Position(call.Lparen).Line), fmt.Sprintf("%s.%s", call.Fun.(*ast.SelectorExpr).X.(*ast.Ident).Name, call.Fun.(*ast.SelectorExpr).Sel.String()), ) writeContext(dom, lit4.Value) writePlural(dom, lit1.Value, lit2.Value) } } } } } } } func parseSetDomain(call *ast.CallExpr) { if call.Args != nil && len(call.Args) == 1 { if lit, ok := call.Args[0].(*ast.BasicLit); ok { if lit.Kind == token.STRING { cd, err := strconv.Unquote(lit.Value) if err == nil { currentDomain = cd } } } } }