mirror of https://github.com/kubernetes/kops.git
Update deps in support of drain
This commit is contained in:
parent
f530704550
commit
357134040f
|
|
@ -232,3 +232,21 @@
|
|||
[submodule "_vendor/k8s.io/gengo"]
|
||||
path = _vendor/k8s.io/gengo
|
||||
url = https://github.com/kubernetes/gengo.git
|
||||
[submodule "_vendor/k8s.io/heapster"]
|
||||
path = _vendor/k8s.io/heapster
|
||||
url = https://github.com/kubernetes/heapster
|
||||
[submodule "_vendor/github.com/MakeNowJust/heredoc"]
|
||||
path = _vendor/github.com/MakeNowJust/heredoc
|
||||
url = https://github.com/MakeNowJust/heredoc
|
||||
[submodule "_vendor/github.com/daviddengcn/go-colortext"]
|
||||
path = _vendor/github.com/daviddengcn/go-colortext
|
||||
url = https://github.com/daviddengcn/go-colortext
|
||||
[submodule "_vendor/github.com/docker/spdystream"]
|
||||
path = _vendor/github.com/docker/spdystream
|
||||
url = https://github.com/docker/spdystream
|
||||
[submodule "_vendor/github.com/renstrom/dedent"]
|
||||
path = _vendor/github.com/renstrom/dedent
|
||||
url = https://github.com/renstrom/dedent
|
||||
[submodule "_vendor/github.com/chai2010/gettext-go"]
|
||||
path = _vendor/github.com/chai2010/gettext-go
|
||||
url = https://github.com/chai2010/gettext-go
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 1d91351acdc1cb2f2c995864674b754134b86ca7
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c6fed771bfd517099caf0f7a961671fa8ed08723
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 511bcaf42ccd42c38aba7427b6673277bf19e2a1
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 449fdfce4d962303d702fec724ef0ad181c92528
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 020d11c3b9c0c7a3c2efcc8e5cf5b9ef7bcea21f
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c2ac40f1adf8c42a79badddb2a2acd673cae3bcb
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 TSUYUSATO Kitsune
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#heredoc [](https://drone.io/github.com/MakeNowJust/heredoc/latest) [](https://gowalker.org/github.com/MakeNowJust/heredoc)
|
||||
|
||||
##About
|
||||
|
||||
Package heredoc provides the here-document with keeping indent.
|
||||
|
||||
##Install
|
||||
|
||||
```console
|
||||
$ go get github.com/MakeNowJust/heredoc
|
||||
```
|
||||
|
||||
##Import
|
||||
|
||||
```go
|
||||
// usual
|
||||
import "github.com/MakeNowJust/heredoc"
|
||||
// shortcuts
|
||||
import . "github.com/MakeNowJust/heredoc/dot"
|
||||
```
|
||||
|
||||
##Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/MakeNowJust/heredoc/dot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(D(`
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, ...
|
||||
`))
|
||||
// Output:
|
||||
// Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
// sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
// aliqua. Ut enim ad minim veniam, ...
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
##API Document
|
||||
|
||||
- [Go Walker - github.com/MakeNowJust/heredoc](https://gowalker.org/github.com/MakeNowJust/heredoc)
|
||||
- [Go Walker - github.com/MakeNowJust/heredoc/dot](https://gowalker.org/github.com/MakeNowJust/heredoc/dot)
|
||||
|
||||
##License
|
||||
|
||||
This software is released under the MIT License, see LICENSE.
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2014 TSUYUSATO Kitsune
|
||||
// This software is released under the MIT License.
|
||||
// http://opensource.org/licenses/mit-license.php
|
||||
|
||||
// Package heredoc_dot is the set of shortcuts for dot import.
|
||||
//
|
||||
// For example:
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "runtime"
|
||||
// . "github.com/MakeNowJust/heredoc/dot"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// fmt.Printf(D(`
|
||||
// GOROOT: %s
|
||||
// GOARCH: %s
|
||||
// GOOS : %s
|
||||
// `), runtime.GOROOT(), runtime.GOARCH, runtime.GOOS)
|
||||
// }
|
||||
package heredoc_dot
|
||||
|
||||
import "github.com/MakeNowJust/heredoc"
|
||||
|
||||
// Shortcut heredoc.Doc
|
||||
func D(raw string) string {
|
||||
return heredoc.Doc(raw)
|
||||
}
|
||||
|
||||
// Shortcut heredoc.Docf
|
||||
func Df(raw string, args ...interface{}) string {
|
||||
return heredoc.Docf(raw, args...)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) 2014 TSUYUSATO Kitsune
|
||||
// This software is released under the MIT License.
|
||||
// http://opensource.org/licenses/mit-license.php
|
||||
|
||||
package heredoc_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
import "github.com/MakeNowJust/heredoc"
|
||||
|
||||
func ExampleDoc_lipsum() {
|
||||
fmt.Print(heredoc.Doc(`
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, ...
|
||||
`))
|
||||
// Output:
|
||||
// Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
// sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
// aliqua. Ut enim ad minim veniam, ...
|
||||
//
|
||||
}
|
||||
|
||||
func ExampleDoc_spec() {
|
||||
// Single line string is no change.
|
||||
fmt.Println(heredoc.Doc(`It is single line.`))
|
||||
// If first line is empty, heredoc.Doc removes first line.
|
||||
fmt.Println(heredoc.Doc(`
|
||||
It is first line.
|
||||
It is second line.`))
|
||||
// If last line is empty and more little length than indents,
|
||||
// heredoc.Doc removes last line's content.
|
||||
fmt.Println(heredoc.Doc(`
|
||||
Next is last line.
|
||||
`))
|
||||
fmt.Println("Previous is last line.")
|
||||
// Output:
|
||||
// It is single line.
|
||||
// It is first line.
|
||||
// It is second line.
|
||||
// Next is last line.
|
||||
//
|
||||
// Previous is last line.
|
||||
}
|
||||
|
||||
func ExampleDocf() {
|
||||
libName := "github.com/MakeNowJust/heredoc"
|
||||
author := "TSUYUSATO Kitsune (@MakeNowJust)"
|
||||
fmt.Printf(heredoc.Docf(`
|
||||
Library Name : %s
|
||||
Author : %s
|
||||
Repository URL: http://%s.git
|
||||
`, libName, author, libName))
|
||||
// Output:
|
||||
// Library Name : github.com/MakeNowJust/heredoc
|
||||
// Author : TSUYUSATO Kitsune (@MakeNowJust)
|
||||
// Repository URL: http://github.com/MakeNowJust/heredoc.git
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2014 TSUYUSATO Kitsune
|
||||
// This software is released under the MIT License.
|
||||
// http://opensource.org/licenses/mit-license.php
|
||||
|
||||
// Package heredoc provides the here-document with keeping indent.
|
||||
//
|
||||
// Golang supports raw-string syntax.
|
||||
// doc := `
|
||||
// Foo
|
||||
// Bar
|
||||
// `
|
||||
// But raw-string cannot recognize indent. Thus such content is indented string, equivalent to
|
||||
// "\n\tFoo\n\tBar\n"
|
||||
// I dont't want this!
|
||||
//
|
||||
// However this problem is solved by package heredoc.
|
||||
// doc := heredoc.Doc(`
|
||||
// Foo
|
||||
// Bar
|
||||
// `)
|
||||
// It is equivalent to
|
||||
// "Foo\nBar\n"
|
||||
package heredoc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// heredoc.Doc retutns unindented string as here-document.
|
||||
//
|
||||
// Process of making here-document:
|
||||
// 1. Find most little indent size. (Skip empty lines)
|
||||
// 2. Remove this indents of lines.
|
||||
func Doc(raw string) string {
|
||||
skipFirstLine := false
|
||||
if raw[0] == '\n' {
|
||||
raw = raw[1:]
|
||||
} else {
|
||||
skipFirstLine = true
|
||||
}
|
||||
|
||||
minIndentSize := int(^uint(0) >> 1) // Max value of type int
|
||||
lines := strings.Split(raw, "\n")
|
||||
|
||||
// 1.
|
||||
for i, line := range lines {
|
||||
if i == 0 && skipFirstLine {
|
||||
continue
|
||||
}
|
||||
|
||||
indentSize := 0
|
||||
for _, r := range []rune(line) {
|
||||
if unicode.IsSpace(r) {
|
||||
indentSize += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(line) == indentSize {
|
||||
if i == len(lines)-1 && indentSize < minIndentSize {
|
||||
lines[i] = ""
|
||||
}
|
||||
} else if indentSize < minIndentSize {
|
||||
minIndentSize = indentSize
|
||||
}
|
||||
}
|
||||
|
||||
// 2.
|
||||
for i, line := range lines {
|
||||
if i == 0 && skipFirstLine {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(lines[i]) >= minIndentSize {
|
||||
lines[i] = line[minIndentSize:]
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// heredoc.Docf returns unindented and formatted string as here-document.
|
||||
// This format is same with package fmt's format.
|
||||
func Docf(raw string, args ...interface{}) string {
|
||||
return fmt.Sprintf(Doc(raw), args...)
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) 2014 TSUYUSATO Kitsune
|
||||
// This software is released under the MIT License.
|
||||
// http://opensource.org/licenses/mit-license.php
|
||||
|
||||
package heredoc_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
import "github.com/MakeNowJust/heredoc"
|
||||
|
||||
type testCase struct {
|
||||
raw, expect string
|
||||
}
|
||||
|
||||
var tests = []testCase{
|
||||
{`
|
||||
Foo
|
||||
Bar
|
||||
`,
|
||||
"Foo\nBar\n"},
|
||||
{`Foo
|
||||
Bar`,
|
||||
"Foo\nBar"},
|
||||
{`Foo
|
||||
|
||||
Bar
|
||||
`,
|
||||
"Foo\n\t\nBar\n"},
|
||||
{`
|
||||
Foo
|
||||
Bar
|
||||
Hoge
|
||||
`,
|
||||
"Foo\n\tBar\n\t\tHoge\n\t\t\t"},
|
||||
{`Foo Bar`, "Foo Bar"},
|
||||
{
|
||||
`
|
||||
Foo
|
||||
Bar
|
||||
`, "Foo\nBar\n"},
|
||||
}
|
||||
|
||||
func TestDoc(t *testing.T) {
|
||||
for i, test := range tests {
|
||||
result := heredoc.Doc(test.raw)
|
||||
if result != test.expect {
|
||||
t.Errorf("tests[%d] failed: expected=> %#v, result=> %#v", i, test.expect, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocf(t *testing.T) {
|
||||
// test case
|
||||
str := `
|
||||
int: %3d
|
||||
string: %s
|
||||
`
|
||||
i := 42
|
||||
s := "Hello"
|
||||
expect := "int: 42\nstring: Hello\n"
|
||||
|
||||
result := heredoc.Docf(str, i, s)
|
||||
if result != expect {
|
||||
t.Errorf("test failed: expected=> %#v, result=> %#v", expect, result)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
gettext-go
|
||||
==========
|
||||
|
||||
PkgDoc: [http://godoc.org/github.com/chai2010/gettext-go/gettext](http://godoc.org/github.com/chai2010/gettext-go/gettext)
|
||||
|
||||
Install
|
||||
========
|
||||
|
||||
1. `go get github.com/chai2010/gettext-go/gettext`
|
||||
2. `go run hello.go`
|
||||
|
||||
The godoc.org or gowalker.org has more information.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chai2010/gettext-go/gettext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gettext.SetLocale("zh_CN")
|
||||
gettext.Textdomain("hello")
|
||||
|
||||
gettext.BindTextdomain("hello", "local", nil)
|
||||
|
||||
// gettext.BindTextdomain("hello", "local", nil) // from local dir
|
||||
// gettext.BindTextdomain("hello", "local.zip", nil) // from local zip file
|
||||
// gettext.BindTextdomain("hello", "local.zip", zipData) // from embedded zip data
|
||||
|
||||
// translate source text
|
||||
fmt.Println(gettext.Gettext("Hello, world!"))
|
||||
// Output: 你好, 世界!
|
||||
|
||||
// if no msgctxt in PO file (only msgid and msgstr),
|
||||
// specify context as "" by
|
||||
fmt.Println(gettext.PGettext("", "Hello, world!"))
|
||||
// Output: 你好, 世界!
|
||||
|
||||
// translate resource
|
||||
fmt.Println(string(gettext.Getdata("poems.txt"))))
|
||||
// Output: ...
|
||||
}
|
||||
```
|
||||
|
||||
Go file: [hello.go](https://github.com/chai2010/gettext-go/blob/master/examples/hello.go); PO file: [hello.po](https://github.com/chai2010/gettext-go/blob/master/examples/local/default/LC_MESSAGES/hello.po);
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
Please report bugs to <chaishushan@gmail.com>.
|
||||
|
||||
Thanks!
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
default:
|
||||
msgfmt -o local/default/LC_MESSAGES/hello.mo local/default/LC_MESSAGES/hello.po
|
||||
msgfmt -o local/zh_CN/LC_MESSAGES/hello.mo local/zh_CN/LC_MESSAGES/hello.po
|
||||
msgfmt -o local/zh_TW/LC_MESSAGES/hello.mo local/zh_TW/LC_MESSAGES/hello.po
|
||||
7z a -tzip -scsUTF-8 local.zip local
|
||||
go run hello.go
|
||||
|
||||
clean:
|
||||
rm local.zip
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This is a gettext-go exmaple.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chai2010/gettext-go/examples/hi"
|
||||
"github.com/chai2010/gettext-go/gettext"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// bind app domain
|
||||
gettext.BindTextdomain("hello", "local", nil)
|
||||
gettext.Textdomain("hello")
|
||||
|
||||
// $(LC_MESSAGES) or $(LANG) or empty
|
||||
fmt.Println(gettext.Gettext("Gettext in init."))
|
||||
fmt.Println(gettext.PGettext("main.init", "Gettext in init."))
|
||||
hi.SayHi()
|
||||
// Output(depends on local environment):
|
||||
// ?
|
||||
// ?
|
||||
// ?
|
||||
// ?
|
||||
|
||||
// set simple chinese
|
||||
gettext.SetLocale("zh_CN")
|
||||
|
||||
// simple chinese
|
||||
fmt.Println(gettext.Gettext("Gettext in init."))
|
||||
fmt.Println(gettext.PGettext("main.init", "Gettext in init."))
|
||||
hi.SayHi()
|
||||
// Output:
|
||||
// Init函数中的Gettext.
|
||||
// Init函数中的Gettext.
|
||||
// 来自"Hi"包的问候: 你好, 世界!
|
||||
// 来自"Hi"包的问候: 你好, 世界!
|
||||
}
|
||||
|
||||
func main() {
|
||||
// simple chinese
|
||||
fmt.Println(gettext.Gettext("Hello, world!"))
|
||||
fmt.Println(gettext.PGettext("main.main", "Hello, world!"))
|
||||
hi.SayHi()
|
||||
// Output:
|
||||
// 你好, 世界!
|
||||
// 你好, 世界!
|
||||
// 来自"Hi"包的问候: 你好, 世界!
|
||||
// 来自"Hi"包的问候: 你好, 世界!
|
||||
|
||||
// set traditional chinese
|
||||
gettext.SetLocale("zh_TW")
|
||||
|
||||
// traditional chinese
|
||||
func() {
|
||||
fmt.Println(gettext.Gettext("Gettext in func."))
|
||||
fmt.Println(gettext.PGettext("main.func", "Gettext in func."))
|
||||
hi.SayHi()
|
||||
// Output:
|
||||
// 閉包函數中的Gettext.
|
||||
// 閉包函數中的Gettext.
|
||||
// 來自"Hi"包的問候: 你好, 世界!
|
||||
// 來自"Hi"包的問候: 你好, 世界!
|
||||
}()
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// translate resource
|
||||
gettext.SetLocale("zh_CN")
|
||||
fmt.Println("poems(simple chinese):")
|
||||
fmt.Println(string(gettext.Getdata("poems.txt")))
|
||||
gettext.SetLocale("zh_TW")
|
||||
fmt.Println("poems(traditional chinese):")
|
||||
fmt.Println(string(gettext.Getdata("poems.txt")))
|
||||
gettext.SetLocale("??")
|
||||
fmt.Println("poems(default is english):")
|
||||
fmt.Println(string(gettext.Getdata("poems.txt")))
|
||||
// Output: ...
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package hi is a example pkg.
|
||||
package hi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chai2010/gettext-go/gettext"
|
||||
)
|
||||
|
||||
func SayHi() {
|
||||
fmt.Println(gettext.Gettext("pkg hi: Hello, world!"))
|
||||
fmt.Println(gettext.PGettext("code.google.com/p/gettext-go/examples/hi.SayHi", "pkg hi: Hello, world!"))
|
||||
}
|
||||
Binary file not shown.
BIN
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_MESSAGES/hello.mo
generated
vendored
Normal file
BIN
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_MESSAGES/hello.mo
generated
vendored
Normal file
Binary file not shown.
35
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_MESSAGES/hello.po
generated
vendored
Normal file
35
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_MESSAGES/hello.po
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 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.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gettext-go-examples-hello\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-12-12 20:03+0000\n"
|
||||
"PO-Revision-Date: 2013-12-30 20:47+0800\n"
|
||||
"Last-Translator: chai2010 <chaishushan@gmail.com>\n"
|
||||
"Language-Team: chai2010(团队) <chaishushan@gmail.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
msgctxt "main.init"
|
||||
msgid "Gettext in init."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "main.main"
|
||||
msgid "Hello, world!"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "main.func"
|
||||
msgid "Gettext in func."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "github.com/chai2010/gettext-go/examples/hi.SayHi"
|
||||
msgid "pkg hi: Hello, world!"
|
||||
msgstr ""
|
||||
BIN
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_RESOURCE/hello/favicon.ico
generated
vendored
Normal file
BIN
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_RESOURCE/hello/favicon.ico
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
23
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_RESOURCE/hello/poems.txt
generated
vendored
Normal file
23
vendor/github.com/chai2010/gettext-go/examples/local/default/LC_RESOURCE/hello/poems.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
Drinking Alone Under the Moon
|
||||
Li Bai
|
||||
|
||||
flowers among one jar liquor
|
||||
alone carouse without mutual intimate
|
||||
|
||||
raise cup greet bright moon
|
||||
facing shadow become three persons
|
||||
|
||||
moon since not free to-drink
|
||||
shadow follow accompany my body
|
||||
|
||||
briefly accompany moon with shadow
|
||||
go happy should avail-oneself-of spring
|
||||
|
||||
my song moon walk-to-and-fro irresolute
|
||||
my dance shadow fragments disorderly
|
||||
|
||||
sober time together mix glad
|
||||
drunk after each divide scatter
|
||||
|
||||
eternal connect without consciouness-of-self roam
|
||||
mutual appointment remote cloud Milky-Way
|
||||
BIN
vendor/github.com/chai2010/gettext-go/examples/local/zh_CN/LC_MESSAGES/hello.mo
generated
vendored
Normal file
BIN
vendor/github.com/chai2010/gettext-go/examples/local/zh_CN/LC_MESSAGES/hello.mo
generated
vendored
Normal file
Binary file not shown.
35
vendor/github.com/chai2010/gettext-go/examples/local/zh_CN/LC_MESSAGES/hello.po
generated
vendored
Normal file
35
vendor/github.com/chai2010/gettext-go/examples/local/zh_CN/LC_MESSAGES/hello.po
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 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.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gettext-go-examples-hello\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-12-12 20:03+0000\n"
|
||||
"PO-Revision-Date: 2013-12-30 20:47+0800\n"
|
||||
"Last-Translator: chai2010 <chaishushan@gmail.com>\n"
|
||||
"Language-Team: chai2010(团队) <chaishushan@gmail.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
msgctxt "main.init"
|
||||
msgid "Gettext in init."
|
||||
msgstr "Init函数中的Gettext."
|
||||
|
||||
msgctxt "main.main"
|
||||
msgid "Hello, world!"
|
||||
msgstr "你好, 世界!"
|
||||
|
||||
msgctxt "main.func"
|
||||
msgid "Gettext in func."
|
||||
msgstr "闭包函数中的Gettext."
|
||||
|
||||
msgctxt "github.com/chai2010/gettext-go/examples/hi.SayHi"
|
||||
msgid "pkg hi: Hello, world!"
|
||||
msgstr "来自\"Hi\"包的问候: 你好, 世界!"
|
||||
19
vendor/github.com/chai2010/gettext-go/examples/local/zh_CN/LC_RESOURCE/hello/poems.txt
generated
vendored
Normal file
19
vendor/github.com/chai2010/gettext-go/examples/local/zh_CN/LC_RESOURCE/hello/poems.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
yuèxiàdúzhuó
|
||||
月下独酌
|
||||
lǐbái
|
||||
李白
|
||||
|
||||
huājiānyīhújiǔ,dúzhuówúxiānɡqīn。
|
||||
花间一壶酒,独酌无相亲。
|
||||
jǔbēiyāomínɡyuè,duìyǐnɡchénɡsānrén。
|
||||
举杯邀明月,对影成三人。
|
||||
yuèjìbùjiěyǐn,yǐnɡtúsuíwǒshēn。
|
||||
月既不解饮,影徒随我身。
|
||||
zànbànyuèjiānɡyǐnɡ,xínɡlèxūjíchūn。
|
||||
暂伴月将影,行乐须及春。
|
||||
wǒɡēyuèpáihuái,wǒwǔyǐnɡlínɡluàn。
|
||||
我歌月徘徊,我舞影零乱。
|
||||
xǐnɡshítónɡjiāohuān,zuìhòuɡèfēnsàn。
|
||||
醒时同交欢,醉后各分散。
|
||||
yǒnɡjiéwúqínɡyóu,xiānɡqīmiǎoyúnhàn。
|
||||
永结无情游,相期邈云汉。
|
||||
BIN
vendor/github.com/chai2010/gettext-go/examples/local/zh_TW/LC_MESSAGES/hello.mo
generated
vendored
Normal file
BIN
vendor/github.com/chai2010/gettext-go/examples/local/zh_TW/LC_MESSAGES/hello.mo
generated
vendored
Normal file
Binary file not shown.
35
vendor/github.com/chai2010/gettext-go/examples/local/zh_TW/LC_MESSAGES/hello.po
generated
vendored
Normal file
35
vendor/github.com/chai2010/gettext-go/examples/local/zh_TW/LC_MESSAGES/hello.po
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# 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.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gettext-go-examples-hello\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-12-12 20:03+0000\n"
|
||||
"PO-Revision-Date: 2014-01-01 11:39+0800\n"
|
||||
"Last-Translator: chai2010 <chaishushan@gmail.com>\n"
|
||||
"Language-Team: chai2010(团队) <chaishushan@gmail.com>\n"
|
||||
"Language: zh_TW\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
msgctxt "main.init"
|
||||
msgid "Gettext in init."
|
||||
msgstr "Init函數中的Gettext."
|
||||
|
||||
msgctxt "main.main"
|
||||
msgid "Hello, world!"
|
||||
msgstr "你好, 世界!"
|
||||
|
||||
msgctxt "main.func"
|
||||
msgid "Gettext in func."
|
||||
msgstr "閉包函數中的Gettext."
|
||||
|
||||
msgctxt "github.com/chai2010/gettext-go/examples/hi.SayHi"
|
||||
msgid "pkg hi: Hello, world!"
|
||||
msgstr "來自\"Hi\"包的問候: 你好, 世界!"
|
||||
19
vendor/github.com/chai2010/gettext-go/examples/local/zh_TW/LC_RESOURCE/hello/poems.txt
generated
vendored
Normal file
19
vendor/github.com/chai2010/gettext-go/examples/local/zh_TW/LC_RESOURCE/hello/poems.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
yuèxiàdúzhuó
|
||||
月下獨酌
|
||||
lǐbái
|
||||
李白
|
||||
|
||||
huājiānyīhújiǔ,dúzhuówúxiānɡqīn。
|
||||
花間一壺酒,獨酌無相親。
|
||||
jǔbēiyāomínɡyuè,duìyǐnɡchénɡsānrén。
|
||||
舉杯邀明月,對影成三人。
|
||||
yuèjìbùjiěyǐn,yǐnɡtúsuíwǒshēn。
|
||||
月既不解飲,影徒隨我身。
|
||||
zànbànyuèjiānɡyǐnɡ,xínɡlèxūjíchūn。
|
||||
暫伴月將影,行樂須及春。
|
||||
wǒɡēyuèpáihuái,wǒwǔyǐnɡlínɡluàn。
|
||||
我歌月徘徊,我舞影零亂。
|
||||
xǐnɡshítónɡjiāohuān,zuìhòuɡèfēnsàn。
|
||||
醒時同交歡,醉後各分散。
|
||||
yǒnɡjiéwúqínɡyóu,xiānɡqīmiǎoyúnhàn。
|
||||
永結無情遊,相期邈雲漢。
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
reInit = regexp.MustCompile(`init·\d+$`) // main.init·1
|
||||
reClosure = regexp.MustCompile(`func·\d+$`) // main.func·001
|
||||
)
|
||||
|
||||
// caller types:
|
||||
// runtime.goexit
|
||||
// runtime.main
|
||||
// main.init
|
||||
// main.main
|
||||
// main.init·1 -> main.init
|
||||
// main.func·001 -> main.func
|
||||
// code.google.com/p/gettext-go/gettext.TestCallerName
|
||||
// ...
|
||||
func callerName(skip int) string {
|
||||
pc, _, _, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
if reInit.MatchString(name) {
|
||||
return reInit.ReplaceAllString(name, "init")
|
||||
}
|
||||
if reClosure.MatchString(name) {
|
||||
return reClosure.ReplaceAllString(name, "func")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testInitCallerName0 string = callerName(1)
|
||||
testInitCallerName1 string
|
||||
testInitCallerName2 string
|
||||
)
|
||||
|
||||
func init() {
|
||||
testInitCallerName1 = callerName(1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
testInitCallerName2 = callerName(1)
|
||||
}
|
||||
|
||||
var tCaller = func(skip int) string {
|
||||
return callerName(skip + 1)
|
||||
}
|
||||
|
||||
func TestCallerName(t *testing.T) {
|
||||
var name string
|
||||
|
||||
// init
|
||||
name = `github.com/chai2010/gettext-go/gettext.init`
|
||||
if s := testInitCallerName0; s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
name = `github.com/chai2010/gettext-go/gettext.init`
|
||||
if s := testInitCallerName1; s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
name = `github.com/chai2010/gettext-go/gettext.init`
|
||||
if s := testInitCallerName2; s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
|
||||
// tInit -> gettext.func
|
||||
name = `github.com/chai2010/gettext-go/gettext.func`
|
||||
if s := tCaller(0); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
|
||||
// caller stack
|
||||
name = `github.com/chai2010/gettext-go/gettext.callerName`
|
||||
if s := callerName(0); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
name = `github.com/chai2010/gettext-go/gettext.TestCallerName`
|
||||
if s := callerName(1); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
name = `testing.tRunner`
|
||||
if s := callerName(2); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
name = `runtime.goexit`
|
||||
if s := callerName(3); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
name = ""
|
||||
if s := callerName(4); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
|
||||
// closure
|
||||
func() {
|
||||
name = `github.com/chai2010/gettext-go/gettext.func`
|
||||
if s := callerName(1); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
}()
|
||||
func() {
|
||||
func() {
|
||||
name = `github.com/chai2010/gettext-go/gettext.func`
|
||||
if s := callerName(1); s != name {
|
||||
t.Fatalf("expect = %s, got = %s", name, s)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gettext implements a basic GNU's gettext library.
|
||||
|
||||
Example:
|
||||
import (
|
||||
"github.com/chai2010/gettext-go/gettext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gettext.SetLocale("zh_CN")
|
||||
gettext.Textdomain("hello")
|
||||
|
||||
// gettext.BindTextdomain("hello", "local", nil) // from local dir
|
||||
// gettext.BindTextdomain("hello", "local.zip", nil) // from local zip file
|
||||
// gettext.BindTextdomain("hello", "local.zip", zipData) // from embedded zip data
|
||||
|
||||
gettext.BindTextdomain("hello", "local", nil)
|
||||
|
||||
// translate source text
|
||||
fmt.Println(gettext.Gettext("Hello, world!"))
|
||||
// Output: 你好, 世界!
|
||||
|
||||
// translate resource
|
||||
fmt.Println(string(gettext.Getdata("poems.txt")))
|
||||
// Output: ...
|
||||
}
|
||||
|
||||
Translate directory struct("../examples/local.zip"):
|
||||
|
||||
Root: "path" or "file.zip/zipBaseName"
|
||||
+-default # local: $(LC_MESSAGES) or $(LANG) or "default"
|
||||
| +-LC_MESSAGES # just for `gettext.Gettext`
|
||||
| | +-hello.mo # $(Root)/$(local)/LC_MESSAGES/$(domain).mo
|
||||
| | \-hello.po # $(Root)/$(local)/LC_MESSAGES/$(domain).mo
|
||||
| |
|
||||
| \-LC_RESOURCE # just for `gettext.Getdata`
|
||||
| +-hello # domain map a dir in resource translate
|
||||
| +-favicon.ico # $(Root)/$(local)/LC_RESOURCE/$(domain)/$(filename)
|
||||
| \-poems.txt
|
||||
|
|
||||
\-zh_CN # simple chinese translate
|
||||
+-LC_MESSAGES
|
||||
| +-hello.mo # try "$(domain).mo" first
|
||||
| \-hello.po # try "$(domain).po" second
|
||||
|
|
||||
\-LC_RESOURCE
|
||||
+-hello
|
||||
+-favicon.ico # try "$(local)/$(domain)/file" first
|
||||
\-poems.txt # try "default/$(domain)/file" second
|
||||
|
||||
See:
|
||||
http://en.wikipedia.org/wiki/Gettext
|
||||
http://www.gnu.org/software/gettext/manual/html_node
|
||||
http://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html
|
||||
http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
||||
http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
|
||||
http://www.poedit.net/
|
||||
|
||||
Please report bugs to <chaishushan{AT}gmail.com>.
|
||||
Thanks!
|
||||
*/
|
||||
package gettext
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type domainManager struct {
|
||||
mutex sync.Mutex
|
||||
locale string
|
||||
domain string
|
||||
domainMap map[string]*fileSystem
|
||||
trTextMap map[string]*translator
|
||||
}
|
||||
|
||||
func newDomainManager() *domainManager {
|
||||
return &domainManager{
|
||||
locale: DefaultLocale,
|
||||
domainMap: make(map[string]*fileSystem),
|
||||
trTextMap: make(map[string]*translator),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *domainManager) makeTrMapKey(domain, locale string) string {
|
||||
return domain + "_$$$_" + locale
|
||||
}
|
||||
|
||||
func (p *domainManager) Bind(domain, path string, data []byte) (domains, paths []string) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
switch {
|
||||
case domain != "" && path != "": // bind new domain
|
||||
p.bindDomainTranslators(domain, path, data)
|
||||
case domain != "" && path == "": // delete domain
|
||||
p.deleteDomain(domain)
|
||||
}
|
||||
|
||||
// return all bind domain
|
||||
for k, fs := range p.domainMap {
|
||||
domains = append(domains, k)
|
||||
paths = append(paths, fs.FsName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *domainManager) SetLocale(locale string) string {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if locale != "" {
|
||||
p.locale = locale
|
||||
}
|
||||
return p.locale
|
||||
}
|
||||
|
||||
func (p *domainManager) SetDomain(domain string) string {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if domain != "" {
|
||||
p.domain = domain
|
||||
}
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *domainManager) Getdata(name string) []byte {
|
||||
return p.getdata(p.domain, name)
|
||||
}
|
||||
|
||||
func (p *domainManager) DGetdata(domain, name string) []byte {
|
||||
return p.getdata(domain, name)
|
||||
}
|
||||
|
||||
func (p *domainManager) PNGettext(msgctxt, msgid, msgidPlural string, n int) string {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
return p.gettext(p.domain, msgctxt, msgid, msgidPlural, n)
|
||||
}
|
||||
|
||||
func (p *domainManager) DPNGettext(domain, msgctxt, msgid, msgidPlural string, n int) string {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
return p.gettext(domain, msgctxt, msgid, msgidPlural, n)
|
||||
}
|
||||
|
||||
func (p *domainManager) gettext(domain, msgctxt, msgid, msgidPlural string, n int) string {
|
||||
if p.locale == "" || p.domain == "" {
|
||||
return msgid
|
||||
}
|
||||
if _, ok := p.domainMap[domain]; !ok {
|
||||
return msgid
|
||||
}
|
||||
if f, ok := p.trTextMap[p.makeTrMapKey(domain, p.locale)]; ok {
|
||||
return f.PNGettext(msgctxt, msgid, msgidPlural, n)
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
|
||||
func (p *domainManager) getdata(domain, name string) []byte {
|
||||
if p.locale == "" || p.domain == "" {
|
||||
return nil
|
||||
}
|
||||
if _, ok := p.domainMap[domain]; !ok {
|
||||
return nil
|
||||
}
|
||||
if fs, ok := p.domainMap[domain]; ok {
|
||||
if data, err := fs.LoadResourceFile(domain, p.locale, name); err == nil {
|
||||
return data
|
||||
}
|
||||
if p.locale != "default" {
|
||||
if data, err := fs.LoadResourceFile(domain, "default", name); err == nil {
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *domainManager) bindDomainTranslators(domain, path string, data []byte) {
|
||||
if _, ok := p.domainMap[domain]; ok {
|
||||
p.deleteDomain(domain) // delete old domain
|
||||
}
|
||||
fs := newFileSystem(path, data)
|
||||
for locale, _ := range fs.LocaleMap {
|
||||
trMapKey := p.makeTrMapKey(domain, locale)
|
||||
if data, err := fs.LoadMessagesFile(domain, locale, ".mo"); err == nil {
|
||||
p.trTextMap[trMapKey], _ = newMoTranslator(
|
||||
fmt.Sprintf("%s_%s.mo", domain, locale),
|
||||
data,
|
||||
)
|
||||
continue
|
||||
}
|
||||
if data, err := fs.LoadMessagesFile(domain, locale, ".po"); err == nil {
|
||||
p.trTextMap[trMapKey], _ = newPoTranslator(
|
||||
fmt.Sprintf("%s_%s.po", domain, locale),
|
||||
data,
|
||||
)
|
||||
continue
|
||||
}
|
||||
p.trTextMap[p.makeTrMapKey(domain, locale)] = nilTranslator
|
||||
}
|
||||
p.domainMap[domain] = fs
|
||||
}
|
||||
|
||||
func (p *domainManager) deleteDomain(domain string) {
|
||||
if _, ok := p.domainMap[domain]; !ok {
|
||||
return
|
||||
}
|
||||
// delete all mo files
|
||||
trMapKeyPrefix := p.makeTrMapKey(domain, "")
|
||||
for k, _ := range p.trTextMap {
|
||||
if strings.HasPrefix(k, trMapKeyPrefix) {
|
||||
delete(p.trTextMap, k)
|
||||
}
|
||||
}
|
||||
delete(p.domainMap, domain)
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fileSystem struct {
|
||||
FsName string
|
||||
FsRoot string
|
||||
FsZipData []byte
|
||||
LocaleMap map[string]bool
|
||||
}
|
||||
|
||||
func newFileSystem(path string, data []byte) *fileSystem {
|
||||
fs := &fileSystem{
|
||||
FsName: path,
|
||||
FsZipData: data,
|
||||
}
|
||||
if err := fs.init(); err != nil {
|
||||
log.Printf("gettext-go: invalid domain, err = %v", err)
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func (p *fileSystem) init() error {
|
||||
zipName := func(name string) string {
|
||||
if x := strings.LastIndexAny(name, `\/`); x != -1 {
|
||||
name = name[x+1:]
|
||||
}
|
||||
name = strings.TrimSuffix(name, ".zip")
|
||||
return name
|
||||
}
|
||||
|
||||
// zip data
|
||||
if len(p.FsZipData) != 0 {
|
||||
p.FsRoot = zipName(p.FsName)
|
||||
p.LocaleMap = p.lsZip(p.FsZipData)
|
||||
return nil
|
||||
}
|
||||
|
||||
// local dir or zip file
|
||||
fi, err := os.Stat(p.FsName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// local dir
|
||||
if fi.IsDir() {
|
||||
p.FsRoot = p.FsName
|
||||
p.LocaleMap = p.lsDir(p.FsName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// local zip file
|
||||
p.FsZipData, err = ioutil.ReadFile(p.FsName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.FsRoot = zipName(p.FsName)
|
||||
p.LocaleMap = p.lsZip(p.FsZipData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fileSystem) LoadMessagesFile(domain, local, ext string) ([]byte, error) {
|
||||
if len(p.FsZipData) == 0 {
|
||||
trName := p.makeMessagesFileName(domain, local, ext)
|
||||
rcData, err := ioutil.ReadFile(trName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rcData, nil
|
||||
} else {
|
||||
r, err := zip.NewReader(bytes.NewReader(p.FsZipData), int64(len(p.FsZipData)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trName := p.makeMessagesFileName(domain, local, ext)
|
||||
for _, f := range r.File {
|
||||
if f.Name != trName {
|
||||
continue
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rcData, err := ioutil.ReadAll(rc)
|
||||
rc.Close()
|
||||
return rcData, err
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileSystem) LoadResourceFile(domain, local, name string) ([]byte, error) {
|
||||
if len(p.FsZipData) == 0 {
|
||||
rcName := p.makeResourceFileName(domain, local, name)
|
||||
rcData, err := ioutil.ReadFile(rcName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rcData, nil
|
||||
} else {
|
||||
r, err := zip.NewReader(bytes.NewReader(p.FsZipData), int64(len(p.FsZipData)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rcName := p.makeResourceFileName(domain, local, name)
|
||||
for _, f := range r.File {
|
||||
if f.Name != rcName {
|
||||
continue
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rcData, err := ioutil.ReadAll(rc)
|
||||
rc.Close()
|
||||
return rcData, err
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileSystem) makeMessagesFileName(domain, local, ext string) string {
|
||||
return fmt.Sprintf("%s/%s/LC_MESSAGES/%s%s", p.FsRoot, local, domain, ext)
|
||||
}
|
||||
|
||||
func (p *fileSystem) makeResourceFileName(domain, local, name string) string {
|
||||
return fmt.Sprintf("%s/%s/LC_RESOURCE/%s/%s", p.FsRoot, local, domain, name)
|
||||
}
|
||||
|
||||
func (p *fileSystem) lsZip(data []byte) map[string]bool {
|
||||
r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ssMap := make(map[string]bool)
|
||||
for _, f := range r.File {
|
||||
if x := strings.Index(f.Name, "LC_MESSAGES"); x != -1 {
|
||||
s := strings.TrimRight(f.Name[:x], `\/`)
|
||||
if x = strings.LastIndexAny(s, `\/`); x != -1 {
|
||||
s = s[x+1:]
|
||||
}
|
||||
if s != "" {
|
||||
ssMap[s] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if x := strings.Index(f.Name, "LC_RESOURCE"); x != -1 {
|
||||
s := strings.TrimRight(f.Name[:x], `\/`)
|
||||
if x = strings.LastIndexAny(s, `\/`); x != -1 {
|
||||
s = s[x+1:]
|
||||
}
|
||||
if s != "" {
|
||||
ssMap[s] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
return ssMap
|
||||
}
|
||||
|
||||
func (p *fileSystem) lsDir(path string) map[string]bool {
|
||||
list, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ssMap := make(map[string]bool)
|
||||
for _, dir := range list {
|
||||
if dir.IsDir() {
|
||||
ssMap[dir.Name()] = true
|
||||
}
|
||||
}
|
||||
return ssMap
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
var (
|
||||
defaultManager = newDomainManager()
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultLocale = getDefaultLocale() // use $(LC_MESSAGES) or $(LANG) or "default"
|
||||
)
|
||||
|
||||
// SetLocale sets and queries the program's current locale.
|
||||
//
|
||||
// If the locale is not empty string, set the new local.
|
||||
//
|
||||
// If the locale is empty string, don't change anything.
|
||||
//
|
||||
// Returns is the current locale.
|
||||
//
|
||||
// Examples:
|
||||
// SetLocale("") // get locale: return DefaultLocale
|
||||
// SetLocale("zh_CN") // set locale: return zh_CN
|
||||
// SetLocale("") // get locale: return zh_CN
|
||||
func SetLocale(locale string) string {
|
||||
return defaultManager.SetLocale(locale)
|
||||
}
|
||||
|
||||
// BindTextdomain sets and queries program's domains.
|
||||
//
|
||||
// If the domain and path are all not empty string, bind the new domain.
|
||||
// If the domain already exists, return error.
|
||||
//
|
||||
// If the domain is not empty string, but the path is the empty string,
|
||||
// delete the domain.
|
||||
// If the domain don't exists, return error.
|
||||
//
|
||||
// If the domain and the path are all empty string, don't change anything.
|
||||
//
|
||||
// Returns is the all bind domains.
|
||||
//
|
||||
// Examples:
|
||||
// BindTextdomain("poedit", "local", nil) // bind "poedit" domain
|
||||
// BindTextdomain("", "", nil) // return all domains
|
||||
// BindTextdomain("poedit", "", nil) // delete "poedit" domain
|
||||
// BindTextdomain("", "", nil) // return all domains
|
||||
//
|
||||
// Use zip file:
|
||||
// BindTextdomain("poedit", "local.zip", nil) // bind "poedit" domain
|
||||
// BindTextdomain("poedit", "local.zip", zipData) // bind "poedit" domain
|
||||
//
|
||||
func BindTextdomain(domain, path string, zipData []byte) (domains, paths []string) {
|
||||
return defaultManager.Bind(domain, path, zipData)
|
||||
}
|
||||
|
||||
// Textdomain sets and retrieves the current message domain.
|
||||
//
|
||||
// If the domain is not empty string, set the new domains.
|
||||
//
|
||||
// If the domain is empty string, don't change anything.
|
||||
//
|
||||
// Returns is the all used domains.
|
||||
//
|
||||
// Examples:
|
||||
// Textdomain("poedit") // set domain: poedit
|
||||
// Textdomain("") // get domain: return poedit
|
||||
func Textdomain(domain string) string {
|
||||
return defaultManager.SetDomain(domain)
|
||||
}
|
||||
|
||||
// Gettext attempt to translate a text string into the user's native language,
|
||||
// by looking up the translation in a message catalog.
|
||||
//
|
||||
// It use the caller's function name as the msgctxt.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.Gettext("Hello") // msgctxt is "some/package/name.Foo"
|
||||
// }
|
||||
func Gettext(msgid string) string {
|
||||
return PGettext(callerName(2), msgid)
|
||||
}
|
||||
|
||||
// Getdata attempt to translate a resource file into the user's native language,
|
||||
// by looking up the translation in a message catalog.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// Textdomain("hello")
|
||||
// BindTextdomain("hello", "local.zip", nilOrZipData)
|
||||
// poems := gettext.Getdata("poems.txt")
|
||||
// }
|
||||
func Getdata(name string) []byte {
|
||||
return defaultManager.Getdata(name)
|
||||
}
|
||||
|
||||
// NGettext attempt to translate a text string into the user's native language,
|
||||
// by looking up the appropriate plural form of the translation in a message
|
||||
// catalog.
|
||||
//
|
||||
// It use the caller's function name as the msgctxt.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.NGettext("%d people", "%d peoples", 2)
|
||||
// }
|
||||
func NGettext(msgid, msgidPlural string, n int) string {
|
||||
return PNGettext(callerName(2), msgid, msgidPlural, n)
|
||||
}
|
||||
|
||||
// PGettext attempt to translate a text string into the user's native language,
|
||||
// by looking up the translation in a message catalog.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.PGettext("gettext-go.example", "Hello") // msgctxt is "gettext-go.example"
|
||||
// }
|
||||
func PGettext(msgctxt, msgid string) string {
|
||||
return PNGettext(msgctxt, msgid, "", 0)
|
||||
}
|
||||
|
||||
// PNGettext attempt to translate a text string into the user's native language,
|
||||
// by looking up the appropriate plural form of the translation in a message
|
||||
// catalog.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.PNGettext("gettext-go.example", "%d people", "%d peoples", 2)
|
||||
// }
|
||||
func PNGettext(msgctxt, msgid, msgidPlural string, n int) string {
|
||||
return defaultManager.PNGettext(msgctxt, msgid, msgidPlural, n)
|
||||
}
|
||||
|
||||
// DGettext like Gettext(), but looking up the message in the specified domain.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.DGettext("poedit", "Hello")
|
||||
// }
|
||||
func DGettext(domain, msgid string) string {
|
||||
return DPGettext(domain, callerName(2), msgid)
|
||||
}
|
||||
|
||||
// DNGettext like NGettext(), but looking up the message in the specified domain.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.PNGettext("poedit", "gettext-go.example", "%d people", "%d peoples", 2)
|
||||
// }
|
||||
func DNGettext(domain, msgid, msgidPlural string, n int) string {
|
||||
return DPNGettext(domain, callerName(2), msgid, msgidPlural, n)
|
||||
}
|
||||
|
||||
// DPGettext like PGettext(), but looking up the message in the specified domain.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.DPGettext("poedit", "gettext-go.example", "Hello")
|
||||
// }
|
||||
func DPGettext(domain, msgctxt, msgid string) string {
|
||||
return DPNGettext(domain, msgctxt, msgid, "", 0)
|
||||
}
|
||||
|
||||
// DPNGettext like PNGettext(), but looking up the message in the specified domain.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.DPNGettext("poedit", "gettext-go.example", "%d people", "%d peoples", 2)
|
||||
// }
|
||||
func DPNGettext(domain, msgctxt, msgid, msgidPlural string, n int) string {
|
||||
return defaultManager.DPNGettext(domain, msgctxt, msgid, msgidPlural, n)
|
||||
}
|
||||
|
||||
// DGetdata like Getdata(), but looking up the resource in the specified domain.
|
||||
//
|
||||
// Examples:
|
||||
// func Foo() {
|
||||
// msg := gettext.DGetdata("hello", "poems.txt")
|
||||
// }
|
||||
func DGetdata(domain, name string) []byte {
|
||||
return defaultManager.DGetdata(domain, name)
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testZipData = func() []byte {
|
||||
if data, err := ioutil.ReadFile("../examples/local.zip"); err == nil {
|
||||
return data
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
func TestGettext(t *testing.T) {
|
||||
Textdomain("hello")
|
||||
|
||||
// local file system
|
||||
BindTextdomain("hello", "../examples/local", nil)
|
||||
testGettext(t, true)
|
||||
BindTextdomain("hello", "", nil)
|
||||
testGettext(t, false)
|
||||
|
||||
// local zip file system
|
||||
BindTextdomain("hello", "../examples/local.zip", nil)
|
||||
testGettext(t, true)
|
||||
BindTextdomain("hello", "", nil)
|
||||
testGettext(t, false)
|
||||
|
||||
// embedded zip file system
|
||||
BindTextdomain("hello", "local.zip", testZipData)
|
||||
testGettext(t, true)
|
||||
BindTextdomain("hello", "", nil)
|
||||
testGettext(t, false)
|
||||
}
|
||||
|
||||
func TestGetdata(t *testing.T) {
|
||||
Textdomain("hello")
|
||||
|
||||
// local file system
|
||||
BindTextdomain("hello", "../examples/local", nil)
|
||||
testGetdata(t, true)
|
||||
BindTextdomain("hello", "", nil)
|
||||
testGetdata(t, false)
|
||||
|
||||
// local zip file system
|
||||
BindTextdomain("hello", "../examples/local.zip", nil)
|
||||
testGetdata(t, true)
|
||||
BindTextdomain("hello", "", nil)
|
||||
testGetdata(t, false)
|
||||
|
||||
// embedded zip file system
|
||||
BindTextdomain("hello", "local.zip", testZipData)
|
||||
testGetdata(t, true)
|
||||
BindTextdomain("hello", "", nil)
|
||||
testGetdata(t, false)
|
||||
}
|
||||
|
||||
func testGettext(t *testing.T, hasTransle bool) {
|
||||
for i, v := range testTexts {
|
||||
if lang := SetLocale(v.lang); lang != v.lang {
|
||||
t.Fatalf("%d: expect = %s, got = %v", i, v.lang, lang)
|
||||
}
|
||||
if hasTransle {
|
||||
if dst := PGettext(v.ctx, v.src); dst != v.dst {
|
||||
t.Fatalf("%d: expect = %q, got = %q", i, v.dst, dst)
|
||||
}
|
||||
} else {
|
||||
if dst := PGettext(v.ctx, v.src); dst != v.src {
|
||||
t.Fatalf("%d: expect = %s, got = %v", i, v.src, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetdata(t *testing.T, hasTransle bool) {
|
||||
for i, v := range testResources {
|
||||
if lang := SetLocale(v.lang); lang != v.lang {
|
||||
t.Fatalf("%d: expect = %s, got = %v", i, v.lang, lang)
|
||||
}
|
||||
if hasTransle {
|
||||
v.data = strings.Replace(v.data, "\r", "", -1)
|
||||
data := strings.Replace(string(Getdata(v.path)), "\r", "", -1)
|
||||
if data != v.data {
|
||||
t.Fatalf("%d: expect = %q, got = %q", i, v.data, data)
|
||||
}
|
||||
} else {
|
||||
if data := string(Getdata(v.path)); data != "" {
|
||||
t.Fatalf("%d: expect = %s, got = %v", i, "", data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGettext(b *testing.B) {
|
||||
SetLocale("zh_CN")
|
||||
BindTextdomain("hello", "../examples/local", nil)
|
||||
Textdomain("hello")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
PGettext(testTexts[0].ctx, testTexts[0].src)
|
||||
}
|
||||
}
|
||||
func BenchmarkGettext_Zip(b *testing.B) {
|
||||
SetLocale("zh_CN")
|
||||
BindTextdomain("hello", "../examples/local.zip", nil)
|
||||
Textdomain("hello")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
PGettext(testTexts[0].ctx, testTexts[0].src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetdata(b *testing.B) {
|
||||
SetLocale("zh_CN")
|
||||
BindTextdomain("hello", "../examples/local", nil)
|
||||
Textdomain("hello")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Getdata(testResources[0].path)
|
||||
}
|
||||
}
|
||||
func BenchmarkGetdata_Zip(b *testing.B) {
|
||||
SetLocale("zh_CN")
|
||||
BindTextdomain("hello", "../examples/local.zip", nil)
|
||||
Textdomain("hello")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Getdata(testResources[0].path)
|
||||
}
|
||||
}
|
||||
|
||||
var testTexts = []struct {
|
||||
lang string
|
||||
ctx string
|
||||
src string
|
||||
dst string
|
||||
}{
|
||||
// default
|
||||
{"default", "main.init", "Gettext in init.", "Gettext in init."},
|
||||
{"default", "main.main", "Hello, world!", "Hello, world!"},
|
||||
{"default", "main.func", "Gettext in func.", "Gettext in func."},
|
||||
{"default", "github.com/chai2010/gettext-go/examples/hi.SayHi", "pkg hi: Hello, world!", "pkg hi: Hello, world!"},
|
||||
|
||||
// zh_CN
|
||||
{"zh_CN", "main.init", "Gettext in init.", "Init函数中的Gettext."},
|
||||
{"zh_CN", "main.main", "Hello, world!", "你好, 世界!"},
|
||||
{"zh_CN", "main.func", "Gettext in func.", "闭包函数中的Gettext."},
|
||||
{"zh_CN", "github.com/chai2010/gettext-go/examples/hi.SayHi", "pkg hi: Hello, world!", "来自\"Hi\"包的问候: 你好, 世界!"},
|
||||
|
||||
// zh_TW
|
||||
{"zh_TW", "main.init", "Gettext in init.", "Init函數中的Gettext."},
|
||||
{"zh_TW", "main.main", "Hello, world!", "你好, 世界!"},
|
||||
{"zh_TW", "main.func", "Gettext in func.", "閉包函數中的Gettext."},
|
||||
{"zh_TW", "github.com/chai2010/gettext-go/examples/hi.SayHi", "pkg hi: Hello, world!", "來自\"Hi\"包的問候: 你好, 世界!"},
|
||||
}
|
||||
|
||||
var testResources = []struct {
|
||||
lang string
|
||||
path string
|
||||
data string
|
||||
}{
|
||||
// default
|
||||
{
|
||||
"default",
|
||||
"poems.txt",
|
||||
`Drinking Alone Under the Moon
|
||||
Li Bai
|
||||
|
||||
flowers among one jar liquor
|
||||
alone carouse without mutual intimate
|
||||
|
||||
raise cup greet bright moon
|
||||
facing shadow become three persons
|
||||
|
||||
moon since not free to-drink
|
||||
shadow follow accompany my body
|
||||
|
||||
briefly accompany moon with shadow
|
||||
go happy should avail-oneself-of spring
|
||||
|
||||
my song moon walk-to-and-fro irresolute
|
||||
my dance shadow fragments disorderly
|
||||
|
||||
sober time together mix glad
|
||||
drunk after each divide scatter
|
||||
|
||||
eternal connect without consciouness-of-self roam
|
||||
mutual appointment remote cloud Milky-Way
|
||||
`,
|
||||
},
|
||||
|
||||
// zh_CN
|
||||
{
|
||||
"zh_CN",
|
||||
"poems.txt",
|
||||
`yuèxiàdúzhuó
|
||||
月下独酌
|
||||
lǐbái
|
||||
李白
|
||||
|
||||
huājiānyīhújiǔ,dúzhuówúxiānɡqīn。
|
||||
花间一壶酒,独酌无相亲。
|
||||
jǔbēiyāomínɡyuè,duìyǐnɡchénɡsānrén。
|
||||
举杯邀明月,对影成三人。
|
||||
yuèjìbùjiěyǐn,yǐnɡtúsuíwǒshēn。
|
||||
月既不解饮,影徒随我身。
|
||||
zànbànyuèjiānɡyǐnɡ,xínɡlèxūjíchūn。
|
||||
暂伴月将影,行乐须及春。
|
||||
wǒɡēyuèpáihuái,wǒwǔyǐnɡlínɡluàn。
|
||||
我歌月徘徊,我舞影零乱。
|
||||
xǐnɡshítónɡjiāohuān,zuìhòuɡèfēnsàn。
|
||||
醒时同交欢,醉后各分散。
|
||||
yǒnɡjiéwúqínɡyóu,xiānɡqīmiǎoyúnhàn。
|
||||
永结无情游,相期邈云汉。
|
||||
`,
|
||||
},
|
||||
|
||||
// zh_TW
|
||||
{
|
||||
"zh_TW",
|
||||
"poems.txt",
|
||||
`yuèxiàdúzhuó
|
||||
月下獨酌
|
||||
lǐbái
|
||||
李白
|
||||
|
||||
huājiānyīhújiǔ,dúzhuówúxiānɡqīn。
|
||||
花間一壺酒,獨酌無相親。
|
||||
jǔbēiyāomínɡyuè,duìyǐnɡchénɡsānrén。
|
||||
舉杯邀明月,對影成三人。
|
||||
yuèjìbùjiěyǐn,yǐnɡtúsuíwǒshēn。
|
||||
月既不解飲,影徒隨我身。
|
||||
zànbànyuèjiānɡyǐnɡ,xínɡlèxūjíchūn。
|
||||
暫伴月將影,行樂須及春。
|
||||
wǒɡēyuèpáihuái,wǒwǔyǐnɡlínɡluàn。
|
||||
我歌月徘徊,我舞影零亂。
|
||||
xǐnɡshítónɡjiāohuān,zuìhòuɡèfēnsàn。
|
||||
醒時同交歡,醉後各分散。
|
||||
yǒnɡjiéwúqínɡyóu,xiānɡqīmiǎoyúnhàn。
|
||||
永結無情遊,相期邈雲漢。
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2013 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chai2010/gettext-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gettext.SetLocale("zh_CN")
|
||||
gettext.BindTextdomain("hello", "../examples/local", nil)
|
||||
gettext.Textdomain("hello")
|
||||
|
||||
fmt.Println(gettext.Gettext("Hello, world!"))
|
||||
// Output: 你好, 世界!
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getDefaultLocale() string {
|
||||
if v := os.Getenv("LC_MESSAGES"); v != "" {
|
||||
return simplifiedLocale(v)
|
||||
}
|
||||
if v := os.Getenv("LANG"); v != "" {
|
||||
return simplifiedLocale(v)
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
func simplifiedLocale(lang string) string {
|
||||
// en_US/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/...
|
||||
if idx := strings.Index(lang, ":"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "@"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "."); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
return strings.TrimSpace(lang)
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package mo provides support for reading and writing GNU MO file.
|
||||
|
||||
Examples:
|
||||
import (
|
||||
"github.com/chai2010/gettext-go/gettext/mo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
moFile, err := mo.Load("test.mo")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%v", moFile)
|
||||
}
|
||||
|
||||
GNU MO file struct:
|
||||
|
||||
byte
|
||||
+------------------------------------------+
|
||||
0 | magic number = 0x950412de |
|
||||
| |
|
||||
4 | file format revision = 0 |
|
||||
| |
|
||||
8 | number of strings | == N
|
||||
| |
|
||||
12 | offset of table with original strings | == O
|
||||
| |
|
||||
16 | offset of table with translation strings | == T
|
||||
| |
|
||||
20 | size of hashing table | == S
|
||||
| |
|
||||
24 | offset of hashing table | == H
|
||||
| |
|
||||
. .
|
||||
. (possibly more entries later) .
|
||||
. .
|
||||
| |
|
||||
O | length & offset 0th string ----------------.
|
||||
O + 8 | length & offset 1st string ------------------.
|
||||
... ... | |
|
||||
O + ((N-1)*8)| length & offset (N-1)th string | | |
|
||||
| | | |
|
||||
T | length & offset 0th translation ---------------.
|
||||
T + 8 | length & offset 1st translation -----------------.
|
||||
... ... | | | |
|
||||
T + ((N-1)*8)| length & offset (N-1)th translation | | | | |
|
||||
| | | | | |
|
||||
H | start hash table | | | | |
|
||||
... ... | | | |
|
||||
H + S * 4 | end hash table | | | | |
|
||||
| | | | | |
|
||||
| NUL terminated 0th string <----------------' | | |
|
||||
| | | | |
|
||||
| NUL terminated 1st string <------------------' | |
|
||||
| | | |
|
||||
... ... | |
|
||||
| | | |
|
||||
| NUL terminated 0th translation <---------------' |
|
||||
| | |
|
||||
| NUL terminated 1st translation <-----------------'
|
||||
| |
|
||||
... ...
|
||||
| |
|
||||
+------------------------------------------+
|
||||
|
||||
The GNU MO file specification is at
|
||||
http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html.
|
||||
*/
|
||||
package mo
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type moHeader struct {
|
||||
MagicNumber uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
MsgIdCount uint32
|
||||
MsgIdOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
}
|
||||
|
||||
type moStrPos struct {
|
||||
Size uint32 // must keep fields order
|
||||
Addr uint32
|
||||
}
|
||||
|
||||
func encodeFile(f *File) []byte {
|
||||
hdr := &moHeader{
|
||||
MagicNumber: MoMagicLittleEndian,
|
||||
}
|
||||
data := encodeData(hdr, f)
|
||||
data = append(encodeHeader(hdr), data...)
|
||||
return data
|
||||
}
|
||||
|
||||
// encode data and init moHeader
|
||||
func encodeData(hdr *moHeader, f *File) []byte {
|
||||
msgList := []Message{f.MimeHeader.toMessage()}
|
||||
for _, v := range f.Messages {
|
||||
if len(v.MsgId) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(v.MsgStr) == 0 && len(v.MsgStrPlural) == 0 {
|
||||
continue
|
||||
}
|
||||
msgList = append(msgList, v)
|
||||
}
|
||||
sort.Sort(byMessages(msgList))
|
||||
|
||||
var buf bytes.Buffer
|
||||
var msgIdPosList = make([]moStrPos, len(msgList))
|
||||
var msgStrPosList = make([]moStrPos, len(msgList))
|
||||
for i, v := range msgList {
|
||||
// write msgid
|
||||
msgId := encodeMsgId(v)
|
||||
msgIdPosList[i].Addr = uint32(buf.Len() + MoHeaderSize)
|
||||
msgIdPosList[i].Size = uint32(len(msgId))
|
||||
buf.WriteString(msgId)
|
||||
// write msgstr
|
||||
msgStr := encodeMsgStr(v)
|
||||
msgStrPosList[i].Addr = uint32(buf.Len() + MoHeaderSize)
|
||||
msgStrPosList[i].Size = uint32(len(msgStr))
|
||||
buf.WriteString(msgStr)
|
||||
}
|
||||
|
||||
hdr.MsgIdOffset = uint32(buf.Len() + MoHeaderSize)
|
||||
binary.Write(&buf, binary.LittleEndian, msgIdPosList)
|
||||
hdr.MsgStrOffset = uint32(buf.Len() + MoHeaderSize)
|
||||
binary.Write(&buf, binary.LittleEndian, msgStrPosList)
|
||||
|
||||
hdr.MsgIdCount = uint32(len(msgList))
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// must called after encodeData
|
||||
func encodeHeader(hdr *moHeader) []byte {
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.LittleEndian, hdr)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func encodeMsgId(v Message) string {
|
||||
if v.MsgContext != "" && v.MsgIdPlural != "" {
|
||||
return v.MsgContext + EotSeparator + v.MsgId + NulSeparator + v.MsgIdPlural
|
||||
}
|
||||
if v.MsgContext != "" && v.MsgIdPlural == "" {
|
||||
return v.MsgContext + EotSeparator + v.MsgId
|
||||
}
|
||||
if v.MsgContext == "" && v.MsgIdPlural != "" {
|
||||
return v.MsgId + NulSeparator + v.MsgIdPlural
|
||||
}
|
||||
return v.MsgId
|
||||
}
|
||||
|
||||
func encodeMsgStr(v Message) string {
|
||||
if v.MsgIdPlural != "" {
|
||||
return strings.Join(v.MsgStrPlural, NulSeparator)
|
||||
}
|
||||
return v.MsgStr
|
||||
}
|
||||
|
||||
type byMessages []Message
|
||||
|
||||
func (d byMessages) Len() int {
|
||||
return len(d)
|
||||
}
|
||||
func (d byMessages) Less(i, j int) bool {
|
||||
if a, b := d[i].MsgContext, d[j].MsgContext; a != b {
|
||||
return a < b
|
||||
}
|
||||
if a, b := d[i].MsgId, d[j].MsgId; a != b {
|
||||
return a < b
|
||||
}
|
||||
if a, b := d[i].MsgIdPlural, d[j].MsgIdPlural; a != b {
|
||||
return a < b
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (d byMessages) Swap(i, j int) {
|
||||
d[i], d[j] = d[j], d[i]
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFile_Data(t *testing.T) {
|
||||
f, err := LoadData(testMoFile.Data())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a, b := len(f.Messages), len(testMoFile.Messages); a != b {
|
||||
t.Logf("size not equal: expect = %d, got = %d", b, a)
|
||||
}
|
||||
for i, v := range f.Messages {
|
||||
if !reflect.DeepEqual(&v, &testMoFile.Messages[i]) {
|
||||
t.Fatalf("%d: expect = %v, got = %v", i, testMoFile.Messages[i], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
sort.Sort(byMessages(testMoFile.Messages))
|
||||
}
|
||||
|
||||
var testMoFile = &File{
|
||||
Messages: []Message{
|
||||
Message{
|
||||
MsgContext: "main.init",
|
||||
MsgId: "Gettext in init.",
|
||||
MsgStr: "Init函数中的Gettext.",
|
||||
},
|
||||
Message{
|
||||
MsgContext: "main.main",
|
||||
MsgId: "Hello, world!",
|
||||
MsgStr: "你好, 世界!",
|
||||
},
|
||||
Message{
|
||||
MsgContext: "main.func",
|
||||
MsgId: "Gettext in func.",
|
||||
MsgStr: "闭包函数中的Gettext.",
|
||||
},
|
||||
Message{
|
||||
MsgContext: "code.google.com/p/gettext-go/examples/hi.SayHi",
|
||||
MsgId: "pkg hi: Hello, world!",
|
||||
MsgStr: "来自\"Hi\"包的问候: 你好, 世界!",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MoHeaderSize = 28
|
||||
MoMagicLittleEndian = 0x950412de
|
||||
MoMagicBigEndian = 0xde120495
|
||||
|
||||
EotSeparator = "\x04" // msgctxt and msgid separator
|
||||
NulSeparator = "\x00" // msgid and msgstr separator
|
||||
)
|
||||
|
||||
// File represents an MO File.
|
||||
//
|
||||
// See http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
|
||||
type File struct {
|
||||
MagicNumber uint32
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
MsgIdCount uint32
|
||||
MsgIdOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
MimeHeader Header
|
||||
Messages []Message
|
||||
}
|
||||
|
||||
// Load loads a named mo file.
|
||||
func Load(name string) (*File, error) {
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return LoadData(data)
|
||||
}
|
||||
|
||||
// LoadData loads mo file format data.
|
||||
func LoadData(data []byte) (*File, error) {
|
||||
r := bytes.NewReader(data)
|
||||
|
||||
var magicNumber uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
var bo binary.ByteOrder
|
||||
switch magicNumber {
|
||||
case MoMagicLittleEndian:
|
||||
bo = binary.LittleEndian
|
||||
case MoMagicBigEndian:
|
||||
bo = binary.BigEndian
|
||||
default:
|
||||
return nil, fmt.Errorf("gettext: %v", "invalid magic number")
|
||||
}
|
||||
|
||||
var header struct {
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
MsgIdCount uint32
|
||||
MsgIdOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
}
|
||||
if err := binary.Read(r, bo, &header); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if v := header.MajorVersion; v != 0 && v != 1 {
|
||||
return nil, fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
if v := header.MinorVersion; v != 0 && v != 1 {
|
||||
return nil, fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
|
||||
msgIdStart := make([]uint32, header.MsgIdCount)
|
||||
msgIdLen := make([]uint32, header.MsgIdCount)
|
||||
if _, err := r.Seek(int64(header.MsgIdOffset), 0); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIdCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgIdLen[i]); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgIdStart[i]); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
msgStrStart := make([]int32, header.MsgIdCount)
|
||||
msgStrLen := make([]int32, header.MsgIdCount)
|
||||
if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIdCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
file := &File{
|
||||
MagicNumber: magicNumber,
|
||||
MajorVersion: header.MajorVersion,
|
||||
MinorVersion: header.MinorVersion,
|
||||
MsgIdCount: header.MsgIdCount,
|
||||
MsgIdOffset: header.MsgIdOffset,
|
||||
MsgStrOffset: header.MsgStrOffset,
|
||||
HashSize: header.HashSize,
|
||||
HashOffset: header.HashOffset,
|
||||
}
|
||||
for i := 0; i < int(header.MsgIdCount); i++ {
|
||||
if _, err := r.Seek(int64(msgIdStart[i]), 0); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgIdData := make([]byte, msgIdLen[i])
|
||||
if _, err := r.Read(msgIdData); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgStrData := make([]byte, msgStrLen[i])
|
||||
if _, err := r.Read(msgStrData); err != nil {
|
||||
return nil, fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if len(msgIdData) == 0 {
|
||||
var msg = Message{
|
||||
MsgId: string(msgIdData),
|
||||
MsgStr: string(msgStrData),
|
||||
}
|
||||
file.MimeHeader.fromMessage(&msg)
|
||||
} else {
|
||||
var msg = Message{
|
||||
MsgId: string(msgIdData),
|
||||
MsgStr: string(msgStrData),
|
||||
}
|
||||
// Is this a context message?
|
||||
if idx := strings.Index(msg.MsgId, EotSeparator); idx != -1 {
|
||||
msg.MsgContext, msg.MsgId = msg.MsgId[:idx], msg.MsgId[idx+1:]
|
||||
}
|
||||
// Is this a plural message?
|
||||
if idx := strings.Index(msg.MsgId, NulSeparator); idx != -1 {
|
||||
msg.MsgId, msg.MsgIdPlural = msg.MsgId[:idx], msg.MsgId[idx+1:]
|
||||
msg.MsgStrPlural = strings.Split(msg.MsgStr, NulSeparator)
|
||||
msg.MsgStr = ""
|
||||
}
|
||||
file.Messages = append(file.Messages, msg)
|
||||
}
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Save saves a mo file.
|
||||
func (f *File) Save(name string) error {
|
||||
return ioutil.WriteFile(name, f.Data(), 0666)
|
||||
}
|
||||
|
||||
// Save returns a mo file format data.
|
||||
func (f *File) Data() []byte {
|
||||
return encodeFile(f)
|
||||
}
|
||||
|
||||
// String returns the po format file string.
|
||||
func (f *File) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "# version: %d.%d\n", f.MajorVersion, f.MinorVersion)
|
||||
fmt.Fprintf(&buf, "%s\n", f.MimeHeader.String())
|
||||
fmt.Fprintf(&buf, "\n")
|
||||
|
||||
for k, v := range f.Messages {
|
||||
fmt.Fprintf(&buf, `msgid "%v"`+"\n", k)
|
||||
fmt.Fprintf(&buf, `msgstr "%s"`+"\n", v.MsgStr)
|
||||
fmt.Fprintf(&buf, "\n")
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Header is the initial comments "SOME DESCRIPTIVE TITLE", "YEAR"
|
||||
// and "FIRST AUTHOR <EMAIL@ADDRESS>, YEAR" ought to be replaced by sensible information.
|
||||
//
|
||||
// See http://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html#Header-Entry
|
||||
type Header struct {
|
||||
ProjectIdVersion string // Project-Id-Version: PACKAGE VERSION
|
||||
ReportMsgidBugsTo string // Report-Msgid-Bugs-To: FIRST AUTHOR <EMAIL@ADDRESS>
|
||||
POTCreationDate string // POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE
|
||||
PORevisionDate string // PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
|
||||
LastTranslator string // Last-Translator: FIRST AUTHOR <EMAIL@ADDRESS>
|
||||
LanguageTeam string // Language-Team: golang-china
|
||||
Language string // Language: zh_CN
|
||||
MimeVersion string // MIME-Version: 1.0
|
||||
ContentType string // Content-Type: text/plain; charset=UTF-8
|
||||
ContentTransferEncoding string // Content-Transfer-Encoding: 8bit
|
||||
PluralForms string // Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;
|
||||
XGenerator string // X-Generator: Poedit 1.5.5
|
||||
UnknowFields map[string]string
|
||||
}
|
||||
|
||||
func (p *Header) fromMessage(msg *Message) {
|
||||
if msg.MsgId != "" || msg.MsgStr == "" {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(msg.MsgStr, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
idx := strings.Index(lines[i], ":")
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(lines[i][:idx])
|
||||
val := strings.TrimSpace(lines[i][idx+1:])
|
||||
switch strings.ToUpper(key) {
|
||||
case strings.ToUpper("Project-Id-Version"):
|
||||
p.ProjectIdVersion = val
|
||||
case strings.ToUpper("Report-Msgid-Bugs-To"):
|
||||
p.ReportMsgidBugsTo = val
|
||||
case strings.ToUpper("POT-Creation-Date"):
|
||||
p.POTCreationDate = val
|
||||
case strings.ToUpper("PO-Revision-Date"):
|
||||
p.PORevisionDate = val
|
||||
case strings.ToUpper("Last-Translator"):
|
||||
p.LastTranslator = val
|
||||
case strings.ToUpper("Language-Team"):
|
||||
p.LanguageTeam = val
|
||||
case strings.ToUpper("Language"):
|
||||
p.Language = val
|
||||
case strings.ToUpper("MIME-Version"):
|
||||
p.MimeVersion = val
|
||||
case strings.ToUpper("Content-Type"):
|
||||
p.ContentType = val
|
||||
case strings.ToUpper("Content-Transfer-Encoding"):
|
||||
p.ContentTransferEncoding = val
|
||||
case strings.ToUpper("Plural-Forms"):
|
||||
p.PluralForms = val
|
||||
case strings.ToUpper("X-Generator"):
|
||||
p.XGenerator = val
|
||||
default:
|
||||
if p.UnknowFields == nil {
|
||||
p.UnknowFields = make(map[string]string)
|
||||
}
|
||||
p.UnknowFields[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Header) toMessage() Message {
|
||||
return Message{
|
||||
MsgStr: p.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the po format header string.
|
||||
func (p Header) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, `msgid ""`+"\n")
|
||||
fmt.Fprintf(&buf, `msgstr ""`+"\n")
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Project-Id-Version", p.ProjectIdVersion)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Report-Msgid-Bugs-To", p.ReportMsgidBugsTo)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "POT-Creation-Date", p.POTCreationDate)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "PO-Revision-Date", p.PORevisionDate)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Last-Translator", p.LastTranslator)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Language-Team", p.LanguageTeam)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Language", p.Language)
|
||||
if p.MimeVersion != "" {
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "MIME-Version", p.MimeVersion)
|
||||
}
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Content-Type", p.ContentType)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Content-Transfer-Encoding", p.ContentTransferEncoding)
|
||||
if p.XGenerator != "" {
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "X-Generator", p.XGenerator)
|
||||
}
|
||||
for k, v := range p.UnknowFields {
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", k, v)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHeader(t *testing.T) {
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A MO file is made up of many entries,
|
||||
// each entry holding the relation between an original untranslated string
|
||||
// and its corresponding translation.
|
||||
//
|
||||
// See http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
|
||||
type Message struct {
|
||||
MsgContext string // msgctxt context
|
||||
MsgId string // msgid untranslated-string
|
||||
MsgIdPlural string // msgid_plural untranslated-string-plural
|
||||
MsgStr string // msgstr translated-string
|
||||
MsgStrPlural []string // msgstr[0] translated-string-case-0
|
||||
}
|
||||
|
||||
// String returns the po format entry string.
|
||||
func (p Message) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "msgid %s", encodePoString(p.MsgId))
|
||||
if p.MsgIdPlural != "" {
|
||||
fmt.Fprintf(&buf, "msgid_plural %s", encodePoString(p.MsgIdPlural))
|
||||
}
|
||||
if p.MsgStr != "" {
|
||||
fmt.Fprintf(&buf, "msgstr %s", encodePoString(p.MsgStr))
|
||||
}
|
||||
for i := 0; i < len(p.MsgStrPlural); i++ {
|
||||
fmt.Fprintf(&buf, "msgstr[%d] %s", i, encodePoString(p.MsgStrPlural[i]))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func decodePoString(text string) string {
|
||||
lines := strings.Split(text, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
left := strings.Index(lines[i], `"`)
|
||||
right := strings.LastIndex(lines[i], `"`)
|
||||
if left < 0 || right < 0 || left == right {
|
||||
lines[i] = ""
|
||||
continue
|
||||
}
|
||||
line := lines[i][left+1 : right]
|
||||
data := make([]byte, 0, len(line))
|
||||
for i := 0; i < len(line); i++ {
|
||||
if line[i] != '\\' {
|
||||
data = append(data, line[i])
|
||||
continue
|
||||
}
|
||||
if i+1 >= len(line) {
|
||||
break
|
||||
}
|
||||
switch line[i+1] {
|
||||
case 'n': // \\n -> \n
|
||||
data = append(data, '\n')
|
||||
i++
|
||||
case 't': // \\t -> \n
|
||||
data = append(data, '\t')
|
||||
i++
|
||||
case '\\': // \\\ -> ?
|
||||
data = append(data, '\\')
|
||||
i++
|
||||
}
|
||||
}
|
||||
lines[i] = string(data)
|
||||
}
|
||||
return strings.Join(lines, "")
|
||||
}
|
||||
|
||||
func encodePoString(text string) string {
|
||||
var buf bytes.Buffer
|
||||
lines := strings.Split(text, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if lines[i] == "" {
|
||||
if i != len(lines)-1 {
|
||||
buf.WriteString(`"\n"` + "\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
buf.WriteRune('"')
|
||||
for _, r := range lines[i] {
|
||||
switch r {
|
||||
case '\\':
|
||||
buf.WriteString(`\\`)
|
||||
case '"':
|
||||
buf.WriteString(`\"`)
|
||||
case '\n':
|
||||
buf.WriteString(`\n`)
|
||||
case '\t':
|
||||
buf.WriteString(`\t`)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
buf.WriteString(`\n"` + "\n")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func encodeCommentPoString(text string) string {
|
||||
var buf bytes.Buffer
|
||||
lines := strings.Split(text, "\n")
|
||||
if len(lines) > 1 {
|
||||
buf.WriteString(`""` + "\n")
|
||||
}
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if len(lines) > 0 {
|
||||
buf.WriteString("#| ")
|
||||
}
|
||||
buf.WriteRune('"')
|
||||
for _, r := range lines[i] {
|
||||
switch r {
|
||||
case '\\':
|
||||
buf.WriteString(`\\`)
|
||||
case '"':
|
||||
buf.WriteString(`\"`)
|
||||
case '\n':
|
||||
buf.WriteString(`\n`)
|
||||
case '\t':
|
||||
buf.WriteString(`\t`)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if i < len(lines)-1 {
|
||||
buf.WriteString(`\n"` + "\n")
|
||||
} else {
|
||||
buf.WriteString(`"`)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodePoString(t *testing.T) {
|
||||
if s := decodePoString(poStrEncode); s != poStrDecode {
|
||||
t.Fatalf(`expect = %s got = %s`, poStrDecode, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodePoString(t *testing.T) {
|
||||
if s := encodePoString(poStrDecode); s != poStrEncodeStd {
|
||||
t.Fatalf(`expect = %s; got = %s`, poStrEncodeStd, s)
|
||||
}
|
||||
}
|
||||
|
||||
const poStrEncode = `# noise
|
||||
123456789
|
||||
"Project-Id-Version: Poedit 1.5\n"
|
||||
"Report-Msgid-Bugs-To: poedit@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2012-07-30 10:34+0200\n"
|
||||
"PO-Revision-Date: 2013-02-24 21:00+0800\n"
|
||||
"Last-Translator: Christopher Meng <trans@cicku.me>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.5.5\n"
|
||||
"TestPoString: abc"
|
||||
"123\n"
|
||||
>>
|
||||
123456???
|
||||
`
|
||||
|
||||
const poStrEncodeStd = `"Project-Id-Version: Poedit 1.5\n"
|
||||
"Report-Msgid-Bugs-To: poedit@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2012-07-30 10:34+0200\n"
|
||||
"PO-Revision-Date: 2013-02-24 21:00+0800\n"
|
||||
"Last-Translator: Christopher Meng <trans@cicku.me>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.5.5\n"
|
||||
"TestPoString: abc123\n"
|
||||
`
|
||||
|
||||
const poStrDecode = `Project-Id-Version: Poedit 1.5
|
||||
Report-Msgid-Bugs-To: poedit@googlegroups.com
|
||||
POT-Creation-Date: 2012-07-30 10:34+0200
|
||||
PO-Revision-Date: 2013-02-24 21:00+0800
|
||||
Last-Translator: Christopher Meng <trans@cicku.me>
|
||||
Language-Team:
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Plural-Forms: nplurals=1; plural=0;
|
||||
X-Generator: Poedit 1.5.5
|
||||
TestPoString: abc123
|
||||
`
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package plural provides standard plural formulas.
|
||||
|
||||
Examples:
|
||||
import (
|
||||
"code.google.com/p/gettext-go/gettext/plural"
|
||||
)
|
||||
|
||||
func main() {
|
||||
enFormula := plural.Formula("en_US")
|
||||
xxFormula := plural.Formula("zh_CN")
|
||||
|
||||
fmt.Printf("%s: %d\n", "en", enFormula(0))
|
||||
fmt.Printf("%s: %d\n", "en", enFormula(1))
|
||||
fmt.Printf("%s: %d\n", "en", enFormula(2))
|
||||
fmt.Printf("%s: %d\n", "??", xxFormula(0))
|
||||
fmt.Printf("%s: %d\n", "??", xxFormula(1))
|
||||
fmt.Printf("%s: %d\n", "??", xxFormula(2))
|
||||
fmt.Printf("%s: %d\n", "??", xxFormula(9))
|
||||
// Output:
|
||||
// en: 0
|
||||
// en: 0
|
||||
// en: 1
|
||||
// ??: 0
|
||||
// ??: 0
|
||||
// ??: 1
|
||||
// ??: 8
|
||||
}
|
||||
|
||||
See http://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
|
||||
*/
|
||||
package plural
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package plural
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Formula provides the language's standard plural formula.
|
||||
func Formula(lang string) func(n int) int {
|
||||
if idx := index(lang); idx != -1 {
|
||||
return formulaTable[fmtForms(FormsTable[idx].Value)]
|
||||
}
|
||||
if idx := index("??"); idx != -1 {
|
||||
return formulaTable[fmtForms(FormsTable[idx].Value)]
|
||||
}
|
||||
return func(n int) int {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
func index(lang string) int {
|
||||
for i := 0; i < len(FormsTable); i++ {
|
||||
if strings.HasPrefix(lang, FormsTable[i].Lang) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func fmtForms(forms string) string {
|
||||
forms = strings.TrimSpace(forms)
|
||||
forms = strings.Replace(forms, " ", "", -1)
|
||||
return forms
|
||||
}
|
||||
|
||||
var formulaTable = map[string]func(n int) int{
|
||||
fmtForms("nplurals=n; plural=n-1;"): func(n int) int {
|
||||
if n > 0 {
|
||||
return n - 1
|
||||
}
|
||||
return 0
|
||||
},
|
||||
fmtForms("nplurals=1; plural=0;"): func(n int) int {
|
||||
return 0
|
||||
},
|
||||
fmtForms("nplurals=2; plural=(n != 1);"): func(n int) int {
|
||||
if n <= 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
},
|
||||
fmtForms("nplurals=2; plural=(n > 1);"): func(n int) int {
|
||||
if n <= 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n != 0 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;"): func(n int) int {
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
if n == 2 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;"): func(n int) int {
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
if n == 0 || (n%100 > 0 && n%100 < 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n%10 == 1 && n%100 != 11 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"): func(n int) int {
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
if n >= 2 && n <= 4 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"): func(n int) int {
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
if n >= 2 && n <= 4 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"): func(n int) int {
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
},
|
||||
fmtForms("nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"): func(n int) int {
|
||||
if n%100 == 1 {
|
||||
return 0
|
||||
}
|
||||
if n%100 == 2 {
|
||||
return 1
|
||||
}
|
||||
if n%100 == 3 || n%100 == 4 {
|
||||
return 2
|
||||
}
|
||||
return 3
|
||||
},
|
||||
}
|
||||
50
vendor/github.com/chai2010/gettext-go/gettext/plural/formula_test.go
generated
vendored
Normal file
50
vendor/github.com/chai2010/gettext-go/gettext/plural/formula_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package plural
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormula(t *testing.T) {
|
||||
for i, v := range testData {
|
||||
if out := Formula(v.lang)(v.in); out != v.out {
|
||||
t.Fatalf("%d/%s: expect = %d, got = %d", i, v.lang, v.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testData = []struct {
|
||||
lang string
|
||||
in int
|
||||
out int
|
||||
}{
|
||||
{"#@", 0, 0},
|
||||
{"#@", 1, 0},
|
||||
{"#@", 10, 0},
|
||||
{"#@", -1, 0},
|
||||
|
||||
{"zh", 0, 0},
|
||||
{"zh", 1, 0},
|
||||
{"zh", 10, 0},
|
||||
{"zh", -1, 0},
|
||||
|
||||
{"zh_CN", 0, 0},
|
||||
{"zh_CN", 1, 0},
|
||||
{"zh_CN", 10, 0},
|
||||
{"zh_CN", -1, 0},
|
||||
|
||||
{"en", 0, 0},
|
||||
{"en", 1, 0},
|
||||
{"en", 2, 1},
|
||||
{"en", 10, 1},
|
||||
{"en", -1, 0},
|
||||
|
||||
{"en_US", 0, 0},
|
||||
{"en_US", 1, 0},
|
||||
{"en_US", 2, 1},
|
||||
{"en_US", 10, 1},
|
||||
{"en_US", -1, 0},
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package plural
|
||||
|
||||
// FormsTable are standard hard-coded plural rules.
|
||||
// The application developers and the translators need to understand them.
|
||||
//
|
||||
// See GNU's gettext library source code: gettext/gettext-tools/src/plural-table.c
|
||||
var FormsTable = []struct {
|
||||
Lang string
|
||||
Language string
|
||||
Value string
|
||||
}{
|
||||
{"??", "Unknown", "nplurals=1; plural=0;"},
|
||||
{"ja", "Japanese", "nplurals=1; plural=0;"},
|
||||
{"vi", "Vietnamese", "nplurals=1; plural=0;"},
|
||||
{"ko", "Korean", "nplurals=1; plural=0;"},
|
||||
{"en", "English", "nplurals=2; plural=(n != 1);"},
|
||||
{"de", "German", "nplurals=2; plural=(n != 1);"},
|
||||
{"nl", "Dutch", "nplurals=2; plural=(n != 1);"},
|
||||
{"sv", "Swedish", "nplurals=2; plural=(n != 1);"},
|
||||
{"da", "Danish", "nplurals=2; plural=(n != 1);"},
|
||||
{"no", "Norwegian", "nplurals=2; plural=(n != 1);"},
|
||||
{"nb", "Norwegian Bokmal", "nplurals=2; plural=(n != 1);"},
|
||||
{"nn", "Norwegian Nynorsk", "nplurals=2; plural=(n != 1);"},
|
||||
{"fo", "Faroese", "nplurals=2; plural=(n != 1);"},
|
||||
{"es", "Spanish", "nplurals=2; plural=(n != 1);"},
|
||||
{"pt", "Portuguese", "nplurals=2; plural=(n != 1);"},
|
||||
{"it", "Italian", "nplurals=2; plural=(n != 1);"},
|
||||
{"bg", "Bulgarian", "nplurals=2; plural=(n != 1);"},
|
||||
{"el", "Greek", "nplurals=2; plural=(n != 1);"},
|
||||
{"fi", "Finnish", "nplurals=2; plural=(n != 1);"},
|
||||
{"et", "Estonian", "nplurals=2; plural=(n != 1);"},
|
||||
{"he", "Hebrew", "nplurals=2; plural=(n != 1);"},
|
||||
{"eo", "Esperanto", "nplurals=2; plural=(n != 1);"},
|
||||
{"hu", "Hungarian", "nplurals=2; plural=(n != 1);"},
|
||||
{"tr", "Turkish", "nplurals=2; plural=(n != 1);"},
|
||||
{"pt_BR", "Brazilian", "nplurals=2; plural=(n > 1);"},
|
||||
{"fr", "French", "nplurals=2; plural=(n > 1);"},
|
||||
{"lv", "Latvian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"},
|
||||
{"ga", "Irish", "nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;"},
|
||||
{"ro", "Romanian", "nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;"},
|
||||
{"lt", "Lithuanian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"ru", "Russian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"uk", "Ukrainian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"be", "Belarusian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"sr", "Serbian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"hr", "Croatian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"cs", "Czech", "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"},
|
||||
{"sk", "Slovak", "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"},
|
||||
{"pl", "Polish", "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},
|
||||
{"sl", "Slovenian", "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"},
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Comment represents every message's comments.
|
||||
type Comment struct {
|
||||
StartLine int // comment start line
|
||||
TranslatorComment string // # translator-comments // TrimSpace
|
||||
ExtractedComment string // #. extracted-comments
|
||||
ReferenceFile []string // #: src/msgcmp.c:338 src/po-lex.c:699
|
||||
ReferenceLine []int // #: src/msgcmp.c:338 src/po-lex.c:699
|
||||
Flags []string // #, fuzzy,c-format,range:0..10
|
||||
PrevMsgContext string // #| msgctxt previous-context
|
||||
PrevMsgId string // #| msgid previous-untranslated-string
|
||||
}
|
||||
|
||||
func (p *Comment) less(q *Comment) bool {
|
||||
if p.StartLine != 0 || q.StartLine != 0 {
|
||||
return p.StartLine < q.StartLine
|
||||
}
|
||||
if a, b := len(p.ReferenceFile), len(q.ReferenceFile); a != b {
|
||||
return a < b
|
||||
}
|
||||
for i := 0; i < len(p.ReferenceFile); i++ {
|
||||
if a, b := p.ReferenceFile[i], q.ReferenceFile[i]; a != b {
|
||||
return a < b
|
||||
}
|
||||
if a, b := p.ReferenceLine[i], q.ReferenceLine[i]; a != b {
|
||||
return a < b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Comment) readPoComment(r *lineReader) (err error) {
|
||||
*p = Comment{}
|
||||
if err = r.skipBlankLine(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(oldPos int) {
|
||||
newPos := r.currentPos()
|
||||
if newPos != oldPos && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}(r.currentPos())
|
||||
|
||||
p.StartLine = r.currentPos() + 1
|
||||
for {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if len(s) == 0 || s[0] != '#' {
|
||||
return
|
||||
}
|
||||
|
||||
if err = p.readTranslatorComment(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readExtractedComment(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readReferenceComment(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readFlagsComment(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readPrevMsgContext(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readPrevMsgId(r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Comment) readTranslatorComment(r *lineReader) (err error) {
|
||||
const prefix = "# " // .,:|
|
||||
for {
|
||||
var s string
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) < 1 || s[0] != '#' {
|
||||
r.unreadLine()
|
||||
return nil
|
||||
}
|
||||
if len(s) >= 2 {
|
||||
switch s[1] {
|
||||
case '.', ',', ':', '|':
|
||||
r.unreadLine()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if p.TranslatorComment != "" {
|
||||
p.TranslatorComment += "\n"
|
||||
}
|
||||
p.TranslatorComment += strings.TrimSpace(s[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Comment) readExtractedComment(r *lineReader) (err error) {
|
||||
const prefix = "#."
|
||||
for {
|
||||
var s string
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) < len(prefix) || s[:len(prefix)] != prefix {
|
||||
r.unreadLine()
|
||||
return nil
|
||||
}
|
||||
if p.ExtractedComment != "" {
|
||||
p.ExtractedComment += "\n"
|
||||
}
|
||||
p.ExtractedComment += strings.TrimSpace(s[len(prefix):])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Comment) readReferenceComment(r *lineReader) (err error) {
|
||||
const prefix = "#:"
|
||||
for {
|
||||
var s string
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) < len(prefix) || s[:len(prefix)] != prefix {
|
||||
r.unreadLine()
|
||||
return nil
|
||||
}
|
||||
ss := strings.Split(strings.TrimSpace(s[len(prefix):]), " ")
|
||||
for i := 0; i < len(ss); i++ {
|
||||
idx := strings.Index(ss[i], ":")
|
||||
if idx <= 0 {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(ss[i][:idx])
|
||||
line, _ := strconv.Atoi(strings.TrimSpace(ss[i][idx+1:]))
|
||||
p.ReferenceFile = append(p.ReferenceFile, name)
|
||||
p.ReferenceLine = append(p.ReferenceLine, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Comment) readFlagsComment(r *lineReader) (err error) {
|
||||
const prefix = "#,"
|
||||
for {
|
||||
var s string
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) < len(prefix) || s[:len(prefix)] != prefix {
|
||||
r.unreadLine()
|
||||
return nil
|
||||
}
|
||||
ss := strings.Split(strings.TrimSpace(s[len(prefix):]), ",")
|
||||
for i := 0; i < len(ss); i++ {
|
||||
p.Flags = append(p.Flags, strings.TrimSpace(ss[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Comment) readPrevMsgContext(r *lineReader) (err error) {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !rePrevMsgContextComments.MatchString(s) {
|
||||
return
|
||||
}
|
||||
p.PrevMsgContext, err = p.readString(r)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Comment) readPrevMsgId(r *lineReader) (err error) {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !rePrevMsgIdComments.MatchString(s) {
|
||||
return
|
||||
}
|
||||
p.PrevMsgId, err = p.readString(r)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Comment) readString(r *lineReader) (msg string, err error) {
|
||||
var s string
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return
|
||||
}
|
||||
msg += decodePoString(s)
|
||||
for {
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !reStringLineComments.MatchString(s) {
|
||||
r.unreadLine()
|
||||
break
|
||||
}
|
||||
msg += decodePoString(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetFuzzy gets the fuzzy flag.
|
||||
func (p *Comment) GetFuzzy() bool {
|
||||
for _, s := range p.Flags {
|
||||
if s == "fuzzy" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetFuzzy sets the fuzzy flag.
|
||||
func (p *Comment) SetFuzzy(fuzzy bool) {
|
||||
//
|
||||
}
|
||||
|
||||
// String returns the po format comment string.
|
||||
func (p Comment) String() string {
|
||||
var buf bytes.Buffer
|
||||
if p.TranslatorComment != "" {
|
||||
ss := strings.Split(p.TranslatorComment, "\n")
|
||||
for i := 0; i < len(ss); i++ {
|
||||
fmt.Fprintf(&buf, "# %s\n", ss[i])
|
||||
}
|
||||
}
|
||||
if p.ExtractedComment != "" {
|
||||
ss := strings.Split(p.ExtractedComment, "\n")
|
||||
for i := 0; i < len(ss); i++ {
|
||||
fmt.Fprintf(&buf, "#. %s\n", ss[i])
|
||||
}
|
||||
}
|
||||
if a, b := len(p.ReferenceFile), len(p.ReferenceLine); a != 0 && a == b {
|
||||
fmt.Fprintf(&buf, "#:")
|
||||
for i := 0; i < len(p.ReferenceFile); i++ {
|
||||
fmt.Fprintf(&buf, " %s:%d", p.ReferenceFile[i], p.ReferenceLine[i])
|
||||
}
|
||||
fmt.Fprintf(&buf, "\n")
|
||||
}
|
||||
if len(p.Flags) != 0 {
|
||||
fmt.Fprintf(&buf, "#, %s", p.Flags[0])
|
||||
for i := 1; i < len(p.Flags); i++ {
|
||||
fmt.Fprintf(&buf, ", %s", p.Flags[i])
|
||||
}
|
||||
fmt.Fprintf(&buf, "\n")
|
||||
}
|
||||
if p.PrevMsgContext != "" {
|
||||
s := encodeCommentPoString(p.PrevMsgContext)
|
||||
fmt.Fprintf(&buf, "#| msgctxt %s\n", s)
|
||||
}
|
||||
if p.PrevMsgId != "" {
|
||||
s := encodeCommentPoString(p.PrevMsgId)
|
||||
fmt.Fprintf(&buf, "#| msgid %s\n", s)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPoComment(t *testing.T) {
|
||||
var x Comment
|
||||
for i := 0; i < len(testPoComments); i++ {
|
||||
if i != 2 {
|
||||
continue
|
||||
}
|
||||
err := x.readPoComment(newLineReader(testPoComments[i].Data))
|
||||
if err != nil {
|
||||
t.Fatalf("%d: %v", i, err)
|
||||
}
|
||||
x.StartLine = 0 // ingore comment line
|
||||
if !reflect.DeepEqual(&x, &testPoComments[i].PoComment) {
|
||||
t.Logf("expect(%d):\n", i)
|
||||
t.Logf("\n%v\n", &testPoComments[i].PoComment)
|
||||
t.Logf("got(%d):\n", i)
|
||||
t.Logf("\n%v\n", &x)
|
||||
t.FailNow()
|
||||
}
|
||||
if testPoComments[i].CheckStringer {
|
||||
s := testPoComments[i].PoComment.String()
|
||||
if s != testPoComments[i].Data {
|
||||
t.Logf("expect(%d):\n", i)
|
||||
t.Logf("\n%s\n", testPoComments[i].Data)
|
||||
t.Logf("got(%d):\n", i)
|
||||
t.Logf("\n%s\n", testPoComments[i].PoComment.String())
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testPoComment struct {
|
||||
CheckStringer bool
|
||||
Data string
|
||||
PoComment Comment
|
||||
}
|
||||
|
||||
var testPoComments = []testPoComment{
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// CheckStringer: true
|
||||
// --------------------------------------------------------------
|
||||
|
||||
testPoComment{
|
||||
CheckStringer: true,
|
||||
Data: `# translator comments
|
||||
`,
|
||||
PoComment: Comment{
|
||||
TranslatorComment: `translator comments`,
|
||||
},
|
||||
},
|
||||
testPoComment{
|
||||
CheckStringer: true,
|
||||
Data: `# translator comments
|
||||
`,
|
||||
PoComment: Comment{
|
||||
TranslatorComment: `translator comments`,
|
||||
},
|
||||
},
|
||||
|
||||
testPoComment{
|
||||
CheckStringer: true,
|
||||
Data: `# translator-comments
|
||||
# bad comment
|
||||
#. extracted-comments
|
||||
#: src/msgcmp.c:338 src/po-lex.c:699 src/msg.c:123
|
||||
#, fuzzy, c-format, range:0..10
|
||||
#| msgctxt ""
|
||||
#| "previous-context1\n"
|
||||
#| "previous-context2"
|
||||
#| msgid ""
|
||||
#| "previous-untranslated-string1\n"
|
||||
#| "previous-untranslated-string2"
|
||||
`,
|
||||
PoComment: Comment{
|
||||
TranslatorComment: "translator-comments\nbad comment",
|
||||
ExtractedComment: "extracted-comments",
|
||||
ReferenceFile: []string{"src/msgcmp.c", "src/po-lex.c", "src/msg.c"},
|
||||
ReferenceLine: []int{338, 699, 123},
|
||||
Flags: []string{"fuzzy", "c-format", "range:0..10"},
|
||||
PrevMsgContext: "previous-context1\nprevious-context2",
|
||||
PrevMsgId: "previous-untranslated-string1\nprevious-untranslated-string2",
|
||||
},
|
||||
},
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// CheckStringer: false
|
||||
// --------------------------------------------------------------
|
||||
|
||||
testPoComment{
|
||||
CheckStringer: false,
|
||||
Data: `
|
||||
# translator-comments
|
||||
#bad comment
|
||||
#. extracted-comments
|
||||
#: src/msgcmp.c:338 src/po-lex.c:699
|
||||
#: src/msg.c:123
|
||||
#, fuzzy,c-format,range:0..10
|
||||
#| msgctxt ""
|
||||
#| "previous-context1\n"
|
||||
#| "previous-context2"
|
||||
#| msgid ""
|
||||
#| "previous-untranslated-string1\n"
|
||||
#| "previous-untranslated-string2"
|
||||
`,
|
||||
PoComment: Comment{
|
||||
TranslatorComment: "translator-comments\nbad comment",
|
||||
ExtractedComment: "extracted-comments",
|
||||
ReferenceFile: []string{"src/msgcmp.c", "src/po-lex.c", "src/msg.c"},
|
||||
ReferenceLine: []int{338, 699, 123},
|
||||
Flags: []string{"fuzzy", "c-format", "range:0..10"},
|
||||
PrevMsgContext: "previous-context1\nprevious-context2",
|
||||
PrevMsgId: "previous-untranslated-string1\nprevious-untranslated-string2",
|
||||
},
|
||||
},
|
||||
testPoComment{
|
||||
CheckStringer: false,
|
||||
Data: `
|
||||
# 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.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Poedit 1.5\n"
|
||||
"Report-Msgid-Bugs-To: poedit@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2012-07-30 10:34+0200\n"
|
||||
"PO-Revision-Date: 2013-12-25 09:32+0800\n"
|
||||
"Last-Translator: chai2010 <chaishushan@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
`,
|
||||
PoComment: Comment{
|
||||
TranslatorComment: `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.
|
||||
`,
|
||||
},
|
||||
},
|
||||
testPoComment{
|
||||
CheckStringer: false,
|
||||
Data: `
|
||||
#. TRANSLATORS: This is version information in about dialog, it is followed
|
||||
#. by version number when used (wxWidgets 2.8)
|
||||
#: ../src/edframe.cpp:2431
|
||||
#| msgctxt "previous-context asdasd"
|
||||
"asdad \n asdsad"
|
||||
msgstr ""
|
||||
`,
|
||||
PoComment: Comment{
|
||||
ExtractedComment: `TRANSLATORS: This is version information in about dialog, it is followed
|
||||
by version number when used (wxWidgets 2.8)`,
|
||||
ReferenceFile: []string{"../src/edframe.cpp"},
|
||||
ReferenceLine: []int{2431},
|
||||
PrevMsgContext: "previous-context asdasd",
|
||||
},
|
||||
},
|
||||
testPoComment{
|
||||
CheckStringer: false,
|
||||
Data: `
|
||||
#: tst-gettext2.c:33
|
||||
msgid "First string for testing."
|
||||
msgstr "Lang1: 1st string"
|
||||
`,
|
||||
PoComment: Comment{
|
||||
ReferenceFile: []string{"tst-gettext2.c"},
|
||||
ReferenceLine: []int{33},
|
||||
},
|
||||
},
|
||||
testPoComment{
|
||||
CheckStringer: false,
|
||||
Data: `
|
||||
#: app/app_procs.c:307
|
||||
#, fuzzy, c-format
|
||||
msgid "Can't find output format %s\n"
|
||||
msgstr ""
|
||||
"敲矾弊牢 '%s'甫 佬阑荐 绝嚼聪促\n"
|
||||
"%s"
|
||||
`,
|
||||
PoComment: Comment{
|
||||
ReferenceFile: []string{"app/app_procs.c"},
|
||||
ReferenceLine: []int{307},
|
||||
Flags: []string{"fuzzy", "c-format"},
|
||||
},
|
||||
},
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// END
|
||||
// --------------------------------------------------------------
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package po provides support for reading and writing GNU PO file.
|
||||
|
||||
Examples:
|
||||
import (
|
||||
"github.com/chai2010/gettext-go/gettext/po"
|
||||
)
|
||||
|
||||
func main() {
|
||||
poFile, err := po.Load("test.po")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%v", poFile)
|
||||
}
|
||||
|
||||
The GNU PO file specification is at
|
||||
http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html.
|
||||
*/
|
||||
package po
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// File represents an PO File.
|
||||
//
|
||||
// See http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
||||
type File struct {
|
||||
MimeHeader Header
|
||||
Messages []Message
|
||||
}
|
||||
|
||||
// Load loads a named po file.
|
||||
func Load(name string) (*File, error) {
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return LoadData(data)
|
||||
}
|
||||
|
||||
// LoadData loads po file format data.
|
||||
func LoadData(data []byte) (*File, error) {
|
||||
r := newLineReader(string(data))
|
||||
var file File
|
||||
for {
|
||||
var msg Message
|
||||
if err := msg.readPoEntry(r); err != nil {
|
||||
if err == io.EOF {
|
||||
return &file, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if msg.MsgId == "" {
|
||||
file.MimeHeader.parseHeader(&msg)
|
||||
continue
|
||||
}
|
||||
file.Messages = append(file.Messages, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Save saves a po file.
|
||||
func (f *File) Save(name string) error {
|
||||
return ioutil.WriteFile(name, []byte(f.String()), 0666)
|
||||
}
|
||||
|
||||
// Save returns a po file format data.
|
||||
func (f *File) Data() []byte {
|
||||
// sort the massge as ReferenceFile/ReferenceLine field
|
||||
var messages []Message
|
||||
messages = append(messages, f.Messages...)
|
||||
sort.Sort(byMessages(messages))
|
||||
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s\n", f.MimeHeader.String())
|
||||
for i := 0; i < len(messages); i++ {
|
||||
fmt.Fprintf(&buf, "%s\n", messages[i].String())
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// String returns the po format file string.
|
||||
func (f *File) String() string {
|
||||
return string(f.Data())
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPoFile(t *testing.T) {
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Header is the initial comments "SOME DESCRIPTIVE TITLE", "YEAR"
|
||||
// and "FIRST AUTHOR <EMAIL@ADDRESS>, YEAR" ought to be replaced by sensible information.
|
||||
//
|
||||
// See http://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html#Header-Entry
|
||||
type Header struct {
|
||||
Comment // Header Comments
|
||||
ProjectIdVersion string // Project-Id-Version: PACKAGE VERSION
|
||||
ReportMsgidBugsTo string // Report-Msgid-Bugs-To: FIRST AUTHOR <EMAIL@ADDRESS>
|
||||
POTCreationDate string // POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE
|
||||
PORevisionDate string // PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
|
||||
LastTranslator string // Last-Translator: FIRST AUTHOR <EMAIL@ADDRESS>
|
||||
LanguageTeam string // Language-Team: golang-china
|
||||
Language string // Language: zh_CN
|
||||
MimeVersion string // MIME-Version: 1.0
|
||||
ContentType string // Content-Type: text/plain; charset=UTF-8
|
||||
ContentTransferEncoding string // Content-Transfer-Encoding: 8bit
|
||||
PluralForms string // Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;
|
||||
XGenerator string // X-Generator: Poedit 1.5.5
|
||||
UnknowFields map[string]string
|
||||
}
|
||||
|
||||
func (p *Header) parseHeader(msg *Message) {
|
||||
if msg.MsgId != "" || msg.MsgStr == "" {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(msg.MsgStr, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
idx := strings.Index(lines[i], ":")
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(lines[i][:idx])
|
||||
val := strings.TrimSpace(lines[i][idx+1:])
|
||||
switch strings.ToUpper(key) {
|
||||
case strings.ToUpper("Project-Id-Version"):
|
||||
p.ProjectIdVersion = val
|
||||
case strings.ToUpper("Report-Msgid-Bugs-To"):
|
||||
p.ReportMsgidBugsTo = val
|
||||
case strings.ToUpper("POT-Creation-Date"):
|
||||
p.POTCreationDate = val
|
||||
case strings.ToUpper("PO-Revision-Date"):
|
||||
p.PORevisionDate = val
|
||||
case strings.ToUpper("Last-Translator"):
|
||||
p.LastTranslator = val
|
||||
case strings.ToUpper("Language-Team"):
|
||||
p.LanguageTeam = val
|
||||
case strings.ToUpper("Language"):
|
||||
p.Language = val
|
||||
case strings.ToUpper("MIME-Version"):
|
||||
p.MimeVersion = val
|
||||
case strings.ToUpper("Content-Type"):
|
||||
p.ContentType = val
|
||||
case strings.ToUpper("Content-Transfer-Encoding"):
|
||||
p.ContentTransferEncoding = val
|
||||
case strings.ToUpper("Plural-Forms"):
|
||||
p.PluralForms = val
|
||||
case strings.ToUpper("X-Generator"):
|
||||
p.XGenerator = val
|
||||
default:
|
||||
if p.UnknowFields == nil {
|
||||
p.UnknowFields = make(map[string]string)
|
||||
}
|
||||
p.UnknowFields[key] = val
|
||||
}
|
||||
}
|
||||
p.Comment = msg.Comment
|
||||
}
|
||||
|
||||
// String returns the po format header string.
|
||||
func (p Header) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s", p.Comment.String())
|
||||
fmt.Fprintf(&buf, `msgid ""`+"\n")
|
||||
fmt.Fprintf(&buf, `msgstr ""`+"\n")
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Project-Id-Version", p.ProjectIdVersion)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Report-Msgid-Bugs-To", p.ReportMsgidBugsTo)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "POT-Creation-Date", p.POTCreationDate)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "PO-Revision-Date", p.PORevisionDate)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Last-Translator", p.LastTranslator)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Language-Team", p.LanguageTeam)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Language", p.Language)
|
||||
if p.MimeVersion != "" {
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "MIME-Version", p.MimeVersion)
|
||||
}
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Content-Type", p.ContentType)
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "Content-Transfer-Encoding", p.ContentTransferEncoding)
|
||||
if p.XGenerator != "" {
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", "X-Generator", p.XGenerator)
|
||||
}
|
||||
for k, v := range p.UnknowFields {
|
||||
fmt.Fprintf(&buf, `"%s: %s\n"`+"\n", k, v)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHeader(t *testing.T) {
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type lineReader struct {
|
||||
lines []string
|
||||
pos int
|
||||
}
|
||||
|
||||
func newLineReader(data string) *lineReader {
|
||||
data = strings.Replace(data, "\r", "", -1)
|
||||
lines := strings.Split(data, "\n")
|
||||
return &lineReader{lines: lines}
|
||||
}
|
||||
|
||||
func (r *lineReader) skipBlankLine() error {
|
||||
for ; r.pos < len(r.lines); r.pos++ {
|
||||
if strings.TrimSpace(r.lines[r.pos]) != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if r.pos >= len(r.lines) {
|
||||
return io.EOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *lineReader) currentPos() int {
|
||||
return r.pos
|
||||
}
|
||||
|
||||
func (r *lineReader) currentLine() (s string, pos int, err error) {
|
||||
if r.pos >= len(r.lines) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
s, pos = r.lines[r.pos], r.pos
|
||||
return
|
||||
}
|
||||
|
||||
func (r *lineReader) readLine() (s string, pos int, err error) {
|
||||
if r.pos >= len(r.lines) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
s, pos = r.lines[r.pos], r.pos
|
||||
r.pos++
|
||||
return
|
||||
}
|
||||
|
||||
func (r *lineReader) unreadLine() {
|
||||
if r.pos >= 0 {
|
||||
r.pos--
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A PO file is made up of many entries,
|
||||
// each entry holding the relation between an original untranslated string
|
||||
// and its corresponding translation.
|
||||
//
|
||||
// See http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
||||
type Message struct {
|
||||
Comment // Coments
|
||||
MsgContext string // msgctxt context
|
||||
MsgId string // msgid untranslated-string
|
||||
MsgIdPlural string // msgid_plural untranslated-string-plural
|
||||
MsgStr string // msgstr translated-string
|
||||
MsgStrPlural []string // msgstr[0] translated-string-case-0
|
||||
}
|
||||
|
||||
type byMessages []Message
|
||||
|
||||
func (d byMessages) Len() int {
|
||||
return len(d)
|
||||
}
|
||||
func (d byMessages) Less(i, j int) bool {
|
||||
if d[i].Comment.less(&d[j].Comment) {
|
||||
return true
|
||||
}
|
||||
if a, b := d[i].MsgContext, d[j].MsgContext; a != b {
|
||||
return a < b
|
||||
}
|
||||
if a, b := d[i].MsgId, d[j].MsgId; a != b {
|
||||
return a < b
|
||||
}
|
||||
if a, b := d[i].MsgIdPlural, d[j].MsgIdPlural; a != b {
|
||||
return a < b
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (d byMessages) Swap(i, j int) {
|
||||
d[i], d[j] = d[j], d[i]
|
||||
}
|
||||
|
||||
func (p *Message) readPoEntry(r *lineReader) (err error) {
|
||||
*p = Message{}
|
||||
if err = r.skipBlankLine(); err != nil {
|
||||
return
|
||||
}
|
||||
defer func(oldPos int) {
|
||||
newPos := r.currentPos()
|
||||
if newPos != oldPos && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}(r.currentPos())
|
||||
|
||||
if err = p.Comment.readPoComment(r); err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.isInvalidLine(s) {
|
||||
err = fmt.Errorf("gettext: line %d, %v", r.currentPos(), "invalid line")
|
||||
return
|
||||
}
|
||||
if reComment.MatchString(s) || reBlankLine.MatchString(s) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = p.readMsgContext(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readMsgId(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readMsgIdPlural(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.readMsgStrOrPlural(r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Message) readMsgContext(r *lineReader) (err error) {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !reMsgContext.MatchString(s) {
|
||||
return
|
||||
}
|
||||
p.MsgContext, err = p.readString(r)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Message) readMsgId(r *lineReader) (err error) {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !reMsgId.MatchString(s) {
|
||||
return
|
||||
}
|
||||
p.MsgId, err = p.readString(r)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Message) readMsgIdPlural(r *lineReader) (err error) {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !reMsgIdPlural.MatchString(s) {
|
||||
return
|
||||
}
|
||||
p.MsgIdPlural, err = p.readString(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Message) readMsgStrOrPlural(r *lineReader) (err error) {
|
||||
var s string
|
||||
if s, _, err = r.currentLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !reMsgStr.MatchString(s) && !reMsgStrPlural.MatchString(s) {
|
||||
return
|
||||
}
|
||||
if reMsgStrPlural.MatchString(s) {
|
||||
left, right := strings.Index(s, `[`), strings.LastIndex(s, `]`)
|
||||
idx, _ := strconv.Atoi(s[left+1 : right])
|
||||
s, err = p.readString(r)
|
||||
if n := len(p.MsgStrPlural); (idx + 1) > n {
|
||||
p.MsgStrPlural = append(p.MsgStrPlural, make([]string, (idx+1)-n)...)
|
||||
}
|
||||
p.MsgStrPlural[idx] = s
|
||||
} else {
|
||||
p.MsgStr, err = p.readString(r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Message) readString(r *lineReader) (msg string, err error) {
|
||||
var s string
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return
|
||||
}
|
||||
msg += decodePoString(s)
|
||||
for {
|
||||
if s, _, err = r.readLine(); err != nil {
|
||||
return
|
||||
}
|
||||
if !reStringLine.MatchString(s) {
|
||||
r.unreadLine()
|
||||
break
|
||||
}
|
||||
msg += decodePoString(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// String returns the po format entry string.
|
||||
func (p Message) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s", p.Comment.String())
|
||||
fmt.Fprintf(&buf, "msgid %s", encodePoString(p.MsgId))
|
||||
if p.MsgIdPlural != "" {
|
||||
fmt.Fprintf(&buf, "msgid_plural %s", encodePoString(p.MsgIdPlural))
|
||||
}
|
||||
if p.MsgStr != "" {
|
||||
fmt.Fprintf(&buf, "msgstr %s", encodePoString(p.MsgStr))
|
||||
}
|
||||
for i := 0; i < len(p.MsgStrPlural); i++ {
|
||||
fmt.Fprintf(&buf, "msgstr[%d] %s", i, encodePoString(p.MsgStrPlural[i]))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func _TestPoEntry(t *testing.T) {
|
||||
if len(testPoEntrys) != len(testPoEntryStrings) {
|
||||
t.Fatalf("bad test")
|
||||
}
|
||||
var entry Message
|
||||
for i := 0; i < len(testPoEntrys); i++ {
|
||||
if err := entry.readPoEntry(newLineReader(testPoEntryStrings[i])); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(&entry, &testPoEntrys[i]) {
|
||||
t.Fatalf("%d: expect = %v, got = %v", i, testPoEntrys[i], entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testPoEntryStrings = []string{
|
||||
`
|
||||
# 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.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 项目名称\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-12-12 20:03+0000\n"
|
||||
"PO-Revision-Date: 2013-12-02 17:05+0800\n"
|
||||
"Last-Translator: chai2010 <chaishushan@gmail.com>\n"
|
||||
"Language-Team: chai2010(团队) <chaishushan@gmail.com>\n"
|
||||
"Language: 中文\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
`,
|
||||
}
|
||||
|
||||
var testPoEntrys = []Message{
|
||||
Message{
|
||||
Comment: Comment{
|
||||
TranslatorComment: `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.
|
||||
`,
|
||||
},
|
||||
MsgStr: `
|
||||
Project-Id-Version: 项目名称
|
||||
Report-Msgid-Bugs-To:
|
||||
POT-Creation-Date: 2011-12-12 20:03+0000
|
||||
PO-Revision-Date: 2013-12-02 17:05+0800
|
||||
Last-Translator: chai2010 <chaishushan@gmail.com>
|
||||
Language-Team: chai2010(团队) <chaishushan@gmail.com>
|
||||
Language: 中文
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
X-Generator: Poedit 1.5.7
|
||||
X-Poedit-SourceCharset: UTF-8
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testPoEditPoFile = "../testdata/poedit-1.5.7-zh_CN.po"
|
||||
testPoEditMoFile = "../testdata/poedit-1.5.7-zh_CN.mo"
|
||||
)
|
||||
|
||||
func _TestPoEditPoFile(t *testing.T) {
|
||||
po, err := Load(testPoEditPoFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(&po.MimeHeader, &poEditFile.MimeHeader) {
|
||||
t.Fatalf("expect = %v, got = %v", &poEditFile.MimeHeader, &po.MimeHeader)
|
||||
}
|
||||
if len(po.Messages) != len(poEditFile.Messages) {
|
||||
t.Fatal("size not equal")
|
||||
}
|
||||
for k, v0 := range po.Messages {
|
||||
if v1 := poEditFile.Messages[k]; !reflect.DeepEqual(&v0, &v1) {
|
||||
t.Fatalf("%d: expect = %v, got = %v", k, v1, v0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var poEditFile = &File{}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
reComment = regexp.MustCompile(`^#`) // #
|
||||
reExtractedComments = regexp.MustCompile(`^#\.`) // #.
|
||||
reReferenceComments = regexp.MustCompile(`^#:`) // #:
|
||||
reFlagsComments = regexp.MustCompile(`^#,`) // #, fuzzy,c-format
|
||||
rePrevMsgContextComments = regexp.MustCompile(`^#\|\s+msgctxt`) // #| msgctxt
|
||||
rePrevMsgIdComments = regexp.MustCompile(`^#\|\s+msgid`) // #| msgid
|
||||
reStringLineComments = regexp.MustCompile(`^#\|\s+".*"\s*$`) // #| "message"
|
||||
|
||||
reMsgContext = regexp.MustCompile(`^msgctxt\s+".*"\s*$`) // msgctxt
|
||||
reMsgId = regexp.MustCompile(`^msgid\s+".*"\s*$`) // msgid
|
||||
reMsgIdPlural = regexp.MustCompile(`^msgid_plural\s+".*"\s*$`) // msgid_plural
|
||||
reMsgStr = regexp.MustCompile(`^msgstr\s*".*"\s*$`) // msgstr
|
||||
reMsgStrPlural = regexp.MustCompile(`^msgstr\s*(\[\d+\])\s*".*"\s*$`) // msgstr[0]
|
||||
reStringLine = regexp.MustCompile(`^\s*".*"\s*$`) // "message"
|
||||
reBlankLine = regexp.MustCompile(`^\s*$`) //
|
||||
)
|
||||
|
||||
func (p *Message) isInvalidLine(s string) bool {
|
||||
if reComment.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
if reBlankLine.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
|
||||
if reMsgContext.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
if reMsgId.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
if reMsgIdPlural.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
if reMsgStr.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
if reMsgStrPlural.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
|
||||
if reStringLine.MatchString(s) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func decodePoString(text string) string {
|
||||
lines := strings.Split(text, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
left := strings.Index(lines[i], `"`)
|
||||
right := strings.LastIndex(lines[i], `"`)
|
||||
if left < 0 || right < 0 || left == right {
|
||||
lines[i] = ""
|
||||
continue
|
||||
}
|
||||
line := lines[i][left+1 : right]
|
||||
data := make([]byte, 0, len(line))
|
||||
for i := 0; i < len(line); i++ {
|
||||
if line[i] != '\\' {
|
||||
data = append(data, line[i])
|
||||
continue
|
||||
}
|
||||
if i+1 >= len(line) {
|
||||
break
|
||||
}
|
||||
switch line[i+1] {
|
||||
case 'n': // \\n -> \n
|
||||
data = append(data, '\n')
|
||||
i++
|
||||
case 't': // \\t -> \n
|
||||
data = append(data, '\t')
|
||||
i++
|
||||
case '\\': // \\\ -> ?
|
||||
data = append(data, '\\')
|
||||
i++
|
||||
}
|
||||
}
|
||||
lines[i] = string(data)
|
||||
}
|
||||
return strings.Join(lines, "")
|
||||
}
|
||||
|
||||
func encodePoString(text string) string {
|
||||
var buf bytes.Buffer
|
||||
lines := strings.Split(text, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if lines[i] == "" {
|
||||
if i != len(lines)-1 {
|
||||
buf.WriteString(`"\n"` + "\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
buf.WriteRune('"')
|
||||
for _, r := range lines[i] {
|
||||
switch r {
|
||||
case '\\':
|
||||
buf.WriteString(`\\`)
|
||||
case '"':
|
||||
buf.WriteString(`\"`)
|
||||
case '\n':
|
||||
buf.WriteString(`\n`)
|
||||
case '\t':
|
||||
buf.WriteString(`\t`)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
buf.WriteString(`\n"` + "\n")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func encodeCommentPoString(text string) string {
|
||||
var buf bytes.Buffer
|
||||
lines := strings.Split(text, "\n")
|
||||
if len(lines) > 1 {
|
||||
buf.WriteString(`""` + "\n")
|
||||
}
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if len(lines) > 0 {
|
||||
buf.WriteString("#| ")
|
||||
}
|
||||
buf.WriteRune('"')
|
||||
for _, r := range lines[i] {
|
||||
switch r {
|
||||
case '\\':
|
||||
buf.WriteString(`\\`)
|
||||
case '"':
|
||||
buf.WriteString(`\"`)
|
||||
case '\n':
|
||||
buf.WriteString(`\n`)
|
||||
case '\t':
|
||||
buf.WriteString(`\t`)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if i < len(lines)-1 {
|
||||
buf.WriteString(`\n"` + "\n")
|
||||
} else {
|
||||
buf.WriteString(`"`)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package po
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodePoString(t *testing.T) {
|
||||
if s := decodePoString(poStrEncode); s != poStrDecode {
|
||||
t.Fatalf(`expect = %s got = %s`, poStrDecode, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodePoString(t *testing.T) {
|
||||
if s := encodePoString(poStrDecode); s != poStrEncodeStd {
|
||||
t.Fatalf(`expect = %s; got = %s`, poStrEncodeStd, s)
|
||||
}
|
||||
}
|
||||
|
||||
const poStrEncode = `# noise
|
||||
123456789
|
||||
"Project-Id-Version: Poedit 1.5\n"
|
||||
"Report-Msgid-Bugs-To: poedit@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2012-07-30 10:34+0200\n"
|
||||
"PO-Revision-Date: 2013-02-24 21:00+0800\n"
|
||||
"Last-Translator: Christopher Meng <trans@cicku.me>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.5.5\n"
|
||||
"TestPoString: abc"
|
||||
"123\n"
|
||||
>>
|
||||
123456???
|
||||
`
|
||||
|
||||
const poStrEncodeStd = `"Project-Id-Version: Poedit 1.5\n"
|
||||
"Report-Msgid-Bugs-To: poedit@googlegroups.com\n"
|
||||
"POT-Creation-Date: 2012-07-30 10:34+0200\n"
|
||||
"PO-Revision-Date: 2013-02-24 21:00+0800\n"
|
||||
"Last-Translator: Christopher Meng <trans@cicku.me>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.5.5\n"
|
||||
"TestPoString: abc123\n"
|
||||
`
|
||||
|
||||
const poStrDecode = `Project-Id-Version: Poedit 1.5
|
||||
Report-Msgid-Bugs-To: poedit@googlegroups.com
|
||||
POT-Creation-Date: 2012-07-30 10:34+0200
|
||||
PO-Revision-Date: 2013-02-24 21:00+0800
|
||||
Last-Translator: Christopher Meng <trans@cicku.me>
|
||||
Language-Team:
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Plural-Forms: nplurals=1; plural=0;
|
||||
X-Generator: Poedit 1.5.5
|
||||
TestPoString: abc123
|
||||
`
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testDataDir = "../testdata/"
|
||||
|
||||
var testPoMoFiles = []struct {
|
||||
poFile string
|
||||
moFile string
|
||||
}{
|
||||
{"gettext-3-1.po", "gettext-3-1.mo"},
|
||||
{"gettext-4.po", "gettext-4.mo"},
|
||||
{"gettext-5.po", "gettext-5.mo"},
|
||||
{"gettext-6-1.po", "gettext-6-1.mo"},
|
||||
{"gettext-6-2.po", "gettext-6-2.mo"},
|
||||
{"gettext-7.po", "gettext-7.mo"},
|
||||
{"gettextpo-1.de.po", "gettextpo-1.de.mo"},
|
||||
{"mm-ko-comp.euc-kr.po", "mm-ko-comp.euc-kr.mo"},
|
||||
{"mm-ko.euc-kr.po", "mm-ko.euc-kr.mo"},
|
||||
{"mm-viet.comp.po", "mm-viet.comp.mo"},
|
||||
{"poedit-1.5.7-zh_CN.po", "poedit-1.5.7-zh_CN.mo"},
|
||||
{"qttest2_de.po", "qttest2_de.mo"},
|
||||
{"qttest_pl.po", "qttest_pl.mo"},
|
||||
{"test.po", "test.mo"},
|
||||
}
|
||||
|
||||
func TestPoMoFiles(t *testing.T) {
|
||||
for i := 0; i < len(testPoMoFiles); i++ {
|
||||
poName := testPoMoFiles[i].poFile
|
||||
moName := testPoMoFiles[i].moFile
|
||||
po, err := newPoTranslator(testDataDir+poName, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", poName, err)
|
||||
}
|
||||
mo, err := newMoTranslator(testDataDir+moName, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", poName, err)
|
||||
}
|
||||
// if no translate, the mo will drop the message.
|
||||
// so len(mo) may less than len(po).
|
||||
if a, b := len(po.MessageMap), len(mo.MessageMap); a != b {
|
||||
t.Logf("%s: %v, %d != %d", poName, "size not equal", a, b)
|
||||
}
|
||||
for k, v0 := range po.MessageMap {
|
||||
v1, ok := mo.MessageMap[k]
|
||||
if !ok {
|
||||
t.Logf("%s: %q: missing", poName, v0.MsgId)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&v0, &v1) {
|
||||
t.Fatalf("%s: %q: expect = %v, got = %v", poName, v0.MsgId, v0, v1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"github.com/chai2010/gettext-go/gettext/mo"
|
||||
"github.com/chai2010/gettext-go/gettext/plural"
|
||||
"github.com/chai2010/gettext-go/gettext/po"
|
||||
)
|
||||
|
||||
var nilTranslator = &translator{
|
||||
MessageMap: make(map[string]mo.Message),
|
||||
PluralFormula: plural.Formula("??"),
|
||||
}
|
||||
|
||||
type translator struct {
|
||||
MessageMap map[string]mo.Message
|
||||
PluralFormula func(n int) int
|
||||
}
|
||||
|
||||
func newMoTranslator(name string, data []byte) (*translator, error) {
|
||||
var (
|
||||
f *mo.File
|
||||
err error
|
||||
)
|
||||
if len(data) != 0 {
|
||||
f, err = mo.LoadData(data)
|
||||
} else {
|
||||
f, err = mo.Load(name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tr = &translator{
|
||||
MessageMap: make(map[string]mo.Message),
|
||||
}
|
||||
for _, v := range f.Messages {
|
||||
tr.MessageMap[tr.makeMapKey(v.MsgContext, v.MsgId)] = v
|
||||
}
|
||||
if lang := f.MimeHeader.Language; lang != "" {
|
||||
tr.PluralFormula = plural.Formula(lang)
|
||||
} else {
|
||||
tr.PluralFormula = plural.Formula("??")
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func newPoTranslator(name string, data []byte) (*translator, error) {
|
||||
var (
|
||||
f *po.File
|
||||
err error
|
||||
)
|
||||
if len(data) != 0 {
|
||||
f, err = po.LoadData(data)
|
||||
} else {
|
||||
f, err = po.Load(name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tr = &translator{
|
||||
MessageMap: make(map[string]mo.Message),
|
||||
}
|
||||
for _, v := range f.Messages {
|
||||
tr.MessageMap[tr.makeMapKey(v.MsgContext, v.MsgId)] = mo.Message{
|
||||
MsgContext: v.MsgContext,
|
||||
MsgId: v.MsgId,
|
||||
MsgIdPlural: v.MsgIdPlural,
|
||||
MsgStr: v.MsgStr,
|
||||
MsgStrPlural: v.MsgStrPlural,
|
||||
}
|
||||
}
|
||||
if lang := f.MimeHeader.Language; lang != "" {
|
||||
tr.PluralFormula = plural.Formula(lang)
|
||||
} else {
|
||||
tr.PluralFormula = plural.Formula("??")
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func (p *translator) PGettext(msgctxt, msgid string) string {
|
||||
return p.PNGettext(msgctxt, msgid, "", 0)
|
||||
}
|
||||
|
||||
func (p *translator) PNGettext(msgctxt, msgid, msgidPlural string, n int) string {
|
||||
n = p.PluralFormula(n)
|
||||
if ss := p.findMsgStrPlural(msgctxt, msgid, msgidPlural); len(ss) != 0 {
|
||||
if n >= len(ss) {
|
||||
n = len(ss) - 1
|
||||
}
|
||||
if ss[n] != "" {
|
||||
return ss[n]
|
||||
}
|
||||
}
|
||||
if msgidPlural != "" && n > 0 {
|
||||
return msgidPlural
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
|
||||
func (p *translator) findMsgStrPlural(msgctxt, msgid, msgidPlural string) []string {
|
||||
key := p.makeMapKey(msgctxt, msgid)
|
||||
if v, ok := p.MessageMap[key]; ok {
|
||||
if len(v.MsgIdPlural) != 0 {
|
||||
if len(v.MsgStrPlural) != 0 {
|
||||
return v.MsgStrPlural
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if len(v.MsgStr) != 0 {
|
||||
return []string{v.MsgStr}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *translator) makeMapKey(msgctxt, msgid string) string {
|
||||
if msgctxt != "" {
|
||||
return msgctxt + mo.EotSeparator + msgid
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gettext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/chai2010/gettext-go/gettext/mo"
|
||||
"github.com/chai2010/gettext-go/gettext/po"
|
||||
)
|
||||
|
||||
func TestTranslator_Po(t *testing.T) {
|
||||
tr, err := newPoTranslator("test", []byte(testTrPoData))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, v := range testTrData {
|
||||
if out := tr.PGettext(v.msgctxt, v.msgid); out != v.msgstr {
|
||||
t.Fatalf("%s/%s: expect = %s, got = %s", v.msgctxt, v.msgid, v.msgstr, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslator_Mo(t *testing.T) {
|
||||
tr, err := newMoTranslator("test", poToMoData(t, []byte(testTrPoData)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, v := range testTrData {
|
||||
if out := tr.PGettext(v.msgctxt, v.msgid); out != v.msgstr {
|
||||
t.Fatalf("%s/%s: expect = %s, got = %s", v.msgctxt, v.msgid, v.msgstr, out)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func poToMoData(t *testing.T, data []byte) []byte {
|
||||
poFile, err := po.LoadData(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
moFile := &mo.File{
|
||||
MimeHeader: mo.Header{
|
||||
ProjectIdVersion: poFile.MimeHeader.ProjectIdVersion,
|
||||
ReportMsgidBugsTo: poFile.MimeHeader.ReportMsgidBugsTo,
|
||||
POTCreationDate: poFile.MimeHeader.POTCreationDate,
|
||||
PORevisionDate: poFile.MimeHeader.PORevisionDate,
|
||||
LastTranslator: poFile.MimeHeader.LastTranslator,
|
||||
LanguageTeam: poFile.MimeHeader.LanguageTeam,
|
||||
Language: poFile.MimeHeader.Language,
|
||||
MimeVersion: poFile.MimeHeader.MimeVersion,
|
||||
ContentType: poFile.MimeHeader.ContentType,
|
||||
ContentTransferEncoding: poFile.MimeHeader.ContentTransferEncoding,
|
||||
PluralForms: poFile.MimeHeader.PluralForms,
|
||||
XGenerator: poFile.MimeHeader.XGenerator,
|
||||
UnknowFields: poFile.MimeHeader.UnknowFields,
|
||||
},
|
||||
}
|
||||
for _, v := range poFile.Messages {
|
||||
moFile.Messages = append(moFile.Messages, mo.Message{
|
||||
MsgContext: v.MsgContext,
|
||||
MsgId: v.MsgId,
|
||||
MsgIdPlural: v.MsgIdPlural,
|
||||
MsgStr: v.MsgStr,
|
||||
MsgStrPlural: v.MsgStrPlural,
|
||||
})
|
||||
}
|
||||
return moFile.Data()
|
||||
}
|
||||
|
||||
var testTrData = []struct {
|
||||
msgctxt string
|
||||
msgid string
|
||||
msgstr string
|
||||
}{
|
||||
{"main.init", "Gettext in init.", "Init函数中的Gettext."},
|
||||
{"main.main", "Hello, world!", "你好, 世界!"},
|
||||
{"main.func", "Gettext in func.", "闭包函数中的Gettext."},
|
||||
{"code.google.com/p/gettext-go/examples/hi.SayHi", "pkg hi: Hello, world!", "来自\"Hi\"包的问候: 你好, 世界!"},
|
||||
}
|
||||
|
||||
var testTrPoData = `
|
||||
msgctxt "main.init"
|
||||
msgid "Gettext in init."
|
||||
msgstr "Init函数中的Gettext."
|
||||
|
||||
msgctxt "main.main"
|
||||
msgid "Hello, world!"
|
||||
msgstr "你好, 世界!"
|
||||
|
||||
msgctxt "main.func"
|
||||
msgid "Gettext in func."
|
||||
msgstr "闭包函数中的Gettext."
|
||||
|
||||
msgctxt "code.google.com/p/gettext-go/examples/hi.SayHi"
|
||||
msgid "pkg hi: Hello, world!"
|
||||
msgstr "来自\"Hi\"包的问候: 你好, 世界!"
|
||||
`
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Copyright 2013 ChaiShushan <chaishushan{AT}gmail.com>. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
PO_FILES = $(wildcard *.po)
|
||||
MO_FILES = $(patsubst %.po,%.mo,$(PO_FILES))
|
||||
|
||||
default: $(MO_FILES)
|
||||
|
||||
clean:
|
||||
rm *.mo
|
||||
|
||||
%.mo: %.po
|
||||
msgfmt -o $@ $<
|
||||
|
|
@ -0,0 +1 @@
|
|||
xg-c-1.ok.po has a bad header, msgfmt can't compile it.
|
||||
Binary file not shown.
|
|
@ -0,0 +1,13 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=US-ASCII\n"
|
||||
"Content-Transfer-Encoding: 7-bit\n"
|
||||
|
||||
#: tst-gettext2.c:33
|
||||
msgid "First string for testing."
|
||||
msgstr "Lang1: 1st string"
|
||||
|
||||
#: tst-gettext2.c:34
|
||||
msgid "Another string for testing."
|
||||
msgstr "Lang1: 2nd string"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,13 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=US-ASCII\n"
|
||||
"Content-Transfer-Encoding: 7-bit\n"
|
||||
|
||||
#: tst-gettext2.c:33
|
||||
msgid "First string for testing."
|
||||
msgstr "Lang2: 1st string"
|
||||
|
||||
#: tst-gettext2.c:34
|
||||
msgid "Another string for testing."
|
||||
msgstr "Lang2: 2nd string"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-1\n"
|
||||
"Content-Transfer-Encoding: 8-bit\n"
|
||||
|
||||
msgid "cheese"
|
||||
msgstr "Käse"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-1\n"
|
||||
"Content-Transfer-Encoding: 8-bit\n"
|
||||
|
||||
msgid "cheese"
|
||||
msgstr "Käse"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-1\n"
|
||||
"Content-Transfer-Encoding: 8-bit\n"
|
||||
|
||||
msgid "beauty"
|
||||
msgstr "Schönheit"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-1\n"
|
||||
"Content-Transfer-Encoding: 8-bit\n"
|
||||
|
||||
msgid "beauty"
|
||||
msgstr "beauté"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=ISO-8859-1\n"
|
||||
"Content-Transfer-Encoding: 8-bit\n"
|
||||
|
||||
msgid "cheese"
|
||||
msgstr "Käse"
|
||||
Binary file not shown.
|
|
@ -0,0 +1,43 @@
|
|||
# Test case for the libgettextpo library.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: libgettextpo 0.18.1\n"
|
||||
"Report-Msgid-Bugs-To: bug-gnu-gettext@gnu.org\n"
|
||||
"POT-Creation-Date: 2010-06-04 01:57+0200\n"
|
||||
"PO-Revision-Date: 2010-06-05 14:39+0200\n"
|
||||
"Last-Translator: Bruno Haible <bruno@clisp.org>\n"
|
||||
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: gnulib-lib/w32spawn.h:81
|
||||
#, fuzzy, c-format
|
||||
msgid "cannot restore fd %d: dup2 failed"
|
||||
msgstr "Ausgabedatei »%s« kann nicht erstellt werden"
|
||||
|
||||
#: gnulib-lib/wait-process.c:223 gnulib-lib/wait-process.c:255
|
||||
#: gnulib-lib/wait-process.c:317
|
||||
#, c-format
|
||||
msgid "%s subprocess"
|
||||
msgstr "Subprozeß %s"
|
||||
|
||||
# Adjektiv, kein ganzer Satz!
|
||||
#. Denote a lock's state
|
||||
msgctxt "Lock state"
|
||||
msgid "Open"
|
||||
msgstr "Geöffnet"
|
||||
|
||||
# Französische Weine sind die besten der Welt.
|
||||
#, java-format
|
||||
msgid "a bottle of wine"
|
||||
msgid_plural "{0,number} bottles of wine"
|
||||
msgstr[0] "eine Flasche Wein"
|
||||
msgstr[1] "{0,number} Weinflaschen"
|
||||
|
||||
#. Denote a lock's state
|
||||
#~ msgctxt "Lock state"
|
||||
#~ msgid "Closed"
|
||||
#~ msgstr "Geschlossen"
|
||||
BIN
vendor/github.com/chai2010/gettext-go/testdata/mm-ko-comp.euc-kr.mo
generated
vendored
Normal file
BIN
vendor/github.com/chai2010/gettext-go/testdata/mm-ko-comp.euc-kr.mo
generated
vendored
Normal file
Binary file not shown.
1633
vendor/github.com/chai2010/gettext-go/testdata/mm-ko-comp.euc-kr.po
generated
vendored
Normal file
1633
vendor/github.com/chai2010/gettext-go/testdata/mm-ko-comp.euc-kr.po
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
vendor/github.com/chai2010/gettext-go/testdata/poedit-1.5.7-zh_CN.mo
generated
vendored
Normal file
BIN
vendor/github.com/chai2010/gettext-go/testdata/poedit-1.5.7-zh_CN.mo
generated
vendored
Normal file
Binary file not shown.
1591
vendor/github.com/chai2010/gettext-go/testdata/poedit-1.5.7-zh_CN.po
generated
vendored
Normal file
1591
vendor/github.com/chai2010/gettext-go/testdata/poedit-1.5.7-zh_CN.po
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -0,0 +1,36 @@
|
|||
# German translations for hello-cplusplus-qt package.
|
||||
# Copyright (C) 2005 Yoyodyne, Inc.
|
||||
# This file is distributed under the same license as the hello-cplusplus-qt package.
|
||||
# Bruno Haible <bruno@clisp.org>, 2005.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hello-cplusplus-qt 0\n"
|
||||
"Report-Msgid-Bugs-To: bug-gnu-gettext@gnu.org\n"
|
||||
"POT-Creation-Date: 2003-10-20 10:14+0200\n"
|
||||
"PO-Revision-Date: 2003-10-20 10:13+0200\n"
|
||||
"Last-Translator: Bruno Haible <bruno@clisp.org>\n"
|
||||
"Language-Team: German <de@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: main.cc:17
|
||||
msgctxt "Menu"
|
||||
msgid "File"
|
||||
msgstr "Datei"
|
||||
|
||||
#: main.cc:19
|
||||
msgctxt "Menu"
|
||||
msgid "Edit"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: main.cc:21
|
||||
msgctxt "Menu"
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: data.cc:45
|
||||
msgctxt "Database"
|
||||
msgid "File"
|
||||
msgstr "Archiv"
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue