From be3966fd39d744b0fce2bc71c79f42911cef7b7c Mon Sep 17 00:00:00 2001 From: Nick Snyder Date: Sun, 19 May 2019 10:46:31 -0700 Subject: [PATCH] v2 only (#176) fixes https://github.com/nicksnyder/go-i18n/issues/172 --- .codecov.yml | 10 +- .gitignore | 4 +- v2/.goreleaser.yml => .goreleaser.yml | 0 .travis.yml | 2 - dev.md | 4 +- {v2/example => example}/README.md | 0 {v2/example => example}/active.en.toml | 0 {v2/example => example}/active.es.toml | 0 {v2/example => example}/main.go | 0 go.mod | 9 +- v2/go.sum => go.sum | 0 {v2/goi18n => goi18n}/active.en.toml | 0 {v2/goi18n => goi18n}/common_test.go | 0 goi18n/constants_command.go | 229 ------ goi18n/constants_command_test.go | 42 - goi18n/doc.go | 93 --- {v2/goi18n => goi18n}/extract_command.go | 7 +- {v2/goi18n => goi18n}/extract_command_test.go | 0 goi18n/gendoc.sh | 12 - goi18n/goi18n.go | 55 -- {v2/goi18n => goi18n}/main.go | 7 +- {v2/goi18n => goi18n}/main_test.go | 0 {v2/goi18n => goi18n}/marshal.go | 0 goi18n/merge_command.go | 438 ++++++----- goi18n/merge_command_flat_test.go | 36 - goi18n/merge_command_test.go | 596 +++++++++++++-- goi18n/testdata/en-us.flat.json | 34 - goi18n/testdata/en-us.flat.toml | 25 - goi18n/testdata/en-us.flat.yaml | 29 - goi18n/testdata/en-us.yaml | 35 - goi18n/testdata/expected/R.go | 38 - goi18n/testdata/expected/ar-ar.all.json | 65 -- .../testdata/expected/ar-ar.untranslated.json | 50 -- goi18n/testdata/expected/en-us.all.json | 45 -- .../testdata/expected/en-us.untranslated.json | 1 - goi18n/testdata/expected/flat/ar-ar.all.json | 43 -- .../expected/flat/ar-ar.untranslated.json | 32 - goi18n/testdata/expected/flat/en-us.all.json | 27 - .../expected/flat/en-us.untranslated.json | 1 - .../flat/en-us.untranslated.json.json | 0 goi18n/testdata/expected/flat/fr-fr.all.json | 27 - .../expected/flat/fr-fr.untranslated.json | 27 - goi18n/testdata/expected/fr-fr.all.json | 45 -- .../testdata/expected/fr-fr.untranslated.json | 45 -- goi18n/testdata/input/ar-ar.one.json | 54 -- goi18n/testdata/input/ar-ar.two.json | 54 -- goi18n/testdata/input/en-us.constants.json | 45 -- goi18n/testdata/input/en-us.one.json | 30 - goi18n/testdata/input/en-us.two.json | 26 - goi18n/testdata/input/flat/ar-ar.one.toml | 37 - goi18n/testdata/input/flat/ar-ar.two.json | 45 -- .../testdata/input/flat/en-us.constants.json | 34 - goi18n/testdata/input/flat/en-us.one.yaml | 16 - goi18n/testdata/input/flat/en-us.two.json | 19 - goi18n/testdata/input/flat/fr-fr.json | 0 goi18n/testdata/input/fr-fr.json | 0 goi18n/testdata/input/yaml/ar-ar.one.json | 54 -- goi18n/testdata/input/yaml/ar-ar.two.json | 54 -- goi18n/testdata/input/yaml/en-us.one.yaml | 19 - goi18n/testdata/input/yaml/en-us.two.json | 26 - goi18n/testdata/input/yaml/fr-fr.json | 0 {v2/i18n => i18n}/bundle.go | 0 i18n/bundle/bundle.go | 443 ----------- i18n/bundle/bundle_test.go | 364 --------- {v2/i18n => i18n}/bundle_test.go | 0 {v2/i18n => i18n}/doc.go | 0 i18n/example_test.go | 198 +++-- i18n/exampletemplate_test.go | 64 -- i18n/exampleyaml_test.go | 63 -- i18n/i18n.go | 158 ---- i18n/language/codegen/generate.sh | 5 - i18n/language/codegen/main.go | 132 ---- i18n/language/codegen/plurals.xml | 226 ------ i18n/language/codegen/xml.go | 143 ---- i18n/language/language.go | 99 --- i18n/language/language_test.go | 85 --- i18n/language/operands.go | 119 --- i18n/language/operands_test.go | 45 -- i18n/language/plural.go | 40 - i18n/language/plural_test.go | 28 - i18n/language/pluralspec.go | 75 -- i18n/language/pluralspec_gen.go | 557 -------------- i18n/language/pluralspec_gen_test.go | 631 --------------- i18n/language/pluralspec_test.go | 716 ------------------ {v2/i18n => i18n}/language_test.go | 0 {v2/i18n => i18n}/localizer.go | 0 {v2/i18n => i18n}/localizer_test.go | 0 {v2/i18n => i18n}/message.go | 0 {v2/i18n => i18n}/message_template.go | 0 {v2/i18n => i18n}/message_template_test.go | 0 {v2/i18n => i18n}/message_test.go | 0 {v2/i18n => i18n}/parse.go | 0 {v2/i18n => i18n}/parse_test.go | 0 i18n/translation/plural_translation.go | 82 -- i18n/translation/plural_translation_test.go | 308 -------- i18n/translation/single_translation.go | 61 -- i18n/translation/template.go | 65 -- i18n/translation/template_test.go | 89 --- i18n/translation/translation.go | 84 -- i18n/translation/translation_test.go | 17 - i18n/translations_test.go | 89 --- .../plural/codegen/generate.sh | 0 .../plural/codegen/main.go | 0 .../plural/codegen/plurals.xml | 0 .../plural/codegen/xml.go | 0 {v2/internal => internal}/plural/doc.go | 0 {v2/internal => internal}/plural/form.go | 0 {v2/internal => internal}/plural/operands.go | 0 .../plural/operands_test.go | 0 {v2/internal => internal}/plural/rule.go | 0 {v2/internal => internal}/plural/rule_gen.go | 0 .../plural/rule_gen_test.go | 0 {v2/internal => internal}/plural/rule_test.go | 0 {v2/internal => internal}/plural/rules.go | 0 .../plural/rules_test.go | 0 {v2/internal => internal}/template.go | 0 {v2/internal => internal}/template_test.go | 0 v2/go.mod | 11 - v2/goi18n/merge_command.go | 292 ------- v2/goi18n/merge_command_test.go | 563 -------------- v2/i18n/example_test.go | 141 ---- 121 files changed, 932 insertions(+), 7687 deletions(-) rename v2/.goreleaser.yml => .goreleaser.yml (100%) rename {v2/example => example}/README.md (100%) rename {v2/example => example}/active.en.toml (100%) rename {v2/example => example}/active.es.toml (100%) rename {v2/example => example}/main.go (100%) rename v2/go.sum => go.sum (100%) rename {v2/goi18n => goi18n}/active.en.toml (100%) rename {v2/goi18n => goi18n}/common_test.go (100%) delete mode 100644 goi18n/constants_command.go delete mode 100644 goi18n/constants_command_test.go delete mode 100644 goi18n/doc.go rename {v2/goi18n => goi18n}/extract_command.go (97%) rename {v2/goi18n => goi18n}/extract_command_test.go (100%) delete mode 100644 goi18n/gendoc.sh delete mode 100644 goi18n/goi18n.go rename {v2/goi18n => goi18n}/main.go (96%) rename {v2/goi18n => goi18n}/main_test.go (100%) rename {v2/goi18n => goi18n}/marshal.go (100%) delete mode 100644 goi18n/merge_command_flat_test.go delete mode 100644 goi18n/testdata/en-us.flat.json delete mode 100644 goi18n/testdata/en-us.flat.toml delete mode 100644 goi18n/testdata/en-us.flat.yaml delete mode 100644 goi18n/testdata/en-us.yaml delete mode 100644 goi18n/testdata/expected/R.go delete mode 100644 goi18n/testdata/expected/ar-ar.all.json delete mode 100644 goi18n/testdata/expected/ar-ar.untranslated.json delete mode 100644 goi18n/testdata/expected/en-us.all.json delete mode 100644 goi18n/testdata/expected/en-us.untranslated.json delete mode 100644 goi18n/testdata/expected/flat/ar-ar.all.json delete mode 100644 goi18n/testdata/expected/flat/ar-ar.untranslated.json delete mode 100644 goi18n/testdata/expected/flat/en-us.all.json delete mode 100644 goi18n/testdata/expected/flat/en-us.untranslated.json delete mode 100644 goi18n/testdata/expected/flat/en-us.untranslated.json.json delete mode 100644 goi18n/testdata/expected/flat/fr-fr.all.json delete mode 100644 goi18n/testdata/expected/flat/fr-fr.untranslated.json delete mode 100644 goi18n/testdata/expected/fr-fr.all.json delete mode 100644 goi18n/testdata/expected/fr-fr.untranslated.json delete mode 100644 goi18n/testdata/input/ar-ar.one.json delete mode 100644 goi18n/testdata/input/ar-ar.two.json delete mode 100644 goi18n/testdata/input/en-us.constants.json delete mode 100644 goi18n/testdata/input/en-us.one.json delete mode 100644 goi18n/testdata/input/en-us.two.json delete mode 100644 goi18n/testdata/input/flat/ar-ar.one.toml delete mode 100644 goi18n/testdata/input/flat/ar-ar.two.json delete mode 100644 goi18n/testdata/input/flat/en-us.constants.json delete mode 100644 goi18n/testdata/input/flat/en-us.one.yaml delete mode 100644 goi18n/testdata/input/flat/en-us.two.json delete mode 100644 goi18n/testdata/input/flat/fr-fr.json delete mode 100644 goi18n/testdata/input/fr-fr.json delete mode 100644 goi18n/testdata/input/yaml/ar-ar.one.json delete mode 100644 goi18n/testdata/input/yaml/ar-ar.two.json delete mode 100644 goi18n/testdata/input/yaml/en-us.one.yaml delete mode 100644 goi18n/testdata/input/yaml/en-us.two.json delete mode 100644 goi18n/testdata/input/yaml/fr-fr.json rename {v2/i18n => i18n}/bundle.go (100%) delete mode 100644 i18n/bundle/bundle.go delete mode 100644 i18n/bundle/bundle_test.go rename {v2/i18n => i18n}/bundle_test.go (100%) rename {v2/i18n => i18n}/doc.go (100%) delete mode 100644 i18n/exampletemplate_test.go delete mode 100644 i18n/exampleyaml_test.go delete mode 100644 i18n/i18n.go delete mode 100644 i18n/language/codegen/generate.sh delete mode 100644 i18n/language/codegen/main.go delete mode 100644 i18n/language/codegen/plurals.xml delete mode 100644 i18n/language/codegen/xml.go delete mode 100644 i18n/language/language.go delete mode 100644 i18n/language/language_test.go delete mode 100644 i18n/language/operands.go delete mode 100644 i18n/language/operands_test.go delete mode 100644 i18n/language/plural.go delete mode 100644 i18n/language/plural_test.go delete mode 100644 i18n/language/pluralspec.go delete mode 100644 i18n/language/pluralspec_gen.go delete mode 100644 i18n/language/pluralspec_gen_test.go delete mode 100644 i18n/language/pluralspec_test.go rename {v2/i18n => i18n}/language_test.go (100%) rename {v2/i18n => i18n}/localizer.go (100%) rename {v2/i18n => i18n}/localizer_test.go (100%) rename {v2/i18n => i18n}/message.go (100%) rename {v2/i18n => i18n}/message_template.go (100%) rename {v2/i18n => i18n}/message_template_test.go (100%) rename {v2/i18n => i18n}/message_test.go (100%) rename {v2/i18n => i18n}/parse.go (100%) rename {v2/i18n => i18n}/parse_test.go (100%) delete mode 100644 i18n/translation/plural_translation.go delete mode 100644 i18n/translation/plural_translation_test.go delete mode 100644 i18n/translation/single_translation.go delete mode 100644 i18n/translation/template.go delete mode 100644 i18n/translation/template_test.go delete mode 100644 i18n/translation/translation.go delete mode 100644 i18n/translation/translation_test.go delete mode 100644 i18n/translations_test.go rename {v2/internal => internal}/plural/codegen/generate.sh (100%) rename {v2/internal => internal}/plural/codegen/main.go (100%) rename {v2/internal => internal}/plural/codegen/plurals.xml (100%) rename {v2/internal => internal}/plural/codegen/xml.go (100%) rename {v2/internal => internal}/plural/doc.go (100%) rename {v2/internal => internal}/plural/form.go (100%) rename {v2/internal => internal}/plural/operands.go (100%) rename {v2/internal => internal}/plural/operands_test.go (100%) rename {v2/internal => internal}/plural/rule.go (100%) rename {v2/internal => internal}/plural/rule_gen.go (100%) rename {v2/internal => internal}/plural/rule_gen_test.go (100%) rename {v2/internal => internal}/plural/rule_test.go (100%) rename {v2/internal => internal}/plural/rules.go (100%) rename {v2/internal => internal}/plural/rules_test.go (100%) rename {v2/internal => internal}/template.go (100%) rename {v2/internal => internal}/template_test.go (100%) delete mode 100644 v2/go.mod delete mode 100644 v2/goi18n/merge_command.go delete mode 100644 v2/goi18n/merge_command_test.go delete mode 100644 v2/i18n/example_test.go diff --git a/.codecov.yml b/.codecov.yml index b90d6daf..879fd14f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,13 +4,5 @@ coverage: default: threshold: 50% project: - default: false - v1: - threshold: 0.1% - paths: - - i18n - - goi18n - v2: + default: threshold: 0.1% - paths: - - v2 diff --git a/.gitignore b/.gitignore index d9af66f4..281914e6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ output/ *.test *.swp -v2/example/example -v2/goi18n/goi18n +example/example +goi18n/goi18n dist/ diff --git a/v2/.goreleaser.yml b/.goreleaser.yml similarity index 100% rename from v2/.goreleaser.yml rename to .goreleaser.yml diff --git a/.travis.yml b/.travis.yml index 8d1a79df..a9e9c518 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: go matrix: include: - - go: 1.2.x - env: PKG='./i18n/... ./goi18n/...' - go: 1.9.x env: PKG='./...' - go: 1.12.x diff --git a/dev.md b/dev.md index a55181eb..d0210bb8 100644 --- a/dev.md +++ b/dev.md @@ -4,5 +4,5 @@ 1. Go to http://cldr.unicode.org/index/downloads to find the latest version. 1. Download the latest version of cldr-common (e.g. http://unicode.org/Public/cldr/33/cldr-common-33.0.zip) -1. Unzip and copy `common/supplemental/plurals.xml` to `v2/i18n/internal/plural/codegen/plurals.xml` -1. Run `generate.sh` in `v2/i18n/internal/plural/codegen/` +1. Unzip and copy `common/supplemental/plurals.xml` to `i18n/internal/plural/codegen/plurals.xml` +1. Run `generate.sh` in `i18n/internal/plural/codegen/` diff --git a/v2/example/README.md b/example/README.md similarity index 100% rename from v2/example/README.md rename to example/README.md diff --git a/v2/example/active.en.toml b/example/active.en.toml similarity index 100% rename from v2/example/active.en.toml rename to example/active.en.toml diff --git a/v2/example/active.es.toml b/example/active.es.toml similarity index 100% rename from v2/example/active.es.toml rename to example/active.es.toml diff --git a/v2/example/main.go b/example/main.go similarity index 100% rename from v2/example/main.go rename to example/main.go diff --git a/go.mod b/go.mod index d42c15ab..7aba6f87 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,11 @@ -module github.com/nicksnyder/go-i18n +module github.com/nicksnyder/go-i18n/v2 require ( - github.com/pelletier/go-toml v1.2.0 + github.com/BurntSushi/toml v0.3.0 + golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect + golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect + golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b // indirect + golang.org/x/text v0.3.2 + golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c // indirect gopkg.in/yaml.v2 v2.2.1 ) diff --git a/v2/go.sum b/go.sum similarity index 100% rename from v2/go.sum rename to go.sum diff --git a/v2/goi18n/active.en.toml b/goi18n/active.en.toml similarity index 100% rename from v2/goi18n/active.en.toml rename to goi18n/active.en.toml diff --git a/v2/goi18n/common_test.go b/goi18n/common_test.go similarity index 100% rename from v2/goi18n/common_test.go rename to goi18n/common_test.go diff --git a/goi18n/constants_command.go b/goi18n/constants_command.go deleted file mode 100644 index d877add3..00000000 --- a/goi18n/constants_command.go +++ /dev/null @@ -1,229 +0,0 @@ -package 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 -} diff --git a/goi18n/constants_command_test.go b/goi18n/constants_command_test.go deleted file mode 100644 index 43dea3f3..00000000 --- a/goi18n/constants_command_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package 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") -} diff --git a/goi18n/doc.go b/goi18n/doc.go deleted file mode 100644 index 97c7a7fb..00000000 --- a/goi18n/doc.go +++ /dev/null @@ -1,93 +0,0 @@ -// 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 diff --git a/v2/goi18n/extract_command.go b/goi18n/extract_command.go similarity index 97% rename from v2/goi18n/extract_command.go rename to goi18n/extract_command.go index b7bee376..178a06d7 100644 --- a/v2/goi18n/extract_command.go +++ b/goi18n/extract_command.go @@ -51,16 +51,19 @@ func (ec *extractCommand) name() string { return "extract" } -func (ec *extractCommand) parse(args []string) { +func (ec *extractCommand) parse(args []string) error { flags := flag.NewFlagSet("extract", flag.ExitOnError) flags.Usage = usageExtract flags.Var(&ec.sourceLanguage, "sourceLanguage", "en") flags.StringVar(&ec.outdir, "outdir", ".", "") flags.StringVar(&ec.format, "format", "toml", "") - flags.Parse(args) + if err := flags.Parse(args); err != nil { + return err + } ec.paths = flags.Args() + return nil } func (ec *extractCommand) execute() error { diff --git a/v2/goi18n/extract_command_test.go b/goi18n/extract_command_test.go similarity index 100% rename from v2/goi18n/extract_command_test.go rename to goi18n/extract_command_test.go diff --git a/goi18n/gendoc.sh b/goi18n/gendoc.sh deleted file mode 100644 index f30df34e..00000000 --- a/goi18n/gendoc.sh +++ /dev/null @@ -1,12 +0,0 @@ -go 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 diff --git a/goi18n/goi18n.go b/goi18n/goi18n.go deleted file mode 100644 index 3bd763f4..00000000 --- a/goi18n/goi18n.go +++ /dev/null @@ -1,55 +0,0 @@ -package 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) -} diff --git a/v2/goi18n/main.go b/goi18n/main.go similarity index 96% rename from v2/goi18n/main.go rename to goi18n/main.go index 03e92409..f2f3d2c1 100644 --- a/v2/goi18n/main.go +++ b/goi18n/main.go @@ -88,7 +88,7 @@ Workflow: type command interface { name() string - parse(arguments []string) + parse(arguments []string) error execute() error } @@ -116,7 +116,10 @@ func testableMain(args []string) int { cmdName := flags.Arg(0) for _, cmd := range commands { if cmd.name() == cmdName { - cmd.parse(flags.Args()[1:]) + if err := cmd.parse(flags.Args()[1:]); err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } if err := cmd.execute(); err != nil { fmt.Fprintln(os.Stderr, err) return 1 diff --git a/v2/goi18n/main_test.go b/goi18n/main_test.go similarity index 100% rename from v2/goi18n/main_test.go rename to goi18n/main_test.go diff --git a/v2/goi18n/marshal.go b/goi18n/marshal.go similarity index 100% rename from v2/goi18n/marshal.go rename to goi18n/marshal.go diff --git a/goi18n/merge_command.go b/goi18n/merge_command.go index 1b9d04fd..9765c6d3 100644 --- a/goi18n/merge_command.go +++ b/goi18n/merge_command.go @@ -1,239 +1,295 @@ package main import ( + "crypto/sha1" "encoding/json" "flag" "fmt" + "io" "io/ioutil" - "path/filepath" - "reflect" - "sort" + "os" + + "github.com/BurntSushi/toml" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/nicksnyder/go-i18n/v2/internal" + "github.com/nicksnyder/go-i18n/v2/internal/plural" + "golang.org/x/text/language" + yaml "gopkg.in/yaml.v2" +) - "gopkg.in/yaml.v2" +func usageMerge() { + fmt.Fprintf(os.Stderr, `usage: goi18n merge [options] [message files] - "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" -) +Merge reads all messages in the message files and produces two files per language. -type mergeCommand struct { - translationFiles []string - sourceLanguage string - outdir string - format string - flat bool -} + xx-yy.active.format + This file contains messages that should be loaded at runtime. -func (mc *mergeCommand) execute() error { - if len(mc.translationFiles) < 1 { - return fmt.Errorf("need at least one translation file to parse") - } + xx-yy.translate.format + This file contains messages that are empty and should be translated. - if lang := language.Parse(mc.sourceLanguage); lang == nil { - return fmt.Errorf("invalid source locale: %s", mc.sourceLanguage) - } +Message file names must have a suffix of a supported format (e.g. ".json") and +contain a valid language tag as defined by RFC 5646 (e.g. "en-us", "fr", "zh-hant", etc.). - 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) - } - } +To add support for a new language, create an empty translation file with the +appropriate name and pass it in to goi18n merge. - 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() - } - } - } +Flags: - 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 - } + -sourceLanguage tag + Translate messages from this language (e.g. en, en-US, zh-Hant-CN) + Default: en - 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 + -outdir directory + Write message files to this directory. + Default: . + + -format format + Output message files in this format. + Supported formats: json, toml, yaml + Default: toml +`) } -func (mc *mergeCommand) parse(arguments []string) { +type mergeCommand struct { + messageFiles []string + sourceLanguage languageTag + outdir string + format string +} + +func (mc *mergeCommand) name() string { + return "merge" +} + +func (mc *mergeCommand) parse(args []string) error { flags := flag.NewFlagSet("merge", flag.ExitOnError) flags.Usage = usageMerge - 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 + flags.Var(&mc.sourceLanguage, "sourceLanguage", "en") + flags.StringVar(&mc.outdir, "outdir", ".", "") + flags.StringVar(&mc.format, "format", "toml", "") + if err := flags.Parse(args); err != nil { + return err } -} -func (mc *mergeCommand) SetArgs(args []string) { - mc.translationFiles = args + mc.messageFiles = flags.Args() + return nil } -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 +func (mc *mergeCommand) execute() error { + if len(mc.messageFiles) < 1 { + return fmt.Errorf("need at least one message file to parse") } - - buf, err := mc.marshal(convert(translations)) + inFiles := make(map[string][]byte) + for _, path := range mc.messageFiles { + content, err := ioutil.ReadFile(path) + if err != nil { + return err + } + inFiles[path] = content + } + ops, err := merge(inFiles, mc.sourceLanguage.Tag(), mc.outdir, mc.format) if err != nil { - return fmt.Errorf("failed to marshal %s strings to %s: %s", localeID, mc.format, err) + return 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) + for path, content := range ops.writeFiles { + if err := ioutil.WriteFile(path, content, 0666); err != nil { + return err + } + } + for _, path := range ops.deleteFiles { + // Ignore error since it isn't guaranteed to exist. + os.Remove(path) } return nil } -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) +type fileSystemOp struct { + writeFiles map[string][]byte + deleteFiles []string +} + +func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdir, outputFormat string) (*fileSystemOp, error) { + unmerged := make(map[language.Tag][]map[string]*i18n.MessageTemplate) + sourceMessageTemplates := make(map[string]*i18n.MessageTemplate) + unmarshalFuncs := map[string]i18n.UnmarshalFunc{ + "json": json.Unmarshal, + "toml": toml.Unmarshal, + "yaml": yaml.Unmarshal, + } + for path, content := range messageFiles { + mf, err := i18n.ParseMessageFileBytes(content, path, unmarshalFuncs) + if err != nil { + return nil, fmt.Errorf("failed to load message file %s: %s", path, err) + } + templates := map[string]*i18n.MessageTemplate{} + for _, m := range mf.Messages { + templates[m.ID] = i18n.NewMessageTemplate(m) + } + if mf.Tag == sourceLanguageTag { + for _, template := range templates { + if sourceMessageTemplates[template.ID] != nil { + return nil, fmt.Errorf("multiple source translations for id %s", template.ID) + } + template.Hash = hash(template) + sourceMessageTemplates[template.ID] = template + } } + unmerged[mf.Tag] = append(unmerged[mf.Tag], templates) } - return filtered -} + if len(sourceMessageTemplates) == 0 { + return nil, fmt.Errorf("no messages found for source locale %s", sourceLanguageTag) + } + + pluralRules := plural.DefaultRules() + all := make(map[language.Tag]map[string]*i18n.MessageTemplate) + all[sourceLanguageTag] = sourceMessageTemplates + for _, srcTemplate := range sourceMessageTemplates { + for dstLangTag, messageTemplates := range unmerged { + if dstLangTag == sourceLanguageTag { + continue + } + pluralRule := pluralRules.Rule(dstLangTag) + if pluralRule == nil { + // Non-standard languages not supported because + // we don't know if translations are complete or not. + continue + } + if all[dstLangTag] == nil { + all[dstLangTag] = make(map[string]*i18n.MessageTemplate) + } + dstMessageTemplate := all[dstLangTag][srcTemplate.ID] + if dstMessageTemplate == nil { + dstMessageTemplate = &i18n.MessageTemplate{ + Message: &i18n.Message{ + ID: srcTemplate.ID, + Description: srcTemplate.Description, + Hash: srcTemplate.Hash, + }, + PluralTemplates: make(map[plural.Form]*internal.Template), + } + all[dstLangTag][srcTemplate.ID] = dstMessageTemplate + } -func marshalFlatInterface(translations []translation.Translation) interface{} { - mi := make(map[string]interface{}, len(translations)) - for _, translation := range translations { - mi[translation.ID()] = translation.MarshalFlatInterface() + // Check all unmerged message templates for this message id. + for _, messageTemplates := range messageTemplates { + unmergedTemplate := messageTemplates[srcTemplate.ID] + if unmergedTemplate == nil { + continue + } + // Ignore empty hashes for v1 backward compatibility. + if unmergedTemplate.Hash != "" && unmergedTemplate.Hash != srcTemplate.Hash { + // This was translated from different content so discard. + continue + } + + // Merge in the translated messages. + for pluralForm := range pluralRule.PluralForms { + dt := unmergedTemplate.PluralTemplates[pluralForm] + if dt != nil && dt.Src != "" { + dstMessageTemplate.PluralTemplates[pluralForm] = dt + } + } + } + } } - return mi -} -func marshalInterface(translations []translation.Translation) interface{} { - mi := make([]interface{}, len(translations)) - for i, translation := range translations { - mi[i] = translation.MarshalInterface() + translate := make(map[language.Tag]map[string]*i18n.MessageTemplate) + active := make(map[language.Tag]map[string]*i18n.MessageTemplate) + for langTag, messageTemplates := range all { + active[langTag] = make(map[string]*i18n.MessageTemplate) + if langTag == sourceLanguageTag { + active[langTag] = messageTemplates + continue + } + pluralRule := pluralRules.Rule(langTag) + if pluralRule == nil { + // Non-standard languages not supported because + // we don't know if translations are complete or not. + continue + } + for _, messageTemplate := range messageTemplates { + srcMessageTemplate := sourceMessageTemplates[messageTemplate.ID] + activeMessageTemplate, translateMessageTemplate := activeDst(srcMessageTemplate, messageTemplate, pluralRule) + if translateMessageTemplate != nil { + if translate[langTag] == nil { + translate[langTag] = make(map[string]*i18n.MessageTemplate) + } + translate[langTag][messageTemplate.ID] = translateMessageTemplate + } + if activeMessageTemplate != nil { + active[langTag][messageTemplate.ID] = activeMessageTemplate + } + } } - 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) + writeFiles := make(map[string][]byte, len(translate)+len(active)) + for langTag, messageTemplates := range translate { + path, content, err := writeFile(outdir, "translate", langTag, outputFormat, messageTemplates, false) + if err != nil { + return nil, err + } + writeFiles[path] = content } - return nil, fmt.Errorf("unsupported format: %s\n", mc.format) + deleteFiles := []string{} + for langTag, messageTemplates := range active { + path, content, err := writeFile(outdir, "active", langTag, outputFormat, messageTemplates, langTag == sourceLanguageTag) + if err != nil { + return nil, err + } + if len(content) > 0 { + writeFiles[path] = content + } else { + deleteFiles = append(deleteFiles, path) + } + } + return &fileSystemOp{writeFiles: writeFiles, deleteFiles: deleteFiles}, nil } -func marshalTOML(v interface{}) ([]byte, error) { - m, ok := v.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("invalid format for marshaling to TOML") +// activeDst returns the active part of the dst and whether dst is a complete translation of src. +func activeDst(src, dst *i18n.MessageTemplate, pluralRule *plural.Rule) (active *i18n.MessageTemplate, translateMessageTemplate *i18n.MessageTemplate) { + pluralForms := pluralRule.PluralForms + if len(src.PluralTemplates) == 1 { + pluralForms = map[plural.Form]struct{}{ + plural.Other: {}, + } } - tree, err := toml.TreeFromMap(m) - if err != nil { - return nil, err + for pluralForm := range pluralForms { + dt := dst.PluralTemplates[pluralForm] + if dt == nil || dt.Src == "" { + if translateMessageTemplate == nil { + translateMessageTemplate = &i18n.MessageTemplate{ + Message: &i18n.Message{ + ID: src.ID, + Description: src.Description, + Hash: src.Hash, + }, + PluralTemplates: make(map[plural.Form]*internal.Template), + } + } + translateMessageTemplate.PluralTemplates[pluralForm] = src.PluralTemplates[plural.Other] + continue + } + if active == nil { + active = &i18n.MessageTemplate{ + Message: &i18n.Message{ + ID: src.ID, + Description: src.Description, + Hash: src.Hash, + }, + PluralTemplates: make(map[plural.Form]*internal.Template), + } + } + active.PluralTemplates[pluralForm] = dt } - s, err := tree.ToTomlString() - return []byte(s), err + return } -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 - -`) +func hash(t *i18n.MessageTemplate) string { + h := sha1.New() + _, _ = io.WriteString(h, t.Description) + _, _ = io.WriteString(h, t.PluralTemplates[plural.Other].Src) + return fmt.Sprintf("sha1-%x", h.Sum(nil)) } diff --git a/goi18n/merge_command_flat_test.go b/goi18n/merge_command_flat_test.go deleted file mode 100644 index caa892d2..00000000 --- a/goi18n/merge_command_flat_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package 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") -} diff --git a/goi18n/merge_command_test.go b/goi18n/merge_command_test.go index 425a6b62..ff7c2219 100644 --- a/goi18n/merge_command_test.go +++ b/goi18n/merge_command_test.go @@ -4,72 +4,560 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" "testing" + + "golang.org/x/text/language" ) -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) +type testCase struct { + name string + inFiles map[string][]byte + sourceLanguage language.Tag + outFiles map[string][]byte + deleteFiles []string } -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 expectFile(s string) []byte { + // Trimming leading newlines gives nicer formatting for file literals in test cases. + return bytes.TrimLeft([]byte(s), "\n") } -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, +func TestMerge(t *testing.T) { + testCases := []*testCase{ + { + name: "single identity", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "one.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), + }, + }, + { + name: "plural identity", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "active.en-US.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread email" +other = "{{.Count}} unread emails" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread email" +other = "{{.Count}} unread emails" +`), + }, + }, + { + name: "migrate source lang from v1 format", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "one.en-US.json": []byte(`[ + { + "id": "simple", + "translation": "simple translation" + }, + { + "id": "everything", + "translation": { + "zero": "zero translation", + "one": "one translation", + "two": "two translation", + "few": "few translation", + "many": "many translation", + "other": "other translation" + } } - if err := mc.execute(); err != nil { - t.Fatal(err) +]`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +simple = "simple translation" + +[everything] +few = "few translation" +many = "many translation" +one = "one translation" +other = "other translation" +two = "two translation" +zero = "zero translation" +`), + }, + }, + { + name: "migrate source lang from v1 flat format", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "one.en-US.json": []byte(`{ + "simple": { + "other": "simple translation" + }, + "everything": { + "zero": "zero translation", + "one": "one translation", + "two": "two translation", + "few": "few translation", + "many": "many translation", + "other": "other translation" } +}`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +simple = "simple translation" - 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") -} +[everything] +few = "few translation" +many = "many translation" +one = "one translation" +other = "other translation" +two = "two translation" +zero = "zero translation" +`), + }, + }, + { + name: "merge source files", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "one.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), + "two.en-US.toml": []byte("2GoodbyeMessage = \"Goodbye\"\n"), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": []byte("1HelloMessage = \"Hello\"\n2GoodbyeMessage = \"Goodbye\"\n"), + }, + }, + { + name: "missing hash", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +1HelloMessage = "Hello" +`), + "es-ES.toml": []byte(` +[1HelloMessage] +other = "Hola" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +1HelloMessage = "Hello" +`), + "active.es-ES.toml": expectFile(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" +`), + }, + }, + { + name: "add single translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +1HelloMessage = "Hello" +2GoodbyeMessage = "Goodbye" +`), + "es-ES.toml": []byte(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +1HelloMessage = "Hello" +2GoodbyeMessage = "Goodbye" +`), + "active.es-ES.toml": expectFile(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" +`), + "translate.es-ES.toml": expectFile(` +[2GoodbyeMessage] +hash = "sha1-b5b29c53e3c71cb9c6581ab053d7758fab8ca24d" +other = "Goodbye" +`), + }, + }, + { + name: "remove single translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +1HelloMessage = "Hello" +`), + "es-ES.toml": []byte(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" -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) - } -} +[2GoodbyeMessage] +hash = "sha1-b5b29c53e3c71cb9c6581ab053d7758fab8ca24d" +other = "Goodbye" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +1HelloMessage = "Hello" +`), + "active.es-ES.toml": expectFile(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" +`), + }, + }, + { + name: "edit single translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +1HelloMessage = "Hi" +`), + "es-ES.toml": []byte(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +1HelloMessage = "Hi" +`), + "translate.es-ES.toml": expectFile(` +[1HelloMessage] +hash = "sha1-94dd9e08c129c785f7f256e82fbe0a30e6d1ae40" +other = "Hi" +`), + }, + }, + { + name: "add plural translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread email" +other = "{{.Count}} unread emails" +`), + "es-ES.toml": nil, + "ar-AR.toml": nil, + "zh-CN.toml": nil, + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread email" +other = "{{.Count}} unread emails" +`), + "translate.es-ES.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +`), + "translate.ar-AR.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +few = "{{.Count}} unread emails" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +many = "{{.Count}} unread emails" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +two = "{{.Count}} unread emails" +zero = "{{.Count}} unread emails" +`), + "translate.zh-CN.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +other = "{{.Count}} unread emails" +`), + }, + }, + { + name: "remove plural translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +1HelloMessage = "Hello" +`), + "es-ES.toml": []byte(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" -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) +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +`), + "ar-AR.toml": []byte(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hello" + +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +few = "{{.Count}} unread emails" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +many = "{{.Count}} unread emails" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +two = "{{.Count}} unread emails" +zero = "{{.Count}} unread emails" +`), + "zh-CN.toml": []byte(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hello" + +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +other = "{{.Count}} unread emails" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +1HelloMessage = "Hello" +`), + "active.es-ES.toml": expectFile(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hola" +`), + "active.ar-AR.toml": expectFile(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hello" +`), + "active.zh-CN.toml": expectFile(` +[1HelloMessage] +hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" +other = "Hello" +`), + }, + }, + { + name: "edit plural translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread emails!" +other = "{{.Count}} unread emails!" +`), + "es-ES.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +`), + "ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +few = "{{.Count}} unread emails" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +many = "{{.Count}} unread emails" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +two = "{{.Count}} unread emails" +zero = "{{.Count}} unread emails" +`), + "zh-CN.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +other = "{{.Count}} unread emails" +`), + }, + deleteFiles: []string{ + "active.es-ES.toml", + "active.ar-AR.toml", + "active.zh-CN.toml", + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread emails!" +other = "{{.Count}} unread emails!" +`), + "translate.es-ES.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" +one = "{{.Count}} unread emails!" +other = "{{.Count}} unread emails!" +`), + "translate.ar-AR.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +few = "{{.Count}} unread emails!" +hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" +many = "{{.Count}} unread emails!" +one = "{{.Count}} unread emails!" +other = "{{.Count}} unread emails!" +two = "{{.Count}} unread emails!" +zero = "{{.Count}} unread emails!" +`), + "translate.zh-CN.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" +other = "{{.Count}} unread emails!" +`), + }, + }, + { + name: "merge plural translation", + sourceLanguage: language.AmericanEnglish, + inFiles: map[string][]byte{ + "en-US.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +`), + "zero.ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +zero = "{{.Count}} unread emails" +`), + "one.ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +one = "{{.Count}} unread emails" +`), + "two.ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +two = "{{.Count}} unread emails" +`), + "few.ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +few = "{{.Count}} unread emails" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +`), + "many.ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +many = "{{.Count}} unread emails" +`), + "other.ar-AR.toml": []byte(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +other = "{{.Count}} unread emails" +`), + }, + outFiles: map[string][]byte{ + "active.en-US.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +`), + "active.ar-AR.toml": expectFile(` +[UnreadEmails] +description = "Message that tells the user how many unread emails they have" +few = "{{.Count}} unread emails" +hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" +many = "{{.Count}} unread emails" +one = "{{.Count}} unread emails" +other = "{{.Count}} unread emails" +two = "{{.Count}} unread emails" +zero = "{{.Count}} unread emails" +`), + }, + }, } - if !bytes.Equal(actual, expected) { - t.Errorf("contents of files did not match: %s, %s", expectedName, actualName) + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + indir := mustTempDir("TestMergeCommandIn") + defer os.RemoveAll(indir) + outdir := mustTempDir("TestMergeCommandOut") + defer os.RemoveAll(outdir) + + infiles := make([]string, 0, len(testCase.inFiles)) + for name, content := range testCase.inFiles { + path := filepath.Join(indir, name) + infiles = append(infiles, path) + if err := ioutil.WriteFile(path, content, 0666); err != nil { + t.Fatal(err) + } + } + + for _, name := range testCase.deleteFiles { + path := filepath.Join(outdir, name) + if err := ioutil.WriteFile(path, []byte(`this file should get deleted`), 0666); err != nil { + t.Fatal(err) + } + } + + args := append([]string{"merge", "-sourceLanguage", testCase.sourceLanguage.String(), "-outdir", outdir}, infiles...) + if code := testableMain(args); code != 0 { + t.Fatalf("expected exit code 0; got %d\n", code) + } + + files, err := ioutil.ReadDir(outdir) + if err != nil { + t.Fatal(err) + } + + // Verify that all actual files have expected contents. + actualFiles := make(map[string]struct{}, len(files)) + for _, f := range files { + actualFiles[f.Name()] = struct{}{} + if f.IsDir() { + t.Errorf("found unexpected dir %s", f.Name()) + continue + } + path := filepath.Join(outdir, f.Name()) + actual, err := ioutil.ReadFile(path) + if err != nil { + t.Error(err) + continue + } + expected, ok := testCase.outFiles[f.Name()] + if !ok { + t.Errorf("found unexpected file %s with contents:\n%s\n", f.Name(), actual) + continue + } + if !bytes.Equal(actual, expected) { + t.Errorf("unexpected contents %s\ngot\n%s\nexpected\n%s", f.Name(), actual, expected) + continue + } + } + + // Verify that all expected files are accounted for. + for name := range testCase.outFiles { + if _, ok := actualFiles[name]; !ok { + t.Errorf("did not find expected file %s", name) + } + } + }) } } diff --git a/goi18n/testdata/en-us.flat.json b/goi18n/testdata/en-us.flat.json deleted file mode 100644 index f67d21ca..00000000 --- a/goi18n/testdata/en-us.flat.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "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." - } -} diff --git a/goi18n/testdata/en-us.flat.toml b/goi18n/testdata/en-us.flat.toml deleted file mode 100644 index 5623a6c2..00000000 --- a/goi18n/testdata/en-us.flat.toml +++ /dev/null @@ -1,25 +0,0 @@ -[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" diff --git a/goi18n/testdata/en-us.flat.yaml b/goi18n/testdata/en-us.flat.yaml deleted file mode 100644 index 66412890..00000000 --- a/goi18n/testdata/en-us.flat.yaml +++ /dev/null @@ -1,29 +0,0 @@ - - -# 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" diff --git a/goi18n/testdata/en-us.yaml b/goi18n/testdata/en-us.yaml deleted file mode 100644 index a8f5ed9b..00000000 --- a/goi18n/testdata/en-us.yaml +++ /dev/null @@ -1,35 +0,0 @@ - - - -# 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" diff --git a/goi18n/testdata/expected/R.go b/goi18n/testdata/expected/R.go deleted file mode 100644 index 9b5334a7..00000000 --- a/goi18n/testdata/expected/R.go +++ /dev/null @@ -1,38 +0,0 @@ -// 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" diff --git a/goi18n/testdata/expected/ar-ar.all.json b/goi18n/testdata/expected/ar-ar.all.json deleted file mode 100644 index 26a72ff3..00000000 --- a/goi18n/testdata/expected/ar-ar.all.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "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": "" - } - } -] \ No newline at end of file diff --git a/goi18n/testdata/expected/ar-ar.untranslated.json b/goi18n/testdata/expected/ar-ar.untranslated.json deleted file mode 100644 index a19fa0be..00000000 --- a/goi18n/testdata/expected/ar-ar.untranslated.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "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." - } - } -] \ No newline at end of file diff --git a/goi18n/testdata/expected/en-us.all.json b/goi18n/testdata/expected/en-us.all.json deleted file mode 100644 index 5aedc235..00000000 --- a/goi18n/testdata/expected/en-us.all.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "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." - } - } -] \ No newline at end of file diff --git a/goi18n/testdata/expected/en-us.untranslated.json b/goi18n/testdata/expected/en-us.untranslated.json deleted file mode 100644 index 0637a088..00000000 --- a/goi18n/testdata/expected/en-us.untranslated.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/goi18n/testdata/expected/flat/ar-ar.all.json b/goi18n/testdata/expected/flat/ar-ar.all.json deleted file mode 100644 index 1adb99ca..00000000 --- a/goi18n/testdata/expected/flat/ar-ar.all.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "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": "" - } -} \ No newline at end of file diff --git a/goi18n/testdata/expected/flat/ar-ar.untranslated.json b/goi18n/testdata/expected/flat/ar-ar.untranslated.json deleted file mode 100644 index ea7aa7d1..00000000 --- a/goi18n/testdata/expected/flat/ar-ar.untranslated.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "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." - } -} \ No newline at end of file diff --git a/goi18n/testdata/expected/flat/en-us.all.json b/goi18n/testdata/expected/flat/en-us.all.json deleted file mode 100644 index 766b2a77..00000000 --- a/goi18n/testdata/expected/flat/en-us.all.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "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." - } -} \ No newline at end of file diff --git a/goi18n/testdata/expected/flat/en-us.untranslated.json b/goi18n/testdata/expected/flat/en-us.untranslated.json deleted file mode 100644 index 9e26dfee..00000000 --- a/goi18n/testdata/expected/flat/en-us.untranslated.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/goi18n/testdata/expected/flat/en-us.untranslated.json.json b/goi18n/testdata/expected/flat/en-us.untranslated.json.json deleted file mode 100644 index e69de29b..00000000 diff --git a/goi18n/testdata/expected/flat/fr-fr.all.json b/goi18n/testdata/expected/flat/fr-fr.all.json deleted file mode 100644 index b0ee0311..00000000 --- a/goi18n/testdata/expected/flat/fr-fr.all.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "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": "" - } -} \ No newline at end of file diff --git a/goi18n/testdata/expected/flat/fr-fr.untranslated.json b/goi18n/testdata/expected/flat/fr-fr.untranslated.json deleted file mode 100644 index e6d5c4fb..00000000 --- a/goi18n/testdata/expected/flat/fr-fr.untranslated.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "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." - } -} \ No newline at end of file diff --git a/goi18n/testdata/expected/fr-fr.all.json b/goi18n/testdata/expected/fr-fr.all.json deleted file mode 100644 index 20e0b873..00000000 --- a/goi18n/testdata/expected/fr-fr.all.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "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": "" - } - } -] \ No newline at end of file diff --git a/goi18n/testdata/expected/fr-fr.untranslated.json b/goi18n/testdata/expected/fr-fr.untranslated.json deleted file mode 100644 index e2d3967c..00000000 --- a/goi18n/testdata/expected/fr-fr.untranslated.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "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." - } - } -] \ No newline at end of file diff --git a/goi18n/testdata/input/ar-ar.one.json b/goi18n/testdata/input/ar-ar.one.json deleted file mode 100644 index f5af1d6f..00000000 --- a/goi18n/testdata/input/ar-ar.one.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "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": "" - } - } -] diff --git a/goi18n/testdata/input/ar-ar.two.json b/goi18n/testdata/input/ar-ar.two.json deleted file mode 100644 index e98d7e9b..00000000 --- a/goi18n/testdata/input/ar-ar.two.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "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": "" - } - } -] diff --git a/goi18n/testdata/input/en-us.constants.json b/goi18n/testdata/input/en-us.constants.json deleted file mode 100644 index 5aedc235..00000000 --- a/goi18n/testdata/input/en-us.constants.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "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." - } - } -] \ No newline at end of file diff --git a/goi18n/testdata/input/en-us.one.json b/goi18n/testdata/input/en-us.one.json deleted file mode 100644 index 63a9d6ff..00000000 --- a/goi18n/testdata/input/en-us.one.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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" - } -] diff --git a/goi18n/testdata/input/en-us.two.json b/goi18n/testdata/input/en-us.two.json deleted file mode 100644 index dcc715c4..00000000 --- a/goi18n/testdata/input/en-us.two.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "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" - } - } -] diff --git a/goi18n/testdata/input/flat/ar-ar.one.toml b/goi18n/testdata/input/flat/ar-ar.one.toml deleted file mode 100644 index 364a62cf..00000000 --- a/goi18n/testdata/input/flat/ar-ar.one.toml +++ /dev/null @@ -1,37 +0,0 @@ -[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 = "" diff --git a/goi18n/testdata/input/flat/ar-ar.two.json b/goi18n/testdata/input/flat/ar-ar.two.json deleted file mode 100644 index 5e6fba41..00000000 --- a/goi18n/testdata/input/flat/ar-ar.two.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "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": "" - } -} diff --git a/goi18n/testdata/input/flat/en-us.constants.json b/goi18n/testdata/input/flat/en-us.constants.json deleted file mode 100644 index c41b2b97..00000000 --- a/goi18n/testdata/input/flat/en-us.constants.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "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." - } -} diff --git a/goi18n/testdata/input/flat/en-us.one.yaml b/goi18n/testdata/input/flat/en-us.one.yaml deleted file mode 100644 index 02ae0011..00000000 --- a/goi18n/testdata/input/flat/en-us.one.yaml +++ /dev/null @@ -1,16 +0,0 @@ -program_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" diff --git a/goi18n/testdata/input/flat/en-us.two.json b/goi18n/testdata/input/flat/en-us.two.json deleted file mode 100644 index 06bd28dc..00000000 --- a/goi18n/testdata/input/flat/en-us.two.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "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" - } -} diff --git a/goi18n/testdata/input/flat/fr-fr.json b/goi18n/testdata/input/flat/fr-fr.json deleted file mode 100644 index e69de29b..00000000 diff --git a/goi18n/testdata/input/fr-fr.json b/goi18n/testdata/input/fr-fr.json deleted file mode 100644 index e69de29b..00000000 diff --git a/goi18n/testdata/input/yaml/ar-ar.one.json b/goi18n/testdata/input/yaml/ar-ar.one.json deleted file mode 100644 index f5af1d6f..00000000 --- a/goi18n/testdata/input/yaml/ar-ar.one.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "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": "" - } - } -] diff --git a/goi18n/testdata/input/yaml/ar-ar.two.json b/goi18n/testdata/input/yaml/ar-ar.two.json deleted file mode 100644 index e98d7e9b..00000000 --- a/goi18n/testdata/input/yaml/ar-ar.two.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "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": "" - } - } -] diff --git a/goi18n/testdata/input/yaml/en-us.one.yaml b/goi18n/testdata/input/yaml/en-us.one.yaml deleted file mode 100644 index 3ca8e380..00000000 --- a/goi18n/testdata/input/yaml/en-us.one.yaml +++ /dev/null @@ -1,19 +0,0 @@ -- 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 \ No newline at end of file diff --git a/goi18n/testdata/input/yaml/en-us.two.json b/goi18n/testdata/input/yaml/en-us.two.json deleted file mode 100644 index dcc715c4..00000000 --- a/goi18n/testdata/input/yaml/en-us.two.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "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" - } - } -] diff --git a/goi18n/testdata/input/yaml/fr-fr.json b/goi18n/testdata/input/yaml/fr-fr.json deleted file mode 100644 index e69de29b..00000000 diff --git a/v2/i18n/bundle.go b/i18n/bundle.go similarity index 100% rename from v2/i18n/bundle.go rename to i18n/bundle.go diff --git a/i18n/bundle/bundle.go b/i18n/bundle/bundle.go deleted file mode 100644 index 6376146a..00000000 --- a/i18n/bundle/bundle.go +++ /dev/null @@ -1,443 +0,0 @@ -// 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) - } - 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 -} diff --git a/i18n/bundle/bundle_test.go b/i18n/bundle/bundle_test.go deleted file mode 100644 index da3830a7..00000000 --- a/i18n/bundle/bundle_test.go +++ /dev/null @@ -1,364 +0,0 @@ -package 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) - } -} diff --git a/v2/i18n/bundle_test.go b/i18n/bundle_test.go similarity index 100% rename from v2/i18n/bundle_test.go rename to i18n/bundle_test.go diff --git a/v2/i18n/doc.go b/i18n/doc.go similarity index 100% rename from v2/i18n/doc.go rename to i18n/doc.go diff --git a/i18n/example_test.go b/i18n/example_test.go index 305c5b3d..2256e636 100644 --- a/i18n/example_test.go +++ b/i18n/example_test.go @@ -3,95 +3,139 @@ package i18n_test import ( "fmt" - "github.com/nicksnyder/go-i18n/i18n" + "github.com/BurntSushi/toml" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" ) -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)) +func ExampleLocalizer_MustLocalize() { + bundle := i18n.NewBundle(language.English) + localizer := i18n.NewLocalizer(bundle, "en") + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "HelloWorld", + Other: "Hello World!", + }, + })) + // Output: + // Hello World! +} - 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})) +func ExampleLocalizer_MustLocalize_noDefaultMessage() { + bundle := i18n.NewBundle(language.English) + bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + bundle.MustParseMessageFileBytes([]byte(` +HelloWorld = "Hello World!" +`), "en.toml") + bundle.MustParseMessageFileBytes([]byte(` +HelloWorld = "Hola Mundo!" +`), "es.toml") - 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"})) + { + localizer := i18n.NewLocalizer(bundle, "en-US") + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "HelloWorld"})) + } + { + localizer := i18n.NewLocalizer(bundle, "es-ES") + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "HelloWorld"})) + } + // Output: + // Hello World! + // Hola Mundo! +} - fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ - "Person": "Bob", - "Timeframe": T("d_days", 0), +func ExampleLocalizer_MustLocalize_plural() { + bundle := i18n.NewBundle(language.English) + localizer := i18n.NewLocalizer(bundle, "en") + catsMessage := &i18n.Message{ + ID: "Cats", + One: "I have {{.PluralCount}} cat.", + Other: "I have {{.PluralCount}} cats.", + } + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: catsMessage, + PluralCount: 1, })) - fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ - "Person": "Bob", - "Timeframe": T("d_days", 1), + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: catsMessage, + PluralCount: 2, })) - fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ - "Person": "Bob", - "Timeframe": T("d_days", 2), + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: catsMessage, + PluralCount: "2.5", })) + // Output: + // I have 1 cat. + // I have 2 cats. + // I have 2.5 cats. +} - fmt.Println(T("person_unread_email_count_timeframe", 1, map[string]interface{}{ - "Count": 30, - "Person": "Bob", - "Timeframe": T("d_days", 0), +func ExampleLocalizer_MustLocalize_template() { + bundle := i18n.NewBundle(language.English) + localizer := i18n.NewLocalizer(bundle, "en") + helloPersonMessage := &i18n.Message{ + ID: "HelloPerson", + Other: "Hello {{.Name}}!", + } + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: helloPersonMessage, + TemplateData: map[string]string{"Name": "Nick"}, })) - fmt.Println(T("person_unread_email_count_timeframe", 2, map[string]interface{}{ - "Count": 20, - "Person": "Bob", - "Timeframe": T("d_days", 1), + // Output: + // Hello Nick! +} + +func ExampleLocalizer_MustLocalize_plural_template() { + bundle := i18n.NewBundle(language.English) + localizer := i18n.NewLocalizer(bundle, "en") + personCatsMessage := &i18n.Message{ + ID: "PersonCats", + One: "{{.Name}} has {{.Count}} cat.", + Other: "{{.Name}} has {{.Count}} cats.", + } + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: personCatsMessage, + PluralCount: 1, + TemplateData: map[string]interface{}{ + "Name": "Nick", + "Count": 1, + }, })) - fmt.Println(T("person_unread_email_count_timeframe", 3, map[string]interface{}{ - "Count": 10, - "Person": "Bob", - "Timeframe": T("d_days", 2), + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: personCatsMessage, + PluralCount: 2, + TemplateData: map[string]interface{}{ + "Name": "Nick", + "Count": 2, + }, })) + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: personCatsMessage, + PluralCount: "2.5", + TemplateData: map[string]interface{}{ + "Name": "Nick", + "Count": "2.5", + }, + })) + // Output: + // Nick has 1 cat. + // Nick has 2 cats. + // Nick has 2.5 cats. +} +func ExampleLocalizer_MustLocalize_customTemplateDelims() { + bundle := i18n.NewBundle(language.English) + localizer := i18n.NewLocalizer(bundle, "en") + helloPersonMessage := &i18n.Message{ + ID: "HelloPerson", + Other: "Hello <<.Name>>!", + LeftDelim: "<<", + RightDelim: ">>", + } + fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: helloPersonMessage, + TemplateData: map[string]string{"Name": "Nick"}, + })) // Output: - // Hello 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. + // Hello Nick! } diff --git a/i18n/exampletemplate_test.go b/i18n/exampletemplate_test.go deleted file mode 100644 index 3648bd11..00000000 --- a/i18n/exampletemplate_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package i18n_test - -import ( - "os" - "text/template" - - "github.com/nicksnyder/go-i18n/i18n" -) - -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. -} diff --git a/i18n/exampleyaml_test.go b/i18n/exampleyaml_test.go deleted file mode 100644 index a38dcfc8..00000000 --- a/i18n/exampleyaml_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package i18n_test - -import ( - "fmt" - - "github.com/nicksnyder/go-i18n/i18n" -) - -func Example_yaml() { - 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. -} diff --git a/i18n/i18n.go b/i18n/i18n.go deleted file mode 100644 index c478ff6e..00000000 --- a/i18n/i18n.go +++ /dev/null @@ -1,158 +0,0 @@ -// 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 -} diff --git a/i18n/language/codegen/generate.sh b/i18n/language/codegen/generate.sh deleted file mode 100644 index a9fae846..00000000 --- a/i18n/language/codegen/generate.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/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 diff --git a/i18n/language/codegen/main.go b/i18n/language/codegen/main.go deleted file mode 100644 index 58971033..00000000 --- a/i18n/language/codegen/main.go +++ /dev/null @@ -1,132 +0,0 @@ -package 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) -} diff --git a/i18n/language/codegen/plurals.xml b/i18n/language/codegen/plurals.xml deleted file mode 100644 index 3310c8ee..00000000 --- a/i18n/language/codegen/plurals.xml +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - - - @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, … - - - diff --git a/i18n/language/codegen/xml.go b/i18n/language/codegen/xml.go deleted file mode 100644 index 9d39053c..00000000 --- a/i18n/language/codegen/xml.go +++ /dev/null @@ -1,143 +0,0 @@ -package 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") -} diff --git a/i18n/language/language.go b/i18n/language/language.go deleted file mode 100644 index b045a275..00000000 --- a/i18n/language/language.go +++ /dev/null @@ -1,99 +0,0 @@ -// 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) -} diff --git a/i18n/language/language_test.go b/i18n/language/language_test.go deleted file mode 100644 index 1ab3314d..00000000 --- a/i18n/language/language_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package 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) - } - } -} diff --git a/i18n/language/operands.go b/i18n/language/operands.go deleted file mode 100644 index c0bee68d..00000000 --- a/i18n/language/operands.go +++ /dev/null @@ -1,119 +0,0 @@ -package language - -import ( - "fmt" - "strconv" - "strings" -) - -// Operands is a representation of http://unicode.org/reports/tr35/tr35-numbers.html#Operands -type Operands struct { - N float64 // absolute value of the source number (integer and decimals) - I int64 // integer digits of n - V int64 // number of visible fraction digits in n, with trailing zeros - W int64 // number of visible fraction digits in n, without trailing zeros - F int64 // visible fractional digits in n, with trailing zeros - T int64 // visible fractional digits in n, without trailing zeros -} - -// NequalsAny returns true if o represents an integer equal to any of the arguments. -func (o *Operands) NequalsAny(any ...int64) bool { - for _, i := range any { - if o.I == i && o.T == 0 { - return true - } - } - return false -} - -// NmodEqualsAny returns true if o represents an integer equal to any of the arguments modulo mod. -func (o *Operands) NmodEqualsAny(mod int64, any ...int64) bool { - modI := o.I % mod - for _, i := range any { - if modI == i && o.T == 0 { - return true - } - } - return false -} - -// NinRange returns true if o represents an integer in the closed interval [from, to]. -func (o *Operands) NinRange(from, to int64) bool { - return o.T == 0 && from <= o.I && o.I <= to -} - -// NmodInRange returns true if o represents an integer in the closed interval [from, to] modulo mod. -func (o *Operands) NmodInRange(mod, from, to int64) bool { - modI := o.I % mod - return o.T == 0 && from <= modI && modI <= to -} - -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 -} diff --git a/i18n/language/operands_test.go b/i18n/language/operands_test.go deleted file mode 100644 index e4f33902..00000000 --- a/i18n/language/operands_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package 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) - } - } -} diff --git a/i18n/language/plural.go b/i18n/language/plural.go deleted file mode 100644 index 1f3ea5c6..00000000 --- a/i18n/language/plural.go +++ /dev/null @@ -1,40 +0,0 @@ -package 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) -} diff --git a/i18n/language/plural_test.go b/i18n/language/plural_test.go deleted file mode 100644 index 6336d29b..00000000 --- a/i18n/language/plural_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package 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) - } - } -} diff --git a/i18n/language/pluralspec.go b/i18n/language/pluralspec.go deleted file mode 100644 index fc31e880..00000000 --- a/i18n/language/pluralspec.go +++ /dev/null @@ -1,75 +0,0 @@ -package 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 -} diff --git a/i18n/language/pluralspec_gen.go b/i18n/language/pluralspec_gen.go deleted file mode 100644 index 0268bb92..00000000 --- a/i18n/language/pluralspec_gen.go +++ /dev/null @@ -1,557 +0,0 @@ -package 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 - }, - }) -} diff --git a/i18n/language/pluralspec_gen_test.go b/i18n/language/pluralspec_gen_test.go deleted file mode 100644 index 4cfa97bd..00000000 --- a/i18n/language/pluralspec_gen_test.go +++ /dev/null @@ -1,631 +0,0 @@ -package 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) - } -} diff --git a/i18n/language/pluralspec_test.go b/i18n/language/pluralspec_test.go deleted file mode 100644 index e9ff4074..00000000 --- a/i18n/language/pluralspec_test.go +++ /dev/null @@ -1,716 +0,0 @@ -package 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) = %v expected %v", 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) - } - -} diff --git a/v2/i18n/language_test.go b/i18n/language_test.go similarity index 100% rename from v2/i18n/language_test.go rename to i18n/language_test.go diff --git a/v2/i18n/localizer.go b/i18n/localizer.go similarity index 100% rename from v2/i18n/localizer.go rename to i18n/localizer.go diff --git a/v2/i18n/localizer_test.go b/i18n/localizer_test.go similarity index 100% rename from v2/i18n/localizer_test.go rename to i18n/localizer_test.go diff --git a/v2/i18n/message.go b/i18n/message.go similarity index 100% rename from v2/i18n/message.go rename to i18n/message.go diff --git a/v2/i18n/message_template.go b/i18n/message_template.go similarity index 100% rename from v2/i18n/message_template.go rename to i18n/message_template.go diff --git a/v2/i18n/message_template_test.go b/i18n/message_template_test.go similarity index 100% rename from v2/i18n/message_template_test.go rename to i18n/message_template_test.go diff --git a/v2/i18n/message_test.go b/i18n/message_test.go similarity index 100% rename from v2/i18n/message_test.go rename to i18n/message_test.go diff --git a/v2/i18n/parse.go b/i18n/parse.go similarity index 100% rename from v2/i18n/parse.go rename to i18n/parse.go diff --git a/v2/i18n/parse_test.go b/i18n/parse_test.go similarity index 100% rename from v2/i18n/parse_test.go rename to i18n/parse_test.go diff --git a/i18n/translation/plural_translation.go b/i18n/translation/plural_translation.go deleted file mode 100644 index 17c32609..00000000 --- a/i18n/translation/plural_translation.go +++ /dev/null @@ -1,82 +0,0 @@ -package 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{}) diff --git a/i18n/translation/plural_translation_test.go b/i18n/translation/plural_translation_test.go deleted file mode 100644 index ea7de7fd..00000000 --- a/i18n/translation/plural_translation_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package 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 -} -*/ diff --git a/i18n/translation/single_translation.go b/i18n/translation/single_translation.go deleted file mode 100644 index a76c8c94..00000000 --- a/i18n/translation/single_translation.go +++ /dev/null @@ -1,61 +0,0 @@ -package 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{}) diff --git a/i18n/translation/template.go b/i18n/translation/template.go deleted file mode 100644 index 3310150c..00000000 --- a/i18n/translation/template.go +++ /dev/null @@ -1,65 +0,0 @@ -package 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{}) diff --git a/i18n/translation/template_test.go b/i18n/translation/template_test.go deleted file mode 100644 index a28c3ed8..00000000 --- a/i18n/translation/template_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package translation - -import ( - "bytes" - "fmt" - - "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{} - if err := tmpl.UnmarshalText([]byte("hello {{.World}}")); err != nil { - t.Fatal(err) - } - 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", - }) - } -} - -var sprintfResult string - -func BenchmarkSprintf(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { - sprintfResult = fmt.Sprintf("hello %s", "nick") - } -} diff --git a/i18n/translation/translation.go b/i18n/translation/translation.go deleted file mode 100644 index 93f87f64..00000000 --- a/i18n/translation/translation.go +++ /dev/null @@ -1,84 +0,0 @@ -// 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 -} diff --git a/i18n/translation/translation_test.go b/i18n/translation/translation_test.go deleted file mode 100644 index 4740e027..00000000 --- a/i18n/translation/translation_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package translation - -import ( - "sort" - "testing" -) - -// Check this here to avoid unnecessary import of sort package. -var _ = sort.Interface(make(SortableByID, 0)) - -func TestNewSingleTranslation(t *testing.T) { - t.Skipf("not implemented") -} - -func TestNewPluralTranslation(t *testing.T) { - t.Skipf("not implemented") -} diff --git a/i18n/translations_test.go b/i18n/translations_test.go deleted file mode 100644 index ba1a21b5..00000000 --- a/i18n/translations_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package 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.Errorf("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") -} diff --git a/v2/internal/plural/codegen/generate.sh b/internal/plural/codegen/generate.sh similarity index 100% rename from v2/internal/plural/codegen/generate.sh rename to internal/plural/codegen/generate.sh diff --git a/v2/internal/plural/codegen/main.go b/internal/plural/codegen/main.go similarity index 100% rename from v2/internal/plural/codegen/main.go rename to internal/plural/codegen/main.go diff --git a/v2/internal/plural/codegen/plurals.xml b/internal/plural/codegen/plurals.xml similarity index 100% rename from v2/internal/plural/codegen/plurals.xml rename to internal/plural/codegen/plurals.xml diff --git a/v2/internal/plural/codegen/xml.go b/internal/plural/codegen/xml.go similarity index 100% rename from v2/internal/plural/codegen/xml.go rename to internal/plural/codegen/xml.go diff --git a/v2/internal/plural/doc.go b/internal/plural/doc.go similarity index 100% rename from v2/internal/plural/doc.go rename to internal/plural/doc.go diff --git a/v2/internal/plural/form.go b/internal/plural/form.go similarity index 100% rename from v2/internal/plural/form.go rename to internal/plural/form.go diff --git a/v2/internal/plural/operands.go b/internal/plural/operands.go similarity index 100% rename from v2/internal/plural/operands.go rename to internal/plural/operands.go diff --git a/v2/internal/plural/operands_test.go b/internal/plural/operands_test.go similarity index 100% rename from v2/internal/plural/operands_test.go rename to internal/plural/operands_test.go diff --git a/v2/internal/plural/rule.go b/internal/plural/rule.go similarity index 100% rename from v2/internal/plural/rule.go rename to internal/plural/rule.go diff --git a/v2/internal/plural/rule_gen.go b/internal/plural/rule_gen.go similarity index 100% rename from v2/internal/plural/rule_gen.go rename to internal/plural/rule_gen.go diff --git a/v2/internal/plural/rule_gen_test.go b/internal/plural/rule_gen_test.go similarity index 100% rename from v2/internal/plural/rule_gen_test.go rename to internal/plural/rule_gen_test.go diff --git a/v2/internal/plural/rule_test.go b/internal/plural/rule_test.go similarity index 100% rename from v2/internal/plural/rule_test.go rename to internal/plural/rule_test.go diff --git a/v2/internal/plural/rules.go b/internal/plural/rules.go similarity index 100% rename from v2/internal/plural/rules.go rename to internal/plural/rules.go diff --git a/v2/internal/plural/rules_test.go b/internal/plural/rules_test.go similarity index 100% rename from v2/internal/plural/rules_test.go rename to internal/plural/rules_test.go diff --git a/v2/internal/template.go b/internal/template.go similarity index 100% rename from v2/internal/template.go rename to internal/template.go diff --git a/v2/internal/template_test.go b/internal/template_test.go similarity index 100% rename from v2/internal/template_test.go rename to internal/template_test.go diff --git a/v2/go.mod b/v2/go.mod deleted file mode 100644 index 7aba6f87..00000000 --- a/v2/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/nicksnyder/go-i18n/v2 - -require ( - github.com/BurntSushi/toml v0.3.0 - golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect - golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect - golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b // indirect - golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c // indirect - gopkg.in/yaml.v2 v2.2.1 -) diff --git a/v2/goi18n/merge_command.go b/v2/goi18n/merge_command.go deleted file mode 100644 index 6e6041f6..00000000 --- a/v2/goi18n/merge_command.go +++ /dev/null @@ -1,292 +0,0 @@ -package main - -import ( - "crypto/sha1" - "encoding/json" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/BurntSushi/toml" - "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/nicksnyder/go-i18n/v2/internal" - "github.com/nicksnyder/go-i18n/v2/internal/plural" - "golang.org/x/text/language" - yaml "gopkg.in/yaml.v2" -) - -func usageMerge() { - fmt.Fprintf(os.Stderr, `usage: goi18n merge [options] [message files] - -Merge reads all messages in the message files and produces two files per language. - - xx-yy.active.format - This file contains messages that should be loaded at runtime. - - xx-yy.translate.format - This file contains messages that are empty and should be translated. - -Message file names must have a suffix of a supported format (e.g. ".json") and -contain a valid language tag as defined by RFC 5646 (e.g. "en-us", "fr", "zh-hant", etc.). - -To add support for a new language, create an empty translation file with the -appropriate name and pass it in to goi18n merge. - -Flags: - - -sourceLanguage tag - Translate messages from this language (e.g. en, en-US, zh-Hant-CN) - Default: en - - -outdir directory - Write message files to this directory. - Default: . - - -format format - Output message files in this format. - Supported formats: json, toml, yaml - Default: toml -`) -} - -type mergeCommand struct { - messageFiles []string - sourceLanguage languageTag - outdir string - format string -} - -func (mc *mergeCommand) name() string { - return "merge" -} - -func (mc *mergeCommand) parse(args []string) { - flags := flag.NewFlagSet("merge", flag.ExitOnError) - flags.Usage = usageMerge - - flags.Var(&mc.sourceLanguage, "sourceLanguage", "en") - flags.StringVar(&mc.outdir, "outdir", ".", "") - flags.StringVar(&mc.format, "format", "toml", "") - flags.Parse(args) - - mc.messageFiles = flags.Args() -} - -func (mc *mergeCommand) execute() error { - if len(mc.messageFiles) < 1 { - return fmt.Errorf("need at least one message file to parse") - } - inFiles := make(map[string][]byte) - for _, path := range mc.messageFiles { - content, err := ioutil.ReadFile(path) - if err != nil { - return err - } - inFiles[path] = content - } - ops, err := merge(inFiles, mc.sourceLanguage.Tag(), mc.outdir, mc.format) - if err != nil { - return err - } - for path, content := range ops.writeFiles { - if err := ioutil.WriteFile(path, content, 0666); err != nil { - return err - } - } - for _, path := range ops.deleteFiles { - // Ignore error since it isn't guaranteed to exist. - os.Remove(path) - } - return nil -} - -type fileSystemOp struct { - writeFiles map[string][]byte - deleteFiles []string -} - -func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdir, outputFormat string) (*fileSystemOp, error) { - unmerged := make(map[language.Tag][]map[string]*i18n.MessageTemplate) - sourceMessageTemplates := make(map[string]*i18n.MessageTemplate) - unmarshalFuncs := map[string]i18n.UnmarshalFunc{ - "json": json.Unmarshal, - "toml": toml.Unmarshal, - "yaml": yaml.Unmarshal, - } - for path, content := range messageFiles { - mf, err := i18n.ParseMessageFileBytes(content, path, unmarshalFuncs) - if err != nil { - return nil, fmt.Errorf("failed to load message file %s: %s", path, err) - } - templates := map[string]*i18n.MessageTemplate{} - for _, m := range mf.Messages { - templates[m.ID] = i18n.NewMessageTemplate(m) - } - if mf.Tag == sourceLanguageTag { - for _, template := range templates { - if sourceMessageTemplates[template.ID] != nil { - return nil, fmt.Errorf("multiple source translations for id %s", template.ID) - } - template.Hash = hash(template) - sourceMessageTemplates[template.ID] = template - } - } - unmerged[mf.Tag] = append(unmerged[mf.Tag], templates) - } - - if len(sourceMessageTemplates) == 0 { - return nil, fmt.Errorf("no messages found for source locale %s", sourceLanguageTag) - } - - pluralRules := plural.DefaultRules() - all := make(map[language.Tag]map[string]*i18n.MessageTemplate) - all[sourceLanguageTag] = sourceMessageTemplates - for _, srcTemplate := range sourceMessageTemplates { - for dstLangTag, messageTemplates := range unmerged { - if dstLangTag == sourceLanguageTag { - continue - } - pluralRule := pluralRules.Rule(dstLangTag) - if pluralRule == nil { - // Non-standard languages not supported because - // we don't know if translations are complete or not. - continue - } - if all[dstLangTag] == nil { - all[dstLangTag] = make(map[string]*i18n.MessageTemplate) - } - dstMessageTemplate := all[dstLangTag][srcTemplate.ID] - if dstMessageTemplate == nil { - dstMessageTemplate = &i18n.MessageTemplate{ - Message: &i18n.Message{ - ID: srcTemplate.ID, - Description: srcTemplate.Description, - Hash: srcTemplate.Hash, - }, - PluralTemplates: make(map[plural.Form]*internal.Template), - } - all[dstLangTag][srcTemplate.ID] = dstMessageTemplate - } - - // Check all unmerged message templates for this message id. - for _, messageTemplates := range messageTemplates { - unmergedTemplate := messageTemplates[srcTemplate.ID] - if unmergedTemplate == nil { - continue - } - // Ignore empty hashes for v1 backward compatibility. - if unmergedTemplate.Hash != "" && unmergedTemplate.Hash != srcTemplate.Hash { - // This was translated from different content so discard. - continue - } - - // Merge in the translated messages. - for pluralForm := range pluralRule.PluralForms { - dt := unmergedTemplate.PluralTemplates[pluralForm] - if dt != nil && dt.Src != "" { - dstMessageTemplate.PluralTemplates[pluralForm] = dt - } - } - } - } - } - - translate := make(map[language.Tag]map[string]*i18n.MessageTemplate) - active := make(map[language.Tag]map[string]*i18n.MessageTemplate) - for langTag, messageTemplates := range all { - active[langTag] = make(map[string]*i18n.MessageTemplate) - if langTag == sourceLanguageTag { - active[langTag] = messageTemplates - continue - } - pluralRule := pluralRules.Rule(langTag) - if pluralRule == nil { - // Non-standard languages not supported because - // we don't know if translations are complete or not. - continue - } - for _, messageTemplate := range messageTemplates { - srcMessageTemplate := sourceMessageTemplates[messageTemplate.ID] - activeMessageTemplate, translateMessageTemplate := activeDst(srcMessageTemplate, messageTemplate, pluralRule) - if translateMessageTemplate != nil { - if translate[langTag] == nil { - translate[langTag] = make(map[string]*i18n.MessageTemplate) - } - translate[langTag][messageTemplate.ID] = translateMessageTemplate - } - if activeMessageTemplate != nil { - active[langTag][messageTemplate.ID] = activeMessageTemplate - } - } - } - - writeFiles := make(map[string][]byte, len(translate)+len(active)) - for langTag, messageTemplates := range translate { - path, content, err := writeFile(outdir, "translate", langTag, outputFormat, messageTemplates, false) - if err != nil { - return nil, err - } - writeFiles[path] = content - } - deleteFiles := []string{} - for langTag, messageTemplates := range active { - path, content, err := writeFile(outdir, "active", langTag, outputFormat, messageTemplates, langTag == sourceLanguageTag) - if err != nil { - return nil, err - } - if len(content) > 0 { - writeFiles[path] = content - } else { - deleteFiles = append(deleteFiles, path) - } - } - return &fileSystemOp{writeFiles: writeFiles, deleteFiles: deleteFiles}, nil -} - -// activeDst returns the active part of the dst and whether dst is a complete translation of src. -func activeDst(src, dst *i18n.MessageTemplate, pluralRule *plural.Rule) (active *i18n.MessageTemplate, translateMessageTemplate *i18n.MessageTemplate) { - pluralForms := pluralRule.PluralForms - if len(src.PluralTemplates) == 1 { - pluralForms = map[plural.Form]struct{}{ - plural.Other: {}, - } - } - for pluralForm := range pluralForms { - dt := dst.PluralTemplates[pluralForm] - if dt == nil || dt.Src == "" { - if translateMessageTemplate == nil { - translateMessageTemplate = &i18n.MessageTemplate{ - Message: &i18n.Message{ - ID: src.ID, - Description: src.Description, - Hash: src.Hash, - }, - PluralTemplates: make(map[plural.Form]*internal.Template), - } - } - translateMessageTemplate.PluralTemplates[pluralForm] = src.PluralTemplates[plural.Other] - continue - } - if active == nil { - active = &i18n.MessageTemplate{ - Message: &i18n.Message{ - ID: src.ID, - Description: src.Description, - Hash: src.Hash, - }, - PluralTemplates: make(map[plural.Form]*internal.Template), - } - } - active.PluralTemplates[pluralForm] = dt - } - return -} - -func hash(t *i18n.MessageTemplate) string { - h := sha1.New() - io.WriteString(h, t.Description) - io.WriteString(h, t.PluralTemplates[plural.Other].Src) - return fmt.Sprintf("sha1-%x", h.Sum(nil)) -} diff --git a/v2/goi18n/merge_command_test.go b/v2/goi18n/merge_command_test.go deleted file mode 100644 index ff7c2219..00000000 --- a/v2/goi18n/merge_command_test.go +++ /dev/null @@ -1,563 +0,0 @@ -package main - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "golang.org/x/text/language" -) - -type testCase struct { - name string - inFiles map[string][]byte - sourceLanguage language.Tag - outFiles map[string][]byte - deleteFiles []string -} - -func expectFile(s string) []byte { - // Trimming leading newlines gives nicer formatting for file literals in test cases. - return bytes.TrimLeft([]byte(s), "\n") -} - -func TestMerge(t *testing.T) { - testCases := []*testCase{ - { - name: "single identity", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "one.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), - }, - }, - { - name: "plural identity", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "active.en-US.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread email" -other = "{{.Count}} unread emails" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread email" -other = "{{.Count}} unread emails" -`), - }, - }, - { - name: "migrate source lang from v1 format", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "one.en-US.json": []byte(`[ - { - "id": "simple", - "translation": "simple translation" - }, - { - "id": "everything", - "translation": { - "zero": "zero translation", - "one": "one translation", - "two": "two translation", - "few": "few translation", - "many": "many translation", - "other": "other translation" - } - } -]`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -simple = "simple translation" - -[everything] -few = "few translation" -many = "many translation" -one = "one translation" -other = "other translation" -two = "two translation" -zero = "zero translation" -`), - }, - }, - { - name: "migrate source lang from v1 flat format", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "one.en-US.json": []byte(`{ - "simple": { - "other": "simple translation" - }, - "everything": { - "zero": "zero translation", - "one": "one translation", - "two": "two translation", - "few": "few translation", - "many": "many translation", - "other": "other translation" - } -}`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -simple = "simple translation" - -[everything] -few = "few translation" -many = "many translation" -one = "one translation" -other = "other translation" -two = "two translation" -zero = "zero translation" -`), - }, - }, - { - name: "merge source files", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "one.en-US.toml": []byte("1HelloMessage = \"Hello\"\n"), - "two.en-US.toml": []byte("2GoodbyeMessage = \"Goodbye\"\n"), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": []byte("1HelloMessage = \"Hello\"\n2GoodbyeMessage = \"Goodbye\"\n"), - }, - }, - { - name: "missing hash", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -1HelloMessage = "Hello" -`), - "es-ES.toml": []byte(` -[1HelloMessage] -other = "Hola" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -1HelloMessage = "Hello" -`), - "active.es-ES.toml": expectFile(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" -`), - }, - }, - { - name: "add single translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -1HelloMessage = "Hello" -2GoodbyeMessage = "Goodbye" -`), - "es-ES.toml": []byte(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -1HelloMessage = "Hello" -2GoodbyeMessage = "Goodbye" -`), - "active.es-ES.toml": expectFile(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" -`), - "translate.es-ES.toml": expectFile(` -[2GoodbyeMessage] -hash = "sha1-b5b29c53e3c71cb9c6581ab053d7758fab8ca24d" -other = "Goodbye" -`), - }, - }, - { - name: "remove single translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -1HelloMessage = "Hello" -`), - "es-ES.toml": []byte(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" - -[2GoodbyeMessage] -hash = "sha1-b5b29c53e3c71cb9c6581ab053d7758fab8ca24d" -other = "Goodbye" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -1HelloMessage = "Hello" -`), - "active.es-ES.toml": expectFile(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" -`), - }, - }, - { - name: "edit single translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -1HelloMessage = "Hi" -`), - "es-ES.toml": []byte(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -1HelloMessage = "Hi" -`), - "translate.es-ES.toml": expectFile(` -[1HelloMessage] -hash = "sha1-94dd9e08c129c785f7f256e82fbe0a30e6d1ae40" -other = "Hi" -`), - }, - }, - { - name: "add plural translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread email" -other = "{{.Count}} unread emails" -`), - "es-ES.toml": nil, - "ar-AR.toml": nil, - "zh-CN.toml": nil, - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread email" -other = "{{.Count}} unread emails" -`), - "translate.es-ES.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -`), - "translate.ar-AR.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -few = "{{.Count}} unread emails" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -many = "{{.Count}} unread emails" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -two = "{{.Count}} unread emails" -zero = "{{.Count}} unread emails" -`), - "translate.zh-CN.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -other = "{{.Count}} unread emails" -`), - }, - }, - { - name: "remove plural translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -1HelloMessage = "Hello" -`), - "es-ES.toml": []byte(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" - -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -`), - "ar-AR.toml": []byte(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hello" - -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -few = "{{.Count}} unread emails" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -many = "{{.Count}} unread emails" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -two = "{{.Count}} unread emails" -zero = "{{.Count}} unread emails" -`), - "zh-CN.toml": []byte(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hello" - -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -other = "{{.Count}} unread emails" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -1HelloMessage = "Hello" -`), - "active.es-ES.toml": expectFile(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hola" -`), - "active.ar-AR.toml": expectFile(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hello" -`), - "active.zh-CN.toml": expectFile(` -[1HelloMessage] -hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0" -other = "Hello" -`), - }, - }, - { - name: "edit plural translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread emails!" -other = "{{.Count}} unread emails!" -`), - "es-ES.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -`), - "ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -few = "{{.Count}} unread emails" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -many = "{{.Count}} unread emails" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -two = "{{.Count}} unread emails" -zero = "{{.Count}} unread emails" -`), - "zh-CN.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -other = "{{.Count}} unread emails" -`), - }, - deleteFiles: []string{ - "active.es-ES.toml", - "active.ar-AR.toml", - "active.zh-CN.toml", - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread emails!" -other = "{{.Count}} unread emails!" -`), - "translate.es-ES.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" -one = "{{.Count}} unread emails!" -other = "{{.Count}} unread emails!" -`), - "translate.ar-AR.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -few = "{{.Count}} unread emails!" -hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" -many = "{{.Count}} unread emails!" -one = "{{.Count}} unread emails!" -other = "{{.Count}} unread emails!" -two = "{{.Count}} unread emails!" -zero = "{{.Count}} unread emails!" -`), - "translate.zh-CN.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-92a24983c5bbc0c42462cdc252dca68ebdb46501" -other = "{{.Count}} unread emails!" -`), - }, - }, - { - name: "merge plural translation", - sourceLanguage: language.AmericanEnglish, - inFiles: map[string][]byte{ - "en-US.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -`), - "zero.ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -zero = "{{.Count}} unread emails" -`), - "one.ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -one = "{{.Count}} unread emails" -`), - "two.ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -two = "{{.Count}} unread emails" -`), - "few.ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -few = "{{.Count}} unread emails" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -`), - "many.ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -many = "{{.Count}} unread emails" -`), - "other.ar-AR.toml": []byte(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -other = "{{.Count}} unread emails" -`), - }, - outFiles: map[string][]byte{ - "active.en-US.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -`), - "active.ar-AR.toml": expectFile(` -[UnreadEmails] -description = "Message that tells the user how many unread emails they have" -few = "{{.Count}} unread emails" -hash = "sha1-5afbc91dfedb9755627655c365eb47a89e541099" -many = "{{.Count}} unread emails" -one = "{{.Count}} unread emails" -other = "{{.Count}} unread emails" -two = "{{.Count}} unread emails" -zero = "{{.Count}} unread emails" -`), - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - indir := mustTempDir("TestMergeCommandIn") - defer os.RemoveAll(indir) - outdir := mustTempDir("TestMergeCommandOut") - defer os.RemoveAll(outdir) - - infiles := make([]string, 0, len(testCase.inFiles)) - for name, content := range testCase.inFiles { - path := filepath.Join(indir, name) - infiles = append(infiles, path) - if err := ioutil.WriteFile(path, content, 0666); err != nil { - t.Fatal(err) - } - } - - for _, name := range testCase.deleteFiles { - path := filepath.Join(outdir, name) - if err := ioutil.WriteFile(path, []byte(`this file should get deleted`), 0666); err != nil { - t.Fatal(err) - } - } - - args := append([]string{"merge", "-sourceLanguage", testCase.sourceLanguage.String(), "-outdir", outdir}, infiles...) - if code := testableMain(args); code != 0 { - t.Fatalf("expected exit code 0; got %d\n", code) - } - - files, err := ioutil.ReadDir(outdir) - if err != nil { - t.Fatal(err) - } - - // Verify that all actual files have expected contents. - actualFiles := make(map[string]struct{}, len(files)) - for _, f := range files { - actualFiles[f.Name()] = struct{}{} - if f.IsDir() { - t.Errorf("found unexpected dir %s", f.Name()) - continue - } - path := filepath.Join(outdir, f.Name()) - actual, err := ioutil.ReadFile(path) - if err != nil { - t.Error(err) - continue - } - expected, ok := testCase.outFiles[f.Name()] - if !ok { - t.Errorf("found unexpected file %s with contents:\n%s\n", f.Name(), actual) - continue - } - if !bytes.Equal(actual, expected) { - t.Errorf("unexpected contents %s\ngot\n%s\nexpected\n%s", f.Name(), actual, expected) - continue - } - } - - // Verify that all expected files are accounted for. - for name := range testCase.outFiles { - if _, ok := actualFiles[name]; !ok { - t.Errorf("did not find expected file %s", name) - } - } - }) - } -} diff --git a/v2/i18n/example_test.go b/v2/i18n/example_test.go deleted file mode 100644 index 2256e636..00000000 --- a/v2/i18n/example_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package i18n_test - -import ( - "fmt" - - "github.com/BurntSushi/toml" - "github.com/nicksnyder/go-i18n/v2/i18n" - "golang.org/x/text/language" -) - -func ExampleLocalizer_MustLocalize() { - bundle := i18n.NewBundle(language.English) - localizer := i18n.NewLocalizer(bundle, "en") - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: &i18n.Message{ - ID: "HelloWorld", - Other: "Hello World!", - }, - })) - // Output: - // Hello World! -} - -func ExampleLocalizer_MustLocalize_noDefaultMessage() { - bundle := i18n.NewBundle(language.English) - bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) - bundle.MustParseMessageFileBytes([]byte(` -HelloWorld = "Hello World!" -`), "en.toml") - bundle.MustParseMessageFileBytes([]byte(` -HelloWorld = "Hola Mundo!" -`), "es.toml") - - { - localizer := i18n.NewLocalizer(bundle, "en-US") - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "HelloWorld"})) - } - { - localizer := i18n.NewLocalizer(bundle, "es-ES") - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "HelloWorld"})) - } - // Output: - // Hello World! - // Hola Mundo! -} - -func ExampleLocalizer_MustLocalize_plural() { - bundle := i18n.NewBundle(language.English) - localizer := i18n.NewLocalizer(bundle, "en") - catsMessage := &i18n.Message{ - ID: "Cats", - One: "I have {{.PluralCount}} cat.", - Other: "I have {{.PluralCount}} cats.", - } - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: catsMessage, - PluralCount: 1, - })) - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: catsMessage, - PluralCount: 2, - })) - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: catsMessage, - PluralCount: "2.5", - })) - // Output: - // I have 1 cat. - // I have 2 cats. - // I have 2.5 cats. -} - -func ExampleLocalizer_MustLocalize_template() { - bundle := i18n.NewBundle(language.English) - localizer := i18n.NewLocalizer(bundle, "en") - helloPersonMessage := &i18n.Message{ - ID: "HelloPerson", - Other: "Hello {{.Name}}!", - } - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: helloPersonMessage, - TemplateData: map[string]string{"Name": "Nick"}, - })) - // Output: - // Hello Nick! -} - -func ExampleLocalizer_MustLocalize_plural_template() { - bundle := i18n.NewBundle(language.English) - localizer := i18n.NewLocalizer(bundle, "en") - personCatsMessage := &i18n.Message{ - ID: "PersonCats", - One: "{{.Name}} has {{.Count}} cat.", - Other: "{{.Name}} has {{.Count}} cats.", - } - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: personCatsMessage, - PluralCount: 1, - TemplateData: map[string]interface{}{ - "Name": "Nick", - "Count": 1, - }, - })) - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: personCatsMessage, - PluralCount: 2, - TemplateData: map[string]interface{}{ - "Name": "Nick", - "Count": 2, - }, - })) - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: personCatsMessage, - PluralCount: "2.5", - TemplateData: map[string]interface{}{ - "Name": "Nick", - "Count": "2.5", - }, - })) - // Output: - // Nick has 1 cat. - // Nick has 2 cats. - // Nick has 2.5 cats. -} - -func ExampleLocalizer_MustLocalize_customTemplateDelims() { - bundle := i18n.NewBundle(language.English) - localizer := i18n.NewLocalizer(bundle, "en") - helloPersonMessage := &i18n.Message{ - ID: "HelloPerson", - Other: "Hello <<.Name>>!", - LeftDelim: "<<", - RightDelim: ">>", - } - fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{ - DefaultMessage: helloPersonMessage, - TemplateData: map[string]string{"Name": "Nick"}, - })) - // Output: - // Hello Nick! -}