diff --git a/tools/text-translator/README.md b/tools/text-translator/README.md new file mode 100644 index 0000000..41f5b4d --- /dev/null +++ b/tools/text-translator/README.md @@ -0,0 +1,9 @@ +# SaaS Starter Kit + +Copyright 2019, Geeks Accelerator +twins@geeksaccelerator.com + + +## Description +_text-translator_ is a tool for automatic translation of messages using +AWS Translator service and universal-translator json files. \ No newline at end of file diff --git a/tools/text-translator/aws/awsTranslator.go b/tools/text-translator/aws/awsTranslator.go new file mode 100644 index 0000000..675e590 --- /dev/null +++ b/tools/text-translator/aws/awsTranslator.go @@ -0,0 +1,46 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/translate" + "github.com/pkg/errors" +) + +// Translator is an instance of AWS Translator service +type Translator struct { + t *translate.Translate +} + +// New returns a AWS Translator service which credentials are +// retrieved by the default AWS SDK credential chain (env, conf) +func New() (*Translator, error) { + s, err := session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + }) + if err != nil { + return nil, errors.Wrap(err, "error creating AWS session") + } + + t := translate.New(s, aws.NewConfig().WithMaxRetries(3)) + + return &Translator{ + t: t, + }, nil +} + +// T translate text from an origin locale to a set of target locales, +// the results are in the same order of target locales. +func (awsT *Translator) T(text string, sourceLocale string, targetLocale string) (string, error) { + input := &translate.TextInput{ + SourceLanguageCode: aws.String(sourceLocale), + Text: &text, + } + input.TargetLanguageCode = aws.String(targetLocale) + output, err := awsT.t.Text(input) + if err != nil { + return "", errors.Wrapf(err, "error while translating for locale %v", targetLocale) + } + + return *output.TranslatedText, nil +} diff --git a/tools/text-translator/cmd/main.go b/tools/text-translator/cmd/main.go new file mode 100644 index 0000000..263a6c2 --- /dev/null +++ b/tools/text-translator/cmd/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "flag" + "fmt" + "geeks-accelerator/oss/saas-starter-kit/tools/text-translator/aws" + "geeks-accelerator/oss/saas-starter-kit/tools/text-translator/internal/jsontranslator" + "os" + "path/filepath" + "strings" +) + +var ( + inFile = flag.String("i", "", "source file to translate") + outDir = flag.String("o", "", "output file to translate") + csvTargetLocales = flag.String("t", "", "comma separated list of target locales") +) + +func main() { + flag.Parse() + flag.VisitAll(func(f *flag.Flag) { + if f.Value.String() == "" { + fmt.Printf("-%s flag is required\n", f.Name) + os.Exit(1) + } + }) + + t, err := aws.New() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + sourceTrans, err := jsontranslator.Read(*inFile) + if err != nil { + fmt.Printf("error while reading json file: %v", err) + os.Exit(1) + } + + targetLocales := strings.Split(*csvTargetLocales, ",") + targetTranslations := jsontranslator.Translate(t, sourceTrans, targetLocales) + + _, name := filepath.Split(*inFile) + err = jsontranslator.Save(*outDir, name, targetTranslations) + if err != nil { + fmt.Printf("error while saving translations: %v", err) + os.Exit(1) + } +} diff --git a/tools/text-translator/internal/jsontranslator/translator.go b/tools/text-translator/internal/jsontranslator/translator.go new file mode 100644 index 0000000..d3d869f --- /dev/null +++ b/tools/text-translator/internal/jsontranslator/translator.go @@ -0,0 +1,114 @@ +package jsontranslator + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" + "sync" + + "github.com/pkg/errors" +) + +// JSONTranslation represents a translation for a locale +type JSONTranslation struct { + Locale string + Items []Translation +} + +// Translation represent a translation item for a locale +type Translation struct { + Locale string `json:"locale"` + Key string `json:"key"` + Translation string `json:"trans"` +} + +// TranslateService translates a text from the sourceLocale to the +// targetLocale +type TranslateService interface { + T(text string, sourceLocale string, targetLocale string) (string, error) +} + +// Read parses a JSON file +func Read(path string) (*JSONTranslation, error) { + // TODO: possibly use os.Stat to support paths of directories to make translate multiple files easier + log.Printf("reading %s json file...\n", path) + file, err := os.Open(path) + if err != nil { + return nil, errors.Wrapf(err, "error while opening template file %v", path) + } + defer file.Close() + + content, err := ioutil.ReadAll(file) + if err != nil { + return nil, errors.Wrap(err, "error while reading contents of template file") + } + + var res []Translation + err = json.Unmarshal(content, &res) + if err != nil { + return nil, errors.Wrap(err, "error while unmarshaling template content") + } + + if len(res) == 0 { + return nil, errors.New("file doesn't have any translation items") + } + + return &JSONTranslation{ + Locale: res[0].Locale, + Items: res, + }, nil +} + +// Translate translates items to multiple target locales using the provided +// translator service +func Translate(ts TranslateService, source *JSONTranslation, targetLocales []string) []JSONTranslation { + res := make([]JSONTranslation, len(targetLocales)) + var wg sync.WaitGroup + for i := range targetLocales { + wg.Add(1) + go func(i int) { + log.Printf("translating into %v...\n", targetLocales[i]) + res[i] = JSONTranslation{ + Locale: targetLocales[i], + Items: make([]Translation, 0, len(source.Items)), + } + for _, m := range source.Items { + output, err := ts.T(m.Translation, m.Locale, targetLocales[i]) + if err != nil { + log.Printf("coudn't translate %v: %v\n", m.Translation, err) + } + res[i].Items = append(res[i].Items, Translation{ + Locale: targetLocales[i], + Key: m.Key, + Translation: output, + }) + } + wg.Done() + }(i) + } + wg.Wait() + + return res +} + +// Save saves the translations in the path directory +func Save(dirPath string, name string, translations []JSONTranslation) error { + // TODO: consider "merge" option to avoid overriding existing files + for _, t := range translations { + log.Printf("saving locale %v json file...\n", t.Locale) + b, err := json.MarshalIndent(t.Items, "", " ") + if err != nil { + return errors.Wrap(err, "error while serializing json file") + } + folderPath := filepath.Join(dirPath, t.Locale) + os.MkdirAll(folderPath, os.ModePerm) + err = ioutil.WriteFile(filepath.Join(folderPath, name), b, 0644) + if err != nil { + return errors.Wrap(err, "error while writing to the json file") + } + } + + return nil +}