pax_global_header00006660000000000000000000000064135054120060014506gustar00rootroot0000000000000052 comment=c4faa710783ead174fd0dd6d956fd58e9cddc62c golang-github-nicksnyder-go-i18n.v2-2.0.2/000077500000000000000000000000001350541200600201135ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/.codecov.yml000066400000000000000000000002121350541200600223310ustar00rootroot00000000000000coverage: range: 50..100 status: patch: default: threshold: 50% project: default: threshold: 0.1% golang-github-nicksnyder-go-i18n.v2-2.0.2/.gitignore000066400000000000000000000001251350541200600221010ustar00rootroot00000000000000*.a _* output/ .DS_Store *.test *.swp v2/example/example v2/goi18n/goi18n v2/dist/ golang-github-nicksnyder-go-i18n.v2-2.0.2/.golangci.yml000066400000000000000000000002121350541200600224720ustar00rootroot00000000000000# https://github.com/golangci/golangci/wiki/Configuration # service: # prepare: # - go get -t ./... linters: enable: - gofmt golang-github-nicksnyder-go-i18n.v2-2.0.2/.travis.yml000066400000000000000000000006761350541200600222350ustar00rootroot00000000000000language: go matrix: include: - go: 1.9.x env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on COVER='-coverprofile=coverage.txt -covermode=atomic' script: - cd v2; go test -race $COVER ./... after_success: - bash <(curl -s https://codecov.io/bash) deploy: - provider: script skip_cleanup: true script: cd v2; curl -sL https://git.io/goreleaser | bash on: tags: true condition: $TRAVIS_OS_NAME = linux golang-github-nicksnyder-go-i18n.v2-2.0.2/CHANGELOG.md000066400000000000000000000045441350541200600217330ustar00rootroot00000000000000# Changelog Major version changes are documented in the changelog. To see the documentation for minor or patch version, [view the release notes](https://github.com/nicksnyder/go-i18n/releases). ## v2 ### Motivation The first commit to this project was January 2012 (go1 had not yet been released) and v1.0.0 was tagged June 2015 (go1.4). This project has evolved with the Go ecosystem since then in a backwards compatible way, but there is a growing list of issues and warts that cannot be addressed without breaking compatiblity. v2 is rewrite of the API from first principals to make it more idiomatic Go, and to resolve a backlog of issues: https://github.com/nicksnyder/go-i18n/milestone/1 ### Improvements * Use `golang.org/x/text/language` to get standardized behavior for language matching (https://github.com/nicksnyder/go-i18n/issues/30, https://github.com/nicksnyder/go-i18n/issues/44, https://github.com/nicksnyder/go-i18n/issues/76) * Remove global state so that the race detector does not complain when downstream projects run tests that depend on go-i18n in parallel (https://github.com/nicksnyder/go-i18n/issues/82) * Automatically extract messages from Go source code (https://github.com/nicksnyder/go-i18n/issues/64) * Provide clearer documentation and examples (https://github.com/nicksnyder/go-i18n/issues/27) * Reduce complexity of file format for simple translations (https://github.com/nicksnyder/go-i18n/issues/85) * Support descriptions for messages (https://github.com/nicksnyder/go-i18n/issues/8) * Support custom template delimiters (https://github.com/nicksnyder/go-i18n/issues/88) ### Upgrading from v1 The i18n package in v2 is completely different than v1. Refer to the [documentation](https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n) and [README](https://github.com/nicksnyder/go-i18n/blob/master/README.md) for guidance. The goi18n command has similarities and differences: * `goi18n merge` has a new implementation but accomplishes the same task. * `goi18n extract` extracts messages from Go source files. * `goi18n constants` no longer exists. Prefer to extract messages directly from Go source files. v2 makes changes to the canonical message file format, but you can use v1 message files with v2. Message files will be converted to the new format the first time they are processed by the new `goi18n merge` command. v2 requires Go 1.9 or newer. golang-github-nicksnyder-go-i18n.v2-2.0.2/LICENSE000066400000000000000000000020751350541200600211240ustar00rootroot00000000000000Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-nicksnyder-go-i18n.v2-2.0.2/README.md000066400000000000000000000113211350541200600213700ustar00rootroot00000000000000# go-i18n [![Build Status](https://travis-ci.org/nicksnyder/go-i18n.svg?branch=master)](http://travis-ci.org/nicksnyder/go-i18n) [![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 is a Go [package](#package-i18n) and a [command](#command-goi18n) that helps you translate Go programs into multiple languages. - Supports [pluralized strings](http://cldr.unicode.org/index/cldr-spec/plural-rules) for all 200+ languages in the [Unicode Common Locale Data Repository (CLDR)](http://www.unicode.org/cldr/charts/28/supplemental/language_plural_rules.html). - Code and tests are [automatically generated](https://github.com/nicksnyder/go-i18n/tree/master/i18n/language/codegen) from [CLDR data](http://cldr.unicode.org/index/downloads). - 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). ## Package i18n [![GoDoc](http://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](http://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n) The i18n package provides support for looking up messages according to a set of locale preferences. ```go import "github.com/nicksnyder/go-i18n/v2/i18n" ``` Create a Bundle to use for the lifetime of your application. ```go bundle := i18n.NewBundle(language.English) ``` Load translations into your bundle during initialization. ```go bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.LoadMessageFile("es.toml") ``` Create a Localizer to use for a set of language preferences. ```go func(w http.ResponseWriter, r *http.Request) { lang := r.FormValue("lang") accept := r.Header.Get("Accept-Language") localizer := i18n.NewLocalizer(bundle, lang, accept) } ``` Use the Localizer to lookup messages. ```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. ``` ## Command goi18n [![GoDoc](http://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](http://godoc.org/github.com/nicksnyder/go-i18n/v2/goi18n) The goi18n command manages message files used by the i18n package. ``` go get -u github.com/nicksnyder/go-i18n/v2/goi18n goi18n -help ``` ### Extracting messages Use `goi18n extract` to extract all i18n.Message struct literals in Go source files to a message file for translation. ```toml # active.en.toml [PersonCats] description = "The number of cats a person has" one = "{{.Name}} has {{.Count}} cat." other = "{{.Name}} has {{.Count}} cats." ``` ### Translating a new language 1. Create an empty message file for the language that you want to add (e.g. `translate.es.toml`). 2. Run `goi18n merge active.en.toml translate.es.toml` to populate `translate.es.toml` with the mesages to be translated. ```toml # translate.es.toml [HelloPerson] hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5" other = "Hello {{.Name}}" ``` 3. After `translate.es.toml` has been translated, rename it to `active.es.toml`. ```toml # active.es.toml [HelloPerson] hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5" other = "Hola {{.Name}}" ``` 4. Load `active.es.toml` into your bundle. ```go bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.LoadMessageFile("active.es.toml") ``` ### Translating new messages If you have added new messages to your program: 1. Run `goi18n extract` to update `active.en.toml` with the new messages. 2. Run `goi18n merge active.*.toml` to generate updated `translate.*.toml` files. 3. Translate all the messages in the `translate.*.toml` files. 4. Run `goi18n merge active.*.toml translate.*.toml` to merge the translated messages into the active message files. ## For more information and examples: - Read the [documentation](http://godoc.org/github.com/nicksnyder/go-i18n/v2). - Look at the [code examples](https://github.com/nicksnyder/go-i18n/blob/master/v2/i18n/example_test.go) and [tests](https://github.com/nicksnyder/go-i18n/blob/master/i18n/v2/localizer_test.go). - Look at an example [application](https://github.com/nicksnyder/go-i18n/tree/master/v2/example). ## License go-i18n is available under the MIT license. See the [LICENSE](LICENSE) file for more info. golang-github-nicksnyder-go-i18n.v2-2.0.2/dev.md000066400000000000000000000006221350541200600212130ustar00rootroot00000000000000# Development notes ## How to upgrade CLDR data 1. Go to http://cldr.unicode.org/index/downloads to find the latest version. 1. Download the latest version of cldr-common (e.g. http://unicode.org/Public/cldr/33/cldr-common-33.0.zip) 1. Unzip and copy `v2/common/supplemental/plurals.xml` to `v2/i18n/internal/plural/codegen/plurals.xml` 1. Run `generate.sh` in `v2/i18n/internal/plural/codegen/` golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/000077500000000000000000000000001350541200600204425ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/.goreleaser.yml000066400000000000000000000005261350541200600233760ustar00rootroot00000000000000builds: - binary: goi18n main: ./goi18n/ goos: - windows - darwin - linux goarch: - amd64 env: - CGO_ENABLED=0 archives: - format: binary name_template: "{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" before: hooks: - go mod download checksum: name_template: "checksums.txt" golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/example/000077500000000000000000000000001350541200600220755ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/example/README.md000066400000000000000000000005271350541200600233600ustar00rootroot00000000000000# Example This directory contains an example project that uses go-i18n. ``` go run main.go ``` Then open http://localhost:8080 in your web browser. You can customize the template data and locale via query parameters like this: http://localhost:8080/?name=Nick&unreadEmailCount=2 http://localhost:8080/?name=Nick&unreadEmailCount=2&lang=es golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/example/active.en.toml000066400000000000000000000006121350541200600246450ustar00rootroot00000000000000HelloPerson = "Hello {{.Name}}" [MyUnreadEmails] description = "The number of unread emails I have" one = "I have {{.PluralCount}} unread email." other = "I have {{.PluralCount}} unread emails." [PersonUnreadEmails] description = "The number of unread emails a person has" one = "{{.Name}} has {{.UnreadEmailCount}} unread email." other = "{{.Name}} has {{.UnreadEmailCount}} unread emails." golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/example/active.es.toml000066400000000000000000000011731350541200600246550ustar00rootroot00000000000000[HelloPerson] hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5" other = "Hola {{.Name}}" [MyUnreadEmails] description = "The number of unread emails I have" hash = "sha1-6a65d17f53981a3657db1897630e9cb069053ea8" one = "Tengo {{.PluralCount}} correo electrónico sin leer" other = "Tengo {{.PluralCount}} correos electrónicos no leídos" [PersonUnreadEmails] description = "The number of unread emails a person has" hash = "sha1-3a672fa89c5c8564bb233c907638004983792464" one = "{{.Name}} tiene {{.UnreadEmailCount}} correo electrónico no leído" other = "{{.Name}} tiene {{.UnreadEmailCount}} correos electrónicos no leídos" golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/example/main.go000066400000000000000000000046061350541200600233560ustar00rootroot00000000000000// Command example runs a sample webserver that uses go-i18n/v2/i18n. package main import ( "fmt" "html/template" "log" "net/http" "strconv" "github.com/BurntSushi/toml" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" ) var page = template.Must(template.New("").Parse(`

{{.Title}}

{{range .Paragraphs}}

{{.}}

{{end}} `)) func main() { bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) // No need to load active.en.toml since we are providing default translations. // bundle.MustLoadMessageFile("active.en.toml") bundle.MustLoadMessageFile("active.es.toml") http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { lang := r.FormValue("lang") accept := r.Header.Get("Accept-Language") localizer := i18n.NewLocalizer(bundle, lang, accept) name := r.FormValue("name") if name == "" { name = "Bob" } unreadEmailCount, _ := strconv.ParseInt(r.FormValue("unreadEmailCount"), 10, 64) helloPerson := localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ ID: "HelloPerson", Other: "Hello {{.Name}}", }, TemplateData: map[string]string{ "Name": name, }, }) myUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ ID: "MyUnreadEmails", Description: "The number of unread emails I have", One: "I have {{.PluralCount}} unread email.", Other: "I have {{.PluralCount}} unread emails.", }, PluralCount: unreadEmailCount, }) personUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ ID: "PersonUnreadEmails", Description: "The number of unread emails a person has", One: "{{.Name}} has {{.UnreadEmailCount}} unread email.", Other: "{{.Name}} has {{.UnreadEmailCount}} unread emails.", }, PluralCount: unreadEmailCount, TemplateData: map[string]interface{}{ "Name": name, "UnreadEmailCount": unreadEmailCount, }, }) err := page.Execute(w, map[string]interface{}{ "Title": helloPerson, "Paragraphs": []string{ myUnreadEmails, personUnreadEmails, }, }) if err != nil { panic(err) } }) fmt.Println("Listening on http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/go.mod000066400000000000000000000006241350541200600215520ustar00rootroot00000000000000module github.com/nicksnyder/go-i18n/v2 require ( github.com/BurntSushi/toml v0.3.0 golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b // indirect golang.org/x/text v0.3.2 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c // indirect gopkg.in/yaml.v2 v2.2.1 ) golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/go.sum000066400000000000000000000040141350541200600215740ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38 h1:yr7ItWHARpqySNZjEh5mPMHrw3xPR9tMnomFZVcO1mQ= golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/000077500000000000000000000000001350541200600215475ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/active.en.toml000066400000000000000000000000001350541200600243060ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/common_test.go000066400000000000000000000002501350541200600244220ustar00rootroot00000000000000package main import "io/ioutil" func mustTempDir(prefix string) string { outdir, err := ioutil.TempDir("", prefix) if err != nil { panic(err) } return outdir } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/extract_command.go000066400000000000000000000131111350541200600252430ustar00rootroot00000000000000package main import ( "flag" "fmt" "go/ast" "go/parser" "go/token" "io/ioutil" "os" "path/filepath" "strings" "github.com/nicksnyder/go-i18n/v2/i18n" ) func usageExtract() { fmt.Fprintf(os.Stderr, `usage: goi18n extract [options] [paths] Extract walks the files and directories in paths and extracts all messages to a single file. If no files or paths are provided, it walks the current working directory. xx-yy.active.format This file contains messages that should be loaded at runtime. Flags: -sourceLanguage tag The language tag of the extracted messages (e.g. en, en-US, zh-Hant-CN). Default: en -outdir directory Write message files to this directory. Default: . -format format Output message files in this format. Supported formats: json, toml, yaml Default: toml `) } type extractCommand struct { paths []string sourceLanguage languageTag outdir string format string } func (ec *extractCommand) name() string { return "extract" } func (ec *extractCommand) parse(args []string) error { flags := flag.NewFlagSet("extract", flag.ExitOnError) flags.Usage = usageExtract flags.Var(&ec.sourceLanguage, "sourceLanguage", "en") flags.StringVar(&ec.outdir, "outdir", ".", "") flags.StringVar(&ec.format, "format", "toml", "") if err := flags.Parse(args); err != nil { return err } ec.paths = flags.Args() return nil } func (ec *extractCommand) execute() error { if len(ec.paths) == 0 { ec.paths = []string{"."} } messages := []*i18n.Message{} for _, path := range ec.paths { if err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } if filepath.Ext(path) != ".go" { return nil } // Don't extract from test files. if strings.HasSuffix(path, "_test.go") { return nil } buf, err := ioutil.ReadFile(path) if err != nil { return err } msgs, err := extractMessages(buf) if err != nil { return err } messages = append(messages, msgs...) return nil }); err != nil { return err } } messageTemplates := map[string]*i18n.MessageTemplate{} for _, m := range messages { if mt := i18n.NewMessageTemplate(m); mt != nil { messageTemplates[m.ID] = mt } } path, content, err := writeFile(ec.outdir, "active", ec.sourceLanguage.Tag(), ec.format, messageTemplates, true) if err != nil { return err } return ioutil.WriteFile(path, content, 0666) } // extractMessages extracts messages from the bytes of a Go source file. func extractMessages(buf []byte) ([]*i18n.Message, error) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "", buf, parser.AllErrors) if err != nil { return nil, err } extractor := newExtractor(file) ast.Walk(extractor, file) return extractor.messages, nil } func newExtractor(file *ast.File) *extractor { return &extractor{i18nPackageName: i18nPackageName(file)} } type extractor struct { i18nPackageName string messages []*i18n.Message } func (e *extractor) Visit(node ast.Node) ast.Visitor { e.extractMessages(node) return e } func (e *extractor) extractMessages(node ast.Node) { cl, ok := node.(*ast.CompositeLit) if !ok { return } switch t := cl.Type.(type) { case *ast.SelectorExpr: if !e.isMessageType(t) { return } e.extractMessage(cl) case *ast.ArrayType: if !e.isMessageType(t.Elt) { return } for _, el := range cl.Elts { ecl, ok := el.(*ast.CompositeLit) if !ok { continue } e.extractMessage(ecl) } case *ast.MapType: if !e.isMessageType(t.Value) { return } for _, el := range cl.Elts { kve, ok := el.(*ast.KeyValueExpr) if !ok { continue } vcl, ok := kve.Value.(*ast.CompositeLit) if !ok { continue } e.extractMessage(vcl) } } } func (e *extractor) isMessageType(expr ast.Expr) bool { se := unwrapSelectorExpr(expr) if se == nil { return false } if se.Sel.Name != "Message" && se.Sel.Name != "LocalizeConfig" { return false } x, ok := se.X.(*ast.Ident) if !ok { return false } return x.Name == e.i18nPackageName } func unwrapSelectorExpr(e ast.Expr) *ast.SelectorExpr { switch et := e.(type) { case *ast.SelectorExpr: return et case *ast.StarExpr: se, _ := et.X.(*ast.SelectorExpr) return se default: return nil } } func (e *extractor) extractMessage(cl *ast.CompositeLit) { data := make(map[string]string) for _, elt := range cl.Elts { kve, ok := elt.(*ast.KeyValueExpr) if !ok { continue } key, ok := kve.Key.(*ast.Ident) if !ok { continue } v, ok := extractStringLiteral(kve.Value) if !ok { continue } data[key.Name] = v } if len(data) == 0 { return } if messageID := data["MessageID"]; messageID != "" { data["ID"] = messageID } e.messages = append(e.messages, i18n.MustNewMessage(data)) } func extractStringLiteral(expr ast.Expr) (string, bool) { switch v := expr.(type) { case *ast.BasicLit: if v.Kind != token.STRING { return "", false } s := v.Value[1 : len(v.Value)-1] if v.Value[0] == '"' { s = strings.Replace(s, `\"`, `"`, -1) } return s, true case *ast.BinaryExpr: if v.Op != token.ADD { return "", false } x, ok := extractStringLiteral(v.X) if !ok { return "", false } y, ok := extractStringLiteral(v.Y) if !ok { return "", false } return x + y, true default: return "", false } } func i18nPackageName(file *ast.File) string { for _, i := range file.Imports { if i.Path.Kind == token.STRING && i.Path.Value == `"github.com/nicksnyder/go-i18n/v2/i18n"` { if i.Name == nil { return "i18n" } return i.Name.Name } } return "" } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/extract_command_test.go000066400000000000000000000124761350541200600263170ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "os" "path/filepath" "testing" ) func TestExtract(t *testing.T) { tests := []struct { name string fileName string file string activeFile []byte }{ { name: "no translations", fileName: "file.go", file: `package main`, }, { name: "global declaration", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" var m = &i18n.Message{ ID: "Plural ID", } `, }, { name: "escape", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" var a = &i18n.Message{ ID: "a", Other: "a \" b", } var b = &i18n.Message{ ID: "b", Other: ` + "`" + `a " b` + "`" + `, } `, activeFile: []byte(`a = "a \" b" b = "a \" b" `), }, { name: "array", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" var a = []*i18n.Message{ { ID: "a", Other: "a", }, { ID: "b", Other: "b", }, } `, activeFile: []byte(`a = "a" b = "b" `), }, { name: "map", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" var a = map[string]*i18n.Message{ "a": { ID: "a", Other: "a", }, "b": { ID: "b", Other: "b", }, } `, activeFile: []byte(`a = "a" b = "b" `), }, { name: "no extract from test", fileName: "file_test.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" func main() { bundle := i18n.NewBundle(language.English) l := i18n.NewLocalizer(bundle, "en") l.Localize(&i18n.LocalizeConfig{MessageID: "Plural ID"}) } `, }, { name: "must short form id only", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" func main() { bundle := i18n.NewBundle(language.English) l := i18n.NewLocalizer(bundle, "en") l.MustLocalize(&i18n.LocalizeConfig{MessageID: "Plural ID"}) } `, }, { name: "custom package name", fileName: "file.go", file: `package main import bar "github.com/nicksnyder/go-i18n/v2/i18n" func main() { _ := &bar.Message{ ID: "Plural ID", } } `, }, { name: "exhaustive plural translation", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" func main() { _ := &i18n.Message{ ID: "Plural ID", Description: "Plural description", Zero: "Zero translation", One: "One translation", Two: "Two translation", Few: "Few translation", Many: "Many translation", Other: "Other translation", } } `, activeFile: []byte(`["Plural ID"] description = "Plural description" few = "Few translation" many = "Many translation" one = "One translation" other = "Other translation" two = "Two translation" zero = "Zero translation" `), }, { name: "concat id", fileName: "file.go", file: `package main import "github.com/nicksnyder/go-i18n/v2/i18n" func main() { _ := &i18n.Message{ ID: "Plural" + " " + "ID", } } `, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { indir := mustTempDir("TestExtractCommandIn") defer os.RemoveAll(indir) outdir := mustTempDir("TestExtractCommandOut") defer os.RemoveAll(outdir) inpath := filepath.Join(indir, test.fileName) if err := ioutil.WriteFile(inpath, []byte(test.file), 0666); err != nil { t.Fatal(err) } if code := testableMain([]string{"extract", "-outdir", outdir, indir}); code != 0 { t.Fatalf("expected exit code 0; got %d\n", code) } files, err := ioutil.ReadDir(outdir) if err != nil { t.Fatal(err) } if len(files) != 1 { t.Fatalf("expected 1 file; got %#v", files) } actualFile := files[0] expectedName := "active.en.toml" if actualFile.Name() != expectedName { t.Fatalf("expected %s; got %s", expectedName, actualFile.Name()) } outpath := filepath.Join(outdir, actualFile.Name()) actual, err := ioutil.ReadFile(outpath) if err != nil { t.Fatal(err) } if !bytes.Equal(actual, test.activeFile) { t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", test.activeFile, actual) } }) } } func TestExtractCommand(t *testing.T) { outdir, err := ioutil.TempDir("", "TestExtractCommand") if err != nil { t.Fatal(err) } defer os.RemoveAll(outdir) if code := testableMain([]string{"extract", "-outdir", outdir, "../example/"}); code != 0 { t.Fatalf("expected exit code 0; got %d", code) } actual, err := ioutil.ReadFile(filepath.Join(outdir, "active.en.toml")) if err != nil { t.Fatal(err) } expected := []byte(`HelloPerson = "Hello {{.Name}}" [MyUnreadEmails] description = "The number of unread emails I have" one = "I have {{.PluralCount}} unread email." other = "I have {{.PluralCount}} unread emails." [PersonUnreadEmails] description = "The number of unread emails a person has" one = "{{.Name}} has {{.UnreadEmailCount}} unread email." other = "{{.Name}} has {{.UnreadEmailCount}} unread emails." `) if !bytes.Equal(actual, expected) { t.Fatalf("files not equal\nactual:\n%s\nexpected:\n%s", actual, expected) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/main.go000066400000000000000000000074521350541200600230320ustar00rootroot00000000000000// Command goi18n manages message files used by the i18n package. // // go get -u github.com/nicksnyder/go-i18n/v2/goi18n // goi18n -help // // Use `goi18n extract` to create a message file that contains the messages defined in your Go source files. // # en.toml // [PersonCats] // description = "The number of cats a person has" // one = "{{.Name}} has {{.Count}} cat." // other = "{{.Name}} has {{.Count}} cats." // // Use `goi18n merge` to create message files for translation. // # translate.es.toml // [PersonCats] // description = "The number of cats a person has" // hash = "sha1-f937a0e05e19bfe6cd70937c980eaf1f9832f091" // one = "{{.Name}} has {{.Count}} cat." // other = "{{.Name}} has {{.Count}} cats." // // Use `goi18n merge` to merge translated message files with your existing message files. // # active.es.toml // [PersonCats] // description = "The number of cats a person has" // hash = "sha1-f937a0e05e19bfe6cd70937c980eaf1f9832f091" // one = "{{.Name}} tiene {{.Count}} gato." // other = "{{.Name}} tiene {{.Count}} gatos." // // Load the active messages into your bundle. // bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) // bundle.MustLoadMessageFile("active.es.toml") package main import ( "flag" "fmt" "os" "golang.org/x/text/language" ) func mainUsage() { fmt.Fprintf(os.Stderr, `goi18n (v2) is a tool for managing message translations. Usage: goi18n command [arguments] The commands are: merge merge message files extract extract messages from Go files Workflow: Use 'goi18n extract' to create a message file that contains the messages defined in your Go source files. # en.toml [PersonCats] description = "The number of cats a person has" one = "{{.Name}} has {{.Count}} cat." other = "{{.Name}} has {{.Count}} cats." Use 'goi18n merge' to create message files for translation. # translate.es.toml [PersonCats] description = "The number of cats a person has" hash = "sha1-f937a0e05e19bfe6cd70937c980eaf1f9832f091" one = "{{.Name}} has {{.Count}} cat." other = "{{.Name}} has {{.Count}} cats." Use 'goi18n merge' to merge translated message files with your existing message files. # active.es.toml [PersonCats] description = "The number of cats a person has" hash = "sha1-f937a0e05e19bfe6cd70937c980eaf1f9832f091" one = "{{.Name}} tiene {{.Count}} gato." other = "{{.Name}} tiene {{.Count}} gatos." Load the active messages into your bundle. bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.MustLoadMessageFile("active.es.toml") `) } type command interface { name() string parse(arguments []string) error execute() error } func main() { os.Exit(testableMain(os.Args[1:])) } func testableMain(args []string) int { flags := flag.NewFlagSet("goi18n", flag.ContinueOnError) flags.Usage = mainUsage if err := flags.Parse(args); err != nil { if err == flag.ErrHelp { return 2 } return 1 } if flags.NArg() == 0 { mainUsage() return 2 } commands := []command{ &mergeCommand{}, &extractCommand{}, } cmdName := flags.Arg(0) for _, cmd := range commands { if cmd.name() == cmdName { if err := cmd.parse(flags.Args()[1:]); err != nil { fmt.Fprintln(os.Stderr, err) return 1 } if err := cmd.execute(); err != nil { fmt.Fprintln(os.Stderr, err) return 1 } return 0 } } fmt.Fprintf(os.Stderr, "goi18n: unknown subcommand %s\n", cmdName) return 1 } type languageTag language.Tag func (lt languageTag) String() string { return lt.Tag().String() } func (lt *languageTag) Set(value string) error { t, err := language.Parse(value) if err != nil { return err } *lt = languageTag(t) return nil } func (lt languageTag) Tag() language.Tag { tag := language.Tag(lt) if tag.IsRoot() { return language.English } return tag } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/main_test.go000066400000000000000000000011001350541200600240510ustar00rootroot00000000000000package main import ( "strings" "testing" ) func TestMain(t *testing.T) { testCases := []struct { args []string exitCode int }{ { args: []string{"-help"}, exitCode: 2, }, { args: []string{"extract"}, exitCode: 0, }, { args: []string{"merge"}, exitCode: 1, }, } for _, testCase := range testCases { t.Run(strings.Join(testCase.args, " "), func(t *testing.T) { if code := testableMain(testCase.args); code != testCase.exitCode { t.Fatalf("expected exit code %d; got %d", testCase.exitCode, code) } }) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/marshal.go000066400000000000000000000035221350541200600235270ustar00rootroot00000000000000package main import ( "bytes" "encoding/json" "fmt" "path/filepath" "github.com/BurntSushi/toml" "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) func writeFile(outdir, label string, langTag language.Tag, format string, messageTemplates map[string]*i18n.MessageTemplate, sourceLanguage bool) (path string, content []byte, err error) { v := marshalValue(messageTemplates, sourceLanguage) content, err = marshal(v, format) if err != nil { return "", nil, fmt.Errorf("failed to marshal %s strings to %s: %s", langTag, format, err) } path = filepath.Join(outdir, fmt.Sprintf("%s.%s.%s", label, langTag, format)) return } func marshalValue(messageTemplates map[string]*i18n.MessageTemplate, sourceLanguage bool) interface{} { v := make(map[string]interface{}, len(messageTemplates)) for id, template := range messageTemplates { if other := template.PluralTemplates[plural.Other]; sourceLanguage && len(template.PluralTemplates) == 1 && other != nil && template.Description == "" && template.LeftDelim == "" && template.RightDelim == "" { v[id] = other.Src } else { m := map[string]string{} if template.Description != "" { m["description"] = template.Description } if !sourceLanguage { m["hash"] = template.Hash } for pluralForm, template := range template.PluralTemplates { m[string(pluralForm)] = template.Src } v[id] = m } } return v } func marshal(v interface{}, format string) ([]byte, error) { switch format { case "json": return json.MarshalIndent(v, "", " ") case "toml": var buf bytes.Buffer enc := toml.NewEncoder(&buf) enc.Indent = "" err := enc.Encode(v) return buf.Bytes(), err case "yaml": return yaml.Marshal(v) } return nil, fmt.Errorf("unsupported format: %s", format) } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/merge_command.go000066400000000000000000000213001350541200600246670ustar00rootroot00000000000000package main import ( "crypto/sha1" "encoding/json" "flag" "fmt" "io" "io/ioutil" "os" "github.com/BurntSushi/toml" "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/internal" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) func usageMerge() { fmt.Fprintf(os.Stderr, `usage: goi18n merge [options] [message files] Merge reads all messages in the message files and produces two files per language. xx-yy.active.format This file contains messages that should be loaded at runtime. xx-yy.translate.format This file contains messages that are empty and should be translated. Message file names must have a suffix of a supported format (e.g. ".json") and contain a valid language tag as defined by RFC 5646 (e.g. "en-us", "fr", "zh-hant", etc.). To add support for a new language, create an empty translation file with the appropriate name and pass it in to goi18n merge. Flags: -sourceLanguage tag Translate messages from this language (e.g. en, en-US, zh-Hant-CN) Default: en -outdir directory Write message files to this directory. Default: . -format format Output message files in this format. Supported formats: json, toml, yaml Default: toml `) } type mergeCommand struct { messageFiles []string sourceLanguage languageTag outdir string format string } func (mc *mergeCommand) name() string { return "merge" } func (mc *mergeCommand) parse(args []string) error { flags := flag.NewFlagSet("merge", flag.ExitOnError) flags.Usage = usageMerge flags.Var(&mc.sourceLanguage, "sourceLanguage", "en") flags.StringVar(&mc.outdir, "outdir", ".", "") flags.StringVar(&mc.format, "format", "toml", "") if err := flags.Parse(args); err != nil { return err } mc.messageFiles = flags.Args() return nil } func (mc *mergeCommand) execute() error { if len(mc.messageFiles) < 1 { return fmt.Errorf("need at least one message file to parse") } inFiles := make(map[string][]byte) for _, path := range mc.messageFiles { content, err := ioutil.ReadFile(path) if err != nil { return err } inFiles[path] = content } ops, err := merge(inFiles, mc.sourceLanguage.Tag(), mc.outdir, mc.format) if err != nil { return err } for path, content := range ops.writeFiles { if err := ioutil.WriteFile(path, content, 0666); err != nil { return err } } for _, path := range ops.deleteFiles { // Ignore error since it isn't guaranteed to exist. os.Remove(path) } return nil } type fileSystemOp struct { writeFiles map[string][]byte deleteFiles []string } func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdir, outputFormat string) (*fileSystemOp, error) { unmerged := make(map[language.Tag][]map[string]*i18n.MessageTemplate) sourceMessageTemplates := make(map[string]*i18n.MessageTemplate) unmarshalFuncs := map[string]i18n.UnmarshalFunc{ "json": json.Unmarshal, "toml": toml.Unmarshal, "yaml": yaml.Unmarshal, } for path, content := range messageFiles { mf, err := i18n.ParseMessageFileBytes(content, path, unmarshalFuncs) if err != nil { return nil, fmt.Errorf("failed to load message file %s: %s", path, err) } templates := map[string]*i18n.MessageTemplate{} for _, m := range mf.Messages { templates[m.ID] = i18n.NewMessageTemplate(m) } if mf.Tag == sourceLanguageTag { for _, template := range templates { if sourceMessageTemplates[template.ID] != nil { return nil, fmt.Errorf("multiple source translations for id %s", template.ID) } template.Hash = hash(template) sourceMessageTemplates[template.ID] = template } } unmerged[mf.Tag] = append(unmerged[mf.Tag], templates) } if len(sourceMessageTemplates) == 0 { return nil, fmt.Errorf("no messages found for source locale %s", sourceLanguageTag) } pluralRules := plural.DefaultRules() all := make(map[language.Tag]map[string]*i18n.MessageTemplate) all[sourceLanguageTag] = sourceMessageTemplates for _, srcTemplate := range sourceMessageTemplates { for dstLangTag, messageTemplates := range unmerged { if dstLangTag == sourceLanguageTag { continue } pluralRule := pluralRules.Rule(dstLangTag) if pluralRule == nil { // Non-standard languages not supported because // we don't know if translations are complete or not. continue } if all[dstLangTag] == nil { all[dstLangTag] = make(map[string]*i18n.MessageTemplate) } dstMessageTemplate := all[dstLangTag][srcTemplate.ID] if dstMessageTemplate == nil { dstMessageTemplate = &i18n.MessageTemplate{ Message: &i18n.Message{ ID: srcTemplate.ID, Description: srcTemplate.Description, Hash: srcTemplate.Hash, }, PluralTemplates: make(map[plural.Form]*internal.Template), } all[dstLangTag][srcTemplate.ID] = dstMessageTemplate } // Check all unmerged message templates for this message id. for _, messageTemplates := range messageTemplates { unmergedTemplate := messageTemplates[srcTemplate.ID] if unmergedTemplate == nil { continue } // Ignore empty hashes for v1 backward compatibility. if unmergedTemplate.Hash != "" && unmergedTemplate.Hash != srcTemplate.Hash { // This was translated from different content so discard. continue } // Merge in the translated messages. for pluralForm := range pluralRule.PluralForms { dt := unmergedTemplate.PluralTemplates[pluralForm] if dt != nil && dt.Src != "" { dstMessageTemplate.PluralTemplates[pluralForm] = dt } } } } } translate := make(map[language.Tag]map[string]*i18n.MessageTemplate) active := make(map[language.Tag]map[string]*i18n.MessageTemplate) for langTag, messageTemplates := range all { active[langTag] = make(map[string]*i18n.MessageTemplate) if langTag == sourceLanguageTag { active[langTag] = messageTemplates continue } pluralRule := pluralRules.Rule(langTag) if pluralRule == nil { // Non-standard languages not supported because // we don't know if translations are complete or not. continue } for _, messageTemplate := range messageTemplates { srcMessageTemplate := sourceMessageTemplates[messageTemplate.ID] activeMessageTemplate, translateMessageTemplate := activeDst(srcMessageTemplate, messageTemplate, pluralRule) if translateMessageTemplate != nil { if translate[langTag] == nil { translate[langTag] = make(map[string]*i18n.MessageTemplate) } translate[langTag][messageTemplate.ID] = translateMessageTemplate } if activeMessageTemplate != nil { active[langTag][messageTemplate.ID] = activeMessageTemplate } } } writeFiles := make(map[string][]byte, len(translate)+len(active)) for langTag, messageTemplates := range translate { path, content, err := writeFile(outdir, "translate", langTag, outputFormat, messageTemplates, false) if err != nil { return nil, err } writeFiles[path] = content } deleteFiles := []string{} for langTag, messageTemplates := range active { path, content, err := writeFile(outdir, "active", langTag, outputFormat, messageTemplates, langTag == sourceLanguageTag) if err != nil { return nil, err } if len(content) > 0 { writeFiles[path] = content } else { deleteFiles = append(deleteFiles, path) } } return &fileSystemOp{writeFiles: writeFiles, deleteFiles: deleteFiles}, nil } // activeDst returns the active part of the dst and whether dst is a complete translation of src. func activeDst(src, dst *i18n.MessageTemplate, pluralRule *plural.Rule) (active *i18n.MessageTemplate, translateMessageTemplate *i18n.MessageTemplate) { pluralForms := pluralRule.PluralForms if len(src.PluralTemplates) == 1 { pluralForms = map[plural.Form]struct{}{ plural.Other: {}, } } for pluralForm := range pluralForms { dt := dst.PluralTemplates[pluralForm] if dt == nil || dt.Src == "" { if translateMessageTemplate == nil { translateMessageTemplate = &i18n.MessageTemplate{ Message: &i18n.Message{ ID: src.ID, Description: src.Description, Hash: src.Hash, }, PluralTemplates: make(map[plural.Form]*internal.Template), } } translateMessageTemplate.PluralTemplates[pluralForm] = src.PluralTemplates[plural.Other] continue } if active == nil { active = &i18n.MessageTemplate{ Message: &i18n.Message{ ID: src.ID, Description: src.Description, Hash: src.Hash, }, PluralTemplates: make(map[plural.Form]*internal.Template), } } active.PluralTemplates[pluralForm] = dt } return } func hash(t *i18n.MessageTemplate) string { h := sha1.New() _, _ = io.WriteString(h, t.Description) _, _ = io.WriteString(h, t.PluralTemplates[plural.Other].Src) return fmt.Sprintf("sha1-%x", h.Sum(nil)) } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/goi18n/merge_command_test.go000066400000000000000000000362751350541200600257470ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "os" "path/filepath" "testing" "golang.org/x/text/language" ) type testCase struct { name string inFiles map[string][]byte sourceLanguage language.Tag outFiles map[string][]byte deleteFiles []string } func expectFile(s string) []byte { // Trimming leading newlines gives nicer formatting for file literals in test cases. return bytes.TrimLeft([]byte(s), "\n") } func TestMerge(t *testing.T) { testCases := []*testCase{ { name: "single identity", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "one.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), }, outFiles: map[string][]byte{ "active.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), }, }, { name: "plural identity", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "active.en-US.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread email" other = "{{.Count}} unread emails" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread email" other = "{{.Count}} unread emails" `), }, }, { name: "migrate source lang from v1 format", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "one.en-US.json": []byte(`[ { "id": "simple", "translation": "simple translation" }, { "id": "everything", "translation": { "zero": "zero translation", "one": "one translation", "two": "two translation", "few": "few translation", "many": "many translation", "other": "other translation" } } ]`), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` simple = "simple translation" [everything] few = "few translation" many = "many translation" one = "one translation" other = "other translation" two = "two translation" zero = "zero translation" `), }, }, { name: "migrate source lang from v1 flat format", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "one.en-US.json": []byte(`{ "simple": { "other": "simple translation" }, "everything": { "zero": "zero translation", "one": "one translation", "two": "two translation", "few": "few translation", "many": "many translation", "other": "other translation" } }`), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` simple = "simple translation" [everything] few = "few translation" many = "many translation" one = "one translation" other = "other translation" two = "two translation" zero = "zero translation" `), }, }, { name: "merge source files", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "one.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), "two.en-US.toml": []byte("2GoodbyeMessage = \"Goodbye\"\n"), }, outFiles: map[string][]byte{ "active.en-US.toml": []byte("1HelloMessage = \"Hello\"\n2GoodbyeMessage = \"Goodbye\"\n"), }, }, { name: "missing hash", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` 1HelloMessage = "Hello" `), "es-ES.toml": []byte(` [1HelloMessage] other = "Hola" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` 1HelloMessage = "Hello" `), "active.es-ES.toml": expectFile(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" `), }, }, { name: "add single translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` 1HelloMessage = "Hello" 2GoodbyeMessage = "Goodbye" `), "es-ES.toml": []byte(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` 1HelloMessage = "Hello" 2GoodbyeMessage = "Goodbye" `), "active.es-ES.toml": expectFile(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" `), "translate.es-ES.toml": expectFile(` [2GoodbyeMessage] hash = "sha1-b5b29c53e3c71cb9c6581ab053d7758fab8ca24d" other = "Goodbye" `), }, }, { name: "remove single translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` 1HelloMessage = "Hello" `), "es-ES.toml": []byte(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" [2GoodbyeMessage] hash = "sha1-b5b29c53e3c71cb9c6581ab053d7758fab8ca24d" other = "Goodbye" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` 1HelloMessage = "Hello" `), "active.es-ES.toml": expectFile(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" `), }, }, { name: "edit single translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` 1HelloMessage = "Hi" `), "es-ES.toml": []byte(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` 1HelloMessage = "Hi" `), "translate.es-ES.toml": expectFile(` [1HelloMessage] hash = "sha1-94dd9e08c129c785f7f256e82fbe0a30e6d1ae40" other = "Hi" `), }, }, { name: "add plural translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread email" other = "{{.Count}} unread emails" `), "es-ES.toml": nil, "ar-AR.toml": nil, "zh-CN.toml": nil, }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread email" other = "{{.Count}} unread emails" `), "translate.es-ES.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" `), "translate.ar-AR.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" few = "{{.Count}} unread emails" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" many = "{{.Count}} unread emails" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" two = "{{.Count}} unread emails" zero = "{{.Count}} unread emails" `), "translate.zh-CN.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" other = "{{.Count}} unread emails" `), }, }, { name: "remove plural translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` 1HelloMessage = "Hello" `), "es-ES.toml": []byte(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" `), "ar-AR.toml": []byte(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hello" [UnreadEmails] description = "Message that tells the user how many unread emails they have" few = "{{.Count}} unread emails" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" many = "{{.Count}} unread emails" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" two = "{{.Count}} unread emails" zero = "{{.Count}} unread emails" `), "zh-CN.toml": []byte(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hello" [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" other = "{{.Count}} unread emails" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` 1HelloMessage = "Hello" `), "active.es-ES.toml": expectFile(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hola" `), "active.ar-AR.toml": expectFile(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hello" `), "active.zh-CN.toml": expectFile(` [1HelloMessage] hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" other = "Hello" `), }, }, { name: "edit plural translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread emails!" other = "{{.Count}} unread emails!" `), "es-ES.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" `), "ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" few = "{{.Count}} unread emails" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" many = "{{.Count}} unread emails" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" two = "{{.Count}} unread emails" zero = "{{.Count}} unread emails" `), "zh-CN.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" other = "{{.Count}} unread emails" `), }, deleteFiles: []string{ "active.es-ES.toml", "active.ar-AR.toml", "active.zh-CN.toml", }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread emails!" other = "{{.Count}} unread emails!" `), "translate.es-ES.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" one = "{{.Count}} unread emails!" other = "{{.Count}} unread emails!" `), "translate.ar-AR.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" few = "{{.Count}} unread emails!" hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" many = "{{.Count}} unread emails!" one = "{{.Count}} unread emails!" other = "{{.Count}} unread emails!" two = "{{.Count}} unread emails!" zero = "{{.Count}} unread emails!" `), "translate.zh-CN.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" other = "{{.Count}} unread emails!" `), }, }, { name: "merge plural translation", sourceLanguage: language.AmericanEnglish, inFiles: map[string][]byte{ "en-US.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" `), "zero.ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" zero = "{{.Count}} unread emails" `), "one.ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" one = "{{.Count}} unread emails" `), "two.ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" two = "{{.Count}} unread emails" `), "few.ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" few = "{{.Count}} unread emails" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" `), "many.ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" many = "{{.Count}} unread emails" `), "other.ar-AR.toml": []byte(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" other = "{{.Count}} unread emails" `), }, outFiles: map[string][]byte{ "active.en-US.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" `), "active.ar-AR.toml": expectFile(` [UnreadEmails] description = "Message that tells the user how many unread emails they have" few = "{{.Count}} unread emails" hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" many = "{{.Count}} unread emails" one = "{{.Count}} unread emails" other = "{{.Count}} unread emails" two = "{{.Count}} unread emails" zero = "{{.Count}} unread emails" `), }, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { indir := mustTempDir("TestMergeCommandIn") defer os.RemoveAll(indir) outdir := mustTempDir("TestMergeCommandOut") defer os.RemoveAll(outdir) infiles := make([]string, 0, len(testCase.inFiles)) for name, content := range testCase.inFiles { path := filepath.Join(indir, name) infiles = append(infiles, path) if err := ioutil.WriteFile(path, content, 0666); err != nil { t.Fatal(err) } } for _, name := range testCase.deleteFiles { path := filepath.Join(outdir, name) if err := ioutil.WriteFile(path, []byte(`this file should get deleted`), 0666); err != nil { t.Fatal(err) } } args := append([]string{"merge", "-sourceLanguage", testCase.sourceLanguage.String(), "-outdir", outdir}, infiles...) if code := testableMain(args); code != 0 { t.Fatalf("expected exit code 0; got %d\n", code) } files, err := ioutil.ReadDir(outdir) if err != nil { t.Fatal(err) } // Verify that all actual files have expected contents. actualFiles := make(map[string]struct{}, len(files)) for _, f := range files { actualFiles[f.Name()] = struct{}{} if f.IsDir() { t.Errorf("found unexpected dir %s", f.Name()) continue } path := filepath.Join(outdir, f.Name()) actual, err := ioutil.ReadFile(path) if err != nil { t.Error(err) continue } expected, ok := testCase.outFiles[f.Name()] if !ok { t.Errorf("found unexpected file %s with contents:\n%s\n", f.Name(), actual) continue } if !bytes.Equal(actual, expected) { t.Errorf("unexpected contents %s\ngot\n%s\nexpected\n%s", f.Name(), actual, expected) continue } } // Verify that all expected files are accounted for. for name := range testCase.outFiles { if _, ok := actualFiles[name]; !ok { t.Errorf("did not find expected file %s", name) } } }) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/000077500000000000000000000000001350541200600212215ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/bundle.go000066400000000000000000000101021350541200600230130ustar00rootroot00000000000000package i18n import ( "fmt" "io/ioutil" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" ) // UnmarshalFunc unmarshals data into v. type UnmarshalFunc func(data []byte, v interface{}) error // Bundle stores a set of messages and pluralization rules. // Most applications only need a single bundle // that is initialized early in the application's lifecycle. // It is not goroutine safe to modify the bundle while Localizers // are reading from it. type Bundle struct { defaultLanguage language.Tag unmarshalFuncs map[string]UnmarshalFunc messageTemplates map[language.Tag]map[string]*MessageTemplate pluralRules plural.Rules tags []language.Tag matcher language.Matcher } // artTag is the language tag used for artifical languages // https://en.wikipedia.org/wiki/Codes_for_constructed_languages var artTag = language.MustParse("art") // NewBundle returns a bundle with a default language and a default set of plural rules. func NewBundle(defaultLanguage language.Tag) *Bundle { b := &Bundle{ defaultLanguage: defaultLanguage, pluralRules: plural.DefaultRules(), } b.pluralRules[artTag] = b.pluralRules.Rule(language.English) b.addTag(defaultLanguage) return b } // RegisterUnmarshalFunc registers an UnmarshalFunc for format. func (b *Bundle) RegisterUnmarshalFunc(format string, unmarshalFunc UnmarshalFunc) { if b.unmarshalFuncs == nil { b.unmarshalFuncs = make(map[string]UnmarshalFunc) } b.unmarshalFuncs[format] = unmarshalFunc } // LoadMessageFile loads the bytes from path // and then calls ParseMessageFileBytes. func (b *Bundle) LoadMessageFile(path string) (*MessageFile, error) { buf, err := ioutil.ReadFile(path) if err != nil { return nil, err } return b.ParseMessageFileBytes(buf, path) } // MustLoadMessageFile is similar to LoadTranslationFile // except it panics if an error happens. func (b *Bundle) MustLoadMessageFile(path string) { if _, err := b.LoadMessageFile(path); err != nil { panic(err) } } // ParseMessageFileBytes parses the bytes in buf to add translations to the bundle. // // The format of the file is everything after the last ".". // // The language tag of the file is everything after the second to last "." or after the last path separator, but before the format. func (b *Bundle) ParseMessageFileBytes(buf []byte, path string) (*MessageFile, error) { messageFile, err := ParseMessageFileBytes(buf, path, b.unmarshalFuncs) if err != nil { return nil, err } if err := b.AddMessages(messageFile.Tag, messageFile.Messages...); err != nil { return nil, err } return messageFile, nil } // MustParseMessageFileBytes is similar to ParseMessageFileBytes // except it panics if an error happens. func (b *Bundle) MustParseMessageFileBytes(buf []byte, path string) { if _, err := b.ParseMessageFileBytes(buf, path); err != nil { panic(err) } } // AddMessages adds messages for a language. // It is useful if your messages are in a format not supported by ParseMessageFileBytes. func (b *Bundle) AddMessages(tag language.Tag, messages ...*Message) error { pluralRule := b.pluralRules.Rule(tag) if pluralRule == nil { return fmt.Errorf("no plural rule registered for %s", tag) } if b.messageTemplates == nil { b.messageTemplates = map[language.Tag]map[string]*MessageTemplate{} } if b.messageTemplates[tag] == nil { b.messageTemplates[tag] = map[string]*MessageTemplate{} b.addTag(tag) } for _, m := range messages { b.messageTemplates[tag][m.ID] = NewMessageTemplate(m) } return nil } // MustAddMessages is similar to AddMessages except it panics if an error happens. func (b *Bundle) MustAddMessages(tag language.Tag, messages ...*Message) { if err := b.AddMessages(tag, messages...); err != nil { panic(err) } } func (b *Bundle) addTag(tag language.Tag) { for _, t := range b.tags { if t == tag { // Tag already exists return } } b.tags = append(b.tags, tag) b.matcher = language.NewMatcher(b.tags) } // LanguageTags returns the list of language tags // of all the translations loaded into the bundle func (b *Bundle) LanguageTags() []language.Tag { return b.tags } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/bundle_test.go000066400000000000000000000140171350541200600240630ustar00rootroot00000000000000package i18n import ( "fmt" "reflect" "testing" "github.com/BurntSushi/toml" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) var simpleMessage = MustNewMessage(map[string]string{ "id": "simple", "other": "simple translation", }) var detailMessage = MustNewMessage(map[string]string{ "id": "detail", "description": "detail description", "other": "detail translation", }) var everythingMessage = MustNewMessage(map[string]string{ "id": "everything", "description": "everything description", "zero": "zero translation", "one": "one translation", "two": "two translation", "few": "few translation", "many": "many translation", "other": "other translation", }) func TestConcurrentAccess(t *testing.T) { bundle := NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.MustParseMessageFileBytes([]byte(` # Comment hello = "world" `), "en.toml") count := 10 errch := make(chan error, count) for i := 0; i < count; i++ { go func() { localized := NewLocalizer(bundle, "en").MustLocalize(&LocalizeConfig{MessageID: "hello"}) if localized != "world" { errch <- fmt.Errorf(`expected "world"; got %q`, localized) } else { errch <- nil } }() } for i := 0; i < count; i++ { if err := <-errch; err != nil { t.Fatal(err) } } } func TestPseudoLanguage(t *testing.T) { bundle := NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) expected := "nuqneH" bundle.MustParseMessageFileBytes([]byte(` # Comment hello = "`+expected+`" `), "art-x-klingon.toml") { localized, err := NewLocalizer(bundle, "art-x-klingon").Localize(&LocalizeConfig{MessageID: "hello"}) if err != nil { t.Fatal(err) } if localized != expected { t.Fatalf("expected %q\ngot %q", expected, localized) } } { localized, err := NewLocalizer(bundle, "art").Localize(&LocalizeConfig{MessageID: "hello"}) if err != nil { t.Fatal(err) } if localized != expected { t.Fatalf("expected %q\ngot %q", expected, localized) } } { expected := "" localized, err := NewLocalizer(bundle, "en").Localize(&LocalizeConfig{MessageID: "hello"}) if err == nil { t.Fatal(err) } if localized != expected { t.Fatalf("expected %q\ngot %q", expected, localized) } } } func TestJSON(t *testing.T) { bundle := NewBundle(language.English) bundle.MustParseMessageFileBytes([]byte(`{ "simple": "simple translation", "detail": { "description": "detail description", "other": "detail translation" }, "everything": { "description": "everything description", "zero": "zero translation", "one": "one translation", "two": "two translation", "few": "few translation", "many": "many translation", "other": "other translation" } }`), "en-US.json") expectMessage(t, bundle, language.AmericanEnglish, "simple", simpleMessage) expectMessage(t, bundle, language.AmericanEnglish, "detail", detailMessage) expectMessage(t, bundle, language.AmericanEnglish, "everything", everythingMessage) } func TestYAML(t *testing.T) { bundle := NewBundle(language.English) bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) bundle.MustParseMessageFileBytes([]byte(` # Comment simple: simple translation # Comment detail: description: detail description other: detail translation # Comment everything: description: everything description zero: zero translation one: one translation two: two translation few: few translation many: many translation other: other translation `), "en-US.yaml") expectMessage(t, bundle, language.AmericanEnglish, "simple", simpleMessage) expectMessage(t, bundle, language.AmericanEnglish, "detail", detailMessage) expectMessage(t, bundle, language.AmericanEnglish, "everything", everythingMessage) } func TestTOML(t *testing.T) { bundle := NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.MustParseMessageFileBytes([]byte(` # Comment simple = "simple translation" # Comment [detail] description = "detail description" other = "detail translation" # Comment [everything] description = "everything description" zero = "zero translation" one = "one translation" two = "two translation" few = "few translation" many = "many translation" other = "other translation" `), "en-US.toml") expectMessage(t, bundle, language.AmericanEnglish, "simple", simpleMessage) expectMessage(t, bundle, language.AmericanEnglish, "detail", detailMessage) expectMessage(t, bundle, language.AmericanEnglish, "everything", everythingMessage) } func TestV1Format(t *testing.T) { bundle := NewBundle(language.English) bundle.MustParseMessageFileBytes([]byte(`[ { "id": "simple", "translation": "simple translation" }, { "id": "everything", "translation": { "zero": "zero translation", "one": "one translation", "two": "two translation", "few": "few translation", "many": "many translation", "other": "other translation" } } ] `), "en-US.json") expectMessage(t, bundle, language.AmericanEnglish, "simple", simpleMessage) e := *everythingMessage e.Description = "" expectMessage(t, bundle, language.AmericanEnglish, "everything", &e) } func TestV1FlatFormat(t *testing.T) { bundle := NewBundle(language.English) bundle.MustParseMessageFileBytes([]byte(`{ "simple": { "other": "simple translation" }, "everything": { "zero": "zero translation", "one": "one translation", "two": "two translation", "few": "few translation", "many": "many translation", "other": "other translation" } } `), "en-US.json") expectMessage(t, bundle, language.AmericanEnglish, "simple", simpleMessage) e := *everythingMessage e.Description = "" expectMessage(t, bundle, language.AmericanEnglish, "everything", &e) } func expectMessage(t *testing.T, bundle *Bundle, tag language.Tag, messageID string, message *Message) { expected := NewMessageTemplate(message) actual := bundle.messageTemplates[tag][messageID] if !reflect.DeepEqual(actual, expected) { t.Errorf("bundle.MessageTemplates[%q][%q] = %#v; want %#v", tag, messageID, actual, expected) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/doc.go000066400000000000000000000015371350541200600223230ustar00rootroot00000000000000// Package i18n provides support for looking up messages // according to a set of locale preferences. // // Create a Bundle to use for the lifetime of your application. // bundle := i18n.NewBundle(language.English) // // Load translations into your bundle during initialization. // bundle.LoadMessageFile("en-US.yaml") // // Create a Localizer to use for a set of language preferences. // func(w http.ResponseWriter, r *http.Request) { // lang := r.FormValue("lang") // accept := r.Header.Get("Accept-Language") // localizer := i18n.NewLocalizer(bundle, lang, accept) // } // // Use the Localizer to lookup messages. // localizer.MustLocalize(&i18n.LocalizeConfig{ // DefaultMessage: &i18n.Message{ // ID: "HelloWorld", // Other: "Hello World!", // }, // }) package i18n golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/example_test.go000066400000000000000000000071361350541200600242510ustar00rootroot00000000000000package i18n_test import ( "fmt" "github.com/BurntSushi/toml" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" ) func ExampleLocalizer_MustLocalize() { bundle := i18n.NewBundle(language.English) localizer := i18n.NewLocalizer(bundle, "en") fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: &i18n.Message{ ID: "HelloWorld", Other: "Hello World!", }, })) // Output: // Hello World! } func ExampleLocalizer_MustLocalize_noDefaultMessage() { bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) bundle.MustParseMessageFileBytes([]byte(` HelloWorld = "Hello World!" `), "en.toml") bundle.MustParseMessageFileBytes([]byte(` HelloWorld = "Hola Mundo!" `), "es.toml") { localizer := i18n.NewLocalizer(bundle, "en-US") fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "HelloWorld"})) } { localizer := i18n.NewLocalizer(bundle, "es-ES") fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "HelloWorld"})) } // Output: // Hello World! // Hola Mundo! } func ExampleLocalizer_MustLocalize_plural() { bundle := i18n.NewBundle(language.English) localizer := i18n.NewLocalizer(bundle, "en") catsMessage := &i18n.Message{ ID: "Cats", One: "I have {{.PluralCount}} cat.", Other: "I have {{.PluralCount}} cats.", } fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: catsMessage, PluralCount: 1, })) fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: catsMessage, PluralCount: 2, })) fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: catsMessage, PluralCount: "2.5", })) // Output: // I have 1 cat. // I have 2 cats. // I have 2.5 cats. } func ExampleLocalizer_MustLocalize_template() { bundle := i18n.NewBundle(language.English) localizer := i18n.NewLocalizer(bundle, "en") helloPersonMessage := &i18n.Message{ ID: "HelloPerson", Other: "Hello {{.Name}}!", } fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: helloPersonMessage, TemplateData: map[string]string{"Name": "Nick"}, })) // Output: // Hello Nick! } func ExampleLocalizer_MustLocalize_plural_template() { bundle := i18n.NewBundle(language.English) localizer := i18n.NewLocalizer(bundle, "en") personCatsMessage := &i18n.Message{ ID: "PersonCats", One: "{{.Name}} has {{.Count}} cat.", Other: "{{.Name}} has {{.Count}} cats.", } fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: personCatsMessage, PluralCount: 1, TemplateData: map[string]interface{}{ "Name": "Nick", "Count": 1, }, })) fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: personCatsMessage, PluralCount: 2, TemplateData: map[string]interface{}{ "Name": "Nick", "Count": 2, }, })) fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: personCatsMessage, PluralCount: "2.5", TemplateData: map[string]interface{}{ "Name": "Nick", "Count": "2.5", }, })) // Output: // Nick has 1 cat. // Nick has 2 cats. // Nick has 2.5 cats. } func ExampleLocalizer_MustLocalize_customTemplateDelims() { bundle := i18n.NewBundle(language.English) localizer := i18n.NewLocalizer(bundle, "en") helloPersonMessage := &i18n.Message{ ID: "HelloPerson", Other: "Hello <<.Name>>!", LeftDelim: "<<", RightDelim: ">>", } fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ DefaultMessage: helloPersonMessage, TemplateData: map[string]string{"Name": "Nick"}, })) // Output: // Hello Nick! } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/language_test.go000066400000000000000000000014431350541200600243740ustar00rootroot00000000000000package i18n_test import ( "testing" "golang.org/x/text/language" ) var matcher language.Matcher func BenchmarkNewMatcher(b *testing.B) { langs := []language.Tag{ language.English, language.AmericanEnglish, language.BritishEnglish, language.Spanish, language.EuropeanSpanish, language.Portuguese, language.French, } b.ResetTimer() for i := 0; i < b.N; i++ { matcher = language.NewMatcher(langs) } } func BenchmarkMatchStrings(b *testing.B) { langs := []language.Tag{ language.English, language.AmericanEnglish, language.BritishEnglish, language.Spanish, language.EuropeanSpanish, language.Portuguese, language.French, } matcher := language.NewMatcher(langs) b.ResetTimer() for i := 0; i < b.N; i++ { language.MatchStrings(matcher, "en-US,en;q=0.9") } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/localizer.go000066400000000000000000000155641350541200600235470ustar00rootroot00000000000000package i18n import ( "fmt" "text/template" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" ) // Localizer provides Localize and MustLocalize methods that return localized messages. type Localizer struct { // bundle contains the messages that can be returned by the Localizer. bundle *Bundle // tags is the list of language tags that the Localizer checks // in order when localizing a message. tags []language.Tag } // NewLocalizer returns a new Localizer that looks up messages // in the bundle according to the language preferences in langs. // It can parse Accept-Language headers as defined in http://www.ietf.org/rfc/rfc2616.txt. func NewLocalizer(bundle *Bundle, langs ...string) *Localizer { return &Localizer{ bundle: bundle, tags: parseTags(langs), } } func parseTags(langs []string) []language.Tag { tags := []language.Tag{} for _, lang := range langs { t, _, err := language.ParseAcceptLanguage(lang) if err != nil { continue } tags = append(tags, t...) } return tags } // LocalizeConfig configures a call to the Localize method on Localizer. type LocalizeConfig struct { // MessageID is the id of the message to lookup. // This field is ignored if DefaultMessage is set. MessageID string // TemplateData is the data passed when executing the message's template. // If TemplateData is nil and PluralCount is not nil, then the message template // will be executed with data that contains the plural count. TemplateData interface{} // PluralCount determines which plural form of the message is used. PluralCount interface{} // DefaultMessage is used if the message is not found in any message files. DefaultMessage *Message // Funcs is used to extend the Go template engine's built in functions Funcs template.FuncMap } type invalidPluralCountErr struct { messageID string pluralCount interface{} err error } func (e *invalidPluralCountErr) Error() string { return fmt.Sprintf("invalid plural count %#v for message id %q: %s", e.pluralCount, e.messageID, e.err) } // MessageNotFoundErr is returned from Localize when a message could not be found. type MessageNotFoundErr struct { messageID string } func (e *MessageNotFoundErr) Error() string { return fmt.Sprintf("message %q not found", e.messageID) } type pluralizeErr struct { messageID string tag language.Tag } func (e *pluralizeErr) Error() string { return fmt.Sprintf("unable to pluralize %q because there no plural rule for %q", e.messageID, e.tag) } type messageIDMismatchErr struct { messageID string defaultMessageID string } func (e *messageIDMismatchErr) Error() string { return fmt.Sprintf("message id %q does not match default message id %q", e.messageID, e.defaultMessageID) } // Localize returns a localized message. func (l *Localizer) Localize(lc *LocalizeConfig) (string, error) { msg, _, err := l.LocalizeWithTag(lc) return msg, err } // Localize returns a localized message. func (l *Localizer) LocalizeMessage(msg *Message) (string, error) { return l.Localize(&LocalizeConfig{ DefaultMessage: msg, }) } // TODO: uncomment this (and the test) when extract has been updated to extract these call sites too. // Localize returns a localized message. // func (l *Localizer) LocalizeMessageID(messageID string) (string, error) { // return l.Localize(&LocalizeConfig{ // MessageID: messageID, // }) // } // LocalizeWithTag returns a localized message and the language tag. // It may return a best effort localized message even if an error happens. func (l *Localizer) LocalizeWithTag(lc *LocalizeConfig) (string, language.Tag, error) { messageID := lc.MessageID if lc.DefaultMessage != nil { if messageID != "" && messageID != lc.DefaultMessage.ID { return "", language.Und, &messageIDMismatchErr{messageID: messageID, defaultMessageID: lc.DefaultMessage.ID} } messageID = lc.DefaultMessage.ID } var operands *plural.Operands templateData := lc.TemplateData if lc.PluralCount != nil { var err error operands, err = plural.NewOperands(lc.PluralCount) if err != nil { return "", language.Und, &invalidPluralCountErr{messageID: messageID, pluralCount: lc.PluralCount, err: err} } if templateData == nil { templateData = map[string]interface{}{ "PluralCount": lc.PluralCount, } } } tag, template := l.getTemplate(messageID, lc.DefaultMessage) if template == nil { return "", language.Und, &MessageNotFoundErr{messageID: messageID} } pluralForm := l.pluralForm(tag, operands) if pluralForm == plural.Invalid { return "", language.Und, &pluralizeErr{messageID: messageID, tag: tag} } msg, err := template.Execute(pluralForm, templateData, lc.Funcs) if err != nil { // Attempt to fallback to "Other" pluralization in case translations are incomplete. if pluralForm != plural.Other { msg2, err2 := template.Execute(plural.Other, templateData, lc.Funcs) if err2 == nil { return msg2, tag, err } } return "", language.Und, err } return msg, tag, nil } func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Tag, *MessageTemplate) { // Fast path. // Optimistically assume this message id is defined in each language. fastTag, template := l.matchTemplate(id, defaultMessage, l.bundle.matcher, l.bundle.tags) if template != nil { return fastTag, template } if len(l.bundle.tags) <= 1 { return l.bundle.defaultLanguage, nil } // Slow path. // We didn't find a translation for the tag suggested by the default matcher // so we need to create a new matcher that contains only the tags in the bundle // that have this message. foundTags := make([]language.Tag, 0, len(l.bundle.messageTemplates)+1) foundTags = append(foundTags, l.bundle.defaultLanguage) for t, templates := range l.bundle.messageTemplates { template := templates[id] if template == nil || template.Other == "" { continue } foundTags = append(foundTags, t) } return l.matchTemplate(id, defaultMessage, language.NewMatcher(foundTags), foundTags) } func (l *Localizer) matchTemplate(id string, defaultMessage *Message, matcher language.Matcher, tags []language.Tag) (language.Tag, *MessageTemplate) { _, i, _ := matcher.Match(l.tags...) tag := tags[i] templates := l.bundle.messageTemplates[tag] if templates != nil && templates[id] != nil { return tag, templates[id] } if tag == l.bundle.defaultLanguage && defaultMessage != nil { return tag, NewMessageTemplate(defaultMessage) } return tag, nil } func (l *Localizer) pluralForm(tag language.Tag, operands *plural.Operands) plural.Form { if operands == nil { return plural.Other } pluralRule := l.bundle.pluralRules.Rule(tag) if pluralRule == nil { return plural.Invalid } return pluralRule.PluralFormFunc(operands) } // MustLocalize is similar to Localize, except it panics if an error happens. func (l *Localizer) MustLocalize(lc *LocalizeConfig) string { localized, err := l.Localize(lc) if err != nil { panic(err) } return localized } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/localizer_test.go000066400000000000000000000422011350541200600245720ustar00rootroot00000000000000package i18n import ( "reflect" "testing" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" ) func TestLocalizer_Localize(t *testing.T) { tests := []struct { name string defaultLanguage language.Tag messages map[language.Tag][]*Message acceptLangs []string conf *LocalizeConfig expectedErr error expectedLocalized string }{ { name: "message id mismatch", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "HelloWorld", DefaultMessage: &Message{ ID: "DefaultHelloWorld", }, }, expectedErr: &messageIDMismatchErr{messageID: "HelloWorld", defaultMessageID: "DefaultHelloWorld"}, }, { name: "message id not mismatched", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ID: "HelloWorld", Other: "Hello!"}}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "HelloWorld", DefaultMessage: &Message{ ID: "HelloWorld", }, }, expectedLocalized: "Hello!", }, { name: "missing translation from default language", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, expectedLocalized: "", }, { name: "empty translation without fallback", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.Spanish: {{ID: "HelloWorld"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, }, { name: "empty translation with fallback", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ID: "HelloWorld", Other: "Hello World!"}}, language.Spanish: {{ID: "HelloWorld"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedLocalized: "Hello World!", }, { name: "missing translation from default language with other translation", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.Spanish: {{ID: "HelloWorld", Other: "other"}}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, expectedLocalized: "", }, { name: "missing translation from not default language", defaultLanguage: language.English, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, expectedLocalized: "", }, { name: "missing translation not default language with other translation", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.French: {{ID: "HelloWorld", Other: "other"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, expectedLocalized: "", }, { name: "accept default language, message in bundle", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ID: "HelloWorld", Other: "other"}}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedLocalized: "other", }, { name: "accept default language, message in bundle, default message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ID: "HelloWorld", Other: "bundle other"}}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "bundle other", }, { name: "accept not default language, message in bundle", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.Spanish: {{ID: "HelloWorld", Other: "other"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, expectedLocalized: "other", }, { name: "accept not default language, other message in bundle, default message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ID: "HelloWorld", Other: "bundle other"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "bundle other", }, { name: "accept not default language, message in bundle, default message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.Spanish: {{ID: "HelloWorld", Other: "bundle other"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "bundle other", }, { name: "accept default language, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "default other", }, { name: "accept not default language, default message", defaultLanguage: language.English, acceptLangs: []string{"es"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "default other", }, { name: "fallback to non-default less specific language", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.Spanish: {{ID: "HelloWorld", Other: "bundle other"}}, }, acceptLangs: []string{"es-ES"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "bundle other", }, { name: "fallback to non-default more specific language", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.EuropeanSpanish: {{ID: "HelloWorld", Other: "bundle other"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ID: "HelloWorld", Other: "default other"}, }, expectedLocalized: "bundle other", }, { name: "plural count one, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "Cats", One: "I have {{.PluralCount}} cat", Other: "I have {{.PluralCount}} cats", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "Cats", PluralCount: 1, }, expectedLocalized: "I have 1 cat", }, { name: "plural count other, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "Cats", One: "I have {{.PluralCount}} cat", Other: "I have {{.PluralCount}} cats", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "Cats", PluralCount: 2, }, expectedLocalized: "I have 2 cats", }, { name: "plural count float, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "Cats", One: "I have {{.PluralCount}} cat", Other: "I have {{.PluralCount}} cats", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "Cats", PluralCount: "2.5", }, expectedLocalized: "I have 2.5 cats", }, { name: "plural count one, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ PluralCount: 1, DefaultMessage: &Message{ ID: "Cats", One: "I have {{.PluralCount}} cat", Other: "I have {{.PluralCount}} cats", }, }, expectedLocalized: "I have 1 cat", }, { name: "plural count missing one, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ PluralCount: 1, DefaultMessage: &Message{ ID: "Cats", Other: "I have {{.PluralCount}} cats", }, }, expectedLocalized: "I have 1 cats", expectedErr: pluralFormNotFoundError{messageID: "Cats", pluralForm: plural.One}, }, { name: "plural count missing other, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ PluralCount: 2, DefaultMessage: &Message{ ID: "Cats", One: "I have {{.PluralCount}} cat", }, }, expectedLocalized: "", expectedErr: pluralFormNotFoundError{messageID: "Cats", pluralForm: plural.Other}, }, { name: "plural count other, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ PluralCount: 2, DefaultMessage: &Message{ ID: "Cats", One: "I have {{.PluralCount}} cat", Other: "I have {{.PluralCount}} cats", }, }, expectedLocalized: "I have 2 cats", }, { name: "plural count float, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ PluralCount: "2.5", DefaultMessage: &Message{ ID: "Cats", One: "I have {{.PluralCount}} cat", Other: "I have {{.PluralCount}} cats", }, }, expectedLocalized: "I have 2.5 cats", }, { name: "template data, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "HelloPerson", Other: "Hello {{.Person}}", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "HelloPerson", TemplateData: map[string]string{ "Person": "Nick", }, }, expectedLocalized: "Hello Nick", }, { name: "template data, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ ID: "HelloPerson", Other: "Hello {{.Person}}", }, TemplateData: map[string]string{ "Person": "Nick", }, }, expectedLocalized: "Hello Nick", }, { name: "template data, custom delims, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "HelloPerson", Other: "Hello <<.Person>>", LeftDelim: "<<", RightDelim: ">>", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "HelloPerson", TemplateData: map[string]string{ "Person": "Nick", }, }, expectedLocalized: "Hello Nick", }, { name: "template data, custom delims, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ ID: "HelloPerson", Other: "Hello <<.Person>>", LeftDelim: "<<", RightDelim: ">>", }, TemplateData: map[string]string{ "Person": "Nick", }, }, expectedLocalized: "Hello Nick", }, { name: "template data, plural count one, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "PersonCats", One: "{{.Person}} has {{.Count}} cat", Other: "{{.Person}} has {{.Count}} cats", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "PersonCats", TemplateData: map[string]interface{}{ "Person": "Nick", "Count": 1, }, PluralCount: 1, }, expectedLocalized: "Nick has 1 cat", }, { name: "template data, plural count other, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "PersonCats", One: "{{.Person}} has {{.Count}} cat", Other: "{{.Person}} has {{.Count}} cats", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "PersonCats", TemplateData: map[string]interface{}{ "Person": "Nick", "Count": 2, }, PluralCount: 2, }, expectedLocalized: "Nick has 2 cats", }, { name: "template data, plural count float, bundle message", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ language.English: {{ ID: "PersonCats", One: "{{.Person}} has {{.Count}} cat", Other: "{{.Person}} has {{.Count}} cats", }}, }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ MessageID: "PersonCats", TemplateData: map[string]interface{}{ "Person": "Nick", "Count": "2.5", }, PluralCount: "2.5", }, expectedLocalized: "Nick has 2.5 cats", }, { name: "template data, plural count one, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ ID: "PersonCats", One: "{{.Person}} has {{.Count}} cat", Other: "{{.Person}} has {{.Count}} cats", }, TemplateData: map[string]interface{}{ "Person": "Nick", "Count": 1, }, PluralCount: 1, }, expectedLocalized: "Nick has 1 cat", }, { name: "template data, plural count other, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ ID: "PersonCats", One: "{{.Person}} has {{.Count}} cat", Other: "{{.Person}} has {{.Count}} cats", }, TemplateData: map[string]interface{}{ "Person": "Nick", "Count": 2, }, PluralCount: 2, }, expectedLocalized: "Nick has 2 cats", }, { name: "template data, plural count float, default message", defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ ID: "PersonCats", One: "{{.Person}} has {{.Count}} cat", Other: "{{.Person}} has {{.Count}} cats", }, TemplateData: map[string]interface{}{ "Person": "Nick", "Count": "2.5", }, PluralCount: "2.5", }, expectedLocalized: "Nick has 2.5 cats", }, { name: "test slow path", defaultLanguage: language.Spanish, messages: map[language.Tag][]*Message{ language.English: {{ ID: "Hello", Other: "Hello!", }}, language.AmericanEnglish: {{ ID: "Goodbye", Other: "Goodbye!", }}, }, acceptLangs: []string{"en-US"}, conf: &LocalizeConfig{ MessageID: "Hello", }, expectedLocalized: "Hello!", }, { name: "test slow path default message", defaultLanguage: language.Spanish, messages: map[language.Tag][]*Message{ language.English: {{ ID: "Goodbye", Other: "Goodbye!", }}, language.AmericanEnglish: {{ ID: "Goodbye", Other: "Goodbye!", }}, }, acceptLangs: []string{"en-US"}, conf: &LocalizeConfig{ DefaultMessage: &Message{ ID: "Hello", Other: "Hola!", }, }, expectedLocalized: "Hola!", }, { name: "test slow path no message", defaultLanguage: language.Spanish, messages: map[language.Tag][]*Message{ language.English: {{ ID: "Goodbye", Other: "Goodbye!", }}, language.AmericanEnglish: {{ ID: "Goodbye", Other: "Goodbye!", }}, }, acceptLangs: []string{"en-US"}, conf: &LocalizeConfig{ MessageID: "Hello", }, expectedErr: &MessageNotFoundErr{messageID: "Hello"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { bundle := NewBundle(test.defaultLanguage) for tag, messages := range test.messages { if err := bundle.AddMessages(tag, messages...); err != nil { t.Fatal(err) } } check := func(localized string, err error) { t.Helper() if !reflect.DeepEqual(err, test.expectedErr) { t.Errorf("expected error %#v; got %#v", test.expectedErr, err) } if localized != test.expectedLocalized { t.Errorf("expected localized string %q; got %q", test.expectedLocalized, localized) } } localizer := NewLocalizer(bundle, test.acceptLangs...) check(localizer.Localize(test.conf)) if test.conf.DefaultMessage != nil && reflect.DeepEqual(test.conf, &LocalizeConfig{DefaultMessage: test.conf.DefaultMessage}) { check(localizer.LocalizeMessage(test.conf.DefaultMessage)) } // if test.conf.MessageID != "" && reflect.DeepEqual(test.conf, &LocalizeConfig{MessageID: test.conf.MessageID}) { // check(localizer.LocalizeMessageID(test.conf.MessageID)) // } }) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/message.go000066400000000000000000000117031350541200600231760ustar00rootroot00000000000000package i18n import ( "fmt" "strings" ) // Message is a string that can be localized. type Message struct { // ID uniquely identifies the message. ID string // Hash uniquely identifies the content of the message // that this message was translated from. Hash string // Description describes the message to give additional // context to translators that may be relevant for translation. Description string // LeftDelim is the left Go template delimiter. LeftDelim string // RightDelim is the right Go template delimiter.`` RightDelim string // Zero is the content of the message for the CLDR plural form "zero". Zero string // One is the content of the message for the CLDR plural form "one". One string // Two is the content of the message for the CLDR plural form "two". Two string // Few is the content of the message for the CLDR plural form "few". Few string // Many is the content of the message for the CLDR plural form "many". Many string // Other is the content of the message for the CLDR plural form "other". Other string } // NewMessage parses data and returns a new message. func NewMessage(data interface{}) (*Message, error) { m := &Message{} if err := m.unmarshalInterface(data); err != nil { return nil, err } return m, nil } // MustNewMessage is similar to NewMessage except it panics if an error happens. func MustNewMessage(data interface{}) *Message { m, err := NewMessage(data) if err != nil { panic(err) } return m } // unmarshalInterface unmarshals a message from data. func (m *Message) unmarshalInterface(v interface{}) error { strdata, err := stringMap(v) if err != nil { return err } for k, v := range strdata { switch strings.ToLower(k) { case "id": m.ID = v case "description": m.Description = v case "hash": m.Hash = v case "leftdelim": m.LeftDelim = v case "rightdelim": m.RightDelim = v case "zero": m.Zero = v case "one": m.One = v case "two": m.Two = v case "few": m.Few = v case "many": m.Many = v case "other": m.Other = v } } return nil } type keyTypeErr struct { key interface{} } func (err *keyTypeErr) Error() string { return fmt.Sprintf("expected key to be a string but got %#v", err.key) } type valueTypeErr struct { value interface{} } func (err *valueTypeErr) Error() string { return fmt.Sprintf("unsupported type %#v", err.value) } func stringMap(v interface{}) (map[string]string, error) { switch value := v.(type) { case string: return map[string]string{ "other": value, }, nil case map[string]string: return value, nil case map[string]interface{}: strdata := make(map[string]string, len(value)) for k, v := range value { err := stringSubmap(k, v, strdata) if err != nil { return nil, err } } return strdata, nil case map[interface{}]interface{}: strdata := make(map[string]string, len(value)) for k, v := range value { kstr, ok := k.(string) if !ok { return nil, &keyTypeErr{key: k} } err := stringSubmap(kstr, v, strdata) if err != nil { return nil, err } } return strdata, nil default: return nil, &valueTypeErr{value: value} } } func stringSubmap(k string, v interface{}, strdata map[string]string) error { if k == "translation" { switch vt := v.(type) { case string: strdata["other"] = vt default: v1Message, err := stringMap(v) if err != nil { return err } for kk, vv := range v1Message { strdata[kk] = vv } } return nil } switch vt := v.(type) { case string: strdata[k] = vt return nil case nil: return nil default: return fmt.Errorf("expected value for key %q be a string but got %#v", k, v) } } // isMessage tells whether the given data is a message, or a map containing // nested messages. // A map is assumed to be a message if it contains any of the "reserved" keys: // "id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other" // with a string value. // e.g., // - {"message": {"description": "world"}} is a message // - {"message": {"description": "world", "foo": "bar"}} is a message ("foo" key is ignored) // - {"notmessage": {"description": {"hello": "world"}}} is not // - {"notmessage": {"foo": "bar"}} is not func isMessage(v interface{}) bool { reservedKeys := []string{"id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"} switch data := v.(type) { case string: return true case map[string]interface{}: for _, key := range reservedKeys { val, ok := data[key] if !ok { continue } _, ok = val.(string) if !ok { continue } // v is a message if it contains a "reserved" key holding a string value return true } case map[interface{}]interface{}: for _, key := range reservedKeys { val, ok := data[key] if !ok { continue } _, ok = val.(string) if !ok { continue } // v is a message if it contains a "reserved" key holding a string value return true } } return false } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/message_template.go000066400000000000000000000037111350541200600250710ustar00rootroot00000000000000package i18n import ( "fmt" "text/template" "github.com/nicksnyder/go-i18n/v2/internal" "github.com/nicksnyder/go-i18n/v2/internal/plural" ) // MessageTemplate is an executable template for a message. type MessageTemplate struct { *Message PluralTemplates map[plural.Form]*internal.Template } // NewMessageTemplate returns a new message template. func NewMessageTemplate(m *Message) *MessageTemplate { pluralTemplates := map[plural.Form]*internal.Template{} setPluralTemplate(pluralTemplates, plural.Zero, m.Zero, m.LeftDelim, m.RightDelim) setPluralTemplate(pluralTemplates, plural.One, m.One, m.LeftDelim, m.RightDelim) setPluralTemplate(pluralTemplates, plural.Two, m.Two, m.LeftDelim, m.RightDelim) setPluralTemplate(pluralTemplates, plural.Few, m.Few, m.LeftDelim, m.RightDelim) setPluralTemplate(pluralTemplates, plural.Many, m.Many, m.LeftDelim, m.RightDelim) setPluralTemplate(pluralTemplates, plural.Other, m.Other, m.LeftDelim, m.RightDelim) if len(pluralTemplates) == 0 { return nil } return &MessageTemplate{ Message: m, PluralTemplates: pluralTemplates, } } func setPluralTemplate(pluralTemplates map[plural.Form]*internal.Template, pluralForm plural.Form, src, leftDelim, rightDelim string) { if src != "" { pluralTemplates[pluralForm] = &internal.Template{ Src: src, LeftDelim: leftDelim, RightDelim: rightDelim, } } } type pluralFormNotFoundError struct { pluralForm plural.Form messageID string } func (e pluralFormNotFoundError) Error() string { return fmt.Sprintf("message %q has no plural form %q", e.messageID, e.pluralForm) } // Execute executes the template for the plural form and template data. func (mt *MessageTemplate) Execute(pluralForm plural.Form, data interface{}, funcs template.FuncMap) (string, error) { t := mt.PluralTemplates[pluralForm] if t == nil { return "", pluralFormNotFoundError{ pluralForm: pluralForm, messageID: mt.Message.ID, } } return t.Execute(funcs, data) } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/message_template_test.go000066400000000000000000000015551350541200600261340ustar00rootroot00000000000000package i18n import ( "reflect" "testing" "github.com/nicksnyder/go-i18n/v2/internal/plural" ) func TestMessageTemplate(t *testing.T) { mt := NewMessageTemplate(&Message{ID: "HelloWorld", Other: "Hello World"}) if mt.PluralTemplates[plural.Other].Src != "Hello World" { panic(mt.PluralTemplates) } } func TestNilMessageTemplate(t *testing.T) { if mt := NewMessageTemplate(&Message{ID: "HelloWorld"}); mt != nil { panic(mt) } } func TestMessageTemplatePluralFormMissing(t *testing.T) { mt := NewMessageTemplate(&Message{ID: "HelloWorld", Other: "Hello World"}) s, err := mt.Execute(plural.Few, nil, nil) if s != "" { t.Errorf("expected %q; got %q", "", s) } expectedErr := pluralFormNotFoundError{pluralForm: plural.Few, messageID: "HelloWorld"} if !reflect.DeepEqual(err, expectedErr) { t.Errorf("expected error %#v; got %#v", expectedErr, err) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/message_test.go000066400000000000000000000067431350541200600242450ustar00rootroot00000000000000package i18n import ( "reflect" "testing" ) func TestNewMessage(t *testing.T) { tests := []struct { name string data interface{} message *Message err error }{ { name: "string", data: "other", message: &Message{ Other: "other", }, }, { name: "nil value", data: map[interface{}]interface{}{ "ID": "id", "Zero": nil, "Other": "other", }, message: &Message{ ID: "id", Other: "other", }, }, { name: "map[string]string", data: map[string]string{ "ID": "id", "Hash": "hash", "Description": "description", "LeftDelim": "leftdelim", "RightDelim": "rightdelim", "Zero": "zero", "One": "one", "Two": "two", "Few": "few", "Many": "many", "Other": "other", }, message: &Message{ ID: "id", Hash: "hash", Description: "description", LeftDelim: "leftdelim", RightDelim: "rightdelim", Zero: "zero", One: "one", Two: "two", Few: "few", Many: "many", Other: "other", }, }, { name: "map[string]interface{}", data: map[string]interface{}{ "ID": "id", "Hash": "hash", "Description": "description", "LeftDelim": "leftdelim", "RightDelim": "rightdelim", "Zero": "zero", "One": "one", "Two": "two", "Few": "few", "Many": "many", "Other": "other", }, message: &Message{ ID: "id", Hash: "hash", Description: "description", LeftDelim: "leftdelim", RightDelim: "rightdelim", Zero: "zero", One: "one", Two: "two", Few: "few", Many: "many", Other: "other", }, }, { name: "map[interface{}]interface{}", data: map[interface{}]interface{}{ "ID": "id", "Hash": "hash", "Description": "description", "LeftDelim": "leftdelim", "RightDelim": "rightdelim", "Zero": "zero", "One": "one", "Two": "two", "Few": "few", "Many": "many", "Other": "other", }, message: &Message{ ID: "id", Hash: "hash", Description: "description", LeftDelim: "leftdelim", RightDelim: "rightdelim", Zero: "zero", One: "one", Two: "two", Few: "few", Many: "many", Other: "other", }, }, { name: "map[int]int", data: map[interface{}]interface{}{ 1: 2, }, err: &keyTypeErr{key: 1}, }, { name: "int", data: 1, err: &valueTypeErr{value: 1}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual, err := NewMessage(test.data) if !reflect.DeepEqual(err, test.err) { t.Fatalf("expected %#v; got %#v", test.err, err) } if !reflect.DeepEqual(actual, test.message) { t.Fatalf("\nexpected\n%#v\ngot\n%#v", test.message, actual) } }) } } func TestKeyTypeErr(t *testing.T) { expected := "expected key to be a string but got 1" if actual := (&keyTypeErr{key: 1}).Error(); actual != expected { t.Fatalf("expected %#v; got %#v", expected, actual) } } func TestValueTypeErr(t *testing.T) { expected := "unsupported type 1" if actual := (&valueTypeErr{value: 1}).Error(); actual != expected { t.Fatalf("expected %#v; got %#v", expected, actual) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/parse.go000066400000000000000000000075371350541200600226760ustar00rootroot00000000000000package i18n import ( "encoding/json" "errors" "fmt" "os" "golang.org/x/text/language" ) // MessageFile represents a parsed message file. type MessageFile struct { Path string Tag language.Tag Format string Messages []*Message } // ParseMessageFileBytes returns the messages parsed from file. func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]UnmarshalFunc) (*MessageFile, error) { lang, format := parsePath(path) tag := language.Make(lang) messageFile := &MessageFile{ Path: path, Tag: tag, Format: format, } if len(buf) == 0 { return messageFile, nil } unmarshalFunc := unmarshalFuncs[messageFile.Format] if unmarshalFunc == nil { if messageFile.Format == "json" { unmarshalFunc = json.Unmarshal } else { return nil, fmt.Errorf("no unmarshaler registered for %s", messageFile.Format) } } var err error var raw interface{} if err = unmarshalFunc(buf, &raw); err != nil { return nil, err } if messageFile.Messages, err = recGetMessages(raw, isMessage(raw), true); err != nil { return nil, err } return messageFile, nil } const nestedSeparator = "." var errInvalidTranslationFile = errors.New("invalid translation file, expected key-values, got a single value") // recGetMessages looks for translation messages inside "raw" parameter, // scanning nested maps using recursion. func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Message, error) { var messages []*Message var err error switch data := raw.(type) { case string: if isInitialCall { return nil, errInvalidTranslationFile } m, err := NewMessage(data) return []*Message{m}, err case map[string]interface{}: if isMapMessage { m, err := NewMessage(data) return []*Message{m}, err } messages = make([]*Message, 0, len(data)) for id, data := range data { // recursively scan map items messages, err = addChildMessages(id, data, messages) if err != nil { return nil, err } } case map[interface{}]interface{}: if isMapMessage { m, err := NewMessage(data) return []*Message{m}, err } messages = make([]*Message, 0, len(data)) for id, data := range data { strid, ok := id.(string) if !ok { return nil, fmt.Errorf("expected key to be string but got %#v", id) } // recursively scan map items messages, err = addChildMessages(strid, data, messages) if err != nil { return nil, err } } case []interface{}: // Backward compatibility for v1 file format. messages = make([]*Message, 0, len(data)) for _, data := range data { // recursively scan slice items childMessages, err := recGetMessages(data, isMessage(data), false) if err != nil { return nil, err } messages = append(messages, childMessages...) } default: return nil, fmt.Errorf("unsupported file format %T", raw) } return messages, nil } func addChildMessages(id string, data interface{}, messages []*Message) ([]*Message, error) { isChildMessage := isMessage(data) childMessages, err := recGetMessages(data, isChildMessage, false) if err != nil { return nil, err } for _, m := range childMessages { if isChildMessage { if m.ID == "" { m.ID = id // start with innermost key } } else { m.ID = id + nestedSeparator + m.ID // update ID with each nested key on the way } messages = append(messages, m) } return messages, nil } func parsePath(path string) (langTag, format string) { formatStartIdx := -1 for i := len(path) - 1; i >= 0; i-- { c := path[i] if os.IsPathSeparator(c) { if formatStartIdx != -1 { langTag = path[i+1 : formatStartIdx] } return } if path[i] == '.' { if formatStartIdx != -1 { langTag = path[i+1 : formatStartIdx] return } if formatStartIdx == -1 { format = path[i+1:] formatStartIdx = i } } } if formatStartIdx != -1 { langTag = path[:formatStartIdx] } return } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/i18n/parse_test.go000066400000000000000000000120071350541200600237210ustar00rootroot00000000000000package i18n import ( "reflect" "sort" "testing" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) func TestParseMessageFileBytes(t *testing.T) { testCases := []struct { name string file string path string unmarshalFuncs map[string]UnmarshalFunc messageFile *MessageFile err error }{ { name: "basic test", file: `{"hello": "world"}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "hello", Other: "world", }}, }, }, { name: "basic test with dot separator in key", file: `{"prepended.hello": "world"}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "prepended.hello", Other: "world", }}, }, }, { name: "invalid test (no key)", file: `"hello"`, path: "en.json", err: errInvalidTranslationFile, }, { name: "nested test", file: `{"nested": {"hello": "world"}}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "nested.hello", Other: "world", }}, }, }, { name: "basic test with description", file: `{"notnested": {"description": "world"}}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "notnested", Description: "world", }}, }, }, { name: "basic test with id", file: `{"key": {"id": "forced.id"}}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "forced.id", }}, }, }, { name: "basic test with description and dummy", file: `{"notnested": {"description": "world", "dummy": "nothing"}}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "notnested", Description: "world", }}, }, }, { name: "deeply nested test", file: `{"outer": {"nested": {"inner": "value"}}}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "outer.nested.inner", Other: "value", }}, }, }, { name: "multiple nested test", file: `{"nested": {"hello": "world", "bye": "all"}}`, path: "en.json", messageFile: &MessageFile{ Path: "en.json", Tag: language.English, Format: "json", Messages: []*Message{{ ID: "nested.hello", Other: "world", }, { ID: "nested.bye", Other: "all", }}, }, }, { name: "YAML nested test", file: ` outer: nested: inner: "value"`, path: "en.yaml", unmarshalFuncs: map[string]UnmarshalFunc{"yaml": yaml.Unmarshal}, messageFile: &MessageFile{ Path: "en.yaml", Tag: language.English, Format: "yaml", Messages: []*Message{{ ID: "outer.nested.inner", Other: "value", }}, }, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { actual, err := ParseMessageFileBytes([]byte(testCase.file), testCase.path, testCase.unmarshalFuncs) if (err == nil && testCase.err != nil) || (err != nil && testCase.err == nil) || (err != nil && testCase.err != nil && err.Error() != testCase.err.Error()) { t.Fatalf("expected error %#v; got %#v", testCase.err, err) } if testCase.messageFile == nil && actual != nil || testCase.messageFile != nil && actual == nil { t.Fatalf("expected message file %#v; got %#v", testCase.messageFile, actual) } if testCase.messageFile != nil { if actual.Path != testCase.messageFile.Path { t.Errorf("expected path %q; got %q", testCase.messageFile.Path, actual.Path) } if actual.Tag != testCase.messageFile.Tag { t.Errorf("expected tag %q; got %q", testCase.messageFile.Tag, actual.Tag) } if actual.Format != testCase.messageFile.Format { t.Errorf("expected format %q; got %q", testCase.messageFile.Format, actual.Format) } if !equalMessages(actual.Messages, testCase.messageFile.Messages) { t.Errorf("expected %#v; got %#v", testCase.messageFile.Messages, actual.Messages) } } }) } } // equalMessages compares two slices of messages, ignoring private fields and order. // Sorts both input slices, which are therefore modified by this function. func equalMessages(m1, m2 []*Message) bool { if len(m1) != len(m2) { return false } var less = func(m []*Message) func(int, int) bool { return func(i, j int) bool { return m[i].ID < m[j].ID } } sort.Slice(m1, less(m1)) sort.Slice(m2, less(m2)) for i, m := range m1 { if !reflect.DeepEqual(m, m2[i]) { return false } } return true } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/000077500000000000000000000000001350541200600222565ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/000077500000000000000000000000001350541200600235555ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/codegen/000077500000000000000000000000001350541200600251615ustar00rootroot00000000000000golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/codegen/generate.sh000066400000000000000000000003031350541200600273030ustar00rootroot00000000000000#!/bin/sh OUT=.. go build && ./codegen -cout $OUT/rule_gen.go -tout $OUT/rule_gen_test.go && \ gofmt -w=true $OUT/rule_gen.go && \ gofmt -w=true $OUT/rule_gen_test.go && \ rm codegen golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/codegen/main.go000066400000000000000000000065051350541200600264420ustar00rootroot00000000000000package main import ( "encoding/xml" "flag" "fmt" "io/ioutil" "os" "text/template" ) var usage = `%[1]s generates Go code to support CLDR plural rules. Usage: %[1]s [options] Options: ` func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, usage, os.Args[0]) flag.PrintDefaults() } var in, cout, tout string flag.StringVar(&in, "i", "plurals.xml", "the input XML file containing CLDR plural rules") flag.StringVar(&cout, "cout", "", "the code output file") flag.StringVar(&tout, "tout", "", "the test output file") flag.BoolVar(&verbose, "v", false, "verbose output") flag.Parse() buf, err := ioutil.ReadFile(in) if err != nil { fatalf("failed to read file: %s", err) } var data SupplementalData if err := xml.Unmarshal(buf, &data); err != nil { fatalf("failed to unmarshal xml: %s", err) } count := 0 for _, pg := range data.PluralGroups { count += len(pg.SplitLocales()) } infof("parsed %d locales", count) if cout != "" { file := openWritableFile(cout) if err := codeTemplate.Execute(file, data); err != nil { fatalf("unable to execute code template because %s", err) } else { infof("generated %s", cout) } } else { infof("not generating code file (use -cout)") } if tout != "" { file := openWritableFile(tout) if err := testTemplate.Execute(file, data); err != nil { fatalf("unable to execute test template because %s", err) } else { infof("generated %s", tout) } } else { infof("not generating test file (use -tout)") } } func openWritableFile(name string) *os.File { file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { fatalf("failed to write file %s because %s", name, err) } return file } var codeTemplate = template.Must(template.New("rule").Parse(`// This file is generated by i18n/plural/codegen/generate.sh; DO NOT EDIT package plural // DefaultRules returns a map of Rules generated from CLDR language data. func DefaultRules() Rules { rules := Rules{} {{range .PluralGroups}} addPluralRules(rules, {{printf "%#v" .SplitLocales}}, &Rule{ PluralForms: newPluralFormSet({{range $i, $e := .PluralRules}}{{if $i}}, {{end}}{{$e.CountTitle}}{{end}}), PluralFormFunc: func(ops *Operands) Form { {{range .PluralRules}}{{if .GoCondition}} // {{.Condition}} if {{.GoCondition}} { return {{.CountTitle}} }{{end}}{{end}} return Other }, }){{end}} return rules } `)) var testTemplate = template.Must(template.New("rule").Parse(`// This file is generated by i18n/plural/codegen/generate.sh; DO NOT EDIT package plural import "testing" {{range .PluralGroups}} func Test{{.Name}}(t *testing.T) { var tests []pluralFormTest {{range .PluralRules}} {{if .IntegerExamples}}tests = appendIntegerTests(tests, {{.CountTitle}}, {{printf "%#v" .IntegerExamples}}){{end}} {{if .DecimalExamples}}tests = appendDecimalTests(tests, {{.CountTitle}}, {{printf "%#v" .DecimalExamples}}){{end}} {{end}} locales := {{printf "%#v" .SplitLocales}} for _, locale := range locales { runTests(t, locale, tests) } } {{end}} `)) func infof(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, format+"\n", args...) } var verbose bool func verbosef(format string, args ...interface{}) { if verbose { infof(format, args...) } } func fatalf(format string, args ...interface{}) { infof("fatal: "+format+"\n", args...) os.Exit(1) } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/codegen/plurals.xml000066400000000000000000000542451350541200600273770ustar00rootroot00000000000000 @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 0,1 @integer 0, 1 @decimal 0.0~1.5 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 0..1 @integer 0, 1 @decimal 0.0~1.5 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 1 and v = 0 @integer 1 @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0 @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6 @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, … @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, … @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, … n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, … @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, … n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000 i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000 n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000 @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04 n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00 @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 1 and v = 0 @integer 1 v != 0 or n = 0 or n % 100 = 2..19 @integer 0, 2~16, 102, 1002, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … @integer 20~35, 100, 1000, 10000, 100000, 1000000, … v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, … v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, … @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000 n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000 n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00 @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, … v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, … v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, … @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 1 and v = 0 @integer 1 i = 2 and v = 0 @integer 2 v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, … @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … i = 1 and v = 0 @integer 1 i = 2..4 and v = 0 @integer 2~4 v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … i = 1 and v = 0 @integer 1 v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, … n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, … n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, … n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, … n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, … f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, … @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, … n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, … @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, … n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, … n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, … n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, … @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, … n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000 n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000 n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000 @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, … v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, … v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, … v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … @integer 3~10, 13~19, 23, 103, 1003, … n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000 n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000 n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, … n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, … @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000 n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000 n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000 n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000 @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, … n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000 n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000 n % 100 = 2,22,42,62,82 or n%1000 = 0 and n%100000=1000..20000,40000,60000,80000 or n!=0 and n%1000000=100000@integer 2, 22, 42, 62, 82, 102, 122, 142, 1002, … @decimal 2.0, 22.0, 42.0, 62.0, 82.0, 102.0, 122.0, 142.0, 1002.0, … n % 100 = 3,23,43,63,83 @integer 3, 23, 43, 63, 83, 103, 123, 143, 1003, … @decimal 3.0, 23.0, 43.0, 63.0, 83.0, 103.0, 123.0, 143.0, 1003.0, … n != 1 and n % 100 = 1,21,41,61,81 @integer 21, 41, 61, 81, 101, 121, 141, 161, 1001, … @decimal 21.0, 41.0, 61.0, 81.0, 101.0, 121.0, 141.0, 161.0, 1001.0, … @integer 4~19, 100, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000000.0, … golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/codegen/xml.go000066400000000000000000000100341350541200600263060ustar00rootroot00000000000000package main import ( "encoding/xml" "fmt" "regexp" "strings" ) // SupplementalData is the top level struct of plural.xml type SupplementalData struct { XMLName xml.Name `xml:"supplementalData"` PluralGroups []PluralGroup `xml:"plurals>pluralRules"` } // PluralGroup is a group of locales with the same plural rules. type PluralGroup struct { Locales string `xml:"locales,attr"` PluralRules []PluralRule `xml:"pluralRule"` } // Name returns a unique name for this plural group. func (pg *PluralGroup) Name() string { n := strings.Title(pg.Locales) return strings.Replace(n, " ", "", -1) } // SplitLocales returns all the locales in the PluralGroup as a slice. func (pg *PluralGroup) SplitLocales() []string { return strings.Split(pg.Locales, " ") } // PluralRule is the rule for a single plural form. type PluralRule struct { Count string `xml:"count,attr"` Rule string `xml:",innerxml"` } // CountTitle returns the title case of the PluralRule's count. func (pr *PluralRule) CountTitle() string { return strings.Title(pr.Count) } // Condition returns the condition where the PluralRule applies. func (pr *PluralRule) Condition() string { i := strings.Index(pr.Rule, "@") return pr.Rule[:i] } // Examples returns the integer and decimal exmaples for the PLuralRule. func (pr *PluralRule) Examples() (integer []string, decimal []string) { ex := strings.Replace(pr.Rule, ", …", "", -1) ddelim := "@decimal" if i := strings.Index(ex, ddelim); i > 0 { dex := strings.TrimSpace(ex[i+len(ddelim):]) decimal = strings.Split(dex, ", ") ex = ex[:i] } idelim := "@integer" if i := strings.Index(ex, idelim); i > 0 { iex := strings.TrimSpace(ex[i+len(idelim):]) integer = strings.Split(iex, ", ") } return integer, decimal } // IntegerExamples returns the integer exmaples for the PLuralRule. func (pr *PluralRule) IntegerExamples() []string { integer, _ := pr.Examples() return integer } // DecimalExamples returns the decimal exmaples for the PLuralRule. func (pr *PluralRule) DecimalExamples() []string { _, decimal := pr.Examples() return decimal } var relationRegexp = regexp.MustCompile(`([niftvw])(?:\s*%\s*([0-9]+))?\s*(!=|=)(.*)`) // GoCondition converts the XML condition to valid Go code. func (pr *PluralRule) GoCondition() string { var ors []string for _, and := range strings.Split(pr.Condition(), "or") { var ands []string for _, relation := range strings.Split(and, "and") { parts := relationRegexp.FindStringSubmatch(relation) if parts == nil { continue } lvar, lmod, op, rhs := strings.Title(parts[1]), parts[2], parts[3], strings.TrimSpace(parts[4]) if op == "=" { op = "==" } lvar = "ops." + lvar var rhor []string var rany []string for _, rh := range strings.Split(rhs, ",") { if parts := strings.Split(rh, ".."); len(parts) == 2 { from, to := parts[0], parts[1] if lvar == "ops.N" { if lmod != "" { rhor = append(rhor, fmt.Sprintf("ops.NModInRange(%s, %s, %s)", lmod, from, to)) } else { rhor = append(rhor, fmt.Sprintf("ops.NInRange(%s, %s)", from, to)) } } else if lmod != "" { rhor = append(rhor, fmt.Sprintf("intInRange(%s %% %s, %s, %s)", lvar, lmod, from, to)) } else { rhor = append(rhor, fmt.Sprintf("intInRange(%s, %s, %s)", lvar, from, to)) } } else { rany = append(rany, rh) } } if len(rany) > 0 { rh := strings.Join(rany, ",") if lvar == "ops.N" { if lmod != "" { rhor = append(rhor, fmt.Sprintf("ops.NModEqualsAny(%s, %s)", lmod, rh)) } else { rhor = append(rhor, fmt.Sprintf("ops.NEqualsAny(%s)", rh)) } } else if lmod != "" { rhor = append(rhor, fmt.Sprintf("intEqualsAny(%s %% %s, %s)", lvar, lmod, rh)) } else { rhor = append(rhor, fmt.Sprintf("intEqualsAny(%s, %s)", lvar, rh)) } } r := strings.Join(rhor, " || ") if len(rhor) > 1 { r = "(" + r + ")" } if op == "!=" { r = "!" + r } ands = append(ands, r) } ors = append(ors, strings.Join(ands, " && ")) } return strings.Join(ors, " ||\n") } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/doc.go000066400000000000000000000002331350541200600246470ustar00rootroot00000000000000// Package plural provides support for pluralizing messages // according to CLDR rules http://cldr.unicode.org/index/cldr-spec/plural-rules package plural golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/form.go000066400000000000000000000005361350541200600250530ustar00rootroot00000000000000package plural // Form represents a language pluralization form as defined here: // http://cldr.unicode.org/index/cldr-spec/plural-rules type Form string // All defined plural forms. const ( Invalid Form = "" Zero Form = "zero" One Form = "one" Two Form = "two" Few Form = "few" Many Form = "many" Other Form = "other" ) golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/operands.go000066400000000000000000000057241350541200600257270ustar00rootroot00000000000000package plural import ( "fmt" "strconv" "strings" ) // Operands is a representation of http://unicode.org/reports/tr35/tr35-numbers.html#Operands type Operands struct { N float64 // absolute value of the source number (integer and decimals) I int64 // integer digits of n V int64 // number of visible fraction digits in n, with trailing zeros W int64 // number of visible fraction digits in n, without trailing zeros F int64 // visible fractional digits in n, with trailing zeros T int64 // visible fractional digits in n, without trailing zeros } // NEqualsAny returns true if o represents an integer equal to any of the arguments. func (o *Operands) NEqualsAny(any ...int64) bool { for _, i := range any { if o.I == i && o.T == 0 { return true } } return false } // NModEqualsAny returns true if o represents an integer equal to any of the arguments modulo mod. func (o *Operands) NModEqualsAny(mod int64, any ...int64) bool { modI := o.I % mod for _, i := range any { if modI == i && o.T == 0 { return true } } return false } // NInRange returns true if o represents an integer in the closed interval [from, to]. func (o *Operands) NInRange(from, to int64) bool { return o.T == 0 && from <= o.I && o.I <= to } // NModInRange returns true if o represents an integer in the closed interval [from, to] modulo mod. func (o *Operands) NModInRange(mod, from, to int64) bool { modI := o.I % mod return o.T == 0 && from <= modI && modI <= to } // NewOperands returns the operands for number. func NewOperands(number interface{}) (*Operands, error) { switch number := number.(type) { case int: return newOperandsInt64(int64(number)), nil case int8: return newOperandsInt64(int64(number)), nil case int16: return newOperandsInt64(int64(number)), nil case int32: return newOperandsInt64(int64(number)), nil case int64: return newOperandsInt64(number), nil case string: return newOperandsString(number) case float32, float64: return nil, fmt.Errorf("floats should be formatted into a string") default: return nil, fmt.Errorf("invalid type %T; expected integer or string", number) } } func newOperandsInt64(i int64) *Operands { if i < 0 { i = -i } return &Operands{float64(i), i, 0, 0, 0, 0} } func newOperandsString(s string) (*Operands, error) { if s[0] == '-' { s = s[1:] } n, err := strconv.ParseFloat(s, 64) if err != nil { return nil, err } ops := &Operands{N: n} parts := strings.SplitN(s, ".", 2) ops.I, err = strconv.ParseInt(parts[0], 10, 64) if err != nil { return nil, err } if len(parts) == 1 { return ops, nil } fraction := parts[1] ops.V = int64(len(fraction)) for i := ops.V - 1; i >= 0; i-- { if fraction[i] != '0' { ops.W = i + 1 break } } if ops.V > 0 { f, err := strconv.ParseInt(fraction, 10, 0) if err != nil { return nil, err } ops.F = f } if ops.W > 0 { t, err := strconv.ParseInt(fraction[:ops.W], 10, 0) if err != nil { return nil, err } ops.T = t } return ops, nil } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/operands_test.go000066400000000000000000000024651350541200600267650ustar00rootroot00000000000000package plural import ( "reflect" "testing" ) func TestNewOperands(t *testing.T) { tests := []struct { input interface{} ops *Operands err bool }{ {int64(0), &Operands{0.0, 0, 0, 0, 0, 0}, false}, {int64(1), &Operands{1.0, 1, 0, 0, 0, 0}, false}, {"0", &Operands{0.0, 0, 0, 0, 0, 0}, false}, {"1", &Operands{1.0, 1, 0, 0, 0, 0}, false}, {"1.0", &Operands{1.0, 1, 1, 0, 0, 0}, false}, {"1.00", &Operands{1.0, 1, 2, 0, 0, 0}, false}, {"1.3", &Operands{1.3, 1, 1, 1, 3, 3}, false}, {"1.30", &Operands{1.3, 1, 2, 1, 30, 3}, false}, {"1.03", &Operands{1.03, 1, 2, 2, 3, 3}, false}, {"1.230", &Operands{1.23, 1, 3, 2, 230, 23}, false}, {"20.0230", &Operands{20.023, 20, 4, 3, 230, 23}, false}, {20.0230, nil, true}, } for _, test := range tests { ops, err := NewOperands(test.input) if err != nil && !test.err { t.Errorf("NewOperands(%#v) unexpected error: %s", test.input, err) } else if err == nil && test.err { t.Errorf("NewOperands(%#v) returned %#v; expected error", test.input, ops) } else if !reflect.DeepEqual(ops, test.ops) { t.Errorf("NewOperands(%#v) returned %#v; expected %#v", test.input, ops, test.ops) } } } func BenchmarkNewOperand(b *testing.B) { for i := 0; i < b.N; i++ { if _, err := NewOperands("1234.56780000"); err != nil { b.Fatal(err) } } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/rule.go000066400000000000000000000016341350541200600250570ustar00rootroot00000000000000package plural import ( "golang.org/x/text/language" ) // Rule defines the CLDR plural rules for a language. // http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html // http://unicode.org/reports/tr35/tr35-numbers.html#Operands type Rule struct { PluralForms map[Form]struct{} PluralFormFunc func(*Operands) Form } func addPluralRules(rules Rules, ids []string, ps *Rule) { for _, id := range ids { if id == "root" { continue } tag := language.MustParse(id) rules[tag] = ps } } func newPluralFormSet(pluralForms ...Form) map[Form]struct{} { set := make(map[Form]struct{}, len(pluralForms)) for _, plural := range pluralForms { set[plural] = struct{}{} } return set } func intInRange(i, from, to int64) bool { return from <= i && i <= to } func intEqualsAny(i int64, any ...int64) bool { for _, a := range any { if i == a { return true } } return false } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/rule_gen.go000066400000000000000000000417511350541200600257140ustar00rootroot00000000000000// This file is generated by i18n/plural/codegen/generate.sh; DO NOT EDIT package plural // DefaultRules returns a map of Rules generated from CLDR language data. func DefaultRules() Rules { rules := Rules{} addPluralRules(rules, []string{"bm", "bo", "dz", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "root", "sah", "ses", "sg", "th", "to", "vi", "wo", "yo", "yue", "zh"}, &Rule{ PluralForms: newPluralFormSet(Other), PluralFormFunc: func(ops *Operands) Form { return Other }, }) addPluralRules(rules, []string{"am", "as", "bn", "fa", "gu", "hi", "kn", "zu"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // i = 0 or n = 1 if intEqualsAny(ops.I, 0) || ops.NEqualsAny(1) { return One } return Other }, }) addPluralRules(rules, []string{"ff", "fr", "hy", "kab"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // i = 0,1 if intEqualsAny(ops.I, 0, 1) { return One } return Other }, }) addPluralRules(rules, []string{"pt"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // i = 0..1 if intInRange(ops.I, 0, 1) { return One } return Other }, }) addPluralRules(rules, []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "it", "ji", "nl", "pt_PT", "sc", "scn", "sv", "sw", "ur", "yi"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } return Other }, }) addPluralRules(rules, []string{"si"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0,1 or i = 0 and f = 1 if ops.NEqualsAny(0, 1) || intEqualsAny(ops.I, 0) && intEqualsAny(ops.F, 1) { return One } return Other }, }) addPluralRules(rules, []string{"ak", "bh", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0..1 if ops.NInRange(0, 1) { return One } return Other }, }) addPluralRules(rules, []string{"tzm"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0..1 or n = 11..99 if ops.NInRange(0, 1) || ops.NInRange(11, 99) { return One } return Other }, }) addPluralRules(rules, []string{"af", "asa", "az", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "es", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "mr", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 1 if ops.NEqualsAny(1) { return One } return Other }, }) addPluralRules(rules, []string{"da"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 1 or t != 0 and i = 0,1 if ops.NEqualsAny(1) || !intEqualsAny(ops.T, 0) && intEqualsAny(ops.I, 0, 1) { return One } return Other }, }) addPluralRules(rules, []string{"is"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 if intEqualsAny(ops.T, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) || !intEqualsAny(ops.T, 0) { return One } return Other }, }) addPluralRules(rules, []string{"mk"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) || intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) { return One } return Other }, }) addPluralRules(rules, []string{"ceb", "fil", "tl"}, &Rule{ PluralForms: newPluralFormSet(One, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I, 1, 2, 3) || intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I%10, 4, 6, 9) || !intEqualsAny(ops.V, 0) && !intEqualsAny(ops.F%10, 4, 6, 9) { return One } return Other }, }) addPluralRules(rules, []string{"lv", "prg"}, &Rule{ PluralForms: newPluralFormSet(Zero, One, Other), PluralFormFunc: func(ops *Operands) Form { // n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 if ops.NModEqualsAny(10, 0) || ops.NModInRange(100, 11, 19) || intEqualsAny(ops.V, 2) && intInRange(ops.F%100, 11, 19) { return Zero } // n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) || intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) || !intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) { return One } return Other }, }) addPluralRules(rules, []string{"lag"}, &Rule{ PluralForms: newPluralFormSet(Zero, One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0 if ops.NEqualsAny(0) { return Zero } // i = 0,1 and n != 0 if intEqualsAny(ops.I, 0, 1) && !ops.NEqualsAny(0) { return One } return Other }, }) addPluralRules(rules, []string{"ksh"}, &Rule{ PluralForms: newPluralFormSet(Zero, One, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0 if ops.NEqualsAny(0) { return Zero } // n = 1 if ops.NEqualsAny(1) { return One } return Other }, }) addPluralRules(rules, []string{"iu", "naq", "se", "sma", "smi", "smj", "smn", "sms"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Other), PluralFormFunc: func(ops *Operands) Form { // n = 1 if ops.NEqualsAny(1) { return One } // n = 2 if ops.NEqualsAny(2) { return Two } return Other }, }) addPluralRules(rules, []string{"shi"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Other), PluralFormFunc: func(ops *Operands) Form { // i = 0 or n = 1 if intEqualsAny(ops.I, 0) || ops.NEqualsAny(1) { return One } // n = 2..10 if ops.NInRange(2, 10) { return Few } return Other }, }) addPluralRules(rules, []string{"mo", "ro"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Other), PluralFormFunc: func(ops *Operands) Form { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } // v != 0 or n = 0 or n % 100 = 2..19 if !intEqualsAny(ops.V, 0) || ops.NEqualsAny(0) || ops.NModInRange(100, 2, 19) { return Few } return Other }, }) addPluralRules(rules, []string{"bs", "hr", "sh", "sr"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) || intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) { return One } // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) || intInRange(ops.F%10, 2, 4) && !intInRange(ops.F%100, 12, 14) { return Few } return Other }, }) addPluralRules(rules, []string{"gd"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Few, Other), PluralFormFunc: func(ops *Operands) Form { // n = 1,11 if ops.NEqualsAny(1, 11) { return One } // n = 2,12 if ops.NEqualsAny(2, 12) { return Two } // n = 3..10,13..19 if ops.NInRange(3, 10) || ops.NInRange(13, 19) { return Few } return Other }, }) addPluralRules(rules, []string{"sl"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Few, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i % 100 = 1 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) { return One } // v = 0 and i % 100 = 2 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) { return Two } // v = 0 and i % 100 = 3..4 or v != 0 if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) || !intEqualsAny(ops.V, 0) { return Few } return Other }, }) addPluralRules(rules, []string{"dsb", "hsb"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Few, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i % 100 = 1 or f % 100 = 1 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) || intEqualsAny(ops.F%100, 1) { return One } // v = 0 and i % 100 = 2 or f % 100 = 2 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) || intEqualsAny(ops.F%100, 2) { return Two } // v = 0 and i % 100 = 3..4 or f % 100 = 3..4 if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) || intInRange(ops.F%100, 3, 4) { return Few } return Other }, }) addPluralRules(rules, []string{"he", "iw"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Many, Other), PluralFormFunc: func(ops *Operands) Form { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } // i = 2 and v = 0 if intEqualsAny(ops.I, 2) && intEqualsAny(ops.V, 0) { return Two } // v = 0 and n != 0..10 and n % 10 = 0 if intEqualsAny(ops.V, 0) && !ops.NInRange(0, 10) && ops.NModEqualsAny(10, 0) { return Many } return Other }, }) addPluralRules(rules, []string{"cs", "sk"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } // i = 2..4 and v = 0 if intInRange(ops.I, 2, 4) && intEqualsAny(ops.V, 0) { return Few } // v != 0 if !intEqualsAny(ops.V, 0) { return Many } return Other }, }) addPluralRules(rules, []string{"pl"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) { return Few } // v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 if intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I, 1) && intInRange(ops.I%10, 0, 1) || intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) || intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 12, 14) { return Many } return Other }, }) addPluralRules(rules, []string{"be"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n % 10 = 1 and n % 100 != 11 if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) { return One } // n % 10 = 2..4 and n % 100 != 12..14 if ops.NModInRange(10, 2, 4) && !ops.NModInRange(100, 12, 14) { return Few } // n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 if ops.NModEqualsAny(10, 0) || ops.NModInRange(10, 5, 9) || ops.NModInRange(100, 11, 14) { return Many } return Other }, }) addPluralRules(rules, []string{"lt"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n % 10 = 1 and n % 100 != 11..19 if ops.NModEqualsAny(10, 1) && !ops.NModInRange(100, 11, 19) { return One } // n % 10 = 2..9 and n % 100 != 11..19 if ops.NModInRange(10, 2, 9) && !ops.NModInRange(100, 11, 19) { return Few } // f != 0 if !intEqualsAny(ops.F, 0) { return Many } return Other }, }) addPluralRules(rules, []string{"mt"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n = 1 if ops.NEqualsAny(1) { return One } // n = 0 or n % 100 = 2..10 if ops.NEqualsAny(0) || ops.NModInRange(100, 2, 10) { return Few } // n % 100 = 11..19 if ops.NModInRange(100, 11, 19) { return Many } return Other }, }) addPluralRules(rules, []string{"ru", "uk"}, &Rule{ PluralForms: newPluralFormSet(One, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i % 10 = 1 and i % 100 != 11 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) { return One } // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) { return Few } // v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 0) || intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) || intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 11, 14) { return Many } return Other }, }) addPluralRules(rules, []string{"br"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n % 10 = 1 and n % 100 != 11,71,91 if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11, 71, 91) { return One } // n % 10 = 2 and n % 100 != 12,72,92 if ops.NModEqualsAny(10, 2) && !ops.NModEqualsAny(100, 12, 72, 92) { return Two } // n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 if (ops.NModInRange(10, 3, 4) || ops.NModEqualsAny(10, 9)) && !(ops.NModInRange(100, 10, 19) || ops.NModInRange(100, 70, 79) || ops.NModInRange(100, 90, 99)) { return Few } // n != 0 and n % 1000000 = 0 if !ops.NEqualsAny(0) && ops.NModEqualsAny(1000000, 0) { return Many } return Other }, }) addPluralRules(rules, []string{"ga"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n = 1 if ops.NEqualsAny(1) { return One } // n = 2 if ops.NEqualsAny(2) { return Two } // n = 3..6 if ops.NInRange(3, 6) { return Few } // n = 7..10 if ops.NInRange(7, 10) { return Many } return Other }, }) addPluralRules(rules, []string{"gv"}, &Rule{ PluralForms: newPluralFormSet(One, Two, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // v = 0 and i % 10 = 1 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) { return One } // v = 0 and i % 10 = 2 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 2) { return Two } // v = 0 and i % 100 = 0,20,40,60,80 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 0, 20, 40, 60, 80) { return Few } // v != 0 if !intEqualsAny(ops.V, 0) { return Many } return Other }, }) addPluralRules(rules, []string{"ar", "ars"}, &Rule{ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0 if ops.NEqualsAny(0) { return Zero } // n = 1 if ops.NEqualsAny(1) { return One } // n = 2 if ops.NEqualsAny(2) { return Two } // n % 100 = 3..10 if ops.NModInRange(100, 3, 10) { return Few } // n % 100 = 11..99 if ops.NModInRange(100, 11, 99) { return Many } return Other }, }) addPluralRules(rules, []string{"cy"}, &Rule{ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0 if ops.NEqualsAny(0) { return Zero } // n = 1 if ops.NEqualsAny(1) { return One } // n = 2 if ops.NEqualsAny(2) { return Two } // n = 3 if ops.NEqualsAny(3) { return Few } // n = 6 if ops.NEqualsAny(6) { return Many } return Other }, }) addPluralRules(rules, []string{"kw"}, &Rule{ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other), PluralFormFunc: func(ops *Operands) Form { // n = 0 if ops.NEqualsAny(0) { return Zero } // n = 1 if ops.NEqualsAny(1) { return One } // n % 100 = 2,22,42,62,82 or n%1000 = 0 and n%100000=1000..20000,40000,60000,80000 or n!=0 and n%1000000=100000 if ops.NModEqualsAny(100, 2, 22, 42, 62, 82) || ops.NModEqualsAny(1000, 0) && (ops.NModInRange(100000, 1000, 20000) || ops.NModEqualsAny(100000, 40000, 60000, 80000)) || !ops.NEqualsAny(0) && ops.NModEqualsAny(1000000, 100000) { return Two } // n % 100 = 3,23,43,63,83 if ops.NModEqualsAny(100, 3, 23, 43, 63, 83) { return Few } // n != 1 and n % 100 = 1,21,41,61,81 if !ops.NEqualsAny(1) && ops.NModEqualsAny(100, 1, 21, 41, 61, 81) { return Many } return Other }, }) return rules } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/rule_gen_test.go000066400000000000000000000710751350541200600267550ustar00rootroot00000000000000// This file is generated by i18n/plural/codegen/generate.sh; DO NOT EDIT package plural import "testing" func TestBmBoDzIdIgIiInJaJboJvJwKdeKeaKmKoLktLoMsMyNqoRootSahSesSgThToViWoYoYueZh(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Other, []string{"0~15", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"bm", "bo", "dz", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "root", "sah", "ses", "sg", "th", "to", "vi", "wo", "yo", "yue", "zh"} for _, locale := range locales { runTests(t, locale, tests) } } func TestAmAsBnFaGuHiKnZu(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1"}) tests = appendDecimalTests(tests, One, []string{"0.0~1.0", "0.00~0.04"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"1.1~2.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"am", "as", "bn", "fa", "gu", "hi", "kn", "zu"} for _, locale := range locales { runTests(t, locale, tests) } } func TestFfFrHyKab(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1"}) tests = appendDecimalTests(tests, One, []string{"0.0~1.5"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ff", "fr", "hy", "kab"} for _, locale := range locales { runTests(t, locale, tests) } } func TestPt(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1"}) tests = appendDecimalTests(tests, One, []string{"0.0~1.5"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"pt"} for _, locale := range locales { runTests(t, locale, tests) } } func TestAstCaDeEnEtFiFyGlIaIoItJiNlPt_PTScScnSvSwUrYi(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendIntegerTests(tests, Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "it", "ji", "nl", "pt_PT", "sc", "scn", "sv", "sw", "ur", "yi"} for _, locale := range locales { runTests(t, locale, tests) } } func TestSi(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1"}) tests = appendDecimalTests(tests, One, []string{"0.0", "0.1", "1.0", "0.00", "0.01", "1.00", "0.000", "0.001", "1.000", "0.0000", "0.0001", "1.0000"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.2~0.9", "1.1~1.8", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"si"} for _, locale := range locales { runTests(t, locale, tests) } } func TestAkBhGuwLnMgNsoPaTiWa(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1"}) tests = appendDecimalTests(tests, One, []string{"0.0", "1.0", "0.00", "1.00", "0.000", "1.000", "0.0000", "1.0000"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ak", "bh", "guw", "ln", "mg", "nso", "pa", "ti", "wa"} for _, locale := range locales { runTests(t, locale, tests) } } func TestTzm(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1", "11~24"}) tests = appendDecimalTests(tests, One, []string{"0.0", "1.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "19.0", "20.0", "21.0", "22.0", "23.0", "24.0"}) tests = appendIntegerTests(tests, Other, []string{"2~10", "100~106", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"tzm"} for _, locale := range locales { runTests(t, locale, tests) } } func TestAfAsaAzBemBezBgBrxCeCggChrCkbDvEeElEoEsEuFoFurGswHaHawHuJgoJmcKaKajKcgKkKkjKlKsKsbKuKyLbLgMasMgoMlMnMrNahNbNdNeNnNnhNoNrNyNynOmOrOsPapPsRmRofRwkSaqSdSdhSehSnSoSqSsSsyStSyrTaTeTeoTigTkTnTrTsUgUzVeVoVunWaeXhXog(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"af", "asa", "az", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "es", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "mr", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"} for _, locale := range locales { runTests(t, locale, tests) } } func TestDa(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"0.1~1.6"}) tests = appendIntegerTests(tests, Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0", "2.0~3.4", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"da"} for _, locale := range locales { runTests(t, locale, tests) } } func TestIs(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"0.1~1.6", "10.1", "100.1", "1000.1"}) tests = appendIntegerTests(tests, Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"is"} for _, locale := range locales { runTests(t, locale, tests) } } func TestMk(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"0.1", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"}) tests = appendIntegerTests(tests, Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0", "0.2~1.0", "1.2~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"mk"} for _, locale := range locales { runTests(t, locale, tests) } } func TestCebFilTl(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0~3", "5", "7", "8", "10~13", "15", "17", "18", "20", "21", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, One, []string{"0.0~0.3", "0.5", "0.7", "0.8", "1.0~1.3", "1.5", "1.7", "1.8", "2.0", "2.1", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendIntegerTests(tests, Other, []string{"4", "6", "9", "14", "16", "19", "24", "26", "104", "1004"}) tests = appendDecimalTests(tests, Other, []string{"0.4", "0.6", "0.9", "1.4", "1.6", "1.9", "2.4", "2.6", "10.4", "100.4", "1000.4"}) locales := []string{"ceb", "fil", "tl"} for _, locale := range locales { runTests(t, locale, tests) } } func TestLvPrg(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Zero, []string{"0", "10~20", "30", "40", "50", "60", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Zero, []string{"0.0", "10.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"0.1", "1.0", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"}) tests = appendIntegerTests(tests, Other, []string{"2~9", "22~29", "102", "1002"}) tests = appendDecimalTests(tests, Other, []string{"0.2~0.9", "1.2~1.9", "10.2", "100.2", "1000.2"}) locales := []string{"lv", "prg"} for _, locale := range locales { runTests(t, locale, tests) } } func TestLag(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Zero, []string{"0"}) tests = appendDecimalTests(tests, Zero, []string{"0.0", "0.00", "0.000", "0.0000"}) tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"0.1~1.6"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"lag"} for _, locale := range locales { runTests(t, locale, tests) } } func TestKsh(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Zero, []string{"0"}) tests = appendDecimalTests(tests, Zero, []string{"0.0", "0.00", "0.000", "0.0000"}) tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ksh"} for _, locale := range locales { runTests(t, locale, tests) } } func TestIuNaqSeSmaSmiSmjSmnSms(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Two, []string{"2"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "2.00", "2.000", "2.0000"}) tests = appendIntegerTests(tests, Other, []string{"0", "3~17", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"iu", "naq", "se", "sma", "smi", "smj", "smn", "sms"} for _, locale := range locales { runTests(t, locale, tests) } } func TestShi(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"0", "1"}) tests = appendDecimalTests(tests, One, []string{"0.0~1.0", "0.00~0.04"}) tests = appendIntegerTests(tests, Few, []string{"2~10"}) tests = appendDecimalTests(tests, Few, []string{"2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "2.00", "3.00", "4.00", "5.00", "6.00", "7.00", "8.00"}) tests = appendIntegerTests(tests, Other, []string{"11~26", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"1.1~1.9", "2.1~2.7", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"shi"} for _, locale := range locales { runTests(t, locale, tests) } } func TestMoRo(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendIntegerTests(tests, Few, []string{"0", "2~16", "102", "1002"}) tests = appendDecimalTests(tests, Few, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendIntegerTests(tests, Other, []string{"20~35", "100", "1000", "10000", "100000", "1000000"}) locales := []string{"mo", "ro"} for _, locale := range locales { runTests(t, locale, tests) } } func TestBsHrShSr(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"0.1", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"}) tests = appendIntegerTests(tests, Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"}) tests = appendDecimalTests(tests, Few, []string{"0.2~0.4", "1.2~1.4", "2.2~2.4", "3.2~3.4", "4.2~4.4", "5.2", "10.2", "100.2", "1000.2"}) tests = appendIntegerTests(tests, Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0", "0.5~1.0", "1.5~2.0", "2.5~2.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"bs", "hr", "sh", "sr"} for _, locale := range locales { runTests(t, locale, tests) } } func TestGd(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "11"}) tests = appendDecimalTests(tests, One, []string{"1.0", "11.0", "1.00", "11.00", "1.000", "11.000", "1.0000"}) tests = appendIntegerTests(tests, Two, []string{"2", "12"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "12.0", "2.00", "12.00", "2.000", "12.000", "2.0000"}) tests = appendIntegerTests(tests, Few, []string{"3~10", "13~19"}) tests = appendDecimalTests(tests, Few, []string{"3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "19.0", "3.00"}) tests = appendIntegerTests(tests, Other, []string{"0", "20~34", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~0.9", "1.1~1.6", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"gd"} for _, locale := range locales { runTests(t, locale, tests) } } func TestSl(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "101", "201", "301", "401", "501", "601", "701", "1001"}) tests = appendIntegerTests(tests, Two, []string{"2", "102", "202", "302", "402", "502", "602", "702", "1002"}) tests = appendIntegerTests(tests, Few, []string{"3", "4", "103", "104", "203", "204", "303", "304", "403", "404", "503", "504", "603", "604", "703", "704", "1003"}) tests = appendDecimalTests(tests, Few, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendIntegerTests(tests, Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) locales := []string{"sl"} for _, locale := range locales { runTests(t, locale, tests) } } func TestDsbHsb(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "101", "201", "301", "401", "501", "601", "701", "1001"}) tests = appendDecimalTests(tests, One, []string{"0.1", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"}) tests = appendIntegerTests(tests, Two, []string{"2", "102", "202", "302", "402", "502", "602", "702", "1002"}) tests = appendDecimalTests(tests, Two, []string{"0.2", "1.2", "2.2", "3.2", "4.2", "5.2", "6.2", "7.2", "10.2", "100.2", "1000.2"}) tests = appendIntegerTests(tests, Few, []string{"3", "4", "103", "104", "203", "204", "303", "304", "403", "404", "503", "504", "603", "604", "703", "704", "1003"}) tests = appendDecimalTests(tests, Few, []string{"0.3", "0.4", "1.3", "1.4", "2.3", "2.4", "3.3", "3.4", "4.3", "4.4", "5.3", "5.4", "6.3", "6.4", "7.3", "7.4", "10.3", "100.3", "1000.3"}) tests = appendIntegerTests(tests, Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0", "0.5~1.0", "1.5~2.0", "2.5~2.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"dsb", "hsb"} for _, locale := range locales { runTests(t, locale, tests) } } func TestHeIw(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendIntegerTests(tests, Two, []string{"2"}) tests = appendIntegerTests(tests, Many, []string{"20", "30", "40", "50", "60", "70", "80", "90", "100", "1000", "10000", "100000", "1000000"}) tests = appendIntegerTests(tests, Other, []string{"0", "3~17", "101", "1001"}) tests = appendDecimalTests(tests, Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"he", "iw"} for _, locale := range locales { runTests(t, locale, tests) } } func TestCsSk(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendIntegerTests(tests, Few, []string{"2~4"}) tests = appendDecimalTests(tests, Many, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendIntegerTests(tests, Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) locales := []string{"cs", "sk"} for _, locale := range locales { runTests(t, locale, tests) } } func TestPl(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendIntegerTests(tests, Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"}) tests = appendIntegerTests(tests, Many, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"pl"} for _, locale := range locales { runTests(t, locale, tests) } } func TestBe(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"1.0", "21.0", "31.0", "41.0", "51.0", "61.0", "71.0", "81.0", "101.0", "1001.0"}) tests = appendIntegerTests(tests, Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"}) tests = appendDecimalTests(tests, Few, []string{"2.0", "3.0", "4.0", "22.0", "23.0", "24.0", "32.0", "33.0", "102.0", "1002.0"}) tests = appendIntegerTests(tests, Many, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Many, []string{"0.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "11.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.1", "1000.1"}) locales := []string{"be"} for _, locale := range locales { runTests(t, locale, tests) } } func TestLt(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"1.0", "21.0", "31.0", "41.0", "51.0", "61.0", "71.0", "81.0", "101.0", "1001.0"}) tests = appendIntegerTests(tests, Few, []string{"2~9", "22~29", "102", "1002"}) tests = appendDecimalTests(tests, Few, []string{"2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "22.0", "102.0", "1002.0"}) tests = appendDecimalTests(tests, Many, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.1", "1000.1"}) tests = appendIntegerTests(tests, Other, []string{"0", "10~20", "30", "40", "50", "60", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0", "10.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"lt"} for _, locale := range locales { runTests(t, locale, tests) } } func TestMt(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Few, []string{"0", "2~10", "102~107", "1002"}) tests = appendDecimalTests(tests, Few, []string{"0.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "10.0", "102.0", "1002.0"}) tests = appendIntegerTests(tests, Many, []string{"11~19", "111~117", "1011"}) tests = appendDecimalTests(tests, Many, []string{"11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "111.0", "1011.0"}) tests = appendIntegerTests(tests, Other, []string{"20~35", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"mt"} for _, locale := range locales { runTests(t, locale, tests) } } func TestRuUk(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"}) tests = appendIntegerTests(tests, Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"}) tests = appendIntegerTests(tests, Many, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ru", "uk"} for _, locale := range locales { runTests(t, locale, tests) } } func TestBr(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "21", "31", "41", "51", "61", "81", "101", "1001"}) tests = appendDecimalTests(tests, One, []string{"1.0", "21.0", "31.0", "41.0", "51.0", "61.0", "81.0", "101.0", "1001.0"}) tests = appendIntegerTests(tests, Two, []string{"2", "22", "32", "42", "52", "62", "82", "102", "1002"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "22.0", "32.0", "42.0", "52.0", "62.0", "82.0", "102.0", "1002.0"}) tests = appendIntegerTests(tests, Few, []string{"3", "4", "9", "23", "24", "29", "33", "34", "39", "43", "44", "49", "103", "1003"}) tests = appendDecimalTests(tests, Few, []string{"3.0", "4.0", "9.0", "23.0", "24.0", "29.0", "33.0", "34.0", "103.0", "1003.0"}) tests = appendIntegerTests(tests, Many, []string{"1000000"}) tests = appendDecimalTests(tests, Many, []string{"1000000.0", "1000000.00", "1000000.000"}) tests = appendIntegerTests(tests, Other, []string{"0", "5~8", "10~20", "100", "1000", "10000", "100000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0"}) locales := []string{"br"} for _, locale := range locales { runTests(t, locale, tests) } } func TestGa(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Two, []string{"2"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "2.00", "2.000", "2.0000"}) tests = appendIntegerTests(tests, Few, []string{"3~6"}) tests = appendDecimalTests(tests, Few, []string{"3.0", "4.0", "5.0", "6.0", "3.00", "4.00", "5.00", "6.00", "3.000", "4.000", "5.000", "6.000", "3.0000", "4.0000", "5.0000", "6.0000"}) tests = appendIntegerTests(tests, Many, []string{"7~10"}) tests = appendDecimalTests(tests, Many, []string{"7.0", "8.0", "9.0", "10.0", "7.00", "8.00", "9.00", "10.00", "7.000", "8.000", "9.000", "10.000", "7.0000", "8.0000", "9.0000", "10.0000"}) tests = appendIntegerTests(tests, Other, []string{"0", "11~25", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.0~0.9", "1.1~1.6", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ga"} for _, locale := range locales { runTests(t, locale, tests) } } func TestGv(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, One, []string{"1", "11", "21", "31", "41", "51", "61", "71", "101", "1001"}) tests = appendIntegerTests(tests, Two, []string{"2", "12", "22", "32", "42", "52", "62", "72", "102", "1002"}) tests = appendIntegerTests(tests, Few, []string{"0", "20", "40", "60", "80", "100", "120", "140", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Many, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) tests = appendIntegerTests(tests, Other, []string{"3~10", "13~19", "23", "103", "1003"}) locales := []string{"gv"} for _, locale := range locales { runTests(t, locale, tests) } } func TestArArs(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Zero, []string{"0"}) tests = appendDecimalTests(tests, Zero, []string{"0.0", "0.00", "0.000", "0.0000"}) tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Two, []string{"2"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "2.00", "2.000", "2.0000"}) tests = appendIntegerTests(tests, Few, []string{"3~10", "103~110", "1003"}) tests = appendDecimalTests(tests, Few, []string{"3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "103.0", "1003.0"}) tests = appendIntegerTests(tests, Many, []string{"11~26", "111", "1011"}) tests = appendDecimalTests(tests, Many, []string{"11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "111.0", "1011.0"}) tests = appendIntegerTests(tests, Other, []string{"100~102", "200~202", "300~302", "400~402", "500~502", "600", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"ar", "ars"} for _, locale := range locales { runTests(t, locale, tests) } } func TestCy(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Zero, []string{"0"}) tests = appendDecimalTests(tests, Zero, []string{"0.0", "0.00", "0.000", "0.0000"}) tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Two, []string{"2"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "2.00", "2.000", "2.0000"}) tests = appendIntegerTests(tests, Few, []string{"3"}) tests = appendDecimalTests(tests, Few, []string{"3.0", "3.00", "3.000", "3.0000"}) tests = appendIntegerTests(tests, Many, []string{"6"}) tests = appendDecimalTests(tests, Many, []string{"6.0", "6.00", "6.000", "6.0000"}) tests = appendIntegerTests(tests, Other, []string{"4", "5", "7~20", "100", "1000", "10000", "100000", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"}) locales := []string{"cy"} for _, locale := range locales { runTests(t, locale, tests) } } func TestKw(t *testing.T) { var tests []pluralFormTest tests = appendIntegerTests(tests, Zero, []string{"0"}) tests = appendDecimalTests(tests, Zero, []string{"0.0", "0.00", "0.000", "0.0000"}) tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendDecimalTests(tests, One, []string{"1.0", "1.00", "1.000", "1.0000"}) tests = appendIntegerTests(tests, Two, []string{"2", "22", "42", "62", "82", "102", "122", "142", "1002"}) tests = appendDecimalTests(tests, Two, []string{"2.0", "22.0", "42.0", "62.0", "82.0", "102.0", "122.0", "142.0", "1002.0"}) tests = appendIntegerTests(tests, Few, []string{"3", "23", "43", "63", "83", "103", "123", "143", "1003"}) tests = appendDecimalTests(tests, Few, []string{"3.0", "23.0", "43.0", "63.0", "83.0", "103.0", "123.0", "143.0", "1003.0"}) tests = appendIntegerTests(tests, Many, []string{"21", "41", "61", "81", "101", "121", "141", "161", "1001"}) tests = appendDecimalTests(tests, Many, []string{"21.0", "41.0", "61.0", "81.0", "101.0", "121.0", "141.0", "161.0", "1001.0"}) tests = appendIntegerTests(tests, Other, []string{"4~19", "100", "1000000"}) tests = appendDecimalTests(tests, Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000000.0"}) locales := []string{"kw"} for _, locale := range locales { runTests(t, locale, tests) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/rule_test.go000066400000000000000000000040271350541200600261150ustar00rootroot00000000000000package plural import ( "strconv" "strings" "testing" "golang.org/x/text/language" ) type pluralFormTest struct { num interface{} form Form } func runTests(t *testing.T, pluralRuleID string, tests []pluralFormTest) { if pluralRuleID == "root" { return } pluralRules := DefaultRules() tag := language.MustParse(pluralRuleID) if rule := pluralRules.Rule(tag); rule != nil { for _, test := range tests { ops, err := NewOperands(test.num) if err != nil { t.Errorf("%s: NewOperands(%d) errored with %s", pluralRuleID, test.num, err) break } if pluralForm := rule.PluralFormFunc(ops); pluralForm != test.form { t.Errorf("%s: PluralFormFunc(%#v) returned %q, %v; expected %q", pluralRuleID, ops, pluralForm, err, test.form) } } } else { t.Errorf("could not find plural rule for locale %s", pluralRuleID) } } func appendIntegerTests(tests []pluralFormTest, form Form, examples []string) []pluralFormTest { for _, ex := range expandExamples(examples) { i, err := strconv.ParseInt(ex, 10, 64) if err != nil { panic(err) } tests = append(tests, pluralFormTest{ex, form}, pluralFormTest{i, form}) } return tests } func appendDecimalTests(tests []pluralFormTest, form Form, examples []string) []pluralFormTest { for _, ex := range expandExamples(examples) { tests = append(tests, pluralFormTest{ex, form}) } return tests } func expandExamples(examples []string) []string { var expanded []string for _, ex := range examples { if parts := strings.Split(ex, "~"); len(parts) == 2 { for ex := parts[0]; ; ex = increment(ex) { expanded = append(expanded, ex) if ex == parts[1] { break } } } else { expanded = append(expanded, ex) } } return expanded } func increment(dec string) string { runes := []rune(dec) carry := true for i := len(runes) - 1; carry && i >= 0; i-- { switch runes[i] { case '.': continue case '9': runes[i] = '0' default: runes[i]++ carry = false } } if carry { runes = append([]rune{'1'}, runes...) } return string(runes) } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/rules.go000066400000000000000000000007651350541200600252460ustar00rootroot00000000000000package plural import "golang.org/x/text/language" // Rules is a set of plural rules by language tag. type Rules map[language.Tag]*Rule // Rule returns the closest matching plural rule for the language tag // or nil if no rule could be found. func (r Rules) Rule(tag language.Tag) *Rule { t := tag for { if rule := r[t]; rule != nil { return rule } t = t.Parent() if t.IsRoot() { break } } base, _ := tag.Base() baseTag, _ := language.Parse(base.String()) return r[baseTag] } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/plural/rules_test.go000066400000000000000000000026271350541200600263040ustar00rootroot00000000000000package plural import ( "testing" "golang.org/x/text/language" ) func TestRules(t *testing.T) { expectedRule := &Rule{} testCases := []struct { name string rules Rules tag language.Tag rule *Rule }{ { name: "exact match", rules: Rules{ language.English: expectedRule, language.Spanish: &Rule{}, }, tag: language.English, rule: expectedRule, }, { name: "inexact match", rules: Rules{ language.English: expectedRule, }, tag: language.AmericanEnglish, rule: expectedRule, }, { name: "portuguese doesn't match european portuguese", rules: Rules{ language.EuropeanPortuguese: &Rule{}, }, tag: language.Portuguese, rule: nil, }, { name: "european portuguese preferred", rules: Rules{ language.Portuguese: &Rule{}, language.EuropeanPortuguese: expectedRule, }, tag: language.EuropeanPortuguese, rule: expectedRule, }, { name: "zh-Hans", rules: Rules{ language.Chinese: expectedRule, }, tag: language.SimplifiedChinese, rule: expectedRule, }, { name: "zh-Hant", rules: Rules{ language.Chinese: expectedRule, }, tag: language.TraditionalChinese, rule: expectedRule, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { if rule := testCase.rules.Rule(testCase.tag); rule != testCase.rule { panic(rule) } }) } } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/template.go000066400000000000000000000022071350541200600244210ustar00rootroot00000000000000package internal import ( "bytes" "strings" "sync" gotemplate "text/template" ) // Template stores the template for a string. type Template struct { Src string LeftDelim string RightDelim string parseOnce sync.Once parsedTemplate *gotemplate.Template parseError error } func (t *Template) Execute(funcs gotemplate.FuncMap, data interface{}) (string, error) { leftDelim := t.LeftDelim if leftDelim == "" { leftDelim = "{{" } if !strings.Contains(t.Src, leftDelim) { // Fast path to avoid parsing a template that has no actions. return t.Src, nil } var gt *gotemplate.Template var err error if funcs == nil { t.parseOnce.Do(func() { // If funcs is nil, then we only need to parse this template once. t.parsedTemplate, t.parseError = gotemplate.New("").Delims(t.LeftDelim, t.RightDelim).Parse(t.Src) }) gt, err = t.parsedTemplate, t.parseError } else { gt, err = gotemplate.New("").Delims(t.LeftDelim, t.RightDelim).Funcs(funcs).Parse(t.Src) } if err != nil { return "", err } var buf bytes.Buffer if err := gt.Execute(&buf, data); err != nil { return "", err } return buf.String(), nil } golang-github-nicksnyder-go-i18n.v2-2.0.2/v2/internal/template_test.go000066400000000000000000000026761350541200600254720ustar00rootroot00000000000000package internal import ( "testing" "text/template" ) func TestExecute(t *testing.T) { tests := []struct { template *Template funcs template.FuncMap data interface{} result string err string noallocs bool }{ { template: &Template{ Src: "hello", }, result: "hello", noallocs: true, }, { template: &Template{ Src: "hello {{.Noun}}", }, data: map[string]string{ "Noun": "world", }, result: "hello world", }, { template: &Template{ Src: "hello {{world}}", }, funcs: template.FuncMap{ "world": func() string { return "world" }, }, result: "hello world", }, { template: &Template{ Src: "hello {{", }, err: "template: :1: unexpected unclosed action in command", noallocs: true, }, } 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 result != test.result { t.Errorf("expected result %q; got %q", test.result, result) } allocs := testing.AllocsPerRun(10, func() { _, _ = test.template.Execute(test.funcs, test.data) }) if test.noallocs && allocs > 0 { t.Errorf("expected no allocations; got %f", allocs) } }) } } func str(err error) string { if err == nil { return "" } return err.Error() }