feat: fork
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/xgettext-go
|
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# xgettext-go
|
||||||
|
|
||||||
|
* [Forked from here](https://github.com/canonical/snapd/tree/master/i18n/xgettext-go)
|
||||||
|
* [The reason why](https://git.coopcloud.tech/toolshed/abra/issues/647)
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module git.coopcloud.tech/toolshed/xgettext-go
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jessevdk/go-flags v1.6.1
|
||||||
|
github.com/snapcore/snapd v0.0.0-20250919210715-9cb4b26eed4e
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/kr/pretty v0.2.2-0.20200810074440-814ac30b4b18 // indirect
|
||||||
|
github.com/kr/text v0.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
23
go.sum
Normal file
23
go.sum
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||||
|
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.2.2-0.20200810074440-814ac30b4b18 h1:fth7xdJYakAjo/XH38edyXuBEqYGJ8Me0RPolN1ZiQE=
|
||||||
|
github.com/kr/pretty v0.2.2-0.20200810074440-814ac30b4b18/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/snapcore/snapd v0.0.0-20250919210715-9cb4b26eed4e h1:GiuazaPktUEeR6sSi+38LE366fOnGs2reqzcmB8sr2s=
|
||||||
|
github.com/snapcore/snapd v0.0.0-20250919210715-9cb4b26eed4e/go.mod h1:z8KQXflnXuM2z1Xi7uJ9PJ+QEE3kxEapNK8jNN71fXI=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
||||||
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
348
main.go
Normal file
348
main.go
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jessevdk/go-flags"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgID struct {
|
||||||
|
msgidPlural string
|
||||||
|
comment string
|
||||||
|
fname string
|
||||||
|
line int
|
||||||
|
formatHint string
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgIDs map[string][]msgID
|
||||||
|
|
||||||
|
func formatComment(com string) string {
|
||||||
|
out := ""
|
||||||
|
for _, rawline := range strings.Split(com, "\n") {
|
||||||
|
line := rawline
|
||||||
|
line = strings.TrimPrefix(line, "//")
|
||||||
|
line = strings.TrimPrefix(line, "/*")
|
||||||
|
line = strings.TrimSuffix(line, "*/")
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
out += fmt.Sprintf("#. %s\n", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func findCommentsForTranslation(fset *token.FileSet, f *ast.File, posCall token.Position) string {
|
||||||
|
com := ""
|
||||||
|
for _, cg := range f.Comments {
|
||||||
|
// search for all comments in the previous line
|
||||||
|
for i := len(cg.List) - 1; i >= 0; i-- {
|
||||||
|
c := cg.List[i]
|
||||||
|
|
||||||
|
posComment := fset.Position(c.End())
|
||||||
|
//println(posCall.Line, posComment.Line, c.Text)
|
||||||
|
if posCall.Line == posComment.Line+1 {
|
||||||
|
posCall = posComment
|
||||||
|
com = fmt.Sprintf("%s\n%s", c.Text, com)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only return if we have a matching prefix
|
||||||
|
formatedComment := formatComment(com)
|
||||||
|
needle := fmt.Sprintf("#. %s", opts.AddCommentsTag)
|
||||||
|
if !strings.HasPrefix(formatedComment, needle) {
|
||||||
|
formatedComment = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatedComment
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructValue(val any) string {
|
||||||
|
switch val.(type) {
|
||||||
|
case *ast.BasicLit:
|
||||||
|
return val.(*ast.BasicLit).Value
|
||||||
|
// this happens for constructs like:
|
||||||
|
// gettext.Gettext("foo" + "bar")
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
// we only support string concat
|
||||||
|
if val.(*ast.BinaryExpr).Op != token.ADD {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
left := constructValue(val.(*ast.BinaryExpr).X)
|
||||||
|
// strip right " (or `)
|
||||||
|
left = left[0 : len(left)-1]
|
||||||
|
right := constructValue(val.(*ast.BinaryExpr).Y)
|
||||||
|
// strip left " (or `)
|
||||||
|
right = right[1:]
|
||||||
|
return left + right
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown type: %v", val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspectNodeForTranslations(fset *token.FileSet, f *ast.File, n ast.Node) bool {
|
||||||
|
// FIXME: this assume we always have a "gettext.Gettext" style keyword
|
||||||
|
l := strings.Split(opts.Keyword, ".")
|
||||||
|
gettextSelector := l[0]
|
||||||
|
gettextFuncName := l[1]
|
||||||
|
|
||||||
|
l = strings.Split(opts.KeywordPlural, ".")
|
||||||
|
gettextSelectorPlural := l[0]
|
||||||
|
gettextFuncNamePlural := l[1]
|
||||||
|
|
||||||
|
switch x := n.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
|
||||||
|
i18nStr := ""
|
||||||
|
i18nStrPlural := ""
|
||||||
|
if sel.Sel.Name == gettextFuncNamePlural && sel.X.(*ast.Ident).Name == gettextSelectorPlural {
|
||||||
|
i18nStr = x.Args[0].(*ast.BasicLit).Value
|
||||||
|
i18nStrPlural = x.Args[1].(*ast.BasicLit).Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if sel.Sel.Name == gettextFuncName && sel.X.(*ast.Ident).Name == gettextSelector {
|
||||||
|
i18nStr = constructValue(x.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
formatI18nStr := func(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// the "`" is special
|
||||||
|
if s[0] == '`' {
|
||||||
|
// keep escaped ", replace inner " with \", replace \n with \\n
|
||||||
|
rep := strings.NewReplacer(`\"`, `\"`, `"`, `\"`, "\n", "\\n")
|
||||||
|
s = rep.Replace(s)
|
||||||
|
}
|
||||||
|
// strip leading and trailing " (or `)
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: too simplistic(?), no %% is considered
|
||||||
|
formatHint := ""
|
||||||
|
if strings.Contains(i18nStr, "%") || strings.Contains(i18nStrPlural, "%") {
|
||||||
|
// well, not quite correct but close enough
|
||||||
|
formatHint = "c-format"
|
||||||
|
}
|
||||||
|
|
||||||
|
if i18nStr != "" {
|
||||||
|
msgidStr := formatI18nStr(i18nStr)
|
||||||
|
posCall := fset.Position(n.Pos())
|
||||||
|
msgIDs[msgidStr] = append(msgIDs[msgidStr], msgID{
|
||||||
|
formatHint: formatHint,
|
||||||
|
msgidPlural: formatI18nStr(i18nStrPlural),
|
||||||
|
fname: posCall.Filename,
|
||||||
|
line: posCall.Line,
|
||||||
|
comment: findCommentsForTranslation(fset, f, posCall),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFiles(args []string) error {
|
||||||
|
// go over the input files
|
||||||
|
msgIDs = make(map[string][]msgID)
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
for _, fname := range args {
|
||||||
|
if err := processSingleGoSource(fset, fname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readContent(fname string) (content []byte, err error) {
|
||||||
|
// If no search directories have been specified or we have an
|
||||||
|
// absolute path, just try to read the contents directly.
|
||||||
|
if len(opts.Directories) == 0 || filepath.IsAbs(fname) {
|
||||||
|
return os.ReadFile(fname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, search for the file in each of the configured
|
||||||
|
// directories.
|
||||||
|
for _, dir := range opts.Directories {
|
||||||
|
content, err = os.ReadFile(filepath.Join(dir, fname))
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func processSingleGoSource(fset *token.FileSet, fname string) error {
|
||||||
|
fnameContent, err := readContent(fname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the AST by parsing src.
|
||||||
|
f, err := parser.ParseFile(fset, fname, fnameContent, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.Inspect(f, func(n ast.Node) bool {
|
||||||
|
return inspectNodeForTranslations(fset, f, n)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatTime = func() string {
|
||||||
|
return time.Now().Format("2006-01-02 15:04-0700")
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustFprintf will write the given format string to the given
|
||||||
|
// writer. Any error will make it panic.
|
||||||
|
func mustFprintf(w io.Writer, format string, a ...any) {
|
||||||
|
_, err := fmt.Fprintf(w, format, a...)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("cannot write output: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePotFile(out io.Writer) {
|
||||||
|
|
||||||
|
header := fmt.Sprintf(`# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr "Project-Id-Version: %s\n"
|
||||||
|
"Report-Msgid-Bugs-To: %s\n"
|
||||||
|
"POT-Creation-Date: %s\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=CHARSET\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
`, opts.PackageName, opts.MsgIDBugsAddress, formatTime())
|
||||||
|
mustFprintf(out, "%s", header)
|
||||||
|
|
||||||
|
// yes, this is the way to do it in go
|
||||||
|
sortedKeys := []string{}
|
||||||
|
for k := range msgIDs {
|
||||||
|
sortedKeys = append(sortedKeys, k)
|
||||||
|
}
|
||||||
|
if opts.SortOutput {
|
||||||
|
sort.Strings(sortedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: use template here?
|
||||||
|
for _, k := range sortedKeys {
|
||||||
|
msgidList := msgIDs[k]
|
||||||
|
for _, msgid := range msgidList {
|
||||||
|
if opts.AddComments || opts.AddCommentsTag != "" {
|
||||||
|
mustFprintf(out, "%s", msgid.comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !opts.NoLocation {
|
||||||
|
mustFprintf(out, "#:")
|
||||||
|
for _, msgid := range msgidList {
|
||||||
|
mustFprintf(out, " %s:%d", msgid.fname, msgid.line)
|
||||||
|
}
|
||||||
|
mustFprintf(out, "\n")
|
||||||
|
}
|
||||||
|
msgid := msgidList[0]
|
||||||
|
if msgid.formatHint != "" {
|
||||||
|
mustFprintf(out, "#, %s\n", msgid.formatHint)
|
||||||
|
}
|
||||||
|
var formatOutput = func(in string) string {
|
||||||
|
// split string with \n into multiple lines
|
||||||
|
// to make the output nicer
|
||||||
|
out := strings.Replace(in, "\\n", "\\n\"\n \"", -1)
|
||||||
|
// cleanup too aggressive splitting (empty "" lines)
|
||||||
|
return strings.TrimSuffix(out, "\"\n \"")
|
||||||
|
}
|
||||||
|
mustFprintf(out, "msgid \"%v\"\n", formatOutput(k))
|
||||||
|
if msgid.msgidPlural != "" {
|
||||||
|
mustFprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural))
|
||||||
|
mustFprintf(out, "msgstr[0] \"\"\n")
|
||||||
|
mustFprintf(out, "msgstr[1] \"\"\n")
|
||||||
|
} else {
|
||||||
|
mustFprintf(out, "msgstr \"\"\n")
|
||||||
|
}
|
||||||
|
mustFprintf(out, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this must be setable via go-flags
|
||||||
|
var opts struct {
|
||||||
|
FilesFrom string `short:"f" long:"files-from" description:"get list of input files from FILE"`
|
||||||
|
|
||||||
|
Directories []string `short:"D" long:"directory" description:"add DIRECTORY to list for input files search"`
|
||||||
|
|
||||||
|
Output string `short:"o" long:"output" description:"output to specified file"`
|
||||||
|
|
||||||
|
AddComments bool `short:"c" long:"add-comments" description:"place all comment blocks preceding keyword lines in output file"`
|
||||||
|
|
||||||
|
AddCommentsTag string `long:"add-comments-tag" description:"place comment blocks starting with TAG and prceding keyword lines in output file"`
|
||||||
|
|
||||||
|
SortOutput bool `short:"s" long:"sort-output" description:"generate sorted output"`
|
||||||
|
|
||||||
|
NoLocation bool `long:"no-location" description:"do not write '#: filename:line' lines"`
|
||||||
|
|
||||||
|
MsgIDBugsAddress string `long:"msgid-bugs-address" default:"EMAIL" description:"set report address for msgid bugs"`
|
||||||
|
|
||||||
|
PackageName string `long:"package-name" description:"set package name in output"`
|
||||||
|
|
||||||
|
Keyword string `short:"k" long:"keyword" default:"gettext.Gettext" description:"look for WORD as the keyword for singular strings"`
|
||||||
|
KeywordPlural string `long:"keyword-plural" default:"gettext.NGettext" description:"look for WORD as the keyword for plural strings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// parse args
|
||||||
|
args, err := flags.ParseArgs(&opts, os.Args)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("ParseArgs failed %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var files []string
|
||||||
|
if opts.FilesFrom != "" {
|
||||||
|
content, err := os.ReadFile(opts.FilesFrom)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot read file %v: %v", opts.FilesFrom, err)
|
||||||
|
}
|
||||||
|
content = bytes.TrimSpace(content)
|
||||||
|
files = strings.Split(string(content), "\n")
|
||||||
|
} else {
|
||||||
|
files = args[1:]
|
||||||
|
}
|
||||||
|
if err := processFiles(files); err != nil {
|
||||||
|
log.Fatalf("processFiles failed with: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := os.Stdout
|
||||||
|
if opts.Output != "" {
|
||||||
|
var err error
|
||||||
|
out, err = os.Create(opts.Output)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create %s: %s", opts.Output, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writePotFile(out)
|
||||||
|
}
|
537
main_test.go
Normal file
537
main_test.go
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
// -*- Mode: Go; indent-tabs-mode: t -*-
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014-2015 Canonical Ltd
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 3 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
|
||||||
|
"github.com/snapcore/snapd/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hook up check.v1 into the "go test" runner
|
||||||
|
func TestT(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
|
type xgettextTestSuite struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&xgettextTestSuite{})
|
||||||
|
|
||||||
|
// test helper
|
||||||
|
func makeGoSourceFile(c *C, content []byte) string {
|
||||||
|
fname := filepath.Join(c.MkDir(), "foo.go")
|
||||||
|
err := os.WriteFile(fname, []byte(content), 0644)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
return fname
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) SetUpTest(c *C) {
|
||||||
|
// our test defaults
|
||||||
|
opts.NoLocation = false
|
||||||
|
opts.AddCommentsTag = "TRANSLATORS:"
|
||||||
|
opts.Keyword = "i18n.G"
|
||||||
|
opts.KeywordPlural = "i18n.NG"
|
||||||
|
opts.SortOutput = true
|
||||||
|
opts.PackageName = "snappy"
|
||||||
|
opts.MsgIDBugsAddress = "snappy-devel@lists.ubuntu.com"
|
||||||
|
|
||||||
|
// mock time
|
||||||
|
formatTime = func() string {
|
||||||
|
return "2015-06-30 14:48+0200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestFormatComment(c *C) {
|
||||||
|
var tests = []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{in: "// foo ", out: "#. foo\n"},
|
||||||
|
{in: "/* foo */", out: "#. foo\n"},
|
||||||
|
{in: "/* foo\n */", out: "#. foo\n"},
|
||||||
|
{in: "/* foo\nbar */", out: "#. foo\n#. bar\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c.Assert(formatComment(test.in), Equals, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestProcessFilesSimple(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// TRANSLATORS: foo comment
|
||||||
|
i18n.G("foo")
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
err := processFiles([]string{fname})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Assert(msgIDs, DeepEquals, map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
comment: "#. TRANSLATORS: foo comment\n",
|
||||||
|
fname: fname,
|
||||||
|
line: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestProcessFilesMultiple(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// TRANSLATORS: foo comment
|
||||||
|
i18n.G("foo")
|
||||||
|
|
||||||
|
// TRANSLATORS: bar comment
|
||||||
|
i18n.G("foo")
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
err := processFiles([]string{fname})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Assert(msgIDs, DeepEquals, map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
comment: "#. TRANSLATORS: foo comment\n",
|
||||||
|
fname: fname,
|
||||||
|
line: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: "#. TRANSLATORS: bar comment\n",
|
||||||
|
fname: fname,
|
||||||
|
line: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = `# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr "Project-Id-Version: snappy\n"
|
||||||
|
"Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"
|
||||||
|
"POT-Creation-Date: 2015-06-30 14:48+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=CHARSET\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
`
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputSimple(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
comment: "#. foo\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#. foo
|
||||||
|
#: fname:2
|
||||||
|
msgid "foo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
comment: "#. comment1\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 4,
|
||||||
|
comment: "#. comment2\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#. comment1
|
||||||
|
#. comment2
|
||||||
|
#: fname:2 fname:4
|
||||||
|
msgid "foo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: fname:2
|
||||||
|
msgid "foo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputNoLocation(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.NoLocation = true
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
msgid "foo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputFormatHint(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
formatHint: "c-format",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: fname:2
|
||||||
|
#, c-format
|
||||||
|
msgid "foo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputPlural(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo": {
|
||||||
|
{
|
||||||
|
msgidPlural: "plural",
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: fname:2
|
||||||
|
msgid "foo"
|
||||||
|
msgid_plural "plural"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputSorted(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"aaa": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"zzz": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.SortOutput = true
|
||||||
|
// we need to run this a bunch of times as the ordering might
|
||||||
|
// be right by pure chance
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: fname:2
|
||||||
|
msgid "aaa"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: fname:2
|
||||||
|
msgid "zzz"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestIntegration(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// TRANSLATORS: foo comment
|
||||||
|
// with multiple lines
|
||||||
|
i18n.G("foo")
|
||||||
|
|
||||||
|
// this comment has no translators tag
|
||||||
|
i18n.G("abc")
|
||||||
|
|
||||||
|
// TRANSLATORS: plural
|
||||||
|
i18n.NG("singular", "plural", 99)
|
||||||
|
|
||||||
|
i18n.G("zz %s")
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
// a real integration test :)
|
||||||
|
outName := filepath.Join(c.MkDir(), "snappy.pot")
|
||||||
|
os.Args = []string{"test-binary",
|
||||||
|
"--output", outName,
|
||||||
|
"--keyword", "i18n.G",
|
||||||
|
"--keyword-plural", "i18n.NG",
|
||||||
|
"--msgid-bugs-address", "snappy-devel@lists.ubuntu.com",
|
||||||
|
"--package-name", "snappy",
|
||||||
|
fname,
|
||||||
|
}
|
||||||
|
main()
|
||||||
|
|
||||||
|
// verify its what we expect
|
||||||
|
c.Assert(outName, testutil.FileEquals, fmt.Sprintf(`%s
|
||||||
|
#: %[2]s:9
|
||||||
|
msgid "abc"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRANSLATORS: foo comment
|
||||||
|
#. with multiple lines
|
||||||
|
#: %[2]s:6
|
||||||
|
msgid "foo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRANSLATORS: plural
|
||||||
|
#: %[2]s:12
|
||||||
|
msgid "singular"
|
||||||
|
msgid_plural "plural"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: %[2]s:14
|
||||||
|
#, c-format
|
||||||
|
msgid "zz %%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header, fname))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestProcessFilesConcat(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// TRANSLATORS: foo comment
|
||||||
|
i18n.G("foo\n" + "bar\n" + "baz")
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
err := processFiles([]string{fname})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Assert(msgIDs, DeepEquals, map[string][]msgID{
|
||||||
|
"foo\\nbar\\nbaz": {
|
||||||
|
{
|
||||||
|
comment: "#. TRANSLATORS: foo comment\n",
|
||||||
|
fname: fname,
|
||||||
|
line: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestProcessFilesWithQuote(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(fmt.Sprintf(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
i18n.G(%[1]s foo "bar"%[1]s)
|
||||||
|
}
|
||||||
|
`, "`")))
|
||||||
|
err := processFiles([]string{fname})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: %[2]s:4
|
||||||
|
msgid " foo \"bar\""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header, fname)
|
||||||
|
c.Check(out.String(), Equals, expected)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputMultilines(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo\\nbar\\nbaz": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
comment: "#. foo\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#. foo
|
||||||
|
#: fname:2
|
||||||
|
msgid "foo\n"
|
||||||
|
"bar\n"
|
||||||
|
"baz"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestWriteOutputTidy(c *C) {
|
||||||
|
msgIDs = map[string][]msgID{
|
||||||
|
"foo\\nbar\\nbaz": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"zzz\\n": {
|
||||||
|
{
|
||||||
|
fname: "fname",
|
||||||
|
line: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: fname:2
|
||||||
|
msgid "foo\n"
|
||||||
|
"bar\n"
|
||||||
|
"baz"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: fname:4
|
||||||
|
msgid "zzz\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header)
|
||||||
|
c.Assert(out.String(), Equals, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestProcessFilesWithDoubleQuote(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
i18n.G("foo \"bar\"")
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
err := processFiles([]string{fname})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: %[2]s:4
|
||||||
|
msgid "foo \"bar\""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header, fname)
|
||||||
|
c.Check(out.String(), Equals, expected)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *xgettextTestSuite) TestDontEscapeAlreadyEscapedQuoteInBacktick(c *C) {
|
||||||
|
fname := makeGoSourceFile(c, []byte(`package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
i18n.G(`+"`"+`Some text: "{\"key\":\"value\"}"`+"`"+`)
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
err := processFiles([]string{fname})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
out := bytes.NewBuffer([]byte(""))
|
||||||
|
writePotFile(out)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`%s
|
||||||
|
#: %[2]s:4
|
||||||
|
msgid "Some text: \"{\"key\":\"value\"}\""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
`, header, fname)
|
||||||
|
c.Check(out.String(), Equals, expected)
|
||||||
|
}
|
Reference in New Issue
Block a user