Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TOML support #66

Merged
merged 1 commit into from
Apr 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go-i18n is a Go [package](#i18n-package) and a [command](#goi18n-command) that h
* Supports [pluralized strings](http://cldr.unicode.org/index/cldr-spec/plural-rules) for all 200+ languages in the [Unicode Common Locale Data Repository (CLDR)](http://www.unicode.org/cldr/charts/28/supplemental/language_plural_rules.html).
* Code and tests are [automatically generated](https://github.com/nicksnyder/go-i18n/tree/master/i18n/language/codegen) from [CLDR data](http://cldr.unicode.org/index/downloads)
* Supports strings with named variables using [text/template](http://golang.org/pkg/text/template/) syntax.
* Translation files are simple JSON or YAML.
* Translation files are simple JSON, TOML or YAML.
* [Documented](http://godoc.org/github.com/nicksnyder/go-i18n) and [tested](https://travis-ci.org/nicksnyder/go-i18n)!

Package i18n [![GoDoc](http://godoc.org/github.com/nicksnyder/go-i18n?status.svg)](http://godoc.org/github.com/nicksnyder/go-i18n/i18n)
Expand Down Expand Up @@ -118,6 +118,10 @@ Here is an example of the default file format that go-i18n supports:

To use a different file format, write a parser for the format and add the parsed translations using [AddTranslation](https://godoc.org/github.com/nicksnyder/go-i18n/i18n#AddTranslation).

Note that TOML only supports the flat format, which is described below.

More examples of translation files: [JSON](https://github.com/nicksnyder/go-i18n/tree/master/goi18n/testdata/input), [TOML](https://github.com/nicksnyder/go-i18n/blob/master/goi18n/testdata/input/flat/ar-ar.one.toml), [YAML](https://github.com/nicksnyder/go-i18n/blob/master/goi18n/testdata/input/yaml/en-us.one.yaml).

Flat Format
-------------

Expand Down Expand Up @@ -166,6 +170,8 @@ and name of substructures (ids) should be always a string.
If there is only one key in substructure and it is "other", then it's non-plural
translation, else plural.

More examples of flat format translation files can be found in [goi18n/testdata/input/flat](https://github.com/nicksnyder/go-i18n/tree/master/goi18n/testdata/input/flat).

Contributions
-------------

Expand Down
31 changes: 26 additions & 5 deletions goi18n/merge_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/nicksnyder/go-i18n/i18n/bundle"
"github.com/nicksnyder/go-i18n/i18n/language"
"github.com/nicksnyder/go-i18n/i18n/translation"
toml "github.com/pelletier/go-toml"
)

type mergeCommand struct {
Expand All @@ -36,7 +37,7 @@ func (mc *mergeCommand) execute() error {
bundle := bundle.New()
for _, tf := range mc.translationFiles {
if err := bundle.LoadTranslationFile(tf); err != nil {
return fmt.Errorf("failed to load translation file %s because %s\n", tf, err)
return fmt.Errorf("failed to load translation file %s: %s\n", tf, err)
}
}

Expand Down Expand Up @@ -91,7 +92,11 @@ func (mc *mergeCommand) parse(arguments []string) {
mc.sourceLanguage = *sourceLanguage
mc.outdir = *outdir
mc.format = *format
mc.flat = *flat
if *format == "toml" {
mc.flat = true
} else {
mc.flat = *flat
}
}

func (mc *mergeCommand) SetArgs(args []string) {
Expand All @@ -110,13 +115,13 @@ func (mc *mergeCommand) writeFile(label string, translations []translation.Trans

buf, err := mc.marshal(convert(translations))
if err != nil {
return fmt.Errorf("failed to marshal %s strings to %s because %s", localeID, mc.format, err)
return fmt.Errorf("failed to marshal %s strings to %s: %s", localeID, mc.format, err)
}

filename := filepath.Join(mc.outdir, fmt.Sprintf("%s.%s.%s", localeID, label, mc.format))

if err := ioutil.WriteFile(filename, buf, 0666); err != nil {
return fmt.Errorf("failed to write %s because %s", filename, err)
return fmt.Errorf("failed to write %s: %s", filename, err)
}
return nil
}
Expand Down Expand Up @@ -152,12 +157,27 @@ func (mc mergeCommand) marshal(v interface{}) ([]byte, error) {
switch mc.format {
case "json":
return json.MarshalIndent(v, "", " ")
case "toml":
return marshalTOML(v)
case "yaml":
return yaml.Marshal(v)
}
return nil, fmt.Errorf("unsupported format: %s\n", mc.format)
}

func marshalTOML(v interface{}) ([]byte, error) {
m, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid format for marshaling to TOML")
}
tree, err := toml.TreeFromMap(m)
if err != nil {
return nil, err
}
s, err := tree.ToTomlString()
return []byte(s), err
}

func usageMerge() {
fmt.Printf(`Merge translation files.

Expand Down Expand Up @@ -207,11 +227,12 @@ Options:

-format format
goi18n encodes the output translation files in this format.
Supported formats: json, yaml
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

`)
Expand Down
6 changes: 3 additions & 3 deletions goi18n/merge_command_flat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package main

import "testing"

func TestMergeExecuteFlatJSON(t *testing.T) {
func TestMergeExecuteFlat(t *testing.T) {
files := []string{
"testdata/input/flat/en-us.one.json",
"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.json",
"testdata/input/flat/ar-ar.one.toml",
"testdata/input/flat/ar-ar.two.json",
}
testFlatMergeExecute(t, files)
Expand Down
25 changes: 25 additions & 0 deletions goi18n/testdata/en-us.flat.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[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"
45 changes: 0 additions & 45 deletions goi18n/testdata/input/flat/ar-ar.one.json

This file was deleted.

37 changes: 37 additions & 0 deletions goi18n/testdata/input/flat/ar-ar.one.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[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 = ""
23 changes: 0 additions & 23 deletions goi18n/testdata/input/flat/en-us.one.json

This file was deleted.

16 changes: 16 additions & 0 deletions goi18n/testdata/input/flat/en-us.one.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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"
25 changes: 22 additions & 3 deletions i18n/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package bundle

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
Expand All @@ -11,6 +12,7 @@ import (

"github.com/nicksnyder/go-i18n/i18n/language"
"github.com/nicksnyder/go-i18n/i18n/translation"
toml "github.com/pelletier/go-toml"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -78,12 +80,29 @@ func (b *Bundle) ParseTranslationFileBytes(filename string, buf []byte) error {
}

func parseTranslations(filename string, buf []byte) ([]translation.Translation, error) {
if buf == nil || len(buf) == 0 {
if len(buf) == 0 {
return []translation.Translation{}, nil
}

ext := filepath.Ext(filename)

// `github.com/pelletier/go-toml` has an Unmarshal function,
// that can't unmarshal to maps, so we should parse TOML format 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 {
Expand Down Expand Up @@ -115,9 +134,9 @@ func unmarshal(ext string, buf []byte, out interface{}) error {
return json.Unmarshal(buf, out)
case ".yaml":
return yaml.Unmarshal(buf, out)
default:
return fmt.Errorf("unsupported file extension %v", ext)
}

return fmt.Errorf("unsupported file extension %v", ext)
}

func parseStandardFormat(data []map[string]interface{}) ([]translation.Translation, error) {
Expand Down
4 changes: 4 additions & 0 deletions i18n/translations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ func TestJSONFlatParse(t *testing.T) {
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")
}