11/* Downloads the latest translations from Transifex */
22import fs from 'fs' ;
33import fetch from 'node-fetch' ;
4- import btoa from 'btoa' ;
54import YAML from 'js-yaml' ;
5+ import { transifexApi } from '@transifex/api' ;
66
77
88function fetchTranslations ( options ) {
99
10- let defaultCredentials = {
11- user : 'api' ,
12- password : ''
13- } ;
14- if ( fs . existsSync ( `${ process . cwd ( ) } /transifex.auth` ) ) {
15- // Credentials can be stored in transifex.auth as a json object. You should probably gitignore this file.
16- // You can use an API key instead of your password: https://docs.transifex.com/api/introduction#authentication
10+ // Transifex doesn't allow anonymous downloading
11+ /* eslint-disable no-process-env */
12+ if ( process . env . transifex_password ) {
13+ // Deployment scripts may prefer environment variables
14+ transifexApi . setup ( { auth : process . env . transifex_password } ) ;
15+ } else {
16+ // Credentials can be stored in transifex.auth as a json object. This file is gitignored.
17+ // You must use an API token for authentication: You can generate one at https://www.transifex.com/user/settings/api/.
1718 // {
18- // "user": "username",
19- // "password": "password"
19+ // "password": "<api_key>"
2020 // }
21- defaultCredentials = JSON . parse ( fs . readFileSync ( ` ${ process . cwd ( ) } /transifex.auth` , 'utf8' ) ) ;
21+ transifexApi . setup ( { auth : JSON . parse ( fs . readFileSync ( '. /transifex.auth' , 'utf8' ) ) . password } ) ;
2222 }
23+ /* eslint-enable no-process-env */
2324
2425 if ( ! options ) options = { } ;
2526 options = Object . assign ( {
26- translCredentials : defaultCredentials ,
2727 translOrgId : '' ,
2828 translProjectId : '' ,
2929 translResourceIds : [ 'presets' ] ,
@@ -38,15 +38,6 @@ function fetchTranslations(options) {
3838 fs . mkdirSync ( outDir ) ;
3939 }
4040
41- const fetchOpts = {
42- headers : {
43- 'Authorization' : 'Basic ' + btoa ( options . translCredentials . user + ':' + options . translCredentials . password ) ,
44- }
45- } ;
46-
47- const apiroot = 'https://www.transifex.com/api/2' ;
48- const projectURL = `${ apiroot } /project/${ options . translProjectId } ` ;
49-
5041 const translResourceIds = options . translResourceIds ;
5142 return new Promise ( function ( resolve ) {
5243 asyncMap ( translResourceIds , getResourceInfo , function ( err , results ) {
@@ -59,37 +50,41 @@ function fetchTranslations(options) {
5950 } ) ;
6051
6152
62- function getResourceInfo ( resourceId , callback ) {
63- let url = `https://api.transifex.com/organizations/${ options . translOrgId } /projects/${ options . translProjectId } /resources/${ resourceId } ` ;
64- fetch ( url , fetchOpts )
65- . then ( res => {
66- process . stdout . write ( `${ res . status } : ${ url } \n` ) ;
67- return res . json ( ) ;
68- } )
69- . then ( json => {
70- callback ( null , json ) ;
71- } )
72- . catch ( err => callback ( err ) ) ;
53+ async function getResourceInfo ( resourceId , callback ) {
54+ try {
55+ const result = [ ] ;
56+ for await ( const stat of transifexApi . ResourceLanguageStats . filter ( {
57+ project : `o:${ options . translOrgId } :p:${ options . translProjectId } ` ,
58+ resource : `o:${ options . translOrgId } :p:${ options . translProjectId } :r:${ resourceId } `
59+ } ) . all ( ) ) {
60+ result . push ( stat ) ;
61+ }
62+ process . stdout . write ( `got resource language stats collection for ${ resourceId } \n` ) ;
63+ callback ( null , result ) ;
64+ } catch ( err ) {
65+ process . stderr . write ( `error while getting resource language stats collection for ${ resourceId } \n` , err ) ;
66+ callback ( err ) ;
67+ }
7368 }
7469
7570 function gotResourceInfo ( err , results ) {
7671 if ( err ) return process . stderr . write ( err + '\n' ) ;
7772
7873 let coverageByLocaleCode = { } ;
7974 results . forEach ( function ( info ) {
80- for ( let code in info . stats ) {
81- let type = 'translated' ;
82- if ( options . translReviewedOnly &&
83- ( ! Array . isArray ( options . translReviewedOnly ) || options . translReviewedOnly . indexOf ( code ) !== - 1 ) ) {
84- // reviewed_1 = reviewed, reviewed_2 = proofread
85- type = 'reviewed_1' ;
75+ info . forEach ( stat => {
76+ let code = stat . relationships . language . data . id . substr ( 2 ) . replace ( / _ / g, '-' ) ;
77+ let type = 'translated_strings' ;
78+ if ( options . translReviewedOnly && (
79+ ! Array . isArray ( options . translReviewedOnly )
80+ || options . translReviewedOnly . indexOf ( code ) !== - 1 ) ) {
81+ type = 'reviewed_strings' ;
8682 }
87- let coveragePart = info . stats [ code ] [ type ] . percentage / results . length ;
83+ let coveragePart = ( stat . attributes [ type ] / stat . attributes . total_strings ) / results . length ;
8884
89- code = code . replace ( / _ / g, '-' ) ;
9085 if ( coverageByLocaleCode [ code ] === undefined ) coverageByLocaleCode [ code ] = 0 ;
9186 coverageByLocaleCode [ code ] += coveragePart ;
92- }
87+ } ) ;
9388 } ) ;
9489 let dataLocales = { } ;
9590 // explicitly list the source locale as having 100% coverage
@@ -112,11 +107,10 @@ function fetchTranslations(options) {
112107 }
113108
114109 function getResource ( resourceId , callback ) {
115- let resourceURL = `${ projectURL } /resource/${ resourceId } ` ;
116- getLanguages ( resourceURL , ( err , codes ) => {
110+ getLanguages ( ( err , codes ) => {
117111 if ( err ) return callback ( err ) ;
118112
119- asyncMap ( codes , getLanguage ( resourceURL ) , ( err , results ) => {
113+ asyncMap ( codes , getLanguage ( resourceId ) , ( err , results ) => {
120114 if ( err ) return callback ( err ) ;
121115
122116 let locale = { } ;
@@ -216,44 +210,48 @@ function fetchTranslations(options) {
216210 }
217211 }
218212
219-
220- function getLanguage ( resourceURL ) {
221- return ( code , callback ) => {
222- code = code . replace ( / - / g, '_' ) ;
223- let url = `${ resourceURL } /translation/${ code } ` ;
224- if ( options . translReviewedOnly &&
225- ( ! Array . isArray ( options . translReviewedOnly ) || options . translReviewedOnly . indexOf ( code ) !== - 1 ) ) {
226-
227- url += '?mode=reviewed' ;
213+ function getLanguage ( resourceId ) {
214+ return async ( code , callback ) => {
215+ try {
216+ code = code . replace ( / - / g, '_' ) ;
217+ let reviewedOnly = options . translReviewedOnly && (
218+ ! Array . isArray ( options . translReviewedOnly )
219+ || options . translReviewedOnly . indexOf ( code ) !== - 1 ) ;
220+ const url = await transifexApi . ResourceTranslationsAsyncDownload . download ( {
221+ resource : { data :{ type :'resources' , id :`o:${ options . translOrgId } :p:${ options . translProjectId } :r:${ resourceId } ` } } ,
222+ language : { data :{ type :'languages' , id :`l:${ code } ` } } ,
223+ // fetch only reviewed strings for some languages
224+ mode : reviewedOnly ? 'reviewed' : 'default'
225+ } ) ;
226+ const data = await fetch ( url ) . then ( d => d . text ( ) ) ;
227+ process . stdout . write ( `got translations for ${ resourceId } , language ${ code } \n` ) ;
228+ callback ( null , YAML . load ( data ) [ code ] ) ;
229+ } catch ( err ) {
230+ process . stderr . write ( `error while getting translations for ${ resourceId } , language ${ code } \n` ) ;
231+ callback ( err ) ;
228232 }
229- fetch ( url , fetchOpts )
230- . then ( res => {
231- process . stdout . write ( `${ res . status } : ${ url } \n` ) ;
232- return res . json ( ) ;
233- } )
234- . then ( json => {
235- callback ( null , YAML . load ( json . content ) [ code ] ) ;
236- } )
237- . catch ( err => callback ( err ) ) ;
238233 } ;
239234 }
240235
241236
242- function getLanguages ( resourceURL , callback ) {
243- let url = `${ resourceURL } ?details` ;
244- fetch ( url , fetchOpts )
245- . then ( res => {
246- process . stdout . write ( `${ res . status } : ${ url } \n` ) ;
247- return res . json ( ) ;
248- } )
249- . then ( json => {
250- callback ( null , json . available_languages
251- . map ( d => d . code . replace ( / _ / g, '-' ) )
252- // we already have the source locale so don't download it
253- . filter ( d => d !== options . sourceLocale )
254- ) ;
255- } )
256- . catch ( err => callback ( err ) ) ;
237+ async function getLanguages ( callback ) {
238+ try {
239+ const result = [ ] ;
240+ const project = await transifexApi . Project . get ( {
241+ organization : `o:${ options . translOrgId } ` ,
242+ slug : options . translProjectId
243+ } ) ;
244+ const lngs = await project . fetch ( 'languages' ) ;
245+ for await ( const lng of lngs . all ( ) ) {
246+ if ( lng . attributes . code === 'en' ) continue ;
247+ result . push ( lng . attributes . code . replace ( / _ / g, '-' ) ) ;
248+ }
249+ process . stdout . write ( 'got project languages\n' ) ;
250+ callback ( null , result ) ;
251+ } catch ( err ) {
252+ process . stderr . write ( 'error while getting project languages\n' ) ;
253+ callback ( err ) ;
254+ }
257255 }
258256}
259257
@@ -269,7 +267,7 @@ function asyncMap(inputs, func, callback) {
269267 function next ( ) {
270268 callFunc ( index ++ ) ;
271269 if ( index < inputs . length ) {
272- setTimeout ( next , 200 ) ;
270+ setTimeout ( next , 50 ) ;
273271 }
274272 }
275273
0 commit comments