From 87b694bc0899d3f08854ce418a6440397a829194 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 20 Nov 2019 14:18:40 +0300 Subject: [PATCH 1/2] helper for easy translations check in transports --- core/translations_extractor.go | 114 ++++++++++++++++++++++++++++ core/translations_extractor_test.go | 60 +++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 core/translations_extractor.go create mode 100644 core/translations_extractor_test.go diff --git a/core/translations_extractor.go b/core/translations_extractor.go new file mode 100644 index 0000000..00b90cf --- /dev/null +++ b/core/translations_extractor.go @@ -0,0 +1,114 @@ +package core + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/gobuffalo/packr/v2" + "gopkg.in/yaml.v2" +) + +// TranslationsExtractor is a tool to load raw translations data from files or from box. +// It is feasible to be used in tests in order to check translation files correctness. +// The easiest way to check correctness is to check keys in translations. +// TranslationsExtractor IS NOT supposed to check correctness of translations - it's just an extractor. +// Translations can be checked manually, or via external library like https://github.com/google/go-cmp +type TranslationsExtractor struct { + fileNameTemplate string + TranslationsBox *packr.Box + TranslationsPath string +} + +// TranslationsExtractor constructor. Use "translate.{}.yml" as template if your translations are named like "translate.en.yml" +func NewTranslationsExtractor(fileNameTemplate string) *TranslationsExtractor { + return &TranslationsExtractor{fileNameTemplate: fileNameTemplate} +} + +// unmarshalToMap returns map with unmarshaled data or error +func (t *TranslationsExtractor) unmarshalToMap(in []byte) (map[string]interface{}, error) { + var dataMap map[string]interface{} + + if err := yaml.Unmarshal(in, &dataMap); err == nil { + return dataMap, nil + } else { + return dataMap, err + } +} + +// loadYAMLBox loads YAML from box +func (t *TranslationsExtractor) loadYAMLBox(fileName string) (map[string]interface{}, error) { + var dataMap map[string]interface{} + + if data, err := t.TranslationsBox.Find(fileName); err == nil { + return t.unmarshalToMap(data) + } else { + return dataMap, err + } +} + +// loadYAMLFile loads YAML from file +func (t *TranslationsExtractor) loadYAMLFile(fileName string) (map[string]interface{}, error) { + var dataMap map[string]interface{} + + if info, err := os.Stat(fileName); err == nil { + if !info.IsDir() { + if path, err := filepath.Abs(fileName); err == nil { + if source, err := ioutil.ReadFile(path); err == nil { + return t.unmarshalToMap(source) + } else { + return dataMap, err + } + } else { + return dataMap, err + } + } else { + return dataMap, errors.New("directory provided instead of file") + } + } else { + return dataMap, err + } +} + +// loadYAML loads YAML from filesystem or from packr box - depends on what was configured. Can return error. +func (t *TranslationsExtractor) loadYAML(fileName string) (map[string]interface{}, error) { + if t.TranslationsBox != nil { + return t.loadYAMLBox(fileName) + } else if t.TranslationsPath != "" { + return t.loadYAMLFile(fileName) + } else { + return map[string]interface{}{}, errors.New("nor box nor translations directory was provided") + } +} + +// GetMapKeys returns sorted map keys from map[string]interface{} - useful to check keys in several translation files +func (t *TranslationsExtractor) GetMapKeys(data map[string]interface{}) []string { + keys := make([]string, len(data)) + + i := 0 + for k := range data { + keys[i] = k + i++ + } + + sort.Strings(keys) + + return keys +} + +// LoadLocale returns translation file data with provided locale +func (t *TranslationsExtractor) LoadLocale(locale string) (map[string]interface{}, error) { + return t.loadYAML(strings.Replace(t.fileNameTemplate, "{}", locale, 1)) +} + +// LoadLocaleKeys returns only sorted keys from translation file +func (t *TranslationsExtractor) LoadLocaleKeys(locale string) ([]string, error) { + if data, err := t.LoadLocale(locale); err == nil { + return t.GetMapKeys(data), nil + } else { + return []string{}, err + } +} diff --git a/core/translations_extractor_test.go b/core/translations_extractor_test.go new file mode 100644 index 0000000..e1d577c --- /dev/null +++ b/core/translations_extractor_test.go @@ -0,0 +1,60 @@ +package core + +import ( + "io/ioutil" + "os" + "reflect" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "gopkg.in/yaml.v2" +) + +type TranslationsExtractorTest struct { + suite.Suite + extractor *TranslationsExtractor +} + +func (t *TranslationsExtractorTest) SetupSuite() { + translation := map[string]string{ + "test": "first", + "another": "second", + } + data, _ := yaml.Marshal(translation) + errWrite := ioutil.WriteFile("/tmp/translate.en.yml", data, os.ModePerm) + require.NoError(t.T(), errWrite) + + t.extractor = NewTranslationsExtractor("translate.{}.yml") + t.extractor.TranslationsPath = "/tmp" +} + +func (t *TranslationsExtractorTest) Test_LoadLocale() { + data, err := t.extractor.LoadLocale("en") + require.NoError(t.T(), err) + assert.Contains(t.T(), data, "test") +} + +func (t *TranslationsExtractorTest) Test_GetMapKeys() { + testMap := map[string]interface{}{ + "a": 1, + "b": 2, + "c": 3, + } + keys := []string{"a", "b", "c"} + mapKeys := t.extractor.GetMapKeys(testMap) + + assert.True(t.T(), reflect.DeepEqual(keys, mapKeys)) +} + +func (t *TranslationsExtractorTest) Test_unmarshalToMap() { + translation := map[string]string{ + "test": "first", + "another": "second", + } + data, _ := yaml.Marshal(translation) + mapData, err := t.extractor.unmarshalToMap(data) + require.NoError(t.T(), err) + + assert.True(t.T(), reflect.DeepEqual(translation, mapData)) +} From b8be21b7c3ebb7d6b0f03f3ff8a10a4d0058e7b5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 20 Nov 2019 14:53:07 +0300 Subject: [PATCH 2/2] fixed typo which caused file reading error --- core/translations_extractor.go | 2 +- core/translations_extractor_test.go | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/core/translations_extractor.go b/core/translations_extractor.go index 00b90cf..1697b50 100644 --- a/core/translations_extractor.go +++ b/core/translations_extractor.go @@ -78,7 +78,7 @@ func (t *TranslationsExtractor) loadYAML(fileName string) (map[string]interface{ if t.TranslationsBox != nil { return t.loadYAMLBox(fileName) } else if t.TranslationsPath != "" { - return t.loadYAMLFile(fileName) + return t.loadYAMLFile(filepath.Join(t.TranslationsPath, fileName)) } else { return map[string]interface{}{}, errors.New("nor box nor translations directory was provided") } diff --git a/core/translations_extractor_test.go b/core/translations_extractor_test.go index e1d577c..ddb3b43 100644 --- a/core/translations_extractor_test.go +++ b/core/translations_extractor_test.go @@ -4,6 +4,8 @@ import ( "io/ioutil" "os" "reflect" + "sort" + "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,6 +18,20 @@ type TranslationsExtractorTest struct { extractor *TranslationsExtractor } +func (t *TranslationsExtractorTest) getKeys(data map[string]interface{}) []string { + keys := make([]string, len(data)) + + i := 0 + for k := range data { + keys[i] = k + i++ + } + + sort.Strings(keys) + + return keys +} + func (t *TranslationsExtractorTest) SetupSuite() { translation := map[string]string{ "test": "first", @@ -27,6 +43,7 @@ func (t *TranslationsExtractorTest) SetupSuite() { t.extractor = NewTranslationsExtractor("translate.{}.yml") t.extractor.TranslationsPath = "/tmp" + t.extractor.TranslationsBox = nil } func (t *TranslationsExtractorTest) Test_LoadLocale() { @@ -48,7 +65,7 @@ func (t *TranslationsExtractorTest) Test_GetMapKeys() { } func (t *TranslationsExtractorTest) Test_unmarshalToMap() { - translation := map[string]string{ + translation := map[string]interface{}{ "test": "first", "another": "second", } @@ -56,5 +73,9 @@ func (t *TranslationsExtractorTest) Test_unmarshalToMap() { mapData, err := t.extractor.unmarshalToMap(data) require.NoError(t.T(), err) - assert.True(t.T(), reflect.DeepEqual(translation, mapData)) + assert.True(t.T(), reflect.DeepEqual(t.getKeys(translation), t.getKeys(mapData))) +} + +func Test_TranslationExtractor(t *testing.T) { + suite.Run(t, new(TranslationsExtractorTest)) }