Skip to content

Commit

Permalink
Add TOML support
Browse files Browse the repository at this point in the history
  • Loading branch information
n10v committed Apr 1, 2017
1 parent be79775 commit 1b15fcf
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 80 deletions.
8 changes: 7 additions & 1 deletion README.md
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
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
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
@@ -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
@@ -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
@@ -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
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
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")
}

0 comments on commit 1b15fcf

Please sign in to comment.