pax_global_header00006660000000000000000000000064147436074470014532gustar00rootroot0000000000000052 comment=bc8ccc3ad4f0c90d611fda1a2da7012186c7756e golang-github-gosexy-gettext-0.9/000077500000000000000000000000001474360744700171275ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/.gitignore000066400000000000000000000000061474360744700211130ustar00rootroot00000000000000*.sw? golang-github-gosexy-gettext-0.9/.travis.yml000066400000000000000000000001141474360744700212340ustar00rootroot00000000000000language: go install: - go get -t -v ./... script: - go test -v ./... golang-github-gosexy-gettext-0.9/LICENSE000066400000000000000000000021121474360744700201300ustar00rootroot00000000000000Copyright (c) 2012-2016 José Carlos Nieto, https://menteslibres.net/xiam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-gosexy-gettext-0.9/README.md000066400000000000000000000042771474360744700204200ustar00rootroot00000000000000[![Build Status][travis-image]][travis-url] # gosexy/gettext Go bindings for [GNU gettext][1], an internationalization and localization library for writing multilingual systems. ## Requeriments * [GNU gettext][1] ### Linux Installation should be straightforward on Linux. ### OSX Installing gettext on a Mac is a bit awkward: ``` brew install gettext export CGO_LDFLAGS=-L/usr/local/opt/gettext/lib export CGO_CPPFLAGS=-I/usr/local/opt/gettext/include go get github.com/gosexy/gettext ``` ## Getting the library Use `go get` to download and install the binding: ```sh go get github.com/gosexy/gettext ``` ## Usage This is an example program showing the `BindTextdomain`, `Textdomain` and `SetLocale` bindings: ```go package main import ( "fmt" "github.com/gosexy/gettext" ) func main() { textDomain := "default" gettext.BindTextdomain(textDomain, "path/to/domains") gettext.Textdomain(textDomain) gettext.SetLocale(gettext.LcAll, "es_MX.utf8") fmt.Println(gettext.Gettext("Hello, world!")) } ``` Set the `LANGUAGE` env to the name of the language you want to use in your program: ```sh export LANGUAGE="es_MX.utf8" ./myapp ``` You can use the `xgettext` command to extract strings to be translated from a Go program: ``` go get github.com/gosexy/gettext/go-xgettext go-xgettext -o outfile.pot --keyword=Gettext --keyword-plural=NGettext infile.go ``` This will generate a `example.pot` file. After actually translating the `.pot` file, you'll have to generate `.po` and `.mo` files with `msginit` and `msgfmt`: ```sh msginit -l es_MX -o example.po -i example.pot msgfmt -c -v -o example.mo example.po ``` Finally, move the `.mo` file to an appropriate location. ```sh mv example.mo examples/es_MX.utf8/LC_MESSAGES/example.mo ``` ## Documentation Check out the API documentation [godoc.org/github.com/gosexy/gettext)](http://godoc.org/github.com/gosexy/gettext). The original gettext documentation: ```sh man 3 gettext ``` And here's a [good tutorial][2] on using gettext. [1]: http://www.gnu.org/software/gettext/ [2]: http://oriya.sarovar.org/docs/gettext_single.html [travis-image]: https://travis-ci.org/gosexy/gettext.svg?branch=master [travis-url]: https://travis-ci.org/gosexy/gettext golang-github-gosexy-gettext-0.9/_examples/000077500000000000000000000000001474360744700211045ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/_examples/de_DE.utf8/000077500000000000000000000000001474360744700227315ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/_examples/de_DE.utf8/LC_MESSAGES/000077500000000000000000000000001474360744700245165ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/_examples/de_DE.utf8/LC_MESSAGES/example.mo000066400000000000000000000011461474360744700265100ustar00rootroot00000000000000Þ•Dlˆ‰ œ ¦ ³eÁ'; L YAn apple%d applesGood bye!Good morningHello, world!Project-Id-Version: de_DE.utf 8 Report-Msgid-Bugs-To: POT-Creation-Date: 2012-10-06 15:47-0500 PO-Revision-Date: 2016-02-20 07:28-0600 Last-Translator: Carlos Reventlov Language-Team: German Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Ein Apfel%d ÄpfelAuf Wiedersehen!Guten morgenHallo, Welt!golang-github-gosexy-gettext-0.9/_examples/de_DE.utf8/example.pot000066400000000000000000000020711474360744700251100ustar00rootroot00000000000000# 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 , YEAR. # #, fuzzy msgid "" msgstr "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-10-06 15:47-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: gettext_test.go:32 gettext_test.go:40 #, c-format msgid "An apple" msgid_plural "%d apples" msgstr[0] "Ein Apfel" msgstr[1] "%d Äpfel" #: gettext_test.go:56 msgid "Good bye!" msgstr "Auf Wiedersehen!" #: gettext_test.go:48 msgid "Good morning" msgstr "Guten morgen" #: gettext_test.go:24 msgid "Hello, world!" msgstr "Hallo, Welt!" golang-github-gosexy-gettext-0.9/_examples/es_MX.utf8/000077500000000000000000000000001474360744700230045ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/_examples/es_MX.utf8/LC_MESSAGES/000077500000000000000000000000001474360744700245715ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/_examples/es_MX.utf8/LC_MESSAGES/example.mo000066400000000000000000000011461474360744700265630ustar00rootroot00000000000000Þ•Dlˆ‰ œ ¦ ³bÁ$< K XAn apple%d applesGood bye!Good morningHello, world!Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2012-10-06 15:47-0500 PO-Revision-Date: 2012-10-06 15:48-0500 Last-Translator: Language-Team: Spanish Language: es_MX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Una manzana%d manzanas¡Hasta luego!Buenos días¡Hola mundo!golang-github-gosexy-gettext-0.9/_examples/es_MX.utf8/example.pot000066400000000000000000000020741474360744700251660ustar00rootroot00000000000000# 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 , YEAR. # #, fuzzy msgid "" msgstr "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-10-06 15:47-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: gettext_test.go:32 gettext_test.go:40 #, c-format msgid "An apple" msgid_plural "%d apples" msgstr[0] "Una manzana" msgstr[1] "%d manzanas" #: gettext_test.go:56 msgid "Good bye!" msgstr "¡Hasta luego!" #: gettext_test.go:48 msgid "Good morning" msgstr "Buenos días" #: gettext_test.go:24 msgid "Hello, world!" msgstr "¡Hola mundo!" golang-github-gosexy-gettext-0.9/_examples/gettext.go000066400000000000000000000006671474360744700231300ustar00rootroot00000000000000package main import ( "fmt" "github.com/gosexy/gettext" ) func main() { gettext.BindTextdomain("example", "./") gettext.Textdomain("example") gettext.SetLocale(gettext.LcAll, "es_MX.utf8") fmt.Println(gettext.Gettext("Hello, world!")) gettext.SetLocale(gettext.LcAll, "de_DE.utf8") fmt.Println(gettext.Gettext("Hello, world!")) gettext.SetLocale(gettext.LcAll, "en_US.utf8") fmt.Println(gettext.Gettext("Hello, world!")) } golang-github-gosexy-gettext-0.9/gettext.go000066400000000000000000000152311474360744700211440ustar00rootroot00000000000000// Copyright (c) 2012-2016 José Carlos Nieto, https://menteslibres.net/xiam // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // Package gettext provides bindings for GNU Gettext. package gettext /* // #cgo LDFLAGS: -lintl // Use this if: /usr/bin/ld: cannot find -lintl, see https://github.com/gosexy/gettext/issues/1 #include #include #include */ import "C" import ( "fmt" "strings" "unsafe" ) var ( // LcAll is for all of the locale. LcAll = uint(C.LC_ALL) // LcCollate is for regular expression matching (it determines the meaning of // range expressions and equivalence classes) and string collation. LcCollate = uint(C.LC_COLLATE) // LcCtype is for regular expression matching, character classification, // conversion, case-sensitive comparison, and wide character functions. LcCtype = uint(C.LC_CTYPE) // LcMessages is for localizable natural-language messages. LcMessages = uint(C.LC_MESSAGES) // LcMonetary is for monetary formatting. LcMonetary = uint(C.LC_MONETARY) // LcNumeric is for number formatting (such as the decimal point and the // thousands separator). LcNumeric = uint(C.LC_NUMERIC) // LcTime is for time and date formatting. LcTime = uint(C.LC_TIME) ) // Deprecated but kept for backwards compatibility. var ( LC_ALL = LcAll LC_COLLATE = LcCollate LC_CTYPE = LcCtype LC_MESSAGES = LcMessages LC_MONETARY = LcMonetary LC_NUMERIC = LcNumeric LC_TIME = LcTime ) // SetLocale sets the program's current locale. func SetLocale(category uint, locale string) string { clocale := C.CString(locale) defer C.free(unsafe.Pointer(clocale)) return C.GoString(C.setlocale(C.int(category), clocale)) } // BindTextdomain sets the directory containing message catalogs. func BindTextdomain(domainname string, dirname string) string { cdirname := C.CString(dirname) defer C.free(unsafe.Pointer(cdirname)) cdomainname := C.CString(domainname) defer C.free(unsafe.Pointer(cdomainname)) return C.GoString(C.bindtextdomain(cdomainname, cdirname)) } // BindTextdomainCodeset sets the output codeset for message catalogs on the // given domainname. func BindTextdomainCodeset(domainname string, codeset string) string { cdomainname := C.CString(domainname) defer C.free(unsafe.Pointer(cdomainname)) ccodeset := C.CString(codeset) defer C.free(unsafe.Pointer(ccodeset)) return C.GoString(C.bind_textdomain_codeset(cdomainname, ccodeset)) } // Textdomain sets or retrieves the current message domain. func Textdomain(domainname string) string { cdomainname := C.CString(domainname) defer C.free(unsafe.Pointer(cdomainname)) return C.GoString(C.textdomain(cdomainname)) } // Gettext attempts to translate a text string into the user's system language, // by looking up the translation in a message catalog. func Gettext(msgid string) string { cmsgid := C.CString(msgid) defer C.free(unsafe.Pointer(cmsgid)) return C.GoString(C.gettext(cmsgid)) } // DGettext is like Gettext(), but looks up the message in the specified // domain. func DGettext(domain string, msgid string) string { cdomain := cDomainName(domain) defer C.free(unsafe.Pointer(cdomain)) cmsgid := C.CString(msgid) defer C.free(unsafe.Pointer(cmsgid)) return C.GoString(C.dgettext(cdomain, cmsgid)) } // DCGettext is like Gettext(), but looks up the message in the specified // domain and category. func DCGettext(domain string, msgid string, category uint) string { cdomain := cDomainName(domain) defer C.free(unsafe.Pointer(cdomain)) cmsgid := C.CString(msgid) defer C.free(unsafe.Pointer(cmsgid)) return C.GoString(C.dcgettext(cdomain, cmsgid, C.int(category))) } // NGettext attempts to translate a text string into the user's system // language, by looking up the appropriate plural form of the translation in a // message catalog. func NGettext(msgid string, msgidPlural string, n uint64) string { cmsgid := C.CString(msgid) defer C.free(unsafe.Pointer(cmsgid)) cmsgidPlural := C.CString(msgidPlural) defer C.free(unsafe.Pointer(cmsgidPlural)) return C.GoString(C.ngettext(cmsgid, cmsgidPlural, C.ulong(n))) } // Sprintf is like fmt.Sprintf() but without %!(EXTRA) errors. func Sprintf(format string, a ...interface{}) string { expects := strings.Count(format, "%") - strings.Count(format, "%%") if expects > 0 { arguments := make([]interface{}, expects) for i := 0; i < expects; i++ { if len(a) > i { arguments[i] = a[i] } } return fmt.Sprintf(format, arguments...) } return format } // DNGettext is like NGettext(), but looks up the message in the specified // domain. func DNGettext(domainname string, msgid string, msgidPlural string, n uint64) string { cdomainname := cDomainName(domainname) cmsgid := C.CString(msgid) cmsgidPlural := C.CString(msgidPlural) defer func() { C.free(unsafe.Pointer(cdomainname)) C.free(unsafe.Pointer(cmsgid)) C.free(unsafe.Pointer(cmsgidPlural)) }() return C.GoString(C.dngettext(cdomainname, cmsgid, cmsgidPlural, C.ulong(n))) } // DCNGettext is like NGettext(), but looks up the message in the specified // domain and category. func DCNGettext(domainname string, msgid string, msgidPlural string, n uint64, category uint) string { cdomainname := cDomainName(domainname) cmsgid := C.CString(msgid) cmsgidPlural := C.CString(msgidPlural) defer func() { C.free(unsafe.Pointer(cdomainname)) C.free(unsafe.Pointer(cmsgid)) C.free(unsafe.Pointer(cmsgidPlural)) }() return C.GoString(C.dcngettext(cdomainname, cmsgid, cmsgidPlural, C.ulong(n), C.int(category))) } // cDomainName returns the domain name CString that can be nil. func cDomainName(domain string) *C.char { if domain == "" { return nil } // The caller is responsible for freeing this up. return C.CString(domain) } golang-github-gosexy-gettext-0.9/gettext_test.go000066400000000000000000000032731474360744700222060ustar00rootroot00000000000000package gettext import ( "os" "testing" "github.com/stretchr/testify/assert" ) const ( spanishMexico = "es_MX.utf8" deutschDeutschland = "de_DE.utf8" frenchFrance = "fr_FR.utf8" ) // a setUp would be nice func init() { textDomain := "example" BindTextdomain(textDomain, "_examples/") Textdomain(textDomain) } func TestSpanish(t *testing.T) { os.Setenv("LANGUAGE", spanishMexico) SetLocale(LcAll, "") assert.Equal(t, "¡Hola mundo!", Gettext("Hello, world!")) assert.Equal(t, "Una manzana", Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage")) assert.Equal(t, "3 manzanas", Sprintf(NGettext("An apple", "%d apples", 3), 3)) assert.Equal(t, "Buenos días", Gettext("Good morning")) assert.Equal(t, "¡Hasta luego!", Gettext("Good bye!")) } func TestDeutsch(t *testing.T) { os.Setenv("LANGUAGE", deutschDeutschland) SetLocale(LcAll, "") assert.Equal(t, "Hallo, Welt!", Gettext("Hello, world!")) assert.Equal(t, "Ein Apfel", Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage")) assert.Equal(t, "3 Äpfel", Sprintf(NGettext("An apple", "%d apples", 3), 3)) assert.Equal(t, "Guten morgen", Gettext("Good morning")) assert.Equal(t, "Auf Wiedersehen!", Gettext("Good bye!")) } func TestFrench(t *testing.T) { // Note that we don't have a french translation. os.Setenv("LANGUAGE", frenchFrance) SetLocale(LcAll, "") assert.Equal(t, "Hello, world!", Gettext("Hello, world!")) assert.Equal(t, "An apple", Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage")) assert.Equal(t, "3 apples", Sprintf(NGettext("An apple", "%d apples", 3), 3)) assert.Equal(t, "Good morning", Gettext("Good morning")) assert.Equal(t, "Good bye!", Gettext("Good bye!")) } golang-github-gosexy-gettext-0.9/go-xgettext/000077500000000000000000000000001474360744700214065ustar00rootroot00000000000000golang-github-gosexy-gettext-0.9/go-xgettext/main.go000066400000000000000000000231241474360744700226630ustar00rootroot00000000000000// -*- Mode: Go; indent-tabs-mode: t -*- /* * Copyright (C) 2015-2016 Canonical Ltd * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package main import ( "fmt" "go/ast" "go/parser" "go/token" "io" "io/ioutil" "log" "os" "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 interface{}) 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:len(right)] 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 var gettextSelector, gettextFuncName string l := strings.Split(opts.Keyword, ".") if len(l) > 1 { gettextSelector = l[0] gettextFuncName = l[1] } else { gettextFuncName = l[0] } var gettextSelectorPlural, gettextFuncNamePlural string l = strings.Split(opts.KeywordPlural, ".") if len(l) > 1 { gettextSelectorPlural = l[0] gettextFuncNamePlural = l[1] } else { gettextFuncNamePlural = l[0] } switch x := n.(type) { case *ast.CallExpr: var i18nStr, i18nStrPlural string //if sel, ok := x.Fun.(*ast.Ident); ok { //} switch sel := x.Fun.(type) { case *ast.Ident: if sel.Name == gettextFuncNamePlural { i18nStr = x.Args[0].(*ast.BasicLit).Value i18nStrPlural = x.Args[1].(*ast.BasicLit).Value } if sel.Name == gettextFuncName { i18nStr = constructValue(x.Args[0]) } case *ast.SelectorExpr: 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]) } } if i18nStr == "" { break } // FIXME: too simplistic(?), no %% is considered formatHint := "" if strings.Contains(i18nStr, "%") || strings.Contains(i18nStrPlural, "%") { // well, not quite correct but close enough formatHint = "c-format" } 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 formatI18nStr(s string) string { if s == "" { return "" } // the "`" is special if s[0] == '`' { // replace inner " with \" s = strings.Replace(s, "\"", "\\\"", -1) // replace \n with \\n s = strings.Replace(s, "\n", "\\n", -1) } // strip leading and trailing " (or `) s = s[1 : len(s)-1] return s } 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 processSingleGoSource(fset *token.FileSet, fname string) error { fnameContent, err := ioutil.ReadFile(fname) if err != nil { panic(err) } // Create the AST by parsing src. f, err := parser.ParseFile(fset, fname, fnameContent, parser.ParseComments) if err != nil { panic(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") } 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 , 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 \n" "Language-Team: LANGUAGE \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()) fmt.Fprintf(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 != "" { fmt.Fprintf(out, "%s", msgid.comment) } } if !opts.NoLocation { fmt.Fprintf(out, "#:") for _, msgid := range msgidList { fmt.Fprintf(out, " %s:%d", msgid.fname, msgid.line) } fmt.Fprintf(out, "\n") } msgid := msgidList[0] if msgid.formatHint != "" { fmt.Fprintf(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 \"") } fmt.Fprintf(out, "msgid \"%v\"\n", formatOutput(k)) if msgid.msgidPlural != "" { fmt.Fprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural)) fmt.Fprintf(out, "msgstr[0] \"\"\n") fmt.Fprintf(out, "msgstr[1] \"\"\n") } else { fmt.Fprintf(out, "msgstr \"\"\n") } fmt.Fprintf(out, "\n") } } // FIXME: this must be setable via go-flags var opts struct { 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) } if err := processFiles(args[1:]); 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) } golang-github-gosexy-gettext-0.9/go-xgettext/main_test.go000066400000000000000000000242071474360744700237250ustar00rootroot00000000000000// -*- Mode: Go; indent-tabs-mode: t -*- /* * Copyright (C) 2015-2016 Canonical Ltd * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package main import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "testing" . "gopkg.in/check.v1" ) // Hook up check.v1 into the "go test" runner func Test(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 := ioutil.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": []msgID{ { 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": []msgID{ { 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 , 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 \n" "Language-Team: LANGUAGE \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": []msgID{ { 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": []msgID{ { 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": []msgID{ { 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": []msgID{ { 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": []msgID{ { 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": []msgID{ { 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": []msgID{ { fname: "fname", line: 2, }, }, "zzz": []msgID{ { 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 got, err := ioutil.ReadFile(outName) c.Assert(err, IsNil) expected := 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) c.Assert(string(got), Equals, expected) } 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": []msgID{ { 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": []msgID{ { 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": []msgID{ { fname: "fname", line: 2, }, }, "zzz\\n": []msgID{ { 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) }