pax_global_header00006660000000000000000000000064131776676010014530gustar00rootroot0000000000000052 comment=0dc1626d56435e9d605a29875701721c54bc9bbd go-i18n-1.10.0/000077500000000000000000000000001317766760100127715ustar00rootroot00000000000000go-i18n-1.10.0/.gitignore000066400000000000000000000000461317766760100147610ustar00rootroot00000000000000*.a _* output/ .DS_Store *.test *.swp go-i18n-1.10.0/.travis.yml000066400000000000000000000001351317766760100151010ustar00rootroot00000000000000language: go sudo: false go: - 1.2 - 1.3 - 1.4 - 1.5 - 1.6 - 1.7 - 1.8 - 1.9 go-i18n-1.10.0/CHANGELOG000066400000000000000000000002511317766760100142010ustar00rootroot00000000000000Feb 24, 2015 - Add Korean Feb 18, 2015 - Added ParseTranslationFileBytes so translation files may be loaded from an arbitrary serialization format, such as go-bindata. go-i18n-1.10.0/LICENSE000066400000000000000000000020751317766760100140020ustar00rootroot00000000000000Copyright (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. go-i18n-1.10.0/README.md000066400000000000000000000133541317766760100142560ustar00rootroot00000000000000go-i18n [![Build Status](https://travis-ci.org/nicksnyder/go-i18n.svg?branch=master)](http://travis-ci.org/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](#i18n-package) and a [command](#goi18n-command) 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. * Translation files are simple JSON, TOML or YAML. * [Documented](http://godoc.org/github.com/nicksnyder/go-i18n) and [tested](https://travis-ci.org/nicksnyder/go-i18n)! Package i18n [![GoDoc](http://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](http://godoc.org/github.com/nicksnyder/go-i18n/i18n) ------------ The i18n package provides runtime APIs for fetching translated strings. Command goi18n [![GoDoc](http://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](http://godoc.org/github.com/nicksnyder/go-i18n/goi18n) -------------- The goi18n command provides functionality for managing the translation process. Installation ------------ Make sure you have [setup GOPATH](http://golang.org/doc/code.html#GOPATH). go get -u github.com/nicksnyder/go-i18n/goi18n goi18n -help Workflow -------- A typical workflow looks like this: 1. Add a new string to your source code. ```go T("settings_title") ``` 2. Add the string to en-US.all.json ```json [ { "id": "settings_title", "translation": "Settings" } ] ``` 3. Run goi18n ``` goi18n path/to/*.all.json ``` 4. Send `path/to/*.untranslated.json` to get translated. 5. Run goi18n again to merge the translations ```sh goi18n path/to/*.all.json path/to/*.untranslated.json ``` Translation files ----------------- A translation file stores translated and untranslated strings. Here is an example of the default file format that go-i18n supports: ```json [ { "id": "d_days", "translation": { "one": "{{.Count}} day", "other": "{{.Count}} days" } }, { "id": "my_height_in_meters", "translation": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." } }, { "id": "person_greeting", "translation": "Hello {{.Person}}" }, { "id": "person_unread_email_count", "translation": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." } }, { "id": "person_unread_email_count_timeframe", "translation": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "program_greeting", "translation": "Hello world" }, { "id": "your_unread_email_count", "translation": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } } ] ``` To use a different file format, write a parser for the format and add the parsed translations using [AddTranslation](https://godoc.org/github.com/nicksnyder/go-i18n/i18n#AddTranslation). Note that TOML only supports the flat format, which is described below. More examples of translation files: [JSON](https://github.com/nicksnyder/go-i18n/tree/master/goi18n/testdata/input), [TOML](https://github.com/nicksnyder/go-i18n/blob/master/goi18n/testdata/input/flat/ar-ar.one.toml), [YAML](https://github.com/nicksnyder/go-i18n/blob/master/goi18n/testdata/input/yaml/en-us.one.yaml). Flat Format ------------- You can also write shorter translation files with flat format. E.g the example above can be written in this way: ```json { "d_days": { "one": "{{.Count}} day.", "other": "{{.Count}} days." }, "my_height_in_meters": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." }, "person_greeting": { "other": "Hello {{.Person}}" }, "person_unread_email_count": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." }, "person_unread_email_count_timeframe": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "program_greeting": { "other": "Hello world" }, "your_unread_email_count": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } } ``` The logic of flat format is, what it is structure of structures and name of substructures (ids) should be always a string. If there is only one key in substructure and it is "other", then it's non-plural translation, else plural. More examples of flat format translation files can be found in [goi18n/testdata/input/flat](https://github.com/nicksnyder/go-i18n/tree/master/goi18n/testdata/input/flat). Contributions ------------- If you would like to submit a pull request, please 1. Write tests 2. Format code with [goimports](https://github.com/bradfitz/goimports). 3. Read the [common code review comments](https://github.com/golang/go/wiki/CodeReviewComments). License ------- go-i18n is available under the MIT license. See the [LICENSE](LICENSE) file for more info. go-i18n-1.10.0/goi18n/000077500000000000000000000000001317766760100140765ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/constants_command.go000066400000000000000000000116151317766760100201430ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" "path/filepath" "reflect" "regexp" "sort" "strconv" "strings" "text/template" "unicode" "github.com/nicksnyder/go-i18n/i18n/bundle" "github.com/nicksnyder/go-i18n/i18n/language" "github.com/nicksnyder/go-i18n/i18n/translation" ) type constantsCommand struct { translationFiles []string packageName string outdir string } type templateConstants struct { ID string Name string Comments []string } type templateHeader struct { PackageName string Constants []templateConstants } var constTemplate = template.Must(template.New("").Parse(`// DON'T CHANGE THIS FILE MANUALLY // This file was generated using the command: // $ goi18n constants package {{.PackageName}} {{range .Constants}} // {{.Name}} is the identifier for the following localizable string template(s):{{range .Comments}} // {{.}}{{end}} const {{.Name}} = "{{.ID}}" {{end}}`)) func (cc *constantsCommand) execute() error { if len(cc.translationFiles) != 1 { return fmt.Errorf("need one translation file") } bundle := bundle.New() if err := bundle.LoadTranslationFile(cc.translationFiles[0]); err != nil { return fmt.Errorf("failed to load translation file %s because %s\n", cc.translationFiles[0], err) } translations := bundle.Translations() lang := translations[bundle.LanguageTags()[0]] // create an array of id to organize keys := make([]string, len(lang)) i := 0 for id := range lang { keys[i] = id i++ } sort.Strings(keys) tmpl := &templateHeader{ PackageName: cc.packageName, Constants: make([]templateConstants, len(keys)), } for i, id := range keys { tmpl.Constants[i].ID = id tmpl.Constants[i].Name = toCamelCase(id) tmpl.Constants[i].Comments = toComments(lang[id]) } filename := filepath.Join(cc.outdir, cc.packageName+".go") f, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create file %s because %s", filename, err) } defer f.Close() if err = constTemplate.Execute(f, tmpl); err != nil { return fmt.Errorf("failed to write file %s because %s", filename, err) } return nil } func (cc *constantsCommand) parse(arguments []string) { flags := flag.NewFlagSet("constants", flag.ExitOnError) flags.Usage = usageConstants packageName := flags.String("package", "R", "") outdir := flags.String("outdir", ".", "") flags.Parse(arguments) cc.translationFiles = flags.Args() cc.packageName = *packageName cc.outdir = *outdir } func (cc *constantsCommand) SetArgs(args []string) { cc.translationFiles = args } func usageConstants() { fmt.Printf(`Generate constant file from translation file. Usage: goi18n constants [options] [file] Translation files: A translation file contains the strings and translations for a single language. Translation 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.). Options: -package name goi18n generates the constant file under the package name. Default: R -outdir directory goi18n writes the constant file to this directory. Default: . `) } // commonInitialisms is a set of common initialisms. // Only add entries that are highly unlikely to be non-initialisms. // For instance, "ID" is fine (Freudian code is rare), but "AND" is not. // https://github.com/golang/lint/blob/master/lint.go var commonInitialisms = map[string]bool{ "API": true, "ASCII": true, "CPU": true, "CSS": true, "DNS": true, "EOF": true, "GUID": true, "HTML": true, "HTTP": true, "HTTPS": true, "ID": true, "IP": true, "JSON": true, "LHS": true, "QPS": true, "RAM": true, "RHS": true, "RPC": true, "SLA": true, "SMTP": true, "SQL": true, "SSH": true, "TCP": true, "TLS": true, "TTL": true, "UDP": true, "UI": true, "UID": true, "UUID": true, "URI": true, "URL": true, "UTF8": true, "VM": true, "XML": true, "XSRF": true, "XSS": true, } func toCamelCase(id string) string { var result string r := regexp.MustCompile(`[\-\.\_\s]`) words := r.Split(id, -1) for _, w := range words { upper := strings.ToUpper(w) if commonInitialisms[upper] { result += upper continue } if len(w) > 0 { u := []rune(w) u[0] = unicode.ToUpper(u[0]) result += string(u) } } return result } func toComments(trans translation.Translation) []string { var result []string data := trans.MarshalInterface().(map[string]interface{}) t := data["translation"] switch v := reflect.ValueOf(t); v.Kind() { case reflect.Map: for _, k := range []language.Plural{"zero", "one", "two", "few", "many", "other"} { vt := v.MapIndex(reflect.ValueOf(k)) if !vt.IsValid() { continue } result = append(result, string(k)+": "+strconv.Quote(fmt.Sprint(vt.Interface()))) } default: result = append(result, strconv.Quote(fmt.Sprint(t))) } return result } go-i18n-1.10.0/goi18n/constants_command_test.go000066400000000000000000000021131317766760100211730ustar00rootroot00000000000000package main import "testing" func TestConstantsExecute(t *testing.T) { resetDir(t, "testdata/output") cc := &constantsCommand{ translationFiles: []string{"testdata/input/en-us.constants.json"}, packageName: "R", outdir: "testdata/output", } if err := cc.execute(); err != nil { t.Fatal(err) } expectEqualFiles(t, "testdata/output/R.go", "testdata/expected/R.go") } func TestToCamelCase(t *testing.T) { expectEqual := func(test, expected string) { result := toCamelCase(test) if result != expected { t.Fatalf("failed toCamelCase the test %s was expected %s but the result was %s", test, expected, result) } } expectEqual("", "") expectEqual("a", "A") expectEqual("_", "") expectEqual("__code__", "Code") expectEqual("test", "Test") expectEqual("test_one", "TestOne") expectEqual("test.two", "TestTwo") expectEqual("test_alpha_beta", "TestAlphaBeta") expectEqual("word word", "WordWord") expectEqual("test_id", "TestID") expectEqual("tcp_name", "TCPName") expectEqual("こんにちは", "こんにちは") expectEqual("test_a", "TestA") } go-i18n-1.10.0/goi18n/doc.go000066400000000000000000000063231317766760100151760ustar00rootroot00000000000000// The goi18n command formats and merges translation files. // // go get -u github.com/nicksnyder/go-i18n/goi18n // goi18n -help // // Help documentation: // // goi18n manages translation files. // // Usage: // // goi18n merge Merge translation files // goi18n constants Generate constant file from translation file // // For more details execute: // // goi18n [command] -help // // Merge translation files. // // Usage: // // goi18n merge [options] [files...] // // Translation files: // // A translation file contains the strings and translations for a single language. // // Translation 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.). // // For each language represented by at least one input translation file, goi18n will produce 2 output files: // // xx-yy.all.format // This file contains all strings for the language (translated and untranslated). // Use this file when loading strings at runtime. // // xx-yy.untranslated.format // This file contains the strings that have not been translated for this language. // The translations for the strings in this file will be extracted from the source language. // After they are translated, merge them back into xx-yy.all.format using goi18n. // // Merging: // // goi18n will merge multiple translation files for the same language. // Duplicate translations will be merged into the existing translation. // Non-empty fields in the duplicate translation will overwrite those fields in the existing translation. // Empty fields in the duplicate translation are ignored. // // Adding a new language: // // To produce translation files for a new language, create an empty translation file with the // appropriate name and pass it in to goi18n. // // Options: // // -sourceLanguage tag // goi18n uses the strings from this language to seed the translations for other languages. // Default: en-us // // -outdir directory // goi18n writes the output translation files to this directory. // Default: . // // -format format // goi18n encodes the output translation files in this format. // Supported formats: json, yaml // Default: json // // Generate constant file from translation file. // // Usage: // // goi18n constants [options] [file] // // Translation files: // // A translation file contains the strings and translations for a single language. // // Translation 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.). // // Options: // // -package name // goi18n generates the constant file under the package name. // Default: R // // -outdir directory // goi18n writes the constant file to this directory. // Default: . // package main go-i18n-1.10.0/goi18n/gendoc.sh000066400000000000000000000007421317766760100156740ustar00rootroot00000000000000go install echo "// The goi18n command formats and merges translation files." > doc.go echo "//" >> doc.go echo "// go get -u github.com/nicksnyder/go-i18n/goi18n" >> doc.go echo "// goi18n -help" >> doc.go echo "//" >> doc.go echo "// Help documentation:" >> doc.go echo "//" >> doc.go goi18n | sed -e 's/^/\/\/ /' >> doc.go goi18n merge -help | sed -e 's/^/\/\/ /' >> doc.go goi18n constants -help | sed -e 's/^/\/\/ /' >> doc.go echo "package main" >> doc.go go-i18n-1.10.0/goi18n/goi18n.go000066400000000000000000000014021317766760100155270ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" ) type command interface { execute() error parse(arguments []string) } func main() { flag.Usage = usage if len(os.Args) == 1 { usage() } var cmd command switch os.Args[1] { case "merge": cmd = &mergeCommand{} cmd.parse(os.Args[2:]) case "constants": cmd = &constantsCommand{} cmd.parse(os.Args[2:]) default: cmd = &mergeCommand{} cmd.parse(os.Args[1:]) } if err := cmd.execute(); err != nil { fmt.Println(err.Error()) os.Exit(1) } } func usage() { fmt.Printf(`goi18n manages translation files. Usage: goi18n merge Merge translation files goi18n constants Generate constant file from translation file For more details execute: goi18n [command] -help `) os.Exit(1) } go-i18n-1.10.0/goi18n/merge_command.go000066400000000000000000000154631317766760100172330ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "fmt" "io/ioutil" "path/filepath" "reflect" "sort" "gopkg.in/yaml.v2" "github.com/nicksnyder/go-i18n/i18n/bundle" "github.com/nicksnyder/go-i18n/i18n/language" "github.com/nicksnyder/go-i18n/i18n/translation" toml "github.com/pelletier/go-toml" ) type mergeCommand struct { translationFiles []string sourceLanguage string outdir string format string flat bool } func (mc *mergeCommand) execute() error { if len(mc.translationFiles) < 1 { return fmt.Errorf("need at least one translation file to parse") } if lang := language.Parse(mc.sourceLanguage); lang == nil { return fmt.Errorf("invalid source locale: %s", mc.sourceLanguage) } bundle := bundle.New() for _, tf := range mc.translationFiles { if err := bundle.LoadTranslationFile(tf); err != nil { return fmt.Errorf("failed to load translation file %s: %s\n", tf, err) } } translations := bundle.Translations() sourceLanguageTag := language.NormalizeTag(mc.sourceLanguage) sourceTranslations := translations[sourceLanguageTag] if sourceTranslations == nil { return fmt.Errorf("no translations found for source locale %s", sourceLanguageTag) } for translationID, src := range sourceTranslations { for _, localeTranslations := range translations { if dst := localeTranslations[translationID]; dst == nil || reflect.TypeOf(src) != reflect.TypeOf(dst) { localeTranslations[translationID] = src.UntranslatedCopy() } } } for localeID, localeTranslations := range translations { lang := language.MustParse(localeID)[0] all := filter(localeTranslations, func(t translation.Translation) translation.Translation { return t.Normalize(lang) }) if err := mc.writeFile("all", all, localeID); err != nil { return err } untranslated := filter(localeTranslations, func(t translation.Translation) translation.Translation { if t.Incomplete(lang) { return t.Normalize(lang).Backfill(sourceTranslations[t.ID()]) } return nil }) if err := mc.writeFile("untranslated", untranslated, localeID); err != nil { return err } } return nil } func (mc *mergeCommand) parse(arguments []string) { flags := flag.NewFlagSet("merge", flag.ExitOnError) flags.Usage = usageMerge sourceLanguage := flags.String("sourceLanguage", "en-us", "") outdir := flags.String("outdir", ".", "") format := flags.String("format", "json", "") flat := flags.Bool("flat", true, "") flags.Parse(arguments) mc.translationFiles = flags.Args() mc.sourceLanguage = *sourceLanguage mc.outdir = *outdir mc.format = *format if *format == "toml" { mc.flat = true } else { mc.flat = *flat } } func (mc *mergeCommand) SetArgs(args []string) { mc.translationFiles = args } func (mc *mergeCommand) writeFile(label string, translations []translation.Translation, localeID string) error { sort.Sort(translation.SortableByID(translations)) var convert func([]translation.Translation) interface{} if mc.flat { convert = marshalFlatInterface } else { convert = marshalInterface } buf, err := mc.marshal(convert(translations)) if err != nil { return fmt.Errorf("failed to marshal %s strings to %s: %s", localeID, mc.format, err) } filename := filepath.Join(mc.outdir, fmt.Sprintf("%s.%s.%s", localeID, label, mc.format)) if err := ioutil.WriteFile(filename, buf, 0666); err != nil { return fmt.Errorf("failed to write %s: %s", filename, err) } return nil } func filter(translations map[string]translation.Translation, f func(translation.Translation) translation.Translation) []translation.Translation { filtered := make([]translation.Translation, 0, len(translations)) for _, translation := range translations { if t := f(translation); t != nil { filtered = append(filtered, t) } } return filtered } func marshalFlatInterface(translations []translation.Translation) interface{} { mi := make(map[string]interface{}, len(translations)) for _, translation := range translations { mi[translation.ID()] = translation.MarshalFlatInterface() } return mi } func marshalInterface(translations []translation.Translation) interface{} { mi := make([]interface{}, len(translations)) for i, translation := range translations { mi[i] = translation.MarshalInterface() } return mi } func (mc mergeCommand) marshal(v interface{}) ([]byte, error) { switch mc.format { case "json": return json.MarshalIndent(v, "", " ") case "toml": return marshalTOML(v) case "yaml": return yaml.Marshal(v) } return nil, fmt.Errorf("unsupported format: %s\n", mc.format) } func marshalTOML(v interface{}) ([]byte, error) { m, ok := v.(map[string]interface{}) if !ok { return nil, fmt.Errorf("invalid format for marshaling to TOML") } tree, err := toml.TreeFromMap(m) if err != nil { return nil, err } s, err := tree.ToTomlString() return []byte(s), err } func usageMerge() { fmt.Printf(`Merge translation files. Usage: goi18n merge [options] [files...] Translation files: A translation file contains the strings and translations for a single language. Translation 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.). For each language represented by at least one input translation file, goi18n will produce 2 output files: xx-yy.all.format This file contains all strings for the language (translated and untranslated). Use this file when loading strings at runtime. xx-yy.untranslated.format This file contains the strings that have not been translated for this language. The translations for the strings in this file will be extracted from the source language. After they are translated, merge them back into xx-yy.all.format using goi18n. Merging: goi18n will merge multiple translation files for the same language. Duplicate translations will be merged into the existing translation. Non-empty fields in the duplicate translation will overwrite those fields in the existing translation. Empty fields in the duplicate translation are ignored. Adding a new language: To produce translation files for a new language, create an empty translation file with the appropriate name and pass it in to goi18n. Options: -sourceLanguage tag goi18n uses the strings from this language to seed the translations for other languages. Default: en-us -outdir directory goi18n writes the output translation files to this directory. Default: . -format format goi18n encodes the output translation files in this format. Supported formats: json, toml, yaml Default: json -flat goi18n writes the output translation files in flat format. Usage of '-format toml' automitically sets this flag. Default: true `) } go-i18n-1.10.0/goi18n/merge_command_flat_test.go000066400000000000000000000024521317766760100212720ustar00rootroot00000000000000package main import "testing" func TestMergeExecuteFlat(t *testing.T) { files := []string{ "testdata/input/flat/en-us.one.yaml", "testdata/input/flat/en-us.two.json", "testdata/input/flat/fr-fr.json", "testdata/input/flat/ar-ar.one.toml", "testdata/input/flat/ar-ar.two.json", } testFlatMergeExecute(t, files) } func testFlatMergeExecute(t *testing.T, files []string) { resetDir(t, "testdata/output/flat") mc := &mergeCommand{ translationFiles: files, sourceLanguage: "en-us", outdir: "testdata/output/flat", format: "json", flat: true, } if err := mc.execute(); err != nil { t.Fatal(err) } expectEqualFiles(t, "testdata/output/flat/en-us.all.json", "testdata/expected/flat/en-us.all.json") expectEqualFiles(t, "testdata/output/flat/ar-ar.all.json", "testdata/expected/flat/ar-ar.all.json") expectEqualFiles(t, "testdata/output/flat/fr-fr.all.json", "testdata/expected/flat/fr-fr.all.json") expectEqualFiles(t, "testdata/output/flat/en-us.untranslated.json", "testdata/expected/flat/en-us.untranslated.json") expectEqualFiles(t, "testdata/output/flat/ar-ar.untranslated.json", "testdata/expected/flat/ar-ar.untranslated.json") expectEqualFiles(t, "testdata/output/flat/fr-fr.untranslated.json", "testdata/expected/flat/fr-fr.untranslated.json") } go-i18n-1.10.0/goi18n/merge_command_test.go000066400000000000000000000040261317766760100202630ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "os" "testing" ) func TestMergeExecuteJSON(t *testing.T) { files := []string{ "testdata/input/en-us.one.json", "testdata/input/en-us.two.json", "testdata/input/fr-fr.json", "testdata/input/ar-ar.one.json", "testdata/input/ar-ar.two.json", } testMergeExecute(t, files) } func TestMergeExecuteYAML(t *testing.T) { files := []string{ "testdata/input/yaml/en-us.one.yaml", "testdata/input/yaml/en-us.two.json", "testdata/input/yaml/fr-fr.json", "testdata/input/yaml/ar-ar.one.json", "testdata/input/yaml/ar-ar.two.json", } testMergeExecute(t, files) } func testMergeExecute(t *testing.T, files []string) { resetDir(t, "testdata/output") mc := &mergeCommand{ translationFiles: files, sourceLanguage: "en-us", outdir: "testdata/output", format: "json", flat: false, } if err := mc.execute(); err != nil { t.Fatal(err) } expectEqualFiles(t, "testdata/output/en-us.all.json", "testdata/expected/en-us.all.json") expectEqualFiles(t, "testdata/output/ar-ar.all.json", "testdata/expected/ar-ar.all.json") expectEqualFiles(t, "testdata/output/fr-fr.all.json", "testdata/expected/fr-fr.all.json") expectEqualFiles(t, "testdata/output/en-us.untranslated.json", "testdata/expected/en-us.untranslated.json") expectEqualFiles(t, "testdata/output/ar-ar.untranslated.json", "testdata/expected/ar-ar.untranslated.json") expectEqualFiles(t, "testdata/output/fr-fr.untranslated.json", "testdata/expected/fr-fr.untranslated.json") } func resetDir(t *testing.T, dir string) { if err := os.RemoveAll(dir); err != nil { t.Fatal(err) } if err := os.Mkdir(dir, 0777); err != nil { t.Fatal(err) } } func expectEqualFiles(t *testing.T, expectedName, actualName string) { actual, err := ioutil.ReadFile(actualName) if err != nil { t.Fatal(err) } expected, err := ioutil.ReadFile(expectedName) if err != nil { t.Fatal(err) } if !bytes.Equal(actual, expected) { t.Errorf("contents of files did not match: %s, %s", expectedName, actualName) } } go-i18n-1.10.0/goi18n/testdata/000077500000000000000000000000001317766760100157075ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/en-us.flat.json000066400000000000000000000015011317766760100205530ustar00rootroot00000000000000{ "program_greeting": { "other": "Hello world" }, "person_greeting": { "other": "Hello {{.Person}}" }, "my_height_in_meters": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." }, "your_unread_email_count": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." }, "person_unread_email_count": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." }, "person_unread_email_count_timeframe": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "d_days": { "one": "{{.Count}} day.", "other": "{{.Count}} days." } } go-i18n-1.10.0/goi18n/testdata/en-us.flat.toml000066400000000000000000000012531317766760100205610ustar00rootroot00000000000000[program_greeting] other = "Hello world" [person_greeting] other = "Hello {{.Person}}" [my_height_in_meters] one = "I am {{.Count}} meter tall." other = "I am {{.Count}} meters tall." [your_unread_email_count] one = "You have {{.Count}} unread email." other = "You have {{.Count}} unread emails." [person_unread_email_count] one = "{{.Person}} has {{.Count}} unread email." other = "{{.Person}} has {{.Count}} unread emails." [person_unread_email_count_timeframe] one = "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." other = "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." [d_days] one = "{{.Count}} day" other = "{{.Count}} days" go-i18n-1.10.0/goi18n/testdata/en-us.flat.yaml000066400000000000000000000013061317766760100205470ustar00rootroot00000000000000 # Comment # Comment program_greeting: other: "Hello world" person_greeting: other: "Hello {{.Person}}" my_height_in_meters: one: "I am {{.Count}} meter tall." other: "I am {{.Count}} meters tall." your_unread_email_count: one: "You have {{.Count}} unread email." other: "You have {{.Count}} unread emails." person_unread_email_count: one: "{{.Person}} has {{.Count}} unread email." other: "{{.Person}} has {{.Count}} unread emails." person_unread_email_count_timeframe: one: "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." other: "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." d_days: one: "{{.Count}} day" other: "{{.Count}} days" go-i18n-1.10.0/goi18n/testdata/en-us.yaml000066400000000000000000000015251317766760100176250ustar00rootroot00000000000000 # Comment # Comment - id: program_greeting translation: "Hello world" - id: person_greeting translation: "Hello {{.Person}}" - id: my_height_in_meters translation: one: "I am {{.Count}} meter tall." other: "I am {{.Count}} meters tall." - id: your_unread_email_count translation: one: "You have {{.Count}} unread email." other: "You have {{.Count}} unread emails." - id: person_unread_email_count translation: one: "{{.Person}} has {{.Count}} unread email." other: "{{.Person}} has {{.Count}} unread emails." - id: person_unread_email_count_timeframe translation: one: "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." other: "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." - id: d_days translation: one: "{{.Count}} day" other: "{{.Count}} days" go-i18n-1.10.0/goi18n/testdata/expected/000077500000000000000000000000001317766760100175105ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/expected/R.go000066400000000000000000000031251317766760100202410ustar00rootroot00000000000000// DON'T CHANGE THIS FILE MANUALLY // This file was generated using the command: // $ goi18n constants package R // DDays is the identifier for the following localizable string template(s): // one: "{{.Count}} day" // other: "{{.Count}} days" const DDays = "d_days" // MyHeightInMeters is the identifier for the following localizable string template(s): // one: "I am {{.Count}} meter tall." // other: "I am {{.Count}} meters tall." const MyHeightInMeters = "my_height_in_meters" // PersonGreeting is the identifier for the following localizable string template(s): // "Hello {{.Person}}" const PersonGreeting = "person_greeting" // PersonUnreadEmailCount is the identifier for the following localizable string template(s): // one: "{{.Person}} has {{.Count}} unread email." // other: "{{.Person}} has {{.Count}} unread emails." const PersonUnreadEmailCount = "person_unread_email_count" // PersonUnreadEmailCountTimeframe is the identifier for the following localizable string template(s): // one: "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." // other: "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." const PersonUnreadEmailCountTimeframe = "person_unread_email_count_timeframe" // ProgramGreeting is the identifier for the following localizable string template(s): // "Hello world" const ProgramGreeting = "program_greeting" // YourUnreadEmailCount is the identifier for the following localizable string template(s): // one: "You have {{.Count}} unread email." // other: "You have {{.Count}} unread emails." const YourUnreadEmailCount = "your_unread_email_count" go-i18n-1.10.0/goi18n/testdata/expected/ar-ar.all.json000066400000000000000000000026621317766760100221620ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "few": "new arabic few translation of d_days", "many": "arabic many translation of d_days", "one": "arabic one translation of d_days", "other": "", "two": "", "zero": "" } }, { "id": "my_height_in_meters", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "person_greeting", "translation": "new arabic translation of person_greeting" }, { "id": "person_unread_email_count", "translation": { "few": "arabic few translation of person_unread_email_count", "many": "arabic many translation of person_unread_email_count", "one": "arabic one translation of person_unread_email_count", "other": "arabic other translation of person_unread_email_count", "two": "arabic two translation of person_unread_email_count", "zero": "arabic zero translation of person_unread_email_count" } }, { "id": "person_unread_email_count_timeframe", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "program_greeting", "translation": "" }, { "id": "your_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } } ]go-i18n-1.10.0/goi18n/testdata/expected/ar-ar.untranslated.json000066400000000000000000000032471317766760100241160ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "few": "new arabic few translation of d_days", "many": "arabic many translation of d_days", "one": "arabic one translation of d_days", "other": "{{.Count}} days", "two": "{{.Count}} days", "zero": "{{.Count}} days" } }, { "id": "my_height_in_meters", "translation": { "few": "I am {{.Count}} meters tall.", "many": "I am {{.Count}} meters tall.", "one": "I am {{.Count}} meters tall.", "other": "I am {{.Count}} meters tall.", "two": "I am {{.Count}} meters tall.", "zero": "I am {{.Count}} meters tall." } }, { "id": "person_unread_email_count_timeframe", "translation": { "few": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", "many": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", "one": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", "two": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", "zero": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "program_greeting", "translation": "Hello world" }, { "id": "your_unread_email_count", "translation": { "few": "You have {{.Count}} unread emails.", "many": "You have {{.Count}} unread emails.", "one": "You have {{.Count}} unread emails.", "other": "You have {{.Count}} unread emails.", "two": "You have {{.Count}} unread emails.", "zero": "You have {{.Count}} unread emails." } } ]go-i18n-1.10.0/goi18n/testdata/expected/en-us.all.json000066400000000000000000000020331317766760100221770ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "one": "{{.Count}} day", "other": "{{.Count}} days" } }, { "id": "my_height_in_meters", "translation": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." } }, { "id": "person_greeting", "translation": "Hello {{.Person}}" }, { "id": "person_unread_email_count", "translation": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." } }, { "id": "person_unread_email_count_timeframe", "translation": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "program_greeting", "translation": "Hello world" }, { "id": "your_unread_email_count", "translation": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } } ]go-i18n-1.10.0/goi18n/testdata/expected/en-us.untranslated.json000066400000000000000000000000021317766760100241250ustar00rootroot00000000000000[]go-i18n-1.10.0/goi18n/testdata/expected/flat/000077500000000000000000000000001317766760100204365ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/expected/flat/ar-ar.all.json000066400000000000000000000021201317766760100230750ustar00rootroot00000000000000{ "d_days": { "few": "new arabic few translation of d_days", "many": "arabic many translation of d_days", "one": "arabic one translation of d_days", "other": "", "two": "", "zero": "" }, "my_height_in_meters": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" }, "person_greeting": { "other": "new arabic translation of person_greeting" }, "person_unread_email_count": { "few": "arabic few translation of person_unread_email_count", "many": "arabic many translation of person_unread_email_count", "one": "arabic one translation of person_unread_email_count", "other": "arabic other translation of person_unread_email_count", "two": "arabic two translation of person_unread_email_count", "zero": "arabic zero translation of person_unread_email_count" }, "person_unread_email_count_timeframe": { "other": "" }, "program_greeting": { "other": "" }, "your_unread_email_count": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }go-i18n-1.10.0/goi18n/testdata/expected/flat/ar-ar.untranslated.json000066400000000000000000000020621317766760100250360ustar00rootroot00000000000000{ "d_days": { "few": "new arabic few translation of d_days", "many": "arabic many translation of d_days", "one": "arabic one translation of d_days", "other": "{{.Count}} days", "two": "{{.Count}} days", "zero": "{{.Count}} days" }, "my_height_in_meters": { "few": "I am {{.Count}} meters tall.", "many": "I am {{.Count}} meters tall.", "one": "I am {{.Count}} meters tall.", "other": "I am {{.Count}} meters tall.", "two": "I am {{.Count}} meters tall.", "zero": "I am {{.Count}} meters tall." }, "person_unread_email_count_timeframe": { "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "program_greeting": { "other": "Hello world" }, "your_unread_email_count": { "few": "You have {{.Count}} unread emails.", "many": "You have {{.Count}} unread emails.", "one": "You have {{.Count}} unread emails.", "other": "You have {{.Count}} unread emails.", "two": "You have {{.Count}} unread emails.", "zero": "You have {{.Count}} unread emails." } }go-i18n-1.10.0/goi18n/testdata/expected/flat/en-us.all.json000066400000000000000000000013341317766760100231300ustar00rootroot00000000000000{ "d_days": { "one": "{{.Count}} day", "other": "{{.Count}} days" }, "my_height_in_meters": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." }, "person_greeting": { "other": "Hello {{.Person}}" }, "person_unread_email_count": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." }, "person_unread_email_count_timeframe": { "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "program_greeting": { "other": "Hello world" }, "your_unread_email_count": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } }go-i18n-1.10.0/goi18n/testdata/expected/flat/en-us.untranslated.json000066400000000000000000000000021317766760100250530ustar00rootroot00000000000000{}go-i18n-1.10.0/goi18n/testdata/expected/flat/en-us.untranslated.json.json000066400000000000000000000000001317766760100260210ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/expected/flat/fr-fr.all.json000066400000000000000000000006241317766760100231160ustar00rootroot00000000000000{ "d_days": { "one": "", "other": "" }, "my_height_in_meters": { "one": "", "other": "" }, "person_greeting": { "other": "" }, "person_unread_email_count": { "one": "", "other": "" }, "person_unread_email_count_timeframe": { "other": "" }, "program_greeting": { "other": "" }, "your_unread_email_count": { "one": "", "other": "" } }go-i18n-1.10.0/goi18n/testdata/expected/flat/fr-fr.untranslated.json000066400000000000000000000013401317766760100250460ustar00rootroot00000000000000{ "d_days": { "one": "{{.Count}} days", "other": "{{.Count}} days" }, "my_height_in_meters": { "one": "I am {{.Count}} meters tall.", "other": "I am {{.Count}} meters tall." }, "person_greeting": { "other": "Hello {{.Person}}" }, "person_unread_email_count": { "one": "{{.Person}} has {{.Count}} unread emails.", "other": "{{.Person}} has {{.Count}} unread emails." }, "person_unread_email_count_timeframe": { "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "program_greeting": { "other": "Hello world" }, "your_unread_email_count": { "one": "You have {{.Count}} unread emails.", "other": "You have {{.Count}} unread emails." } }go-i18n-1.10.0/goi18n/testdata/expected/fr-fr.all.json000066400000000000000000000012201317766760100221610ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "one": "", "other": "" } }, { "id": "my_height_in_meters", "translation": { "one": "", "other": "" } }, { "id": "person_greeting", "translation": "" }, { "id": "person_unread_email_count", "translation": { "one": "", "other": "" } }, { "id": "person_unread_email_count_timeframe", "translation": { "one": "", "other": "" } }, { "id": "program_greeting", "translation": "" }, { "id": "your_unread_email_count", "translation": { "one": "", "other": "" } } ]go-i18n-1.10.0/goi18n/testdata/expected/fr-fr.untranslated.json000066400000000000000000000020401317766760100241160ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "one": "{{.Count}} days", "other": "{{.Count}} days" } }, { "id": "my_height_in_meters", "translation": { "one": "I am {{.Count}} meters tall.", "other": "I am {{.Count}} meters tall." } }, { "id": "person_greeting", "translation": "Hello {{.Person}}" }, { "id": "person_unread_email_count", "translation": { "one": "{{.Person}} has {{.Count}} unread emails.", "other": "{{.Person}} has {{.Count}} unread emails." } }, { "id": "person_unread_email_count_timeframe", "translation": { "one": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "program_greeting", "translation": "Hello world" }, { "id": "your_unread_email_count", "translation": { "one": "You have {{.Count}} unread emails.", "other": "You have {{.Count}} unread emails." } } ]go-i18n-1.10.0/goi18n/testdata/input/000077500000000000000000000000001317766760100170465ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/input/ar-ar.one.json000066400000000000000000000021011317766760100215150ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "few": "arabic few translation of d_days", "many": "arabic many translation of d_days", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "person_greeting", "translation": "arabic translation of person_greeting" }, { "id": "person_unread_email_count", "translation": { "few": "arabic few translation of person_unread_email_count", "many": "arabic many translation of person_unread_email_count", "one": "arabic one translation of person_unread_email_count", "other": "", "two": "", "zero": "" } }, { "id": "person_unread_email_count_timeframe", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "program_greeting", "translation": "" }, { "id": "your_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } } ] go-i18n-1.10.0/goi18n/testdata/input/ar-ar.two.json000066400000000000000000000021121317766760100215470ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "few": "new arabic few translation of d_days", "many": "", "one": "arabic one translation of d_days", "other": "", "two": "", "zero": "" } }, { "id": "person_greeting", "translation": "new arabic translation of person_greeting" }, { "id": "person_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "arabic other translation of person_unread_email_count", "two": "arabic two translation of person_unread_email_count", "zero": "arabic zero translation of person_unread_email_count" } }, { "id": "person_unread_email_count_timeframe", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "program_greeting", "translation": "" }, { "id": "your_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } } ] go-i18n-1.10.0/goi18n/testdata/input/en-us.constants.json000066400000000000000000000020331317766760100230010ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "one": "{{.Count}} day", "other": "{{.Count}} days" } }, { "id": "my_height_in_meters", "translation": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." } }, { "id": "person_greeting", "translation": "Hello {{.Person}}" }, { "id": "person_unread_email_count", "translation": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." } }, { "id": "person_unread_email_count_timeframe", "translation": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "program_greeting", "translation": "Hello world" }, { "id": "your_unread_email_count", "translation": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } } ]go-i18n-1.10.0/goi18n/testdata/input/en-us.one.json000066400000000000000000000012171317766760100215510ustar00rootroot00000000000000[ { "id": "program_greeting", "translation": "Hello world" }, { "id": "your_unread_email_count", "translation": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } }, { "id": "my_height_in_meters", "translation": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." } }, { "id": "person_unread_email_count_timeframe", "translation": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." } }, { "id": "d_days", "translation": "this should get overwritten" } ] go-i18n-1.10.0/goi18n/testdata/input/en-us.two.json000066400000000000000000000010621317766760100215770ustar00rootroot00000000000000[ { "id": "person_greeting", "translation": "Hello {{.Person}}" }, { "id": "person_unread_email_count", "translation": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." } }, { "id": "person_unread_email_count_timeframe", "translation": { "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "d_days", "translation": { "one": "{{.Count}} day", "other": "{{.Count}} days" } } ] go-i18n-1.10.0/goi18n/testdata/input/flat/000077500000000000000000000000001317766760100177745ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/input/flat/ar-ar.one.toml000066400000000000000000000012151317766760100224520ustar00rootroot00000000000000[d_days] few = "arabic few translation of d_days" many = "arabic many translation of d_days" one = "" other = "" two = "" zero = "" [person_greeting] other = "arabic translation of person_greeting" [person_unread_email_count] few = "arabic few translation of person_unread_email_count" many = "arabic many translation of person_unread_email_count" one = "arabic one translation of person_unread_email_count" other = "" two = "" zero = "" [person_unread_email_count_timeframe] few = "" many = "" one = "" other = "" two = "" zero = "" [program_greeting] other = "" [your_unread_email_count] few = "" many = "" one = "" other = "" two = "" zero = "" go-i18n-1.10.0/goi18n/testdata/input/flat/ar-ar.two.json000066400000000000000000000015531317766760100225050ustar00rootroot00000000000000{ "d_days": { "few": "new arabic few translation of d_days", "many": "", "one": "arabic one translation of d_days", "other": "", "two": "", "zero": "" }, "person_greeting": { "other": "new arabic translation of person_greeting" }, "person_unread_email_count": { "few": "", "many": "", "one": "", "other": "arabic other translation of person_unread_email_count", "two": "arabic two translation of person_unread_email_count", "zero": "arabic zero translation of person_unread_email_count" }, "person_unread_email_count_timeframe": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" }, "program_greeting": { "other": "" }, "your_unread_email_count": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } } go-i18n-1.10.0/goi18n/testdata/input/flat/en-us.constants.json000066400000000000000000000014651317766760100237370ustar00rootroot00000000000000{ "d_days": { "one": "{{.Count}} day", "other": "{{.Count}} days" }, "my_height_in_meters": { "one": "I am {{.Count}} meter tall.", "other": "I am {{.Count}} meters tall." }, "person_greeting": { "other": "Hello {{.Person}}" }, "person_unread_email_count": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." }, "person_unread_email_count_timeframe": { "one": "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}.", "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "program_greeting": { "other": "Hello world" }, "your_unread_email_count": { "one": "You have {{.Count}} unread email.", "other": "You have {{.Count}} unread emails." } } go-i18n-1.10.0/goi18n/testdata/input/flat/en-us.one.yaml000066400000000000000000000006441317766760100224730ustar00rootroot00000000000000program_greeting: other: "Hello world" your_unread_email_count: one: "You have {{.Count}} unread email." other: "You have {{.Count}} unread emails." my_height_in_meters: one: "I am {{.Count}} meter tall." other: "I am {{.Count}} meters tall." person_unread_email_count_timeframe: other: "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." d_days: other: "this should get overwritten" go-i18n-1.10.0/goi18n/testdata/input/flat/en-us.two.json000066400000000000000000000006541317766760100225330ustar00rootroot00000000000000{ "person_greeting": { "other": "Hello {{.Person}}" }, "person_unread_email_count": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." }, "person_unread_email_count_timeframe": { "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." }, "d_days": { "one": "{{.Count}} day", "other": "{{.Count}} days" } } go-i18n-1.10.0/goi18n/testdata/input/flat/fr-fr.json000066400000000000000000000000001317766760100216710ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/input/fr-fr.json000066400000000000000000000000001317766760100207430ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/input/yaml/000077500000000000000000000000001317766760100200105ustar00rootroot00000000000000go-i18n-1.10.0/goi18n/testdata/input/yaml/ar-ar.one.json000066400000000000000000000021011317766760100224570ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "few": "arabic few translation of d_days", "many": "arabic many translation of d_days", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "person_greeting", "translation": "arabic translation of person_greeting" }, { "id": "person_unread_email_count", "translation": { "few": "arabic few translation of person_unread_email_count", "many": "arabic many translation of person_unread_email_count", "one": "arabic one translation of person_unread_email_count", "other": "", "two": "", "zero": "" } }, { "id": "person_unread_email_count_timeframe", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "program_greeting", "translation": "" }, { "id": "your_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } } ] go-i18n-1.10.0/goi18n/testdata/input/yaml/ar-ar.two.json000066400000000000000000000021121317766760100225110ustar00rootroot00000000000000[ { "id": "d_days", "translation": { "few": "new arabic few translation of d_days", "many": "", "one": "arabic one translation of d_days", "other": "", "two": "", "zero": "" } }, { "id": "person_greeting", "translation": "new arabic translation of person_greeting" }, { "id": "person_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "arabic other translation of person_unread_email_count", "two": "arabic two translation of person_unread_email_count", "zero": "arabic zero translation of person_unread_email_count" } }, { "id": "person_unread_email_count_timeframe", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } }, { "id": "program_greeting", "translation": "" }, { "id": "your_unread_email_count", "translation": { "few": "", "many": "", "one": "", "other": "", "two": "", "zero": "" } } ] go-i18n-1.10.0/goi18n/testdata/input/yaml/en-us.one.yaml000066400000000000000000000007651317766760100225130ustar00rootroot00000000000000- id: program_greeting translation: Hello world - id: your_unread_email_count translation: one: You have {{.Count}} unread email. other: You have {{.Count}} unread emails. - id: my_height_in_meters translation: one: I am {{.Count}} meter tall. other: I am {{.Count}} meters tall. - id: person_unread_email_count_timeframe translation: one: "{{.Person}} has {{.Count}} unread email in the past {{.Timeframe}}." - id: d_days translation: this should get overwrittengo-i18n-1.10.0/goi18n/testdata/input/yaml/en-us.two.json000066400000000000000000000010621317766760100225410ustar00rootroot00000000000000[ { "id": "person_greeting", "translation": "Hello {{.Person}}" }, { "id": "person_unread_email_count", "translation": { "one": "{{.Person}} has {{.Count}} unread email.", "other": "{{.Person}} has {{.Count}} unread emails." } }, { "id": "person_unread_email_count_timeframe", "translation": { "other": "{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}." } }, { "id": "d_days", "translation": { "one": "{{.Count}} day", "other": "{{.Count}} days" } } ] go-i18n-1.10.0/goi18n/testdata/input/yaml/fr-fr.json000066400000000000000000000000001317766760100217050ustar00rootroot00000000000000go-i18n-1.10.0/i18n/000077500000000000000000000000001317766760100135505ustar00rootroot00000000000000go-i18n-1.10.0/i18n/bundle/000077500000000000000000000000001317766760100150215ustar00rootroot00000000000000go-i18n-1.10.0/i18n/bundle/bundle.go000066400000000000000000000307231317766760100166260ustar00rootroot00000000000000// Package bundle manages translations for multiple languages. package bundle import ( "bytes" "encoding/json" "fmt" "io/ioutil" "path/filepath" "reflect" "sync" "unicode" "github.com/nicksnyder/go-i18n/i18n/language" "github.com/nicksnyder/go-i18n/i18n/translation" toml "github.com/pelletier/go-toml" "gopkg.in/yaml.v2" ) // TranslateFunc is a copy of i18n.TranslateFunc to avoid a circular dependency. type TranslateFunc func(translationID string, args ...interface{}) string // Bundle stores the translations for multiple languages. type Bundle struct { // The primary translations for a language tag and translation id. translations map[string]map[string]translation.Translation // Translations that can be used when an exact language match is not possible. fallbackTranslations map[string]map[string]translation.Translation sync.RWMutex } // New returns an empty bundle. func New() *Bundle { return &Bundle{ translations: make(map[string]map[string]translation.Translation), fallbackTranslations: make(map[string]map[string]translation.Translation), } } // MustLoadTranslationFile is similar to LoadTranslationFile // except it panics if an error happens. func (b *Bundle) MustLoadTranslationFile(filename string) { if err := b.LoadTranslationFile(filename); err != nil { panic(err) } } // LoadTranslationFile loads the translations from filename into memory. // // The language that the translations are associated with is parsed from the filename (e.g. en-US.json). // // Generally you should load translation files once during your program's initialization. func (b *Bundle) LoadTranslationFile(filename string) error { buf, err := ioutil.ReadFile(filename) if err != nil { return err } return b.ParseTranslationFileBytes(filename, buf) } // ParseTranslationFileBytes is similar to LoadTranslationFile except it parses the bytes in buf. // // It is useful for parsing translation files embedded with go-bindata. func (b *Bundle) ParseTranslationFileBytes(filename string, buf []byte) error { basename := filepath.Base(filename) langs := language.Parse(basename) switch l := len(langs); { case l == 0: return fmt.Errorf("no language found in %q", basename) case l > 1: return fmt.Errorf("multiple languages found in filename %q: %v; expected one", basename, langs) } translations, err := parseTranslations(filename, buf) if err != nil { return err } b.AddTranslation(langs[0], translations...) return nil } func parseTranslations(filename string, buf []byte) ([]translation.Translation, error) { if len(buf) == 0 { return []translation.Translation{}, nil } ext := filepath.Ext(filename) // `github.com/pelletier/go-toml` lacks an Unmarshal function, // so we should parse TOML separately. if ext == ".toml" { tree, err := toml.LoadReader(bytes.NewReader(buf)) if err != nil { return nil, err } m := make(map[string]map[string]interface{}) for k, v := range tree.ToMap() { m[k] = v.(map[string]interface{}) } return parseFlatFormat(m) } // Then parse other formats. if isStandardFormat(ext, buf) { var standardFormat []map[string]interface{} if err := unmarshal(ext, buf, &standardFormat); err != nil { return nil, fmt.Errorf("failed to unmarshal %v: %v", filename, err) } return parseStandardFormat(standardFormat) } else { var flatFormat map[string]map[string]interface{} if err := unmarshal(ext, buf, &flatFormat); err != nil { return nil, fmt.Errorf("failed to unmarshal %v: %v", filename, err) } return parseFlatFormat(flatFormat) } } func isStandardFormat(ext string, buf []byte) bool { buf = deleteLeadingComments(ext, buf) firstRune := rune(buf[0]) return (ext == ".json" && firstRune == '[') || (ext == ".yaml" && firstRune == '-') } // deleteLeadingComments deletes leading newlines and comments in buf. // It only works for ext == ".yaml". func deleteLeadingComments(ext string, buf []byte) []byte { if ext != ".yaml" { return buf } for { buf = bytes.TrimLeftFunc(buf, unicode.IsSpace) if buf[0] == '#' { buf = deleteLine(buf) } else { break } } return buf } func deleteLine(buf []byte) []byte { index := bytes.IndexRune(buf, '\n') if index == -1 { // If there is only one line without newline ... return nil // ... delete it and return nothing. } if index == len(buf)-1 { // If there is only one line with newline ... return nil // ... do the same as above. } return buf[index+1:] } // unmarshal finds an appropriate unmarshal function for ext // (extension of filename) and unmarshals buf to out. out must be a pointer. func unmarshal(ext string, buf []byte, out interface{}) error { switch ext { case ".json": return json.Unmarshal(buf, out) case ".yaml": return yaml.Unmarshal(buf, out) } return fmt.Errorf("unsupported file extension %v", ext) } func parseStandardFormat(data []map[string]interface{}) ([]translation.Translation, error) { translations := make([]translation.Translation, 0, len(data)) for i, translationData := range data { t, err := translation.NewTranslation(translationData) if err != nil { return nil, fmt.Errorf("unable to parse translation #%d because %s\n%v", i, err, translationData) } translations = append(translations, t) } return translations, nil } // parseFlatFormat just converts data from flat format to standard format // and passes it to parseStandardFormat. // // Flat format logic: // key of data must be a string and data[key] must be always map[string]interface{}, // but if there is only "other" key in it then it is non-plural, else plural. func parseFlatFormat(data map[string]map[string]interface{}) ([]translation.Translation, error) { var standardFormatData []map[string]interface{} for id, translationData := range data { dataObject := make(map[string]interface{}) dataObject["id"] = id if len(translationData) == 1 { // non-plural form _, otherExists := translationData["other"] if otherExists { dataObject["translation"] = translationData["other"] } } else { // plural form dataObject["translation"] = translationData } standardFormatData = append(standardFormatData, dataObject) } return parseStandardFormat(standardFormatData) } // AddTranslation adds translations for a language. // // It is useful if your translations are in a format not supported by LoadTranslationFile. func (b *Bundle) AddTranslation(lang *language.Language, translations ...translation.Translation) { b.Lock() defer b.Unlock() if b.translations[lang.Tag] == nil { b.translations[lang.Tag] = make(map[string]translation.Translation, len(translations)) } currentTranslations := b.translations[lang.Tag] for _, newTranslation := range translations { if currentTranslation := currentTranslations[newTranslation.ID()]; currentTranslation != nil { currentTranslations[newTranslation.ID()] = currentTranslation.Merge(newTranslation) } else { currentTranslations[newTranslation.ID()] = newTranslation } } // lang can provide translations for less specific language tags. for _, tag := range lang.MatchingTags() { b.fallbackTranslations[tag] = currentTranslations } } // Translations returns all translations in the bundle. func (b *Bundle) Translations() map[string]map[string]translation.Translation { t := make(map[string]map[string]translation.Translation) b.RLock() for tag, translations := range b.translations { t[tag] = make(map[string]translation.Translation) for id, translation := range translations { t[tag][id] = translation } } b.RUnlock() return t } // LanguageTags returns the tags of all languages that that have been added. func (b *Bundle) LanguageTags() []string { var tags []string b.RLock() for k := range b.translations { tags = append(tags, k) } b.RUnlock() return tags } // LanguageTranslationIDs returns the ids of all translations that have been added for a given language. func (b *Bundle) LanguageTranslationIDs(languageTag string) []string { var ids []string b.RLock() for id := range b.translations[languageTag] { ids = append(ids, id) } b.RUnlock() return ids } // MustTfunc is similar to Tfunc except it panics if an error happens. func (b *Bundle) MustTfunc(pref string, prefs ...string) TranslateFunc { tfunc, err := b.Tfunc(pref, prefs...) if err != nil { panic(err) } return tfunc } // MustTfuncAndLanguage is similar to TfuncAndLanguage except it panics if an error happens. func (b *Bundle) MustTfuncAndLanguage(pref string, prefs ...string) (TranslateFunc, *language.Language) { tfunc, language, err := b.TfuncAndLanguage(pref, prefs...) if err != nil { panic(err) } return tfunc, language } // Tfunc is similar to TfuncAndLanguage except is doesn't return the Language. func (b *Bundle) Tfunc(pref string, prefs ...string) (TranslateFunc, error) { tfunc, _, err := b.TfuncAndLanguage(pref, prefs...) return tfunc, err } // TfuncAndLanguage returns a TranslateFunc for the first Language that // has a non-zero number of translations in the bundle. // // The returned Language matches the the first language preference that could be satisfied, // but this may not strictly match the language of the translations used to satisfy that preference. // // For example, the user may request "zh". If there are no translations for "zh" but there are translations // for "zh-cn", then the translations for "zh-cn" will be used but the returned Language will be "zh". // // It can parse languages from Accept-Language headers (RFC 2616), // but it assumes weights are monotonically decreasing. func (b *Bundle) TfuncAndLanguage(pref string, prefs ...string) (TranslateFunc, *language.Language, error) { lang := b.supportedLanguage(pref, prefs...) var err error if lang == nil { err = fmt.Errorf("no supported languages found %#v", append(prefs, pref)) } return func(translationID string, args ...interface{}) string { return b.translate(lang, translationID, args...) }, lang, err } // supportedLanguage returns the first language which // has a non-zero number of translations in the bundle. func (b *Bundle) supportedLanguage(pref string, prefs ...string) *language.Language { lang := b.translatedLanguage(pref) if lang == nil { for _, pref := range prefs { lang = b.translatedLanguage(pref) if lang != nil { break } } } return lang } func (b *Bundle) translatedLanguage(src string) *language.Language { langs := language.Parse(src) b.RLock() defer b.RUnlock() for _, lang := range langs { if len(b.translations[lang.Tag]) > 0 || len(b.fallbackTranslations[lang.Tag]) > 0 { return lang } } return nil } func (b *Bundle) translate(lang *language.Language, translationID string, args ...interface{}) string { if lang == nil { return translationID } translation := b.translation(lang, translationID) if translation == nil { return translationID } var data interface{} var count interface{} if argc := len(args); argc > 0 { if isNumber(args[0]) { count = args[0] if argc > 1 { data = args[1] } } else { data = args[0] } } if count != nil { if data == nil { data = map[string]interface{}{"Count": count} } else { dataMap := toMap(data) dataMap["Count"] = count data = dataMap } } else { dataMap := toMap(data) if c, ok := dataMap["Count"]; ok { count = c } } p, _ := lang.Plural(count) template := translation.Template(p) if template == nil { return translationID } s := template.Execute(data) if s == "" { return translationID } return s } func (b *Bundle) translation(lang *language.Language, translationID string) translation.Translation { b.RLock() defer b.RUnlock() translations := b.translations[lang.Tag] if translations == nil { translations = b.fallbackTranslations[lang.Tag] if translations == nil { return nil } } return translations[translationID] } func isNumber(n interface{}) bool { switch n.(type) { case int, int8, int16, int32, int64, string: return true } return false } func toMap(input interface{}) map[string]interface{} { if data, ok := input.(map[string]interface{}); ok { return data } v := reflect.ValueOf(input) switch v.Kind() { case reflect.Ptr: return toMap(v.Elem().Interface()) case reflect.Struct: return structToMap(v) default: return nil } } // Converts the top level of a struct to a map[string]interface{}. // Code inspired by github.com/fatih/structs. func structToMap(v reflect.Value) map[string]interface{} { out := make(map[string]interface{}) t := v.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) if field.PkgPath != "" { // unexported field. skip. continue } out[field.Name] = v.FieldByName(field.Name).Interface() } return out } go-i18n-1.10.0/i18n/bundle/bundle_test.go000066400000000000000000000223331317766760100176630ustar00rootroot00000000000000package bundle import ( "fmt" "strconv" "sync" "testing" "reflect" "sort" "github.com/nicksnyder/go-i18n/i18n/language" "github.com/nicksnyder/go-i18n/i18n/translation" ) func TestMustLoadTranslationFile(t *testing.T) { t.Skipf("not implemented") } func TestLoadTranslationFile(t *testing.T) { t.Skipf("not implemented") } func TestParseTranslationFileBytes(t *testing.T) { t.Skipf("not implemented") } func TestAddTranslation(t *testing.T) { t.Skipf("not implemented") } func TestMustTfunc(t *testing.T) { defer func() { if r := recover(); r == nil { t.Errorf("expected MustTfunc to panic") } }() New().MustTfunc("invalid") } func TestLanguageTagsAndTranslationIDs(t *testing.T) { b := New() translationID := "translation_id" englishLanguage := languageWithTag("en-US") frenchLanguage := languageWithTag("fr-FR") spanishLanguage := languageWithTag("es") addFakeTranslation(t, b, englishLanguage, "English"+translationID) addFakeTranslation(t, b, frenchLanguage, translationID) addFakeTranslation(t, b, spanishLanguage, translationID) tags := b.LanguageTags() sort.Strings(tags) compareTo := []string{englishLanguage.Tag, spanishLanguage.Tag, frenchLanguage.Tag} if !reflect.DeepEqual(tags, compareTo) { t.Errorf("LanguageTags() = %#v; expected: %#v", tags, compareTo) } ids := b.LanguageTranslationIDs(englishLanguage.Tag) sort.Strings(ids) compareTo = []string{"English" + translationID} if !reflect.DeepEqual(ids, compareTo) { t.Errorf("LanguageTranslationIDs() = %#v; expected: %#v", ids, compareTo) } } func TestTfuncAndLanguage(t *testing.T) { b := New() translationID := "translation_id" englishLanguage := languageWithTag("en-US") frenchLanguage := languageWithTag("fr-FR") spanishLanguage := languageWithTag("es") chineseLanguage := languageWithTag("zh-hans-cn") englishTranslation := addFakeTranslation(t, b, englishLanguage, translationID) frenchTranslation := addFakeTranslation(t, b, frenchLanguage, translationID) spanishTranslation := addFakeTranslation(t, b, spanishLanguage, translationID) chineseTranslation := addFakeTranslation(t, b, chineseLanguage, translationID) tests := []struct { languageIDs []string result string expectedLanguage *language.Language }{ { []string{"invalid"}, translationID, nil, }, { []string{"invalid", "invalid2"}, translationID, nil, }, { []string{"invalid", "en-US"}, englishTranslation, englishLanguage, }, { []string{"en-US", "invalid"}, englishTranslation, englishLanguage, }, { []string{"en-US", "fr-FR"}, englishTranslation, englishLanguage, }, { []string{"invalid", "es"}, spanishTranslation, spanishLanguage, }, { []string{"zh-CN,fr-XX,es"}, spanishTranslation, spanishLanguage, }, { []string{"fr"}, frenchTranslation, // The language is still "fr" even though the translation is provided by "fr-FR" languageWithTag("fr"), }, { []string{"zh"}, chineseTranslation, // The language is still "zh" even though the translation is provided by "zh-hans-cn" languageWithTag("zh"), }, { []string{"zh-hans"}, chineseTranslation, // The language is still "zh-hans" even though the translation is provided by "zh-hans-cn" languageWithTag("zh-hans"), }, { []string{"zh-hans-cn"}, chineseTranslation, languageWithTag("zh-hans-cn"), }, } for i, test := range tests { tf, lang, err := b.TfuncAndLanguage(test.languageIDs[0], test.languageIDs[1:]...) if err != nil && test.expectedLanguage != nil { t.Errorf("Tfunc(%v) = error{%q}; expected no error", test.languageIDs, err) } if err == nil && test.expectedLanguage == nil { t.Errorf("Tfunc(%v) = nil error; expected error", test.languageIDs) } if result := tf(translationID); result != test.result { t.Errorf("translation %d was %s; expected %s", i, result, test.result) } if (lang == nil && test.expectedLanguage != nil) || (lang != nil && test.expectedLanguage == nil) || (lang != nil && test.expectedLanguage != nil && lang.String() != test.expectedLanguage.String()) { t.Errorf("lang %d was %s; expected %s", i, lang, test.expectedLanguage) } } } func TestConcurrent(t *testing.T) { b := New() // bootstrap bundle translationID := "translation_id" // +1 englishLanguage := languageWithTag("en-US") addFakeTranslation(t, b, englishLanguage, translationID) tf, err := b.Tfunc(englishLanguage.Tag) if err != nil { t.Errorf("Tfunc(%v) = error{%q}; expected no error", []string{englishLanguage.Tag}, err) } const iterations = 1000 var wg sync.WaitGroup wg.Add(iterations) // Using go routines insert 1000 ints into our map. go func() { for i := 0; i < iterations/2; i++ { // Add item to map. translationID := strconv.FormatInt(int64(i), 10) addFakeTranslation(t, b, englishLanguage, translationID) // Retrieve item from map. tf(translationID) wg.Done() } // Call go routine with current index. }() go func() { for i := iterations / 2; i < iterations; i++ { // Add item to map. translationID := strconv.FormatInt(int64(i), 10) addFakeTranslation(t, b, englishLanguage, translationID) // Retrieve item from map. tf(translationID) wg.Done() } // Call go routine with current index. }() // Wait for all go routines to finish. wg.Wait() // Make sure map contains 1000+1 elements. count := len(b.Translations()[englishLanguage.Tag]) if count != iterations+1 { t.Error("Expecting 1001 elements, got", count) } } func addFakeTranslation(t *testing.T, b *Bundle, lang *language.Language, translationID string) string { translation := fakeTranslation(lang, translationID) b.AddTranslation(lang, testNewTranslation(t, map[string]interface{}{ "id": translationID, "translation": translation, })) return translation } func fakeTranslation(lang *language.Language, translationID string) string { return fmt.Sprintf("%s(%s)", lang.Tag, translationID) } func testNewTranslation(t *testing.T, data map[string]interface{}) translation.Translation { translation, err := translation.NewTranslation(data) if err != nil { t.Fatal(err) } return translation } func languageWithTag(tag string) *language.Language { return language.MustParse(tag)[0] } func createBenchmarkTranslateFunc(b *testing.B, translationTemplate interface{}, count interface{}, expected string) func(data interface{}) { bundle := New() lang := "en-US" translationID := "translation_id" translation, err := translation.NewTranslation(map[string]interface{}{ "id": translationID, "translation": translationTemplate, }) if err != nil { b.Fatal(err) } bundle.AddTranslation(languageWithTag(lang), translation) tf, err := bundle.Tfunc(lang) if err != nil { b.Fatal(err) } return func(data interface{}) { var result string if count == nil { result = tf(translationID, data) } else { result = tf(translationID, count, data) } if result != expected { b.Fatalf("expected %q, got %q", expected, result) } } } func createBenchmarkPluralTranslateFunc(b *testing.B) func(data interface{}) { translationTemplate := map[string]interface{}{ "one": "{{.Person}} is {{.Count}} year old.", "other": "{{.Person}} is {{.Count}} years old.", } count := 26 expected := "Bob is 26 years old." return createBenchmarkTranslateFunc(b, translationTemplate, count, expected) } func createBenchmarkNonPluralTranslateFunc(b *testing.B) func(data interface{}) { translationTemplate := "Hi {{.Person}}!" expected := "Hi Bob!" return createBenchmarkTranslateFunc(b, translationTemplate, nil, expected) } func BenchmarkTranslateNonPluralWithMap(b *testing.B) { data := map[string]interface{}{ "Person": "Bob", } tf := createBenchmarkNonPluralTranslateFunc(b) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } func BenchmarkTranslateNonPluralWithStruct(b *testing.B) { data := struct{ Person string }{Person: "Bob"} tf := createBenchmarkNonPluralTranslateFunc(b) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } func BenchmarkTranslateNonPluralWithStructPointer(b *testing.B) { data := &struct{ Person string }{Person: "Bob"} tf := createBenchmarkNonPluralTranslateFunc(b) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } func BenchmarkTranslatePluralWithMap(b *testing.B) { data := map[string]interface{}{ "Person": "Bob", } tf := createBenchmarkPluralTranslateFunc(b) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } func BenchmarkTranslatePluralWithMapAndCountField(b *testing.B) { data := map[string]interface{}{ "Person": "Bob", "Count": 26, } translationTemplate := map[string]interface{}{ "one": "{{.Person}} is {{.Count}} year old.", "other": "{{.Person}} is {{.Count}} years old.", } expected := "Bob is 26 years old." tf := createBenchmarkTranslateFunc(b, translationTemplate, nil, expected) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } func BenchmarkTranslatePluralWithStruct(b *testing.B) { data := struct{ Person string }{Person: "Bob"} tf := createBenchmarkPluralTranslateFunc(b) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } func BenchmarkTranslatePluralWithStructPointer(b *testing.B) { data := &struct{ Person string }{Person: "Bob"} tf := createBenchmarkPluralTranslateFunc(b) b.ResetTimer() for i := 0; i < b.N; i++ { tf(data) } } go-i18n-1.10.0/i18n/example_test.go000066400000000000000000000060401317766760100165710ustar00rootroot00000000000000package i18n_test import ( "fmt" "github.com/nicksnyder/go-i18n/i18n" ) func Example() { i18n.MustLoadTranslationFile("../goi18n/testdata/expected/en-us.all.json") T, _ := i18n.Tfunc("en-US") bobMap := map[string]interface{}{"Person": "Bob"} bobStruct := struct{ Person string }{Person: "Bob"} fmt.Println(T("program_greeting")) fmt.Println(T("person_greeting", bobMap)) fmt.Println(T("person_greeting", bobStruct)) fmt.Println(T("your_unread_email_count", 0)) fmt.Println(T("your_unread_email_count", 1)) fmt.Println(T("your_unread_email_count", 2)) fmt.Println(T("my_height_in_meters", "1.7")) fmt.Println(T("person_unread_email_count", 0, bobMap)) fmt.Println(T("person_unread_email_count", 1, bobMap)) fmt.Println(T("person_unread_email_count", 2, bobMap)) fmt.Println(T("person_unread_email_count", 0, bobStruct)) fmt.Println(T("person_unread_email_count", 1, bobStruct)) fmt.Println(T("person_unread_email_count", 2, bobStruct)) type Count struct{ Count int } fmt.Println(T("your_unread_email_count", Count{0})) fmt.Println(T("your_unread_email_count", Count{1})) fmt.Println(T("your_unread_email_count", Count{2})) fmt.Println(T("your_unread_email_count", map[string]interface{}{"Count": 0})) fmt.Println(T("your_unread_email_count", map[string]interface{}{"Count": "1"})) fmt.Println(T("your_unread_email_count", map[string]interface{}{"Count": "3.14"})) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 0), })) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 1), })) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 2), })) fmt.Println(T("person_unread_email_count_timeframe", 1, map[string]interface{}{ "Count": 30, "Person": "Bob", "Timeframe": T("d_days", 0), })) fmt.Println(T("person_unread_email_count_timeframe", 2, map[string]interface{}{ "Count": 20, "Person": "Bob", "Timeframe": T("d_days", 1), })) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Count": 10, "Person": "Bob", "Timeframe": T("d_days", 2), })) // Output: // Hello world // Hello Bob // Hello Bob // You have 0 unread emails. // You have 1 unread email. // You have 2 unread emails. // I am 1.7 meters tall. // Bob has 0 unread emails. // Bob has 1 unread email. // Bob has 2 unread emails. // Bob has 0 unread emails. // Bob has 1 unread email. // Bob has 2 unread emails. // You have 0 unread emails. // You have 1 unread email. // You have 2 unread emails. // You have 0 unread emails. // You have 1 unread email. // You have 3.14 unread emails. // Bob has 3 unread emails in the past 0 days. // Bob has 3 unread emails in the past 1 day. // Bob has 3 unread emails in the past 2 days. // Bob has 1 unread email in the past 0 days. // Bob has 2 unread emails in the past 1 day. // Bob has 3 unread emails in the past 2 days. } go-i18n-1.10.0/i18n/exampletemplate_test.go000066400000000000000000000025001317766760100203220ustar00rootroot00000000000000package i18n_test import ( "github.com/nicksnyder/go-i18n/i18n" "os" "text/template" ) var funcMap = map[string]interface{}{ "T": i18n.IdentityTfunc, } var tmpl = template.Must(template.New("").Funcs(funcMap).Parse(` {{T "program_greeting"}} {{T "person_greeting" .}} {{T "your_unread_email_count" 0}} {{T "your_unread_email_count" 1}} {{T "your_unread_email_count" 2}} {{T "person_unread_email_count" 0 .}} {{T "person_unread_email_count" 1 .}} {{T "person_unread_email_count" 2 .}} `)) func Example_template() { i18n.MustLoadTranslationFile("../goi18n/testdata/expected/en-us.all.json") T, _ := i18n.Tfunc("en-US") tmpl.Funcs(map[string]interface{}{ "T": T, }) tmpl.Execute(os.Stdout, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 1), }) tmpl.Execute(os.Stdout, struct { Person string Timeframe string }{ Person: "Bob", Timeframe: T("d_days", 1), }) // Output: // Hello world // Hello Bob // You have 0 unread emails. // You have 1 unread email. // You have 2 unread emails. // Bob has 0 unread emails. // Bob has 1 unread email. // Bob has 2 unread emails. // // Hello world // Hello Bob // You have 0 unread emails. // You have 1 unread email. // You have 2 unread emails. // Bob has 0 unread emails. // Bob has 1 unread email. // Bob has 2 unread emails. } go-i18n-1.10.0/i18n/exampleyaml_test.go000066400000000000000000000035031317766760100174550ustar00rootroot00000000000000package i18n_test import ( "fmt" "github.com/nicksnyder/go-i18n/i18n" ) func ExampleYAML() { i18n.MustLoadTranslationFile("../goi18n/testdata/en-us.yaml") T, _ := i18n.Tfunc("en-US") bobMap := map[string]interface{}{"Person": "Bob"} bobStruct := struct{ Person string }{Person: "Bob"} fmt.Println(T("program_greeting")) fmt.Println(T("person_greeting", bobMap)) fmt.Println(T("person_greeting", bobStruct)) fmt.Println(T("your_unread_email_count", 0)) fmt.Println(T("your_unread_email_count", 1)) fmt.Println(T("your_unread_email_count", 2)) fmt.Println(T("my_height_in_meters", "1.7")) fmt.Println(T("person_unread_email_count", 0, bobMap)) fmt.Println(T("person_unread_email_count", 1, bobMap)) fmt.Println(T("person_unread_email_count", 2, bobMap)) fmt.Println(T("person_unread_email_count", 0, bobStruct)) fmt.Println(T("person_unread_email_count", 1, bobStruct)) fmt.Println(T("person_unread_email_count", 2, bobStruct)) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 0), })) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 1), })) fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ "Person": "Bob", "Timeframe": T("d_days", 2), })) // Output: // Hello world // Hello Bob // Hello Bob // You have 0 unread emails. // You have 1 unread email. // You have 2 unread emails. // I am 1.7 meters tall. // Bob has 0 unread emails. // Bob has 1 unread email. // Bob has 2 unread emails. // Bob has 0 unread emails. // Bob has 1 unread email. // Bob has 2 unread emails. // Bob has 3 unread emails in the past 0 days. // Bob has 3 unread emails in the past 1 day. // Bob has 3 unread emails in the past 2 days. } go-i18n-1.10.0/i18n/i18n.go000066400000000000000000000153641317766760100146670ustar00rootroot00000000000000// Package i18n supports string translations with variable substitution and CLDR pluralization. // It is intended to be used in conjunction with the goi18n command, although that is not strictly required. // // Initialization // // Your Go program should load translations during its initialization. // i18n.MustLoadTranslationFile("path/to/fr-FR.all.json") // If your translations are in a file format not supported by (Must)?LoadTranslationFile, // then you can use the AddTranslation function to manually add translations. // // Fetching a translation // // Use Tfunc or MustTfunc to fetch a TranslateFunc that will return the translated string for a specific language. // func handleRequest(w http.ResponseWriter, r *http.Request) { // cookieLang := r.Cookie("lang") // acceptLang := r.Header.Get("Accept-Language") // defaultLang = "en-US" // known valid language // T, err := i18n.Tfunc(cookieLang, acceptLang, defaultLang) // fmt.Println(T("Hello world")) // } // // Usually it is a good idea to identify strings by a generic id rather than the English translation, // but the rest of this documentation will continue to use the English translation for readability. // T("Hello world") // ok // T("programGreeting") // better! // // Variables // // TranslateFunc supports strings that have variables using the text/template syntax. // T("Hello {{.Person}}", map[string]interface{}{ // "Person": "Bob", // }) // // Pluralization // // TranslateFunc supports the pluralization of strings using the CLDR pluralization rules defined here: // http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html // T("You have {{.Count}} unread emails.", 2) // T("I am {{.Count}} meters tall.", "1.7") // // Plural strings may also have variables. // T("{{.Person}} has {{.Count}} unread emails", 2, map[string]interface{}{ // "Person": "Bob", // }) // // Sentences with multiple plural components can be supported with nesting. // T("{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 3, map[string]interface{}{ // "Person": "Bob", // "Timeframe": T("{{.Count}} days", 2), // }) // // Templates // // You can use the .Funcs() method of a text/template or html/template to register a TranslateFunc // for usage inside of that template. package i18n import ( "github.com/nicksnyder/go-i18n/i18n/bundle" "github.com/nicksnyder/go-i18n/i18n/language" "github.com/nicksnyder/go-i18n/i18n/translation" ) // TranslateFunc returns the translation of the string identified by translationID. // // If there is no translation for translationID, then the translationID itself is returned. // This makes it easy to identify missing translations in your app. // // If translationID is a non-plural form, then the first variadic argument may be a map[string]interface{} // or struct that contains template data. // // If translationID is a plural form, the function accepts two parameter signatures // 1. T(count int, data struct{}) // The first variadic argument must be an integer type // (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45"). // The second variadic argument may be a map[string]interface{} or struct{} that contains template data. // 2. T(data struct{}) // data must be a struct{} or map[string]interface{} that contains a Count field and the template data, // Count field must be an integer type (int, int8, int16, int32, int64) // or a float formatted as a string (e.g. "123.45"). type TranslateFunc func(translationID string, args ...interface{}) string // IdentityTfunc returns a TranslateFunc that always returns the translationID passed to it. // // It is a useful placeholder when parsing a text/template or html/template // before the actual Tfunc is available. func IdentityTfunc() TranslateFunc { return func(translationID string, args ...interface{}) string { return translationID } } var defaultBundle = bundle.New() // MustLoadTranslationFile is similar to LoadTranslationFile // except it panics if an error happens. func MustLoadTranslationFile(filename string) { defaultBundle.MustLoadTranslationFile(filename) } // LoadTranslationFile loads the translations from filename into memory. // // The language that the translations are associated with is parsed from the filename (e.g. en-US.json). // // Generally you should load translation files once during your program's initialization. func LoadTranslationFile(filename string) error { return defaultBundle.LoadTranslationFile(filename) } // ParseTranslationFileBytes is similar to LoadTranslationFile except it parses the bytes in buf. // // It is useful for parsing translation files embedded with go-bindata. func ParseTranslationFileBytes(filename string, buf []byte) error { return defaultBundle.ParseTranslationFileBytes(filename, buf) } // AddTranslation adds translations for a language. // // It is useful if your translations are in a format not supported by LoadTranslationFile. func AddTranslation(lang *language.Language, translations ...translation.Translation) { defaultBundle.AddTranslation(lang, translations...) } // LanguageTags returns the tags of all languages that have been added. func LanguageTags() []string { return defaultBundle.LanguageTags() } // LanguageTranslationIDs returns the ids of all translations that have been added for a given language. func LanguageTranslationIDs(languageTag string) []string { return defaultBundle.LanguageTranslationIDs(languageTag) } // MustTfunc is similar to Tfunc except it panics if an error happens. func MustTfunc(languageSource string, languageSources ...string) TranslateFunc { return TranslateFunc(defaultBundle.MustTfunc(languageSource, languageSources...)) } // Tfunc returns a TranslateFunc that will be bound to the first language which // has a non-zero number of translations. // // It can parse languages from Accept-Language headers (RFC 2616). func Tfunc(languageSource string, languageSources ...string) (TranslateFunc, error) { tfunc, err := defaultBundle.Tfunc(languageSource, languageSources...) return TranslateFunc(tfunc), err } // MustTfuncAndLanguage is similar to TfuncAndLanguage except it panics if an error happens. func MustTfuncAndLanguage(languageSource string, languageSources ...string) (TranslateFunc, *language.Language) { tfunc, lang := defaultBundle.MustTfuncAndLanguage(languageSource, languageSources...) return TranslateFunc(tfunc), lang } // TfuncAndLanguage is similar to Tfunc except it also returns the language which TranslateFunc is bound to. func TfuncAndLanguage(languageSource string, languageSources ...string) (TranslateFunc, *language.Language, error) { tfunc, lang, err := defaultBundle.TfuncAndLanguage(languageSource, languageSources...) return TranslateFunc(tfunc), lang, err } go-i18n-1.10.0/i18n/language/000077500000000000000000000000001317766760100153335ustar00rootroot00000000000000go-i18n-1.10.0/i18n/language/codegen/000077500000000000000000000000001317766760100167375ustar00rootroot00000000000000go-i18n-1.10.0/i18n/language/codegen/generate.sh000066400000000000000000000003141317766760100210630ustar00rootroot00000000000000#!/bin/sh go build && ./codegen -cout ../pluralspec_gen.go -tout ../pluralspec_gen_test.go && \ gofmt -w=true ../pluralspec_gen.go && \ gofmt -w=true ../pluralspec_gen_test.go && \ rm codegen go-i18n-1.10.0/i18n/language/codegen/main.go000066400000000000000000000062541317766760100202210ustar00rootroot00000000000000package 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("spec").Parse(`package language // This file is generated by i18n/language/codegen/generate.sh func init() { {{range .PluralGroups}} RegisterPluralSpec({{printf "%#v" .SplitLocales}}, &PluralSpec{ Plurals: newPluralSet({{range $i, $e := .PluralRules}}{{if $i}}, {{end}}{{$e.CountTitle}}{{end}}), PluralFunc: func(ops *Operands) Plural { {{range .PluralRules}}{{if .GoCondition}} // {{.Condition}} if {{.GoCondition}} { return {{.CountTitle}} }{{end}}{{end}} return Other }, }){{end}} } `)) var testTemplate = template.Must(template.New("spec").Parse(`package language // This file is generated by i18n/language/codegen/generate.sh import "testing" {{range .PluralGroups}} func Test{{.Name}}(t *testing.T) { var tests []pluralTest {{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) } go-i18n-1.10.0/i18n/language/codegen/plurals.xml000066400000000000000000000520711317766760100211500ustar00rootroot00000000000000 @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 or f % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 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~10, 12~17, 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 != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @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, … go-i18n-1.10.0/i18n/language/codegen/xml.go000066400000000000000000000100261317766760100200650ustar00rootroot00000000000000package 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])(?: % ([0-9]+))? (!=|=)(.*)") // 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") } go-i18n-1.10.0/i18n/language/language.go000066400000000000000000000053111317766760100174450ustar00rootroot00000000000000// Package language defines languages that implement CLDR pluralization. package language import ( "fmt" "strings" ) // Language is a written human language. type Language struct { // Tag uniquely identifies the language as defined by RFC 5646. // // Most language tags are a two character language code (ISO 639-1) // optionally followed by a dash and a two character country code (ISO 3166-1). // (e.g. en, pt-br) Tag string *PluralSpec } func (l *Language) String() string { return l.Tag } // MatchingTags returns the set of language tags that map to this Language. // e.g. "zh-hans-cn" yields {"zh", "zh-hans", "zh-hans-cn"} // BUG: This should be computed once and stored as a field on Language for efficiency, // but this would require changing how Languages are constructed. func (l *Language) MatchingTags() []string { parts := strings.Split(l.Tag, "-") var prefix, matches []string for _, part := range parts { prefix = append(prefix, part) match := strings.Join(prefix, "-") matches = append(matches, match) } return matches } // Parse returns a slice of supported languages found in src or nil if none are found. // It can parse language tags and Accept-Language headers. func Parse(src string) []*Language { var langs []*Language start := 0 for end, chr := range src { switch chr { case ',', ';', '.': tag := strings.TrimSpace(src[start:end]) if spec := GetPluralSpec(tag); spec != nil { langs = append(langs, &Language{NormalizeTag(tag), spec}) } start = end + 1 } } if start > 0 { tag := strings.TrimSpace(src[start:]) if spec := GetPluralSpec(tag); spec != nil { langs = append(langs, &Language{NormalizeTag(tag), spec}) } return dedupe(langs) } if spec := GetPluralSpec(src); spec != nil { langs = append(langs, &Language{NormalizeTag(src), spec}) } return langs } func dedupe(langs []*Language) []*Language { found := make(map[string]struct{}, len(langs)) deduped := make([]*Language, 0, len(langs)) for _, lang := range langs { if _, ok := found[lang.Tag]; !ok { found[lang.Tag] = struct{}{} deduped = append(deduped, lang) } } return deduped } // MustParse is similar to Parse except it panics instead of retuning a nil Language. func MustParse(src string) []*Language { langs := Parse(src) if len(langs) == 0 { panic(fmt.Errorf("unable to parse language from %q", src)) } return langs } // Add adds support for a new language. func Add(l *Language) { tag := NormalizeTag(l.Tag) pluralSpecs[tag] = l.PluralSpec } // NormalizeTag returns a language tag with all lower-case characters // and dashes "-" instead of underscores "_" func NormalizeTag(tag string) string { tag = strings.ToLower(tag) return strings.Replace(tag, "_", "-", -1) } go-i18n-1.10.0/i18n/language/language_test.go000066400000000000000000000050111317766760100205010ustar00rootroot00000000000000package language import ( "reflect" "testing" ) func TestParse(t *testing.T) { tests := []struct { src string lang []*Language }{ {"en", []*Language{{"en", pluralSpecs["en"]}}}, {"en-US", []*Language{{"en-us", pluralSpecs["en"]}}}, {"en_US", []*Language{{"en-us", pluralSpecs["en"]}}}, {"en-GB", []*Language{{"en-gb", pluralSpecs["en"]}}}, {"zh-CN", []*Language{{"zh-cn", pluralSpecs["zh"]}}}, {"zh-TW", []*Language{{"zh-tw", pluralSpecs["zh"]}}}, {"pt-BR", []*Language{{"pt-br", pluralSpecs["pt"]}}}, {"pt_BR", []*Language{{"pt-br", pluralSpecs["pt"]}}}, {"pt-PT", []*Language{{"pt-pt", pluralSpecs["pt"]}}}, {"pt_PT", []*Language{{"pt-pt", pluralSpecs["pt"]}}}, {"zh-Hans-CN", []*Language{{"zh-hans-cn", pluralSpecs["zh"]}}}, {"zh-Hant-TW", []*Language{{"zh-hant-tw", pluralSpecs["zh"]}}}, {"en-US-en-US", []*Language{{"en-us-en-us", pluralSpecs["en"]}}}, {".en-US..en-US.", []*Language{{"en-us", pluralSpecs["en"]}}}, { "it, xx-zz, xx-ZZ, zh, en-gb;q=0.8, en;q=0.7, es-ES;q=0.6, de-xx", []*Language{ {"it", pluralSpecs["it"]}, {"zh", pluralSpecs["zh"]}, {"en-gb", pluralSpecs["en"]}, {"en", pluralSpecs["en"]}, {"es-es", pluralSpecs["es"]}, {"de-xx", pluralSpecs["de"]}, }, }, { "it-qq,xx,xx-zz,xx-ZZ,zh,en-gb;q=0.8,en;q=0.7,es-ES;q=0.6,de-xx", []*Language{ {"it-qq", pluralSpecs["it"]}, {"zh", pluralSpecs["zh"]}, {"en-gb", pluralSpecs["en"]}, {"en", pluralSpecs["en"]}, {"es-es", pluralSpecs["es"]}, {"de-xx", pluralSpecs["de"]}, }, }, {"en.json", []*Language{{"en", pluralSpecs["en"]}}}, {"en-US.json", []*Language{{"en-us", pluralSpecs["en"]}}}, {"en-us.json", []*Language{{"en-us", pluralSpecs["en"]}}}, {"en-xx.json", []*Language{{"en-xx", pluralSpecs["en"]}}}, {"xx-Yyen-US", nil}, {"en US", nil}, {"", nil}, {"-", nil}, {"_", nil}, {"-en", nil}, {"_en", nil}, {"-en-", nil}, {"_en_", nil}, {"xx", nil}, } for _, test := range tests { lang := Parse(test.src) if !reflect.DeepEqual(lang, test.lang) { t.Errorf("Parse(%q) = %s expected %s", test.src, lang, test.lang) } } } func TestMatchingTags(t *testing.T) { tests := []struct { lang *Language matches []string }{ {&Language{"zh-hans-cn", nil}, []string{"zh", "zh-hans", "zh-hans-cn"}}, {&Language{"foo", nil}, []string{"foo"}}, } for _, test := range tests { if actual := test.lang.MatchingTags(); !reflect.DeepEqual(test.matches, actual) { t.Errorf("matchingTags(%q) = %q expected %q", test.lang.Tag, actual, test.matches) } } } go-i18n-1.10.0/i18n/language/operands.go000066400000000000000000000055301317766760100175000ustar00rootroot00000000000000package language import ( "fmt" "strconv" "strings" ) // 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 } // NmodEqualAny 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 } // NmodEqualAny 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 } // NmodInRange 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 } func newOperands(v interface{}) (*Operands, error) { switch v := v.(type) { case int: return newOperandsInt64(int64(v)), nil case int8: return newOperandsInt64(int64(v)), nil case int16: return newOperandsInt64(int64(v)), nil case int32: return newOperandsInt64(int64(v)), nil case int64: return newOperandsInt64(v), nil case string: return newOperandsString(v) 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", v) } } 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 } go-i18n-1.10.0/i18n/language/operands_test.go000066400000000000000000000024671317766760100205450ustar00rootroot00000000000000package language 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) } } } go-i18n-1.10.0/i18n/language/plural.go000066400000000000000000000014701317766760100171630ustar00rootroot00000000000000package language import ( "fmt" ) // Plural represents a language pluralization form as defined here: // http://cldr.unicode.org/index/cldr-spec/plural-rules type Plural string // All defined plural categories. const ( Invalid Plural = "invalid" Zero = "zero" One = "one" Two = "two" Few = "few" Many = "many" Other = "other" ) // NewPlural returns src as a Plural // or Invalid and a non-nil error if src is not a valid Plural. func NewPlural(src string) (Plural, error) { switch src { case "zero": return Zero, nil case "one": return One, nil case "two": return Two, nil case "few": return Few, nil case "many": return Many, nil case "other": return Other, nil } return Invalid, fmt.Errorf("invalid plural category %s", src) } go-i18n-1.10.0/i18n/language/plural_test.go000066400000000000000000000011351317766760100202200ustar00rootroot00000000000000package language import ( "testing" ) func TestNewPlural(t *testing.T) { tests := []struct { src string plural Plural err bool }{ {"zero", Zero, false}, {"one", One, false}, {"two", Two, false}, {"few", Few, false}, {"many", Many, false}, {"other", Other, false}, {"asdf", Invalid, true}, } for _, test := range tests { plural, err := NewPlural(test.src) wrongErr := (err != nil && !test.err) || (err == nil && test.err) if plural != test.plural || wrongErr { t.Errorf("NewPlural(%#v) returned %#v,%#v; expected %#v", test.src, plural, err, test.plural) } } } go-i18n-1.10.0/i18n/language/pluralspec.go000066400000000000000000000033671317766760100200450ustar00rootroot00000000000000package language import "strings" // PluralSpec 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 PluralSpec struct { Plurals map[Plural]struct{} PluralFunc func(*Operands) Plural } var pluralSpecs = make(map[string]*PluralSpec) func normalizePluralSpecID(id string) string { id = strings.Replace(id, "_", "-", -1) id = strings.ToLower(id) return id } // RegisterPluralSpec registers a new plural spec for the language ids. func RegisterPluralSpec(ids []string, ps *PluralSpec) { for _, id := range ids { id = normalizePluralSpecID(id) pluralSpecs[id] = ps } } // Plural returns the plural category for number as defined by // the language's CLDR plural rules. func (ps *PluralSpec) Plural(number interface{}) (Plural, error) { ops, err := newOperands(number) if err != nil { return Invalid, err } return ps.PluralFunc(ops), nil } // GetPluralSpec returns the PluralSpec that matches the longest prefix of tag. // It returns nil if no PluralSpec matches tag. func GetPluralSpec(tag string) *PluralSpec { tag = NormalizeTag(tag) subtag := tag for { if spec := pluralSpecs[subtag]; spec != nil { return spec } end := strings.LastIndex(subtag, "-") if end == -1 { return nil } subtag = subtag[:end] } } func newPluralSet(plurals ...Plural) map[Plural]struct{} { set := make(map[Plural]struct{}, len(plurals)) for _, plural := range plurals { 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 } go-i18n-1.10.0/i18n/language/pluralspec_gen.go000066400000000000000000000371701317766760100206750ustar00rootroot00000000000000package language // This file is generated by i18n/language/codegen/generate.sh func init() { RegisterPluralSpec([]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"}, &PluralSpec{ Plurals: newPluralSet(Other), PluralFunc: func(ops *Operands) Plural { return Other }, }) RegisterPluralSpec([]string{"am", "as", "bn", "fa", "gu", "hi", "kn", "mr", "zu"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // i = 0 or n = 1 if intEqualsAny(ops.I, 0) || ops.NequalsAny(1) { return One } return Other }, }) RegisterPluralSpec([]string{"ff", "fr", "hy", "kab"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // i = 0,1 if intEqualsAny(ops.I, 0, 1) { return One } return Other }, }) RegisterPluralSpec([]string{"pt"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // i = 0..1 if intInRange(ops.I, 0, 1) { return One } return Other }, }) RegisterPluralSpec([]string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "it", "ji", "nl", "sv", "sw", "ur", "yi"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } return Other }, }) RegisterPluralSpec([]string{"si"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"ak", "bh", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // n = 0..1 if ops.NinRange(0, 1) { return One } return Other }, }) RegisterPluralSpec([]string{"tzm"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // n = 0..1 or n = 11..99 if ops.NinRange(0, 1) || ops.NinRange(11, 99) { return One } return Other }, }) RegisterPluralSpec([]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", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "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"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // n = 1 if ops.NequalsAny(1) { return One } return Other }, }) RegisterPluralSpec([]string{"da"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"is"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"mk"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // v = 0 and i % 10 = 1 or f % 10 = 1 if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) || intEqualsAny(ops.F%10, 1) { return One } return Other }, }) RegisterPluralSpec([]string{"fil", "tl"}, &PluralSpec{ Plurals: newPluralSet(One, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"lv", "prg"}, &PluralSpec{ Plurals: newPluralSet(Zero, One, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"lag"}, &PluralSpec{ Plurals: newPluralSet(Zero, One, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"ksh"}, &PluralSpec{ Plurals: newPluralSet(Zero, One, Other), PluralFunc: func(ops *Operands) Plural { // n = 0 if ops.NequalsAny(0) { return Zero } // n = 1 if ops.NequalsAny(1) { return One } return Other }, }) RegisterPluralSpec([]string{"iu", "kw", "naq", "se", "sma", "smi", "smj", "smn", "sms"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Other), PluralFunc: func(ops *Operands) Plural { // n = 1 if ops.NequalsAny(1) { return One } // n = 2 if ops.NequalsAny(2) { return Two } return Other }, }) RegisterPluralSpec([]string{"shi"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"mo", "ro"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Other), PluralFunc: func(ops *Operands) Plural { // i = 1 and v = 0 if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) { return One } // v != 0 or n = 0 or n != 1 and n % 100 = 1..19 if !intEqualsAny(ops.V, 0) || ops.NequalsAny(0) || !ops.NequalsAny(1) && ops.NmodInRange(100, 1, 19) { return Few } return Other }, }) RegisterPluralSpec([]string{"bs", "hr", "sh", "sr"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"gd"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Few, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"sl"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Few, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"dsb", "hsb"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Few, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"he", "iw"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"cs", "sk"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"pl"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"be"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"lt"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"mt"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"ru", "uk"}, &PluralSpec{ Plurals: newPluralSet(One, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"br"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"ga"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"gv"}, &PluralSpec{ Plurals: newPluralSet(One, Two, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"ar", "ars"}, &PluralSpec{ Plurals: newPluralSet(Zero, One, Two, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) RegisterPluralSpec([]string{"cy"}, &PluralSpec{ Plurals: newPluralSet(Zero, One, Two, Few, Many, Other), PluralFunc: func(ops *Operands) Plural { // 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 }, }) } go-i18n-1.10.0/i18n/language/pluralspec_gen_test.go000066400000000000000000000661061317766760100217350ustar00rootroot00000000000000package language // This file is generated by i18n/language/codegen/generate.sh import "testing" func TestBmBoDzIdIgIiInJaJboJvJwKdeKeaKmKoLktLoMsMyNqoRootSahSesSgThToViWoYoYueZh(t *testing.T) { var tests []pluralTest 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 TestAmAsBnFaGuHiKnMrZu(t *testing.T) { var tests []pluralTest 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", "mr", "zu"} for _, locale := range locales { runTests(t, locale, tests) } } func TestFfFrHyKab(t *testing.T) { var tests []pluralTest 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 []pluralTest 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 TestAstCaDeEnEtFiFyGlItJiNlSvSwUrYi(t *testing.T) { var tests []pluralTest 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", "it", "ji", "nl", "sv", "sw", "ur", "yi"} for _, locale := range locales { runTests(t, locale, tests) } } func TestSi(t *testing.T) { var tests []pluralTest 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 []pluralTest 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 []pluralTest 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 TestAfAsaAzBemBezBgBrxCeCggChrCkbDvEeElEoEsEuFoFurGswHaHawHuJgoJmcKaKajKcgKkKkjKlKsKsbKuKyLbLgMasMgoMlMnNahNbNdNeNnNnhNoNrNyNynOmOrOsPapPsRmRofRwkSaqSdhSehSnSoSqSsSsyStSyrTaTeTeoTigTkTnTrTsUgUzVeVoVunWaeXhXog(t *testing.T) { var tests []pluralTest 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", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "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 []pluralTest 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 []pluralTest 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 []pluralTest tests = appendIntegerTests(tests, One, []string{"1", "11", "21", "31", "41", "51", "61", "71", "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~10", "12~17", "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 TestFilTl(t *testing.T) { var tests []pluralTest 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{"fil", "tl"} for _, locale := range locales { runTests(t, locale, tests) } } func TestLvPrg(t *testing.T) { var tests []pluralTest 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 []pluralTest 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 []pluralTest 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 TestIuKwNaqSeSmaSmiSmjSmnSms(t *testing.T) { var tests []pluralTest 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", "kw", "naq", "se", "sma", "smi", "smj", "smn", "sms"} for _, locale := range locales { runTests(t, locale, tests) } } func TestShi(t *testing.T) { var tests []pluralTest 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 []pluralTest tests = appendIntegerTests(tests, One, []string{"1"}) tests = appendIntegerTests(tests, Few, []string{"0", "2~16", "101", "1001"}) 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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 []pluralTest 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) } } go-i18n-1.10.0/i18n/language/pluralspec_test.go000066400000000000000000000341171317766760100211010ustar00rootroot00000000000000package language import ( "fmt" "strconv" "strings" "testing" ) const onePlusEpsilon = "1.00000000000000000000000000000001" func TestGetPluralSpec(t *testing.T) { tests := []struct { src string spec *PluralSpec }{ {"pl", pluralSpecs["pl"]}, {"en", pluralSpecs["en"]}, {"en-US", pluralSpecs["en"]}, {"en_US", pluralSpecs["en"]}, {"en-GB", pluralSpecs["en"]}, {"zh-CN", pluralSpecs["zh"]}, {"zh-TW", pluralSpecs["zh"]}, {"pt-BR", pluralSpecs["pt"]}, {"pt_BR", pluralSpecs["pt"]}, {"pt-PT", pluralSpecs["pt"]}, {"pt_PT", pluralSpecs["pt"]}, {"zh-Hans-CN", pluralSpecs["zh"]}, {"zh-Hant-TW", pluralSpecs["zh"]}, {"zh-CN", pluralSpecs["zh"]}, {"zh-TW", pluralSpecs["zh"]}, {"zh-Hans", pluralSpecs["zh"]}, {"zh-Hant", pluralSpecs["zh"]}, {"ko-KR", pluralSpecs["ko"]}, {"ko_KR", pluralSpecs["ko"]}, {"ko-KP", pluralSpecs["ko"]}, {"ko_KP", pluralSpecs["ko"]}, {"en-US-en-US", pluralSpecs["en"]}, {"th", pluralSpecs["th"]}, {"th-TH", pluralSpecs["th"]}, {"hr", pluralSpecs["hr"]}, {"bs", pluralSpecs["bs"]}, {"sr", pluralSpecs["sr"]}, {"ti", pluralSpecs["ti"]}, {"vi", pluralSpecs["vi"]}, {"vi-VN", pluralSpecs["vi"]}, {"mk", pluralSpecs["mk"]}, {"mk-MK", pluralSpecs["mk"]}, {"lv", pluralSpecs["lv"]}, {"lv-LV", pluralSpecs["lv"]}, {".en-US..en-US.", nil}, {"zh, en-gb;q=0.8, en;q=0.7", nil}, {"zh,en-gb;q=0.8,en;q=0.7", nil}, {"xx, en-gb;q=0.8, en;q=0.7", nil}, {"xx,en-gb;q=0.8,en;q=0.7", nil}, {"xx-YY,xx;q=0.8,en-US,en;q=0.8,de;q=0.6,nl;q=0.4", nil}, {"/foo/es/en.json", nil}, {"xx-Yyen-US", nil}, {"en US", nil}, {"", nil}, {"-", nil}, {"_", nil}, {".", nil}, {"-en", nil}, {"_en", nil}, {"-en-", nil}, {"_en_", nil}, {"xx", nil}, } for _, test := range tests { spec := GetPluralSpec(test.src) if spec != test.spec { t.Errorf("getPluralSpec(%q) = %q expected %q", test.src, spec, test.spec) } } } type pluralTest struct { num interface{} plural Plural } func appendIntegerTests(tests []pluralTest, plural Plural, examples []string) []pluralTest { for _, ex := range expandExamples(examples) { i, err := strconv.ParseInt(ex, 10, 64) if err != nil { panic(err) } tests = append(tests, pluralTest{ex, plural}, pluralTest{i, plural}) } return tests } func appendDecimalTests(tests []pluralTest, plural Plural, examples []string) []pluralTest { for _, ex := range expandExamples(examples) { tests = append(tests, pluralTest{ex, plural}) } 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) } // // Below here are tests that were manually written before tests were automatically generated. // These are kept around as sanity checks for our code generation. // func TestArabic(t *testing.T) { tests := []pluralTest{ {0, Zero}, {"0", Zero}, {"0.0", Zero}, {"0.00", Zero}, {1, One}, {"1", One}, {"1.0", One}, {"1.00", One}, {onePlusEpsilon, Other}, {2, Two}, {"2", Two}, {"2.0", Two}, {"2.00", Two}, {3, Few}, {"3", Few}, {"3.0", Few}, {"3.00", Few}, {10, Few}, {"10", Few}, {"10.0", Few}, {"10.00", Few}, {103, Few}, {"103", Few}, {"103.0", Few}, {"103.00", Few}, {110, Few}, {"110", Few}, {"110.0", Few}, {"110.00", Few}, {11, Many}, {"11", Many}, {"11.0", Many}, {"11.00", Many}, {99, Many}, {"99", Many}, {"99.0", Many}, {"99.00", Many}, {111, Many}, {"111", Many}, {"111.0", Many}, {"111.00", Many}, {199, Many}, {"199", Many}, {"199.0", Many}, {"199.00", Many}, {100, Other}, {"100", Other}, {"100.0", Other}, {"100.00", Other}, {102, Other}, {"102", Other}, {"102.0", Other}, {"102.00", Other}, {200, Other}, {"200", Other}, {"200.0", Other}, {"200.00", Other}, {202, Other}, {"202", Other}, {"202.0", Other}, {"202.00", Other}, } tests = appendFloatTests(tests, 0.1, 0.9, Other) tests = appendFloatTests(tests, 1.1, 1.9, Other) tests = appendFloatTests(tests, 2.1, 2.9, Other) tests = appendFloatTests(tests, 3.1, 3.9, Other) tests = appendFloatTests(tests, 4.1, 4.9, Other) runTests(t, "ar", tests) } func TestBelarusian(t *testing.T) { tests := []pluralTest{ {0, Many}, {1, One}, {2, Few}, {3, Few}, {4, Few}, {5, Many}, {19, Many}, {20, Many}, {21, One}, {11, Many}, {52, Few}, {101, One}, {"0.1", Other}, {"0.7", Other}, {"1.5", Other}, {"1.0", One}, {onePlusEpsilon, Other}, {"2.0", Few}, {"10.0", Many}, } runTests(t, "be", tests) } func TestBurmese(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "my", tests) } func TestCatalan(t *testing.T) { tests := []pluralTest{ {0, Other}, {"0", Other}, {1, One}, {"1", One}, {"1.0", Other}, {onePlusEpsilon, Other}, {2, Other}, {"2", Other}, } tests = appendIntTests(tests, 2, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "ca", tests) } func TestChinese(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "zh", tests) } func TestCzech(t *testing.T) { tests := []pluralTest{ {0, Other}, {"0", Other}, {1, One}, {"1", One}, {onePlusEpsilon, Many}, {2, Few}, {"2", Few}, {3, Few}, {"3", Few}, {4, Few}, {"4", Few}, {5, Other}, {"5", Other}, } tests = appendFloatTests(tests, 0, 10, Many) runTests(t, "cs", tests) } func TestDanish(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {onePlusEpsilon, One}, {2, Other}, } tests = appendFloatTests(tests, 0.1, 1.9, One) tests = appendFloatTests(tests, 2.0, 10.0, Other) runTests(t, "da", tests) } func TestDutch(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 10.0, Other) runTests(t, "nl", tests) } func TestEnglish(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 10.0, Other) runTests(t, "en", tests) } func TestFrench(t *testing.T) { tests := []pluralTest{ {0, One}, {1, One}, {onePlusEpsilon, One}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 1.9, One) tests = appendFloatTests(tests, 2.0, 10.0, Other) runTests(t, "fr", tests) } func TestGerman(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 10.0, Other) runTests(t, "de", tests) } func TestIcelandic(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {2, Other}, {11, Other}, {21, One}, {111, Other}, {"0.0", Other}, {"0.1", One}, {"2.0", Other}, } runTests(t, "is", tests) } func TestIndonesian(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "id", tests) } func TestItalian(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 10.0, Other) runTests(t, "it", tests) } func TestKorean(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "ko", tests) } func TestLatvian(t *testing.T) { tests := []pluralTest{ {0, Zero}, {"0", Zero}, {"0.1", One}, {1, One}, {"1", One}, {onePlusEpsilon, One}, {"10.0", Zero}, {"10.1", One}, {"10.2", Other}, {21, One}, } tests = appendFloatTests(tests, 0.2, 0.9, Other) tests = appendFloatTests(tests, 1.2, 1.9, Other) tests = appendIntTests(tests, 2, 9, Other) tests = appendIntTests(tests, 10, 20, Zero) tests = appendIntTests(tests, 22, 29, Other) runTests(t, "lv", tests) } func TestJapanese(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "ja", tests) } func TestLithuanian(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {2, Few}, {3, Few}, {9, Few}, {10, Other}, {11, Other}, {"0.1", Many}, {"0.7", Many}, {"1.0", One}, {onePlusEpsilon, Many}, {"2.0", Few}, {"10.0", Other}, } runTests(t, "lt", tests) } func TestMalay(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "ms", tests) } func TestPolish(t *testing.T) { tests := []pluralTest{ {0, Many}, {1, One}, {2, Few}, {3, Few}, {4, Few}, {5, Many}, {19, Many}, {20, Many}, {10, Many}, {11, Many}, {52, Few}, {"0.1", Other}, {"0.7", Other}, {"1.5", Other}, {"1.0", Other}, {onePlusEpsilon, Other}, {"2.0", Other}, {"10.0", Other}, } runTests(t, "pl", tests) } func TestPortuguese(t *testing.T) { tests := []pluralTest{ {0, One}, {"0.0", One}, {1, One}, {"1.0", One}, {onePlusEpsilon, One}, {2, Other}, } tests = appendFloatTests(tests, 0, 1.5, One) tests = appendFloatTests(tests, 2, 10.0, Other) runTests(t, "pt", tests) } func TestMacedonian(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {"1.1", One}, {"2.1", One}, {onePlusEpsilon, One}, {2, Other}, {"2.2", Other}, {11, One}, } runTests(t, "mk", tests) } func TestRussian(t *testing.T) { tests := []pluralTest{ {0, Many}, {1, One}, {2, Few}, {3, Few}, {4, Few}, {5, Many}, {19, Many}, {20, Many}, {21, One}, {11, Many}, {52, Few}, {101, One}, {"0.1", Other}, {"0.7", Other}, {"1.5", Other}, {"1.0", Other}, {onePlusEpsilon, Other}, {"2.0", Other}, {"10.0", Other}, } runTests(t, "ru", tests) } func TestSpanish(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {"1", One}, {"1.0", One}, {"1.00", One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 0.9, Other) tests = appendFloatTests(tests, 1.1, 10.0, Other) runTests(t, "es", tests) } func TestNorweigan(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {"1", One}, {"1.0", One}, {"1.00", One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 0.9, Other) tests = appendFloatTests(tests, 1.1, 10.0, Other) runTests(t, "no", tests) } func TestBulgarian(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {2, Other}, {3, Other}, {9, Other}, {10, Other}, {11, Other}, {"0.1", Other}, {"0.7", Other}, {"1.0", One}, {"1.001", Other}, {onePlusEpsilon, Other}, {"1.1", Other}, {"2.0", Other}, {"10.0", Other}, } runTests(t, "bg", tests) } func TestSwedish(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {onePlusEpsilon, Other}, {2, Other}, } tests = appendFloatTests(tests, 0.0, 10.0, Other) runTests(t, "sv", tests) } func TestThai(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "th", tests) } func TestVietnamese(t *testing.T) { tests := appendIntTests(nil, 0, 10, Other) tests = appendFloatTests(tests, 0, 10, Other) runTests(t, "vi", tests) } func TestTurkish(t *testing.T) { tests := []pluralTest{ {0, Other}, {1, One}, {"1", One}, {"1.0", One}, {"1.00", One}, {"1.001", Other}, {"1.100", Other}, {"1.101", Other}, {onePlusEpsilon, Other}, {2, Other}, {"0.7", Other}, {"2.0", Other}, } runTests(t, "tr", tests) } func TestUkrainian(t *testing.T) { tests := []pluralTest{ {0, Many}, {1, One}, {2, Few}, {3, Few}, {4, Few}, {5, Many}, {19, Many}, {20, Many}, {21, One}, {11, Many}, {52, Few}, {101, One}, {"0.1", Other}, {"0.7", Other}, {"1.5", Other}, {"1.0", Other}, {onePlusEpsilon, Other}, {"2.0", Other}, {"10.0", Other}, } runTests(t, "uk", tests) } func TestCroatian(t *testing.T) { tests := makeCroatianBosnianSerbianTests() runTests(t, "hr", tests) } func TestBosnian(t *testing.T) { tests := makeCroatianBosnianSerbianTests() runTests(t, "bs", tests) } func TestSerbian(t *testing.T) { tests := makeCroatianBosnianSerbianTests() runTests(t, "sr", tests) } func makeCroatianBosnianSerbianTests() []pluralTest { return []pluralTest{ {1, One}, {"0.1", One}, {21, One}, {101, One}, {1001, One}, {51, One}, {"1.1", One}, {"5.1", One}, {"100.1", One}, {"1000.1", One}, {2, Few}, {"0.2", Few}, {22, Few}, {"1.2", Few}, {24, Few}, {"2.4", Few}, {102, Few}, {"100.2", Few}, {1002, Few}, {"1000.2", Few}, {5, Other}, {"0.5", Other}, {0, Other}, {100, Other}, {19, Other}, {"0.0", Other}, {"100.0", Other}, {"1000.0", Other}, } } func TestTigrinya(t *testing.T) { tests := []pluralTest{ {0, One}, {1, One}, } tests = appendIntTests(tests, 2, 10, Other) tests = appendFloatTests(tests, 1.1, 10.0, Other) runTests(t, "ti", tests) } func appendIntTests(tests []pluralTest, from, to int, p Plural) []pluralTest { for i := from; i <= to; i++ { tests = append(tests, pluralTest{i, p}) } return tests } func appendFloatTests(tests []pluralTest, from, to float64, p Plural) []pluralTest { stride := 0.1 format := "%.1f" for f := from; f < to; f += stride { tests = append(tests, pluralTest{fmt.Sprintf(format, f), p}) } tests = append(tests, pluralTest{fmt.Sprintf(format, to), p}) return tests } func runTests(t *testing.T, pluralSpecID string, tests []pluralTest) { pluralSpecID = normalizePluralSpecID(pluralSpecID) if spec := pluralSpecs[pluralSpecID]; spec != nil { for _, test := range tests { if plural, err := spec.Plural(test.num); plural != test.plural { t.Errorf("%s: PluralCategory(%#v) returned %s, %v; expected %s", pluralSpecID, test.num, plural, err, test.plural) } } } else { t.Errorf("could not find plural spec for locale %s", pluralSpecID) } } go-i18n-1.10.0/i18n/translation/000077500000000000000000000000001317766760100161065ustar00rootroot00000000000000go-i18n-1.10.0/i18n/translation/plural_translation.go000066400000000000000000000035621317766760100223600ustar00rootroot00000000000000package translation import ( "github.com/nicksnyder/go-i18n/i18n/language" ) type pluralTranslation struct { id string templates map[language.Plural]*template } func (pt *pluralTranslation) MarshalInterface() interface{} { return map[string]interface{}{ "id": pt.id, "translation": pt.templates, } } func (pt *pluralTranslation) MarshalFlatInterface() interface{} { return pt.templates } func (pt *pluralTranslation) ID() string { return pt.id } func (pt *pluralTranslation) Template(pc language.Plural) *template { return pt.templates[pc] } func (pt *pluralTranslation) UntranslatedCopy() Translation { return &pluralTranslation{pt.id, make(map[language.Plural]*template)} } func (pt *pluralTranslation) Normalize(l *language.Language) Translation { // Delete plural categories that don't belong to this language. for pc := range pt.templates { if _, ok := l.Plurals[pc]; !ok { delete(pt.templates, pc) } } // Create map entries for missing valid categories. for pc := range l.Plurals { if _, ok := pt.templates[pc]; !ok { pt.templates[pc] = mustNewTemplate("") } } return pt } func (pt *pluralTranslation) Backfill(src Translation) Translation { for pc, t := range pt.templates { if (t == nil || t.src == "") && src != nil { pt.templates[pc] = src.Template(language.Other) } } return pt } func (pt *pluralTranslation) Merge(t Translation) Translation { other, ok := t.(*pluralTranslation) if !ok || pt.ID() != t.ID() { return t } for pluralCategory, template := range other.templates { if template != nil && template.src != "" { pt.templates[pluralCategory] = template } } return pt } func (pt *pluralTranslation) Incomplete(l *language.Language) bool { for pc := range l.Plurals { if t := pt.templates[pc]; t == nil || t.src == "" { return true } } return false } var _ = Translation(&pluralTranslation{}) go-i18n-1.10.0/i18n/translation/plural_translation_test.go000066400000000000000000000203211317766760100234070ustar00rootroot00000000000000package translation import ( "reflect" "testing" "github.com/nicksnyder/go-i18n/i18n/language" ) func mustTemplate(t *testing.T, src string) *template { tmpl, err := newTemplate(src) if err != nil { t.Fatal(err) } return tmpl } func pluralTranslationFixture(t *testing.T, id string, pluralCategories ...language.Plural) *pluralTranslation { templates := make(map[language.Plural]*template, len(pluralCategories)) for _, pc := range pluralCategories { templates[pc] = mustTemplate(t, string(pc)) } return &pluralTranslation{id, templates} } func verifyDeepEqual(t *testing.T, actual, expected interface{}) { if !reflect.DeepEqual(actual, expected) { t.Fatalf("\n%#v\nnot equal to expected value\n%#v", actual, expected) } } func TestPluralTranslationMerge(t *testing.T) { pt := pluralTranslationFixture(t, "id", language.One, language.Other) oneTemplate, otherTemplate := pt.templates[language.One], pt.templates[language.Other] pt.Merge(pluralTranslationFixture(t, "id")) verifyDeepEqual(t, pt.templates, map[language.Plural]*template{ language.One: oneTemplate, language.Other: otherTemplate, }) pt2 := pluralTranslationFixture(t, "id", language.One, language.Two) pt.Merge(pt2) verifyDeepEqual(t, pt.templates, map[language.Plural]*template{ language.One: pt2.templates[language.One], language.Two: pt2.templates[language.Two], language.Other: otherTemplate, }) } /* Test implementations from old idea func TestCopy(t *testing.T) { ls := &LocalizedString{ ID: "id", Translation: testingTemplate(t, "translation {{.Hello}}"), Translations: map[language.Plural]*template{ language.One: testingTemplate(t, "plural {{.One}}"), language.Other: testingTemplate(t, "plural {{.Other}}"), }, } c := ls.Copy() delete(c.Translations, language.One) if _, ok := ls.Translations[language.One]; !ok { t.Errorf("deleting plural translation from copy deleted it from the original") } c.Translations[language.Two] = testingTemplate(t, "plural {{.Two}}") if _, ok := ls.Translations[language.Two]; ok { t.Errorf("adding plural translation to copy added it to the original") } } func TestNormalize(t *testing.T) { oneTemplate := testingTemplate(t, "one {{.One}}") ls := &LocalizedString{ Translation: testingTemplate(t, "single {{.Single}}"), Translations: map[language.Plural]*template{ language.One: oneTemplate, language.Two: testingTemplate(t, "two {{.Two}}"), }, } ls.Normalize(LanguageWithCode("en")) if ls.Translation != nil { t.Errorf("ls.Translation is %#v; expected nil", ls.Translation) } if actual := ls.Translations[language.Two]; actual != nil { t.Errorf("ls.Translation[language.Two] is %#v; expected nil", actual) } if actual := ls.Translations[language.One]; actual != oneTemplate { t.Errorf("ls.Translations[language.One] is %#v; expected %#v", actual, oneTemplate) } if _, ok := ls.Translations[language.Other]; !ok { t.Errorf("ls.Translations[language.Other] shouldn't be empty") } } func TestMergeTranslation(t *testing.T) { ls := &LocalizedString{} translation := testingTemplate(t, "one {{.Hello}}") ls.Merge(&LocalizedString{ Translation: translation, }) if ls.Translation != translation { t.Errorf("expected %#v; got %#v", translation, ls.Translation) } ls.Merge(&LocalizedString{}) if ls.Translation != translation { t.Errorf("expected %#v; got %#v", translation, ls.Translation) } translation = testingTemplate(t, "two {{.Hello}}") ls.Merge(&LocalizedString{ Translation: translation, }) if ls.Translation != translation { t.Errorf("expected %#v; got %#v", translation, ls.Translation) } } func TestMergeTranslations(t *testing.T) { ls := &LocalizedString{} oneTemplate := testingTemplate(t, "one {{.One}}") otherTemplate := testingTemplate(t, "other {{.Other}}") ls.Merge(&LocalizedString{ Translations: map[language.Plural]*template{ language.One: oneTemplate, language.Other: otherTemplate, }, }) if actual := ls.Translations[language.One]; actual != oneTemplate { t.Errorf("ls.Translations[language.One] expected %#v; got %#v", oneTemplate, actual) } if actual := ls.Translations[language.Other]; actual != otherTemplate { t.Errorf("ls.Translations[language.Other] expected %#v; got %#v", otherTemplate, actual) } ls.Merge(&LocalizedString{ Translations: map[language.Plural]*template{}, }) if actual := ls.Translations[language.One]; actual != oneTemplate { t.Errorf("ls.Translations[language.One] expected %#v; got %#v", oneTemplate, actual) } if actual := ls.Translations[language.Other]; actual != otherTemplate { t.Errorf("ls.Translations[language.Other] expected %#v; got %#v", otherTemplate, actual) } twoTemplate := testingTemplate(t, "two {{.Two}}") otherTemplate = testingTemplate(t, "second other {{.Other}}") ls.Merge(&LocalizedString{ Translations: map[language.Plural]*template{ language.Two: twoTemplate, language.Other: otherTemplate, }, }) if actual := ls.Translations[language.One]; actual != oneTemplate { t.Errorf("ls.Translations[language.One] expected %#v; got %#v", oneTemplate, actual) } if actual := ls.Translations[language.Two]; actual != twoTemplate { t.Errorf("ls.Translations[language.Two] expected %#v; got %#v", twoTemplate, actual) } if actual := ls.Translations[language.Other]; actual != otherTemplate { t.Errorf("ls.Translations[language.Other] expected %#v; got %#v", otherTemplate, actual) } } func TestMissingTranslations(t *testing.T) { en := LanguageWithCode("en") tests := []struct { localizedString *LocalizedString language *Language expected bool }{ { &LocalizedString{}, en, true, }, { &LocalizedString{Translation: testingTemplate(t, "single {{.Single}}")}, en, false, }, { &LocalizedString{ Translation: testingTemplate(t, "single {{.Single}}"), Translations: map[language.Plural]*template{ language.One: testingTemplate(t, "one {{.One}}"), }}, en, true, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: testingTemplate(t, "one {{.One}}"), }}, en, true, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: nil, language.Other: nil, }}, en, true, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: testingTemplate(t, ""), language.Other: testingTemplate(t, ""), }}, en, true, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: testingTemplate(t, "one {{.One}}"), language.Other: testingTemplate(t, "other {{.Other}}"), }}, en, false, }, } for _, tt := range tests { if actual := tt.localizedString.MissingTranslations(tt.language); actual != tt.expected { t.Errorf("expected %t got %t for %s, %#v", tt.expected, actual, tt.language.code, tt.localizedString) } } } func TestHasTranslations(t *testing.T) { en := LanguageWithCode("en") tests := []struct { localizedString *LocalizedString language *Language expected bool }{ { &LocalizedString{}, en, false, }, { &LocalizedString{Translation: testingTemplate(t, "single {{.Single}}")}, en, true, }, { &LocalizedString{ Translation: testingTemplate(t, "single {{.Single}}"), Translations: map[language.Plural]*template{}}, en, false, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: testingTemplate(t, "one {{.One}}"), }}, en, true, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.Two: testingTemplate(t, "two {{.Two}}"), }}, en, false, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: nil, }}, en, false, }, { &LocalizedString{Translations: map[language.Plural]*template{ language.One: testingTemplate(t, ""), }}, en, false, }, } for _, tt := range tests { if actual := tt.localizedString.HasTranslations(tt.language); actual != tt.expected { t.Errorf("expected %t got %t for %s, %#v", tt.expected, actual, tt.language.code, tt.localizedString) } } } func testingTemplate(t *testing.T, src string) *template { tmpl, err := newTemplate(src) if err != nil { t.Fatal(err) } return tmpl } */ go-i18n-1.10.0/i18n/translation/single_translation.go000066400000000000000000000026021317766760100223340ustar00rootroot00000000000000package translation import ( "github.com/nicksnyder/go-i18n/i18n/language" ) type singleTranslation struct { id string template *template } func (st *singleTranslation) MarshalInterface() interface{} { return map[string]interface{}{ "id": st.id, "translation": st.template, } } func (st *singleTranslation) MarshalFlatInterface() interface{} { return map[string]interface{}{"other": st.template} } func (st *singleTranslation) ID() string { return st.id } func (st *singleTranslation) Template(pc language.Plural) *template { return st.template } func (st *singleTranslation) UntranslatedCopy() Translation { return &singleTranslation{st.id, mustNewTemplate("")} } func (st *singleTranslation) Normalize(language *language.Language) Translation { return st } func (st *singleTranslation) Backfill(src Translation) Translation { if (st.template == nil || st.template.src == "") && src != nil { st.template = src.Template(language.Other) } return st } func (st *singleTranslation) Merge(t Translation) Translation { other, ok := t.(*singleTranslation) if !ok || st.ID() != t.ID() { return t } if other.template != nil && other.template.src != "" { st.template = other.template } return st } func (st *singleTranslation) Incomplete(l *language.Language) bool { return st.template == nil || st.template.src == "" } var _ = Translation(&singleTranslation{}) go-i18n-1.10.0/i18n/translation/template.go000066400000000000000000000021741317766760100202540ustar00rootroot00000000000000package translation import ( "bytes" "encoding" "strings" gotemplate "text/template" ) type template struct { tmpl *gotemplate.Template src string } func newTemplate(src string) (*template, error) { if src == "" { return new(template), nil } var tmpl template err := tmpl.parseTemplate(src) return &tmpl, err } func mustNewTemplate(src string) *template { t, err := newTemplate(src) if err != nil { panic(err) } return t } func (t *template) String() string { return t.src } func (t *template) Execute(args interface{}) string { if t.tmpl == nil { return t.src } var buf bytes.Buffer if err := t.tmpl.Execute(&buf, args); err != nil { return err.Error() } return buf.String() } func (t *template) MarshalText() ([]byte, error) { return []byte(t.src), nil } func (t *template) UnmarshalText(src []byte) error { return t.parseTemplate(string(src)) } func (t *template) parseTemplate(src string) (err error) { t.src = src if strings.Contains(src, "{{") { t.tmpl, err = gotemplate.New(src).Parse(src) } return } var _ = encoding.TextMarshaler(&template{}) var _ = encoding.TextUnmarshaler(&template{}) go-i18n-1.10.0/i18n/translation/template_test.go000066400000000000000000000063101317766760100213070ustar00rootroot00000000000000package translation import ( "bytes" "fmt" //"launchpad.net/goyaml" "testing" gotemplate "text/template" ) func TestNilTemplate(t *testing.T) { expected := "hello" tmpl := &template{ tmpl: nil, src: expected, } if actual := tmpl.Execute(nil); actual != expected { t.Errorf("Execute(nil) returned %s; expected %s", actual, expected) } } func TestMarshalText(t *testing.T) { tmpl := &template{ tmpl: gotemplate.Must(gotemplate.New("id").Parse("this is a {{.foo}} template")), src: "boom", } expectedBuf := []byte(tmpl.src) if buf, err := tmpl.MarshalText(); !bytes.Equal(buf, expectedBuf) || err != nil { t.Errorf("MarshalText() returned %#v, %#v; expected %#v, nil", buf, err, expectedBuf) } } func TestUnmarshalText(t *testing.T) { tmpl := &template{} tmpl.UnmarshalText([]byte("hello {{.World}}")) result := tmpl.Execute(map[string]string{ "World": "world!", }) expected := "hello world!" if result != expected { t.Errorf("expected %#v; got %#v", expected, result) } } /* func TestYAMLMarshal(t *testing.T) { src := "hello {{.World}}" tmpl, err := newTemplate(src) if err != nil { t.Fatal(err) } buf, err := goyaml.Marshal(tmpl) if err != nil { t.Fatal(err) } if !bytes.Equal(buf, []byte(src)) { t.Fatalf(`expected "%s"; got "%s"`, src, buf) } } func TestYAMLUnmarshal(t *testing.T) { buf := []byte(`Tmpl: "hello"`) var out struct { Tmpl *template } var foo map[string]string if err := goyaml.Unmarshal(buf, &foo); err != nil { t.Fatal(err) } if out.Tmpl == nil { t.Fatalf("out.Tmpl was nil") } if out.Tmpl.tmpl == nil { t.Fatalf("out.Tmpl.tmpl was nil") } if expected := "hello {{.World}}"; out.Tmpl.src != expected { t.Fatalf("expected %s; got %s", expected, out.Tmpl.src) } } func TestGetYAML(t *testing.T) { src := "hello" tmpl := &template{ tmpl: nil, src: src, } if tag, value := tmpl.GetYAML(); tag != "" || value != src { t.Errorf("GetYAML() returned (%#v, %#v); expected (%#v, %#v)", tag, value, "", src) } } func TestSetYAML(t *testing.T) { tmpl := &template{} tmpl.SetYAML("tagDoesntMatter", "hello {{.World}}") result := tmpl.Execute(map[string]string{ "World": "world!", }) expected := "hello world!" if result != expected { t.Errorf("expected %#v; got %#v", expected, result) } } */ func BenchmarkExecuteNilTemplate(b *testing.B) { template := &template{src: "hello world"} b.ResetTimer() for i := 0; i < b.N; i++ { template.Execute(nil) } } func BenchmarkExecuteHelloWorldTemplate(b *testing.B) { template, err := newTemplate("hello world") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { template.Execute(nil) } } // Executing a simple template like this is ~6x slower than Sprintf // but it is still only a few microseconds which should be sufficiently fast. // The benefit is that we have nice semantic tags in the translation. func BenchmarkExecuteHelloNameTemplate(b *testing.B) { template, err := newTemplate("hello {{.Name}}") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { template.Execute(map[string]string{ "Name": "Nick", }) } } func BenchmarkSprintf(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Sprintf("hello %s", "nick") } } go-i18n-1.10.0/i18n/translation/translation.go000066400000000000000000000051051317766760100207740ustar00rootroot00000000000000// Package translation defines the interface for a translation. package translation import ( "fmt" "github.com/nicksnyder/go-i18n/i18n/language" ) // Translation is the interface that represents a translated string. type Translation interface { // MarshalInterface returns the object that should be used // to serialize the translation. MarshalInterface() interface{} MarshalFlatInterface() interface{} ID() string Template(language.Plural) *template UntranslatedCopy() Translation Normalize(language *language.Language) Translation Backfill(src Translation) Translation Merge(Translation) Translation Incomplete(l *language.Language) bool } // SortableByID implements sort.Interface for a slice of translations. type SortableByID []Translation func (a SortableByID) Len() int { return len(a) } func (a SortableByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a SortableByID) Less(i, j int) bool { return a[i].ID() < a[j].ID() } // NewTranslation reflects on data to create a new Translation. // // data["id"] must be a string and data["translation"] must be either a string // for a non-plural translation or a map[string]interface{} for a plural translation. func NewTranslation(data map[string]interface{}) (Translation, error) { id, ok := data["id"].(string) if !ok { return nil, fmt.Errorf(`missing "id" key`) } var pluralObject map[string]interface{} switch translation := data["translation"].(type) { case string: tmpl, err := newTemplate(translation) if err != nil { return nil, err } return &singleTranslation{id, tmpl}, nil case map[interface{}]interface{}: // The YAML parser uses interface{} keys so we first convert them to string keys. pluralObject = make(map[string]interface{}) for k, v := range translation { kStr, ok := k.(string) if !ok { return nil, fmt.Errorf(`invalid plural category type %T; expected string`, k) } pluralObject[kStr] = v } case map[string]interface{}: pluralObject = translation case nil: return nil, fmt.Errorf(`missing "translation" key`) default: return nil, fmt.Errorf(`unsupported type for "translation" key %T`, translation) } templates := make(map[language.Plural]*template, len(pluralObject)) for k, v := range pluralObject { pc, err := language.NewPlural(k) if err != nil { return nil, err } str, ok := v.(string) if !ok { return nil, fmt.Errorf(`plural category "%s" has value of type %T; expected string`, pc, v) } tmpl, err := newTemplate(str) if err != nil { return nil, err } templates[pc] = tmpl } return &pluralTranslation{id, templates}, nil } go-i18n-1.10.0/i18n/translation/translation_test.go000066400000000000000000000004771317766760100220420ustar00rootroot00000000000000package translation import ( "sort" "testing" ) // Check this here to avoid unnecessary import of sort package. var _ = sort.Interface(make(SortableByID, 0, 0)) func TestNewSingleTranslation(t *testing.T) { t.Skipf("not implemented") } func TestNewPluralTranslation(t *testing.T) { t.Skipf("not implemented") } go-i18n-1.10.0/i18n/translations_test.go000066400000000000000000000050731317766760100176640ustar00rootroot00000000000000package i18n import ( "testing" "github.com/nicksnyder/go-i18n/i18n/bundle" ) var bobMap = map[string]interface{}{"Person": "Bob"} var bobStruct = struct{ Person string }{Person: "Bob"} var testCases = []struct { id string arg interface{} want string }{ {"program_greeting", nil, "Hello world"}, {"person_greeting", bobMap, "Hello Bob"}, {"person_greeting", bobStruct, "Hello Bob"}, {"your_unread_email_count", 0, "You have 0 unread emails."}, {"your_unread_email_count", 1, "You have 1 unread email."}, {"your_unread_email_count", 2, "You have 2 unread emails."}, {"my_height_in_meters", "1.7", "I am 1.7 meters tall."}, {"person_unread_email_count", []interface{}{0, bobMap}, "Bob has 0 unread emails."}, {"person_unread_email_count", []interface{}{1, bobMap}, "Bob has 1 unread email."}, {"person_unread_email_count", []interface{}{2, bobMap}, "Bob has 2 unread emails."}, {"person_unread_email_count", []interface{}{0, bobStruct}, "Bob has 0 unread emails."}, {"person_unread_email_count", []interface{}{1, bobStruct}, "Bob has 1 unread email."}, {"person_unread_email_count", []interface{}{2, bobStruct}, "Bob has 2 unread emails."}, {"person_unread_email_count_timeframe", []interface{}{3, map[string]interface{}{ "Person": "Bob", "Timeframe": "0 days", }}, "Bob has 3 unread emails in the past 0 days."}, {"person_unread_email_count_timeframe", []interface{}{3, map[string]interface{}{ "Person": "Bob", "Timeframe": "1 day", }}, "Bob has 3 unread emails in the past 1 day."}, {"person_unread_email_count_timeframe", []interface{}{3, map[string]interface{}{ "Person": "Bob", "Timeframe": "2 days", }}, "Bob has 3 unread emails in the past 2 days."}, } func testFile(t *testing.T, path string) { b := bundle.New() b.MustLoadTranslationFile(path) T, err := b.Tfunc("en-US") if err != nil { t.Fatal(err) } for _, tc := range testCases { var args []interface{} if _, ok := tc.arg.([]interface{}); ok { args = tc.arg.([]interface{}) } else { args = []interface{}{tc.arg} } got := T(tc.id, args...) if got != tc.want { t.Error("got: %v; want: %v", got, tc.want) } } } func TestJSONParse(t *testing.T) { testFile(t, "../goi18n/testdata/expected/en-us.all.json") } func TestYAMLParse(t *testing.T) { testFile(t, "../goi18n/testdata/en-us.yaml") } func TestJSONFlatParse(t *testing.T) { testFile(t, "../goi18n/testdata/en-us.flat.json") } func TestYAMLFlatParse(t *testing.T) { testFile(t, "../goi18n/testdata/en-us.flat.yaml") } func TestTOMLFlatParse(t *testing.T) { testFile(t, "../goi18n/testdata/en-us.flat.toml") }