1616package transifex
1717
1818import (
19+ "bytes"
1920 "encoding/json"
2021 "fmt"
22+ "io"
2123 "io/ioutil"
2224 "net/http"
2325 "os"
24- "path"
26+ "sync"
27+ "time"
2528
29+ "github.com/arduino/go-paths-helper"
2630 "github.com/spf13/cobra"
2731)
2832
@@ -33,99 +37,246 @@ var pullTransifexCommand = &cobra.Command{
3337}
3438
3539func getLanguages () []string {
36- req , err := http .NewRequest (
37- "GET" ,
38- fmt .Sprintf (
39- "https://www.transifex.com/api/2/project/%s/resource/%s/stats/" ,
40- project , resource ,
41- ), nil )
40+ url := mainEndpoint + fmt .Sprintf ("projects/o:%s:p:%s/languages" , organization , project )
4241
42+ req , err := http .NewRequest ("GET" , url , nil )
4343 if err != nil {
4444 fmt .Println (err .Error ())
4545 os .Exit (1 )
4646 }
4747
48- req .SetBasicAuth ("api" , apiKey )
49-
50- resp , err := http .DefaultClient .Do (req )
48+ addHeaders (req )
5149
50+ res , err := http .DefaultClient .Do (req )
5251 if err != nil {
5352 fmt .Println (err .Error ())
5453 os .Exit (1 )
5554 }
5655
57- defer resp .Body .Close ()
58-
59- b , err := ioutil .ReadAll (resp .Body )
56+ defer res .Body .Close ()
57+ b , err := ioutil .ReadAll (res .Body )
6058 if err != nil {
6159 fmt .Println (err .Error ())
6260 os .Exit (1 )
6361 }
6462
65- var jsonResp map [string ]interface {}
66- if err := json .Unmarshal (b , & jsonResp ); err != nil {
63+ var jsonRes struct {
64+ Data []struct {
65+ Attributes struct {
66+ Code string `json:"code"`
67+ } `json:"attributes"`
68+ } `json:"data"`
69+ }
70+ if err := json .Unmarshal (b , & jsonRes ); err != nil {
6771 fmt .Println (err .Error ())
6872 os .Exit (1 )
6973 }
7074
71- var langs []string
72- for key := range jsonResp {
73- langs = append (langs , key )
75+ var languages []string
76+ for _ , object := range jsonRes . Data {
77+ languages = append (languages , object . Attributes . Code )
7478 }
75-
76- return langs
79+ return languages
7780}
7881
79- func pullCatalog (cmd * cobra.Command , args []string ) {
80- languages := getLanguages ()
81- fmt .Println ("translations found:" , languages )
82+ // startTranslationDownload notifies Transifex that we want to start downloading
83+ // the resources file for the specified languageCode.
84+ // Returns an id to monitor the download status.
85+ func startTranslationDownload (languageCode string ) string {
86+ url := mainEndpoint + "resource_translations_async_downloads"
8287
83- folder := args [0 ]
88+ type jsonReq struct {
89+ Data struct {
90+ Relationships struct {
91+ Language struct {
92+ Data struct {
93+ ID string `json:"id"`
94+ Type string `json:"type"`
95+ } `json:"data"`
96+ } `json:"language"`
97+ Resource struct {
98+ Data struct {
99+ ID string `json:"id"`
100+ Type string `json:"type"`
101+ } `json:"data"`
102+ } `json:"resource"`
103+ } `json:"relationships"`
104+ Type string `json:"type"`
105+ } `json:"data"`
106+ }
84107
85- for _ , lang := range languages {
108+ jsonData := jsonReq {}
109+ jsonData .Data .Type = "resource_translations_async_downloads"
110+ jsonData .Data .Relationships .Language .Data .ID = fmt .Sprintf ("l:%s" , languageCode )
111+ jsonData .Data .Relationships .Language .Data .Type = "languages"
112+ jsonData .Data .Relationships .Resource .Data .ID = fmt .Sprintf ("o:%s:p:%s:r:%s" , organization , project , resource )
113+ jsonData .Data .Relationships .Resource .Data .Type = "resources"
114+
115+ jsonBytes , err := json .Marshal (jsonData )
116+ if err != nil {
117+ fmt .Println (err )
118+ os .Exit (1 )
119+ }
120+
121+ req , err := http .NewRequest (
122+ "POST" ,
123+ url ,
124+ bytes .NewBuffer (jsonBytes ),
125+ )
126+ if err != nil {
127+ fmt .Println (err )
128+ os .Exit (1 )
129+ }
130+
131+ addHeaders (req )
132+
133+ res , err := http .DefaultClient .Do (req )
134+ if err != nil {
135+ fmt .Println (err )
136+ os .Exit (1 )
137+ }
138+
139+ defer res .Body .Close ()
140+ body , err := io .ReadAll (res .Body )
141+ if err != nil {
142+ fmt .Println (err )
143+ os .Exit (1 )
144+ }
145+
146+ var jsonRes struct {
147+ Data struct {
148+ ID string `json:"id"`
149+ } `json:"data"`
150+ }
151+ if err = json .Unmarshal (body , & jsonRes ); err != nil {
152+ fmt .Println (err )
153+ os .Exit (1 )
154+ }
155+ return jsonRes .Data .ID
156+ }
157+
158+ // getDownloadURL checks for the download status of the languageCode file specified
159+ // by downloadID.
160+ // It return a URL to download the file when ready.
161+ func getDownloadURL (languageCode , downloadID string ) string {
162+ url := mainEndpoint + "resource_translations_async_downloads/" + downloadID
163+ // The download request status must be asked from time to time, if it's
164+ // still pending we try again using exponentional backoff starting from 2.5 seconds.
165+ backoff := 2500 * time .Millisecond
166+ for {
86167
87168 req , err := http .NewRequest (
88169 "GET" ,
89- fmt .Sprintf (
90- "https://www.transifex.com/api/2/project/%s/resource/%s/translation/%s/?mode=reviewed&file=po" ,
91- project , resource , lang ,
92- ), nil )
93-
170+ url ,
171+ nil ,
172+ )
94173 if err != nil {
95- fmt .Println (err . Error () )
174+ fmt .Println (err )
96175 os .Exit (1 )
97176 }
98177
99- req .SetBasicAuth ("api" , apiKey )
100-
101- resp , err := http .DefaultClient .Do (req )
178+ addHeaders (req )
102179
180+ client := http.Client {
181+ CheckRedirect : func (req * http.Request , via []* http.Request ) error {
182+ // We handle redirection manually
183+ return http .ErrUseLastResponse
184+ },
185+ }
186+ res , err := client .Do (req )
103187 if err != nil {
104- fmt .Println (err . Error () )
188+ fmt .Println (err )
105189 os .Exit (1 )
106190 }
107191
108- defer resp .Body .Close ()
109-
110- b , err := ioutil .ReadAll (resp .Body )
192+ if res .StatusCode == 303 {
193+ // Return the URL to download translation file
194+ return res .Header .Get ("location" )
195+ }
111196
197+ body , err := io .ReadAll (res .Body )
112198 if err != nil {
113- fmt .Println (err . Error () )
199+ fmt .Println (err )
114200 os .Exit (1 )
115201 }
202+ res .Body .Close ()
116203
117- os .Remove (path .Join (folder , lang + ".po" ))
118- file , err := os .OpenFile (path .Join (folder , lang + ".po" ), os .O_CREATE | os .O_RDWR , 0644 )
119-
120- if err != nil {
121- fmt .Println (err .Error ())
204+ var jsonRes struct {
205+ Data struct {
206+ Attributes struct {
207+ Status string `json:"status"`
208+ Errors []struct {
209+ Code string `json:"code"`
210+ Detail string `json:"detail"`
211+ } `json:"errors"`
212+ } `json:"attributes"`
213+ } `json:"data"`
214+ }
215+ if err = json .Unmarshal (body , & jsonRes ); err != nil {
216+ fmt .Println (err )
122217 os .Exit (1 )
123218 }
124219
125- _ , err = file .Write (b )
126- if err != nil {
127- fmt .Println (err .Error ())
220+ status := jsonRes .Data .Attributes .Status
221+ switch status {
222+ case "succeeded" :
223+ return ""
224+ case "pending" :
225+ fallthrough
226+ case "processing" :
227+ fmt .Printf ("Current status for language %s: %s\n " , languageCode , status )
228+ time .Sleep (backoff )
229+ backoff = backoff * 2
230+ // Request the status again
231+ continue
232+ case "failed" :
233+ for _ , err := range jsonRes .Data .Attributes .Errors {
234+ fmt .Printf ("%s: %s\n " , err .Code , err .Detail )
235+ }
128236 os .Exit (1 )
129237 }
238+ fmt .Printf ("Status request for language %s failed in an unforeseen way\n " , languageCode )
239+ os .Exit (1 )
240+ }
241+ }
242+
243+ // download file from url and saves it in folder with the specified fileName
244+ func download (folder , fileName , url string ) {
245+ fmt .Printf ("Starting download of %s\n " , fileName )
246+ filePath := paths .New (folder , fileName )
247+
248+ res , err := http .DefaultClient .Get (url )
249+ if err != nil {
250+ fmt .Println (err )
251+ os .Exit (1 )
252+ }
253+
254+ data , err := io .ReadAll (res .Body )
255+ if err != nil {
256+ fmt .Println (err )
257+ os .Exit (1 )
258+ }
259+
260+ filePath .WriteFile (data )
261+ fmt .Printf ("Finished download of %s\n " , fileName )
262+ }
263+
264+ func pullCatalog (cmd * cobra.Command , args []string ) {
265+ languages := getLanguages ()
266+ fmt .Println ("translations found:" , languages )
267+
268+ folder := args [0 ]
269+
270+ var wg sync.WaitGroup
271+ for _ , lang := range languages {
272+ wg .Add (1 )
273+ go func (lang string ) {
274+ downloadID := startTranslationDownload (lang )
275+ url := getDownloadURL (lang , downloadID )
276+ download (folder , lang + ".po" , url )
277+ wg .Done ()
278+ }(lang )
130279 }
280+ wg .Wait ()
281+ fmt .Println ("Translation files downloaded" )
131282}
0 commit comments