From 6e0f91c429225a67f70438017db0931a464c208d Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Wed, 26 Jan 2022 18:27:45 +0100 Subject: [PATCH 1/7] Add organization env var in i18n transifex script --- i18n/cmd/commands/transifex/transifex.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/i18n/cmd/commands/transifex/transifex.go b/i18n/cmd/commands/transifex/transifex.go index 63fb3750ba2..757713dfc98 100644 --- a/i18n/cmd/commands/transifex/transifex.go +++ b/i18n/cmd/commands/transifex/transifex.go @@ -29,6 +29,9 @@ var Command = &cobra.Command{ PersistentPreRun: preRun, } +const mainEndpoint = "https://rest.api.transifex.com/" + +var organization string var project string var resource string var apiKey string @@ -39,9 +42,10 @@ func init() { } func preRun(cmd *cobra.Command, args []string) { - project = os.Getenv("TRANSIFEX_PROJECT") - resource = os.Getenv("TRANSIFEX_RESOURCE") - apiKey = os.Getenv("TRANSIFEX_RESOURCE") + if organization = os.Getenv("TRANSIFEX_ORGANIZATION"); organization == "" { + fmt.Println("missing TRANSIFEX_ORGANIZATION environment variable") + os.Exit(1) + } if project = os.Getenv("TRANSIFEX_PROJECT"); project == "" { fmt.Println("missing TRANSIFEX_PROJECT environment variable") @@ -57,6 +61,4 @@ func preRun(cmd *cobra.Command, args []string) { fmt.Println("missing TRANSIFEX_API_KEY environment variable") os.Exit(1) } - - return } From 6ac6ded40dc90ff7305bedc0f31843e4a71a98fa Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Wed, 26 Jan 2022 18:28:14 +0100 Subject: [PATCH 2/7] Update i18n transifex push command to use API v3 --- i18n/cmd/commands/transifex/push_transifex.go | 157 ++++++++++++++---- 1 file changed, 126 insertions(+), 31 deletions(-) diff --git a/i18n/cmd/commands/transifex/push_transifex.go b/i18n/cmd/commands/transifex/push_transifex.go index 40a28a2bbb6..95f654e7ac9 100644 --- a/i18n/cmd/commands/transifex/push_transifex.go +++ b/i18n/cmd/commands/transifex/push_transifex.go @@ -17,14 +17,15 @@ package transifex import ( "bytes" + "encoding/base64" + "encoding/json" "fmt" "io" - "mime/multipart" "net/http" "os" - "path" - "path/filepath" + "time" + "github.com/arduino/go-paths-helper" "github.com/spf13/cobra" ) @@ -35,56 +36,150 @@ var pushTransifexCommand = &cobra.Command{ Run: pushCatalog, } -func pushFile(folder, lang, url string) { - filename := path.Join(folder, lang+".po") - file, err := os.Open(filename) +// uploadSourceFile starts an async upload of resourceFile. +// Returns an id to monitor the upload status. +func uploadSourceFile(resourceFile *paths.Path) string { + url := mainEndpoint + "resource_strings_async_uploads" + data, err := resourceFile.ReadFile() if err != nil { - fmt.Println(err.Error()) + fmt.Println(err) os.Exit(1) } - defer file.Close() - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile(lang, filepath.Base(filename)) + jsonData := map[string]interface{}{ + "data": map[string]interface{}{ + "attributes": map[string]string{ + "content": base64.StdEncoding.EncodeToString(data), + "content_encoding": "base64", + }, + "relationships": map[string]interface{}{ + "resource": map[string]interface{}{ + "data": map[string]string{ + "id": fmt.Sprintf("o:%s:p:%s:r:%s", organization, project, resource), + "type": "resources", + }, + }, + }, + "type": "resource_strings_async_uploads", + }, + } + jsonBytes, err := json.Marshal(jsonData) if err != nil { - fmt.Println(err.Error()) + fmt.Println(err) os.Exit(1) } - _, err = io.Copy(part, file) - writer.WriteField("file_type", "po") + req, err := http.NewRequest( + "POST", + url, + bytes.NewBuffer(jsonBytes), + ) + if err != nil { + fmt.Println(err) + os.Exit(1) + } - req, err := http.NewRequest("PUT", url, body) - req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Content-Type", "application/vnd.api+json") + req.Header.Set("Authorization", "Bearer "+apiKey) + res, err := http.DefaultClient.Do(req) if err != nil { - fmt.Println(err.Error()) + fmt.Println(err) os.Exit(1) } - req.SetBasicAuth("api", apiKey) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) + } - resp, err := http.DefaultClient.Do(req) + var jsonRes map[string]interface{} + if err = json.Unmarshal(body, &jsonRes); err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Printf("Started upload of resource file %s\n", resourceFile) + return jsonRes["data"].(map[string]interface{})["id"].(string) +} - if err != nil { - fmt.Println(err.Error()) +func checkUploadStatus(uploadID string) { + url := mainEndpoint + "resource_strings_async_uploads/" + uploadID + // The upload request status must be asked from time to time, if it's + // still pending we try again using exponentional backoff starting from 2.5 seconds. + backoff := 2500 * time.Millisecond + + for { + req, err := http.NewRequest( + "GET", + url, + nil, + ) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + req.Header.Set("Content-Type", "application/vnd.api+json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + res, err := http.DefaultClient.Do(req) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + var body []byte + { + defer res.Body.Close() + body, err = io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + var jsonRes map[string]interface{} + if err = json.Unmarshal(body, &jsonRes); err != nil { + fmt.Println(err) + os.Exit(1) + } + + data := jsonRes["data"].(map[string]interface{}) + attributes := data["attributes"].(map[string]interface{}) + status := attributes["status"].(string) + switch status { + case "succeeded": + return + case "pending": + fallthrough + case "processing": + fmt.Printf("Current status: %s\n", status) + time.Sleep(backoff) + backoff = backoff * 2 + // Request the status again + continue + case "failed": + errs := attributes["errors"].([]map[string]string) + for _, err := range errs { + fmt.Printf("%s: %s\n", err["code"], err["detail"]) + } + os.Exit(1) + } + fmt.Println("Status request failed in an unforeseen way") os.Exit(1) } - resp.Body.Close() +} + +func pushFile(resourceFile *paths.Path) { + uploadID := uploadSourceFile(resourceFile) + checkUploadStatus(uploadID) } func pushCatalog(cmd *cobra.Command, args []string) { folder := args[0] - pushFile( - folder, - "en", - fmt.Sprintf( - "https://www.transifex.com/api/2/project/%s/resource/%s/content/", - project, - resource, - ), - ) + pushFile(paths.New(folder, "en.po")) } From 5fc2b04650e3d4f94e5cc0cd45634aeb2439a3a1 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 27 Jan 2022 15:08:55 +0100 Subject: [PATCH 3/7] Update i18n transifex pull command to use API v3 --- i18n/cmd/commands/transifex/pull_transifex.go | 233 ++++++++++++++---- 1 file changed, 184 insertions(+), 49 deletions(-) diff --git a/i18n/cmd/commands/transifex/pull_transifex.go b/i18n/cmd/commands/transifex/pull_transifex.go index af9c90b19b1..d7d119f3ae2 100644 --- a/i18n/cmd/commands/transifex/pull_transifex.go +++ b/i18n/cmd/commands/transifex/pull_transifex.go @@ -16,13 +16,17 @@ package transifex import ( + "bytes" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "os" - "path" + "sync" + "time" + "github.com/arduino/go-paths-helper" "github.com/spf13/cobra" ) @@ -33,99 +37,230 @@ var pullTransifexCommand = &cobra.Command{ } func getLanguages() []string { - req, err := http.NewRequest( - "GET", - fmt.Sprintf( - "https://www.transifex.com/api/2/project/%s/resource/%s/stats/", - project, resource, - ), nil) + url := mainEndpoint + fmt.Sprintf("projects/o:%s:p:%s/languages", organization, project) + req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println(err.Error()) os.Exit(1) } - req.SetBasicAuth("api", apiKey) - - resp, err := http.DefaultClient.Do(req) + req.Header.Set("Content-Type", "application/vnd.api+json") + req.Header.Set("Authorization", "Bearer "+apiKey) + res, err := http.DefaultClient.Do(req) if err != nil { fmt.Println(err.Error()) os.Exit(1) } - defer resp.Body.Close() - - b, err := ioutil.ReadAll(resp.Body) + defer res.Body.Close() + b, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err.Error()) os.Exit(1) } - var jsonResp map[string]interface{} - if err := json.Unmarshal(b, &jsonResp); err != nil { + var jsonRes map[string]interface{} + if err := json.Unmarshal(b, &jsonRes); err != nil { fmt.Println(err.Error()) os.Exit(1) } - var langs []string - for key := range jsonResp { - langs = append(langs, key) + var languages []string + data := jsonRes["data"].([]interface{}) + for _, object := range data { + languageCode := object.(map[string]interface{})["attributes"].(map[string]interface{})["code"].(string) + languages = append(languages, languageCode) } - - return langs + return languages } -func pullCatalog(cmd *cobra.Command, args []string) { - languages := getLanguages() - fmt.Println("translations found:", languages) +// startTranslationDownload notifies Transifex that we want to start downloading +// the resources file for the specified languageCode. +// Returns an id to monitor the download status. +func startTranslationDownload(languageCode string) string { + url := mainEndpoint + "resource_translations_async_downloads" - folder := args[0] + jsonData := map[string]interface{}{ + "data": map[string]interface{}{ + "relationships": map[string]interface{}{ + "language": map[string]interface{}{ + "data": map[string]string{ + "id": fmt.Sprintf("l:%s", languageCode), + "type": "languages", + }, + }, + "resource": map[string]interface{}{ + "data": map[string]string{ + "id": fmt.Sprintf("o:%s:p:%s:r:%s", organization, project, resource), + "type": "resources", + }, + }, + }, + "type": "resource_translations_async_downloads", + }, + } - for _, lang := range languages { + jsonBytes, err := json.Marshal(jsonData) + if err != nil { + fmt.Println(err) + os.Exit(1) + } - req, err := http.NewRequest( - "GET", - fmt.Sprintf( - "https://www.transifex.com/api/2/project/%s/resource/%s/translation/%s/?mode=reviewed&file=po", - project, resource, lang, - ), nil) + req, err := http.NewRequest( + "POST", + url, + bytes.NewBuffer(jsonBytes), + ) + if err != nil { + fmt.Println(err) + os.Exit(1) + } - if err != nil { - fmt.Println(err.Error()) - os.Exit(1) - } + req.Header.Set("Content-Type", "application/vnd.api+json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + res, err := http.DefaultClient.Do(req) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) + } - req.SetBasicAuth("api", apiKey) + var jsonRes map[string]interface{} + if err = json.Unmarshal(body, &jsonRes); err != nil { + fmt.Println(err) + os.Exit(1) + } + return jsonRes["data"].(map[string]interface{})["id"].(string) +} - resp, err := http.DefaultClient.Do(req) +// getDownloadURL checks for the download status of the languageCode file specified +// by downloadID. +// It return a URL to download the file when ready. +func getDownloadURL(languageCode, downloadID string) string { + url := mainEndpoint + "resource_translations_async_downloads/" + downloadID + // The download request status must be asked from time to time, if it's + // still pending we try again using exponentional backoff starting from 2.5 seconds. + backoff := 2500 * time.Millisecond + for { + req, err := http.NewRequest( + "GET", + url, + nil, + ) if err != nil { - fmt.Println(err.Error()) + fmt.Println(err) os.Exit(1) } - defer resp.Body.Close() - - b, err := ioutil.ReadAll(resp.Body) + req.Header.Set("Content-Type", "application/vnd.api+json") + req.Header.Set("Authorization", "Bearer "+apiKey) + client := http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + // We handle redirection manually + return http.ErrUseLastResponse + }, + } + res, err := client.Do(req) if err != nil { - fmt.Println(err.Error()) + fmt.Println(err) os.Exit(1) } - os.Remove(path.Join(folder, lang+".po")) - file, err := os.OpenFile(path.Join(folder, lang+".po"), os.O_CREATE|os.O_RDWR, 0644) + if res.StatusCode == 303 { + // Return the URL to download translation file + return res.Header.Get("location") + } - if err != nil { - fmt.Println(err.Error()) + var body []byte + { + defer res.Body.Close() + body, err = io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + var jsonRes map[string]interface{} + if err = json.Unmarshal(body, &jsonRes); err != nil { + fmt.Println(err) os.Exit(1) } - _, err = file.Write(b) - if err != nil { - fmt.Println(err.Error()) + data := jsonRes["data"].(map[string]interface{}) + attributes := data["attributes"].(map[string]interface{}) + status := attributes["status"].(string) + switch status { + case "succeeded": + return "" + case "pending": + fallthrough + case "processing": + fmt.Printf("Current status for language %s: %s\n", languageCode, status) + time.Sleep(backoff) + backoff = backoff * 2 + // Request the status again + continue + case "failed": + errs := attributes["errors"].([]map[string]string) + for _, err := range errs { + fmt.Printf("%s: %s\n", err["code"], err["detail"]) + } os.Exit(1) } + fmt.Printf("Status request for language %s failed in an unforeseen way\n", languageCode) + os.Exit(1) + } +} + +// download file from url and saves it in folder with the specified fileName +func download(folder, fileName, url string) { + fmt.Printf("Starting download of %s\n", fileName) + filePath := paths.New(folder, fileName) + + res, err := http.DefaultClient.Get(url) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + filePath.WriteFile(data) + fmt.Printf("Finished download of %s\n", fileName) +} + +func pullCatalog(cmd *cobra.Command, args []string) { + languages := getLanguages() + fmt.Println("translations found:", languages) + + folder := args[0] + + var wg sync.WaitGroup + for _, lang := range languages { + wg.Add(1) + go func(lang string) { + downloadID := startTranslationDownload(lang) + url := getDownloadURL(lang, downloadID) + download(folder, lang+".po", url) + wg.Done() + }(lang) } + wg.Wait() + fmt.Println("Translation files downloaded") } From 73dba778762c6a6b1556e13a50c02f9685d20688 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 27 Jan 2022 15:11:58 +0100 Subject: [PATCH 4/7] Remove duplicated code --- i18n/cmd/commands/transifex/pull_transifex.go | 9 +++------ i18n/cmd/commands/transifex/push_transifex.go | 6 ++---- i18n/cmd/commands/transifex/transifex.go | 6 ++++++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/i18n/cmd/commands/transifex/pull_transifex.go b/i18n/cmd/commands/transifex/pull_transifex.go index d7d119f3ae2..3bf89ed7a03 100644 --- a/i18n/cmd/commands/transifex/pull_transifex.go +++ b/i18n/cmd/commands/transifex/pull_transifex.go @@ -45,8 +45,7 @@ func getLanguages() []string { os.Exit(1) } - req.Header.Set("Content-Type", "application/vnd.api+json") - req.Header.Set("Authorization", "Bearer "+apiKey) + addHeaders(req) res, err := http.DefaultClient.Do(req) if err != nil { @@ -118,8 +117,7 @@ func startTranslationDownload(languageCode string) string { os.Exit(1) } - req.Header.Set("Content-Type", "application/vnd.api+json") - req.Header.Set("Authorization", "Bearer "+apiKey) + addHeaders(req) res, err := http.DefaultClient.Do(req) if err != nil { @@ -162,8 +160,7 @@ func getDownloadURL(languageCode, downloadID string) string { os.Exit(1) } - req.Header.Set("Content-Type", "application/vnd.api+json") - req.Header.Set("Authorization", "Bearer "+apiKey) + addHeaders(req) client := http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { diff --git a/i18n/cmd/commands/transifex/push_transifex.go b/i18n/cmd/commands/transifex/push_transifex.go index 95f654e7ac9..560eb0f8c26 100644 --- a/i18n/cmd/commands/transifex/push_transifex.go +++ b/i18n/cmd/commands/transifex/push_transifex.go @@ -80,8 +80,7 @@ func uploadSourceFile(resourceFile *paths.Path) string { os.Exit(1) } - req.Header.Set("Content-Type", "application/vnd.api+json") - req.Header.Set("Authorization", "Bearer "+apiKey) + addHeaders(req) res, err := http.DefaultClient.Do(req) if err != nil { @@ -122,8 +121,7 @@ func checkUploadStatus(uploadID string) { os.Exit(1) } - req.Header.Set("Content-Type", "application/vnd.api+json") - req.Header.Set("Authorization", "Bearer "+apiKey) + addHeaders(req) res, err := http.DefaultClient.Do(req) if err != nil { diff --git a/i18n/cmd/commands/transifex/transifex.go b/i18n/cmd/commands/transifex/transifex.go index 757713dfc98..a7428f55336 100644 --- a/i18n/cmd/commands/transifex/transifex.go +++ b/i18n/cmd/commands/transifex/transifex.go @@ -17,6 +17,7 @@ package transifex import ( "fmt" + "net/http" "os" "github.com/spf13/cobra" @@ -62,3 +63,8 @@ func preRun(cmd *cobra.Command, args []string) { os.Exit(1) } } + +func addHeaders(req *http.Request) { + req.Header.Set("Content-Type", "application/vnd.api+json") + req.Header.Set("Authorization", "Bearer "+apiKey) +} From 657509cfeb5573b9cca6c1ae135054ccc192ae9f Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 27 Jan 2022 15:12:59 +0100 Subject: [PATCH 5/7] Add success message after successfull resource upload --- i18n/cmd/commands/transifex/push_transifex.go | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/cmd/commands/transifex/push_transifex.go b/i18n/cmd/commands/transifex/push_transifex.go index 560eb0f8c26..d0ee9722100 100644 --- a/i18n/cmd/commands/transifex/push_transifex.go +++ b/i18n/cmd/commands/transifex/push_transifex.go @@ -150,6 +150,7 @@ func checkUploadStatus(uploadID string) { status := attributes["status"].(string) switch status { case "succeeded": + fmt.Println("Resource file uploaded") return case "pending": fallthrough From 7b0a0a8f611e90fa5a6d5dbd4c6069b888f4a4ba Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 28 Jan 2022 11:35:35 +0100 Subject: [PATCH 6/7] Reworked i18n scripts to unmarshal data to structs --- i18n/cmd/commands/transifex/pull_transifex.go | 86 ++++++++++++------- i18n/cmd/commands/transifex/push_transifex.go | 67 +++++++++------ 2 files changed, 96 insertions(+), 57 deletions(-) diff --git a/i18n/cmd/commands/transifex/pull_transifex.go b/i18n/cmd/commands/transifex/pull_transifex.go index 3bf89ed7a03..0018c1b95e9 100644 --- a/i18n/cmd/commands/transifex/pull_transifex.go +++ b/i18n/cmd/commands/transifex/pull_transifex.go @@ -60,17 +60,21 @@ func getLanguages() []string { os.Exit(1) } - var jsonRes map[string]interface{} + var jsonRes struct { + Data []struct { + Attributes struct { + Code string `json:"code"` + } `json:"attributes"` + } `json:"data"` + } if err := json.Unmarshal(b, &jsonRes); err != nil { fmt.Println(err.Error()) os.Exit(1) } var languages []string - data := jsonRes["data"].([]interface{}) - for _, object := range data { - languageCode := object.(map[string]interface{})["attributes"].(map[string]interface{})["code"].(string) - languages = append(languages, languageCode) + for _, object := range jsonRes.Data { + languages = append(languages, object.Attributes.Code) } return languages } @@ -81,26 +85,33 @@ func getLanguages() []string { func startTranslationDownload(languageCode string) string { url := mainEndpoint + "resource_translations_async_downloads" - jsonData := map[string]interface{}{ - "data": map[string]interface{}{ - "relationships": map[string]interface{}{ - "language": map[string]interface{}{ - "data": map[string]string{ - "id": fmt.Sprintf("l:%s", languageCode), - "type": "languages", - }, - }, - "resource": map[string]interface{}{ - "data": map[string]string{ - "id": fmt.Sprintf("o:%s:p:%s:r:%s", organization, project, resource), - "type": "resources", - }, - }, - }, - "type": "resource_translations_async_downloads", - }, + type jsonReq struct { + Data struct { + Relationships struct { + Language struct { + Data struct { + ID string `json:"id"` + Type string `json:"type"` + } `json:"data"` + } `json:"language"` + Resource struct { + Data struct { + ID string `json:"id"` + Type string `json:"type"` + } `json:"data"` + } `json:"resource"` + } `json:"relationships"` + Type string `json:"type"` + } `json:"data"` } + jsonData := jsonReq{} + jsonData.Data.Type = "resource_translations_async_downloads" + jsonData.Data.Relationships.Language.Data.ID = fmt.Sprintf("l:%s", languageCode) + jsonData.Data.Relationships.Language.Data.Type = "languages" + jsonData.Data.Relationships.Resource.Data.ID = fmt.Sprintf("o:%s:p:%s:r:%s", organization, project, resource) + jsonData.Data.Relationships.Resource.Data.Type = "resources" + jsonBytes, err := json.Marshal(jsonData) if err != nil { fmt.Println(err) @@ -132,12 +143,16 @@ func startTranslationDownload(languageCode string) string { os.Exit(1) } - var jsonRes map[string]interface{} + var jsonRes struct { + Data struct { + ID string `json:"id"` + } `json:"data"` + } if err = json.Unmarshal(body, &jsonRes); err != nil { fmt.Println(err) os.Exit(1) } - return jsonRes["data"].(map[string]interface{})["id"].(string) + return jsonRes.Data.ID } // getDownloadURL checks for the download status of the languageCode file specified @@ -189,15 +204,23 @@ func getDownloadURL(languageCode, downloadID string) string { } } - var jsonRes map[string]interface{} + var jsonRes struct { + Data struct { + Attributes struct { + Status string `json:"status"` + Errors []struct { + Code string `json:"code"` + Detail string `json:"detail"` + } `json:"errors"` + } `json:"attributes"` + } `json:"data"` + } if err = json.Unmarshal(body, &jsonRes); err != nil { fmt.Println(err) os.Exit(1) } - data := jsonRes["data"].(map[string]interface{}) - attributes := data["attributes"].(map[string]interface{}) - status := attributes["status"].(string) + status := jsonRes.Data.Attributes.Status switch status { case "succeeded": return "" @@ -210,9 +233,8 @@ func getDownloadURL(languageCode, downloadID string) string { // Request the status again continue case "failed": - errs := attributes["errors"].([]map[string]string) - for _, err := range errs { - fmt.Printf("%s: %s\n", err["code"], err["detail"]) + for _, err := range jsonRes.Data.Attributes.Errors { + fmt.Printf("%s: %s\n", err.Code, err.Detail) } os.Exit(1) } diff --git a/i18n/cmd/commands/transifex/push_transifex.go b/i18n/cmd/commands/transifex/push_transifex.go index d0ee9722100..7d8f6c72e06 100644 --- a/i18n/cmd/commands/transifex/push_transifex.go +++ b/i18n/cmd/commands/transifex/push_transifex.go @@ -46,23 +46,29 @@ func uploadSourceFile(resourceFile *paths.Path) string { os.Exit(1) } - jsonData := map[string]interface{}{ - "data": map[string]interface{}{ - "attributes": map[string]string{ - "content": base64.StdEncoding.EncodeToString(data), - "content_encoding": "base64", - }, - "relationships": map[string]interface{}{ - "resource": map[string]interface{}{ - "data": map[string]string{ - "id": fmt.Sprintf("o:%s:p:%s:r:%s", organization, project, resource), - "type": "resources", - }, - }, - }, - "type": "resource_strings_async_uploads", - }, + type jsonReq struct { + Data struct { + Attributes struct { + Content string `json:"content"` + ContentEncoding string `json:"content_encoding"` + } `json:"attributes"` + Relationships struct { + Resource struct { + Data struct { + ID string `json:"id"` + Type string `json:"type"` + } `json:"data"` + } `json:"resource"` + } `json:"relationships"` + Type string `json:"type"` + } `json:"data"` } + jsonData := jsonReq{} + jsonData.Data.Type = "resource_strings_async_uploads" + jsonData.Data.Attributes.Content = base64.StdEncoding.EncodeToString(data) + jsonData.Data.Attributes.ContentEncoding = "base64" + jsonData.Data.Relationships.Resource.Data.ID = fmt.Sprintf("o:%s:p:%s:r:%s", organization, project, resource) + jsonData.Data.Relationships.Resource.Data.Type = "resources" jsonBytes, err := json.Marshal(jsonData) if err != nil { @@ -95,13 +101,17 @@ func uploadSourceFile(resourceFile *paths.Path) string { os.Exit(1) } - var jsonRes map[string]interface{} + var jsonRes struct { + Data struct { + ID string `json:"id"` + } `json:"data"` + } if err = json.Unmarshal(body, &jsonRes); err != nil { fmt.Println(err) os.Exit(1) } fmt.Printf("Started upload of resource file %s\n", resourceFile) - return jsonRes["data"].(map[string]interface{})["id"].(string) + return jsonRes.Data.ID } func checkUploadStatus(uploadID string) { @@ -139,15 +149,23 @@ func checkUploadStatus(uploadID string) { } } - var jsonRes map[string]interface{} + var jsonRes struct { + Data struct { + Attributes struct { + Status string `json:"status"` + Errors []struct { + Code string `json:"code"` + Detail string `json:"detail"` + } `json:"errors"` + } `json:"attributes"` + } `json:"data"` + } if err = json.Unmarshal(body, &jsonRes); err != nil { fmt.Println(err) os.Exit(1) } - data := jsonRes["data"].(map[string]interface{}) - attributes := data["attributes"].(map[string]interface{}) - status := attributes["status"].(string) + status := jsonRes.Data.Attributes.Status switch status { case "succeeded": fmt.Println("Resource file uploaded") @@ -161,9 +179,8 @@ func checkUploadStatus(uploadID string) { // Request the status again continue case "failed": - errs := attributes["errors"].([]map[string]string) - for _, err := range errs { - fmt.Printf("%s: %s\n", err["code"], err["detail"]) + for _, err := range jsonRes.Data.Attributes.Errors { + fmt.Printf("%s: %s\n", err.Code, err.Detail) } os.Exit(1) } From 1b4f9b121c4ca48cb7254a35c7bac2df2c35125a Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 28 Jan 2022 12:02:21 +0100 Subject: [PATCH 7/7] Fixed wrong defer --- i18n/cmd/commands/transifex/pull_transifex.go | 13 +++++-------- i18n/cmd/commands/transifex/push_transifex.go | 13 +++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/i18n/cmd/commands/transifex/pull_transifex.go b/i18n/cmd/commands/transifex/pull_transifex.go index 0018c1b95e9..cabfb8d6fee 100644 --- a/i18n/cmd/commands/transifex/pull_transifex.go +++ b/i18n/cmd/commands/transifex/pull_transifex.go @@ -194,15 +194,12 @@ func getDownloadURL(languageCode, downloadID string) string { return res.Header.Get("location") } - var body []byte - { - defer res.Body.Close() - body, err = io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - os.Exit(1) - } + body, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) } + res.Body.Close() var jsonRes struct { Data struct { diff --git a/i18n/cmd/commands/transifex/push_transifex.go b/i18n/cmd/commands/transifex/push_transifex.go index 7d8f6c72e06..ffb7e42a6bc 100644 --- a/i18n/cmd/commands/transifex/push_transifex.go +++ b/i18n/cmd/commands/transifex/push_transifex.go @@ -139,15 +139,12 @@ func checkUploadStatus(uploadID string) { os.Exit(1) } - var body []byte - { - defer res.Body.Close() - body, err = io.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - os.Exit(1) - } + body, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + os.Exit(1) } + res.Body.Close() var jsonRes struct { Data struct {