diff --git a/.github/README.zh-Hans.md b/.github/README.zh-Hans.md new file mode 100644 index 00000000..c80966a6 --- /dev/null +++ b/.github/README.zh-Hans.md @@ -0,0 +1,145 @@ +# go-i18n ![Build status](https://github.com/nicksnyder/go-i18n/workflows/Build/badge.svg) [![Report card](https://goreportcard.com/badge/github.com/nicksnyder/go-i18n)](https://goreportcard.com/report/github.com/nicksnyder/go-i18n) [![codecov](https://codecov.io/gh/nicksnyder/go-i18n/branch/master/graph/badge.svg)](https://codecov.io/gh/nicksnyder/go-i18n) [![Sourcegraph](https://sourcegraph.com/github.com/nicksnyder/go-i18n/-/badge.svg)](https://sourcegraph.com/github.com/nicksnyder/go-i18n?badge) + +go-i18n 是一个帮助您将 Go 程序翻译成多种语言的 Go [包](#package-i18n) 和 [命令](#command-goi18n)。 + +- 支持 [Unicode Common Locale Data Repository (CLDR)](https://www.unicode.org/cldr/charts/28/supplemental/language_plural_rules.html) 中所有 200 多种语言的 [复数字符](http://cldr.unicode.org/index/cldr-spec/plural-rules)。 + - 代码和测试是从 [CLDR 数据](http://cldr.unicode.org/index/downloads) 中 [自动生成](https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural/codegen) 的。 +- 使用 [text/template](http://golang.org/pkg/text/template/) 语法支持带有命名变量的字符串。 +- 支持任何格式的消息文件(例如:JSON、TOML、YAML)。 + + + + +[**English**](../README.md) · [**简体中文**](README.zh-Hans.md) + + + + +## Package i18n + +[![GoDoc](https://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n) + +i18n 包支持根据一组语言环境首选项查找消息。 + +```go +import "github.com/nicksnyder/go-i18n/v2/i18n" +``` + +创建一个 Bundle 以在应用程序的整个生命周期中使用。 + +```go +bundle := i18n.NewBundle(language.English) +``` + +在初始化期间将翻译加载到您的 bundle 中。 + +```go +bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) +bundle.LoadMessageFile("es.toml") +``` + +```go +// 如果使用 go:embed +//go:embed locale.*.toml +var LocaleFS embed.FS + +bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) +bundle.LoadMessageFileFS(LocaleFS, "locale.es.toml") +``` + +创建一个 Localizer 以用于一组语言首选项。 + +```go +func(w http.ResponseWriter, r *http.Request) { + lang := r.FormValue("lang") + accept := r.Header.Get("Accept-Language") + localizer := i18n.NewLocalizer(bundle, lang, accept) +} +``` + +使用 Localizer 查找消息。 + +```go +localizer.Localize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "PersonCats", + One: "{{.Name}} has {{.Count}} cat.", + Other: "{{.Name}} has {{.Count}} cats.", + }, + TemplateData: map[string]interface{}{ + "Name": "Nick", + "Count": 2, + }, + PluralCount: 2, +}) // Nick has 2 cats. +``` + +## goi18n 命令 + +[![GoDoc](https://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](https://godoc.org/github.com/nicksnyder/go-i18n/v2/goi18n) + +goi18n 命令管理 i18n 包使用的消息文件。 + +``` +go install -v github.com/nicksnyder/go-i18n/v2/goi18n@latest +goi18n -help +``` + +### 提取消息 + +使用 `goi18n extract` 将 Go 源文件中的所有 i18n.Message 结构文字提取到消息文件中以进行翻译。 + +```toml +# active.en.toml +[PersonCats] +description = "The number of cats a person has" +one = "{{.Name}} has {{.Count}} cat." +other = "{{.Name}} has {{.Count}} cats." +``` + +### 翻译一种新语言 + +1. 为您要添加的语言创建一个空消息文件(例如:`translate.es.toml`)。 +2. 运行 `goi18n merge active.en.toml translate.es.toml` 以填充 `translate.es.toml` 要翻译的消息。 + + ```toml + # translate.es.toml + [HelloPerson] + hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5" + other = "Hello {{.Name}}" + ``` + +3. 翻译完成 `translate.es.toml` 后,将其重命名为 `active.es.toml``。 + + ```toml + # active.es.toml + [HelloPerson] + hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5" + other = "Hola {{.Name}}" + ``` + +4. 加载 `active.es.toml` 到您的 bundle 中。 + + ```go + bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + bundle.LoadMessageFile("active.es.toml") + ``` + +### 翻译新消息 + +如果您在程序中添加了新消息: + +1. 运行 `goi18n extract` 以使用新消息更新 `active.en.toml`。 +2. 运行 `goi18n merge active.*.toml` 以生成更新的 `translate.*.toml` 文件。 +3. 翻译 `translate.*.toml` 文件中的所有消息。 +4. 运行 `goi18n merge active.*.toml translate.*.toml` 将翻译后的消息合并到 active 消息文件中。 + +## 有关更多信息和示例: + +- 阅读 [文档](https://godoc.org/github.com/nicksnyder/go-i18n/v2)。 +- 查看 [代码示例](https://github.com/nicksnyder/go-i18n/blob/main/v2/i18n/example_test.go) 和 [测试](https://github.com/nicksnyder/go-i18n/blob/main/v2/i18n/localizer_test.go)。 +- 查看一个示例 [程序](https://github.com/nicksnyder/go-i18n/tree/main/v2/example)。 + +## 许可证 + +go-i18n 在 MIT 许可下可用。有关更多信息,请参阅 [许可证](LICENSE) 文件。 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3d1e925..735e02d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,11 +9,11 @@ jobs: if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request' steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.15.2 + go-version: stable - name: Git checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build uses: goreleaser/goreleaser-action@v2 with: @@ -25,23 +25,24 @@ jobs: run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... - name: Upload coverage uses: codecov/codecov-action@v1 - build_1_9_7: - name: Build with Go 1.9.7 + build_1_12: + name: Build with Go 1.12.17 runs-on: ubuntu-latest if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request' steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.9.7 + go-version: '1.12.17' - name: Git checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: gopath/src/github.com/nicksnyder/go-i18n - name: Build and test working-directory: gopath/src/github.com/nicksnyder/go-i18n/v2 env: GOPATH: ${{ github.workspace }}/gopath + GO111MODULE: on run: | - go get -t ./... + go get ./... go test -race ./... diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index a4da7048..3148e4d8 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -18,7 +18,7 @@ jobs: name: Set up Go uses: actions/setup-go@v2 with: - go-version: '^1.15.2' + go-version: '^1.19.3' - name: Release uses: goreleaser/goreleaser-action@v2 diff --git a/.github/workflows/lsif-go.yml b/.github/workflows/lsif-go.yml index 6ab74e00..7e56f795 100644 --- a/.github/workflows/lsif-go.yml +++ b/.github/workflows/lsif-go.yml @@ -10,5 +10,7 @@ jobs: - uses: actions/checkout@v1 - name: Generate LSIF data run: lsif-go + working-directory: v2 - name: Upload LSIF data to Sourcegraph.com run: src lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} -ignore-upload-failure + working-directory: v2 diff --git a/README.md b/README.md index a13f268b..c703817a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,14 @@ go-i18n is a Go [package](#package-i18n) and a [command](#command-goi18n) that h - Supports strings with named variables using [text/template](http://golang.org/pkg/text/template/) syntax. - Supports message files of any format (e.g. JSON, TOML, YAML). + + + +[**English**](README.md) · [**简体中文**](.github/README.zh-Hans.md) + + + + ## Package i18n [![GoDoc](https://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n) @@ -29,6 +37,15 @@ bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.LoadMessageFile("es.toml") ``` +```go +// If use go:embed +//go:embed locale.*.toml +var LocaleFS embed.FS + +bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) +bundle.LoadMessageFileFS(LocaleFS, "locale.es.toml") +``` + Create a Localizer to use for a set of language preferences. ```go @@ -62,7 +79,7 @@ localizer.Localize(&i18n.LocalizeConfig{ The goi18n command manages message files used by the i18n package. ``` -go get -u github.com/nicksnyder/go-i18n/v2/goi18n +go install -v github.com/nicksnyder/go-i18n/v2/goi18n@latest goi18n -help ``` diff --git a/v2/i18n/bundle.go b/v2/i18n/bundle.go index 7d36d9e2..308430fa 100644 --- a/v2/i18n/bundle.go +++ b/v2/i18n/bundle.go @@ -108,7 +108,7 @@ func (b *Bundle) LoadMessageFile(path string) (*MessageFile, error) { return b.ParseMessageFileBytes(buf, path) } -// MustLoadMessageFile is similar to LoadTranslationFile +// MustLoadMessageFile is similar to LoadMessageFile // except it panics if an error happens. func (b *Bundle) MustLoadMessageFile(path string) { if _, err := b.LoadMessageFile(path); err != nil { diff --git a/v2/i18n/localizer.go b/v2/i18n/localizer.go index 59a14652..4415c7d7 100644 --- a/v2/i18n/localizer.go +++ b/v2/i18n/localizer.go @@ -9,6 +9,15 @@ import ( ) // Localizer provides Localize and MustLocalize methods that return localized messages. +// Localize and MustLocalize methods use a language.Tag matching algorithm based +// on the best possible value. This algorithm may cause an unexpected language.Tag returned +// value depending on the order of the tags stored in memory. For example, if the bundle +// used to create a Localizer instance ingested locales following this order +// ["en-US", "en-GB", "en-IE", "en"] and the locale "en" is asked, the underlying matching +// algorithm will return "en-US" thinking it is the best match possible. More information +// about the algorithm in this Github issue: https://github.com/golang/go/issues/49176. +// There is additionnal informations inside the Go code base: +// https://github.com/golang/text/blob/master/language/match.go#L142 type Localizer struct { // bundle contains the messages that can be returned by the Localizer. bundle *Bundle diff --git a/v2/internal/template_test.go b/v2/internal/template_test.go index 2f5d991b..4f569b9c 100644 --- a/v2/internal/template_test.go +++ b/v2/internal/template_test.go @@ -1,6 +1,7 @@ package internal import ( + "strings" "testing" "text/template" ) @@ -45,7 +46,7 @@ func TestExecute(t *testing.T) { template: &Template{ Src: "hello {{", }, - err: "template: :1: unexpected unclosed action in command", + err: "unclosed action", noallocs: true, }, } @@ -53,8 +54,8 @@ func TestExecute(t *testing.T) { for _, test := range tests { t.Run(test.template.Src, func(t *testing.T) { result, err := test.template.Execute(test.funcs, test.data) - if actual := str(err); actual != test.err { - t.Errorf("expected err %q; got %q", test.err, actual) + if actual := str(err); !strings.Contains(str(err), test.err) { + t.Errorf("expected err %q to contain %q", actual, test.err) } if result != test.result { t.Errorf("expected result %q; got %q", test.result, result)