pxt-core 9.3.16 → 9.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/built/cli.js CHANGED
@@ -335,7 +335,7 @@ function checkIfTaggedCommitAsync() {
335
335
  });
336
336
  }
337
337
  let readJson = nodeutil.readJson;
338
- function ciAsync() {
338
+ async function ciAsync() {
339
339
  forceCloudBuild = true;
340
340
  const buildInfo = ciBuildInfo();
341
341
  pxt.log(`ci build using ${buildInfo.ci}`);
@@ -381,56 +381,55 @@ function ciAsync() {
381
381
  let pkg = readJson("package.json");
382
382
  if (pkg["name"] == "pxt-core") {
383
383
  pxt.log("pxt-core build");
384
- return checkIfTaggedCommitAsync()
385
- .then(isTaggedCommit => {
386
- pxt.log(`is tagged commit: ${isTaggedCommit}`);
387
- let p = npmPublishAsync();
388
- if (branch === "master" && isTaggedCommit) {
389
- if (uploadDocs)
390
- p = p
391
- .then(() => buildWebStringsAsync())
392
- .then(() => crowdin.execCrowdinAsync("upload", "built/webstrings.json"))
393
- .then(() => crowdin.execCrowdinAsync("upload", "built/skillmap-strings.json"))
394
- .then(() => crowdin.execCrowdinAsync("upload", "built/authcode-strings.json"))
395
- .then(() => crowdin.execCrowdinAsync("upload", "built/multiplayer-strings.json"))
396
- .then(() => crowdin.execCrowdinAsync("upload", "built/kiosk-strings.json"))
397
- .then(() => crowdin.execCrowdinAsync("upload", "built/teachertool-strings.json"));
398
- if (uploadApiStrings)
399
- p = p.then(() => crowdin.execCrowdinAsync("upload", "built/strings.json"));
400
- if (uploadDocs || uploadApiStrings)
401
- p = p.then(() => crowdin.internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs));
384
+ const isTaggedCommit = await checkIfTaggedCommitAsync();
385
+ pxt.log(`is tagged commit: ${isTaggedCommit}`);
386
+ await npmPublishAsync();
387
+ if (branch === "master" && isTaggedCommit) {
388
+ if (uploadDocs) {
389
+ await buildWebStringsAsync();
390
+ await crowdin.uploadBuiltStringsAsync("built/webstrings.json");
391
+ await crowdin.uploadBuiltStringsAsync(`built/skillmap-strings.json`);
392
+ await crowdin.uploadBuiltStringsAsync(`built/authcode-strings.json`);
393
+ await crowdin.uploadBuiltStringsAsync(`built/multiplayer-strings.json`);
394
+ await crowdin.uploadBuiltStringsAsync(`built/kiosk-strings.json`);
395
+ await crowdin.uploadBuiltStringsAsync(`built/teachertool-strings.json`);
396
+ }
397
+ if (uploadApiStrings) {
398
+ await crowdin.uploadBuiltStringsAsync("built/strings.json");
402
399
  }
403
- return p;
404
- });
400
+ if (uploadDocs || uploadApiStrings) {
401
+ await crowdin.internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs);
402
+ pxt.log("translations uploaded");
403
+ }
404
+ else {
405
+ pxt.log("skipping translations upload");
406
+ }
407
+ }
405
408
  }
406
409
  else {
407
410
  pxt.log("target build");
408
- return internalBuildTargetAsync()
409
- .then(() => internalCheckDocsAsync(true))
410
- .then(() => blockTestsAsync())
411
- .then(() => npmPublishAsync())
412
- .then(() => {
413
- if (!process.env["PXT_ACCESS_TOKEN"]) {
414
- // pull request, don't try to upload target
415
- pxt.log('no token, skipping upload');
416
- return Promise.resolve();
417
- }
411
+ await internalBuildTargetAsync();
412
+ await internalCheckDocsAsync(true);
413
+ await blockTestsAsync();
414
+ await npmPublishAsync();
415
+ if (!process.env["PXT_ACCESS_TOKEN"]) {
416
+ // pull request, don't try to upload target
417
+ pxt.log('no token, skipping upload');
418
+ }
419
+ else {
418
420
  const trg = readLocalPxTarget();
419
421
  const label = `${trg.id}/${tag || latest}`;
420
422
  pxt.log(`uploading target with label ${label}...`);
421
- return uploadTargetAsync(label);
422
- })
423
- .then(() => {
424
- pxt.log("target uploaded");
425
- if (uploadDocs || uploadApiStrings) {
426
- return crowdin.internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs)
427
- .then(() => pxt.log("translations uploaded"));
428
- }
429
- else {
430
- pxt.log("skipping translations upload");
431
- return Promise.resolve();
432
- }
433
- });
423
+ await uploadTargetAsync(label);
424
+ }
425
+ pxt.log("target uploaded");
426
+ if (uploadDocs || uploadApiStrings) {
427
+ await crowdin.internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs);
428
+ pxt.log("translations uploaded");
429
+ }
430
+ else {
431
+ pxt.log("skipping translations upload");
432
+ }
434
433
  }
435
434
  }
436
435
  function lintJSONInDirectory(dir) {
@@ -6311,7 +6310,7 @@ ${pxt.crowdin.KEY_VARIABLE} - crowdin key
6311
6310
  advanced: true,
6312
6311
  }, pc => uploadTargetRefsAsync(pc.args[0]));
6313
6312
  advancedCommand("uploadtt", "upload tagged release", uploadTaggedTargetAsync, "");
6314
- advancedCommand("downloadtrgtranslations", "download translations from bundled projects", crowdin.downloadTargetTranslationsAsync, "<package>");
6313
+ advancedCommand("downloadtrgtranslations", "download translations from bundled projects", crowdin.downloadTargetTranslationsAsync, "[package]");
6315
6314
  p.defineCommand({
6316
6315
  name: "checkdocs",
6317
6316
  onlineHelp: true,
@@ -2,6 +2,7 @@ import Map = pxt.Map;
2
2
  import * as commandParser from './commandparser';
3
3
  export declare function uploadTargetTranslationsAsync(parsed?: commandParser.ParsedCommand): Promise<void>;
4
4
  export declare function internalUploadTargetTranslationsAsync(uploadApiStrings: boolean, uploadDocs: boolean): Promise<void>;
5
+ export declare function uploadBuiltStringsAsync(filename: string, crowdinDir?: string): Promise<void>;
5
6
  export declare function downloadTargetTranslationsAsync(parsed?: commandParser.ParsedCommand): Promise<void>;
6
- export declare function buildAllTranslationsAsync(langToStringsHandlerAsync: (fileName: string) => Promise<Map<Map<string>>>, singleDir?: string): Promise<void>;
7
+ export declare function buildAllTranslationsAsync(fetchFileTranslationAsync: (fileName: string) => Promise<Map<Map<string>>>, singleDir?: string): Promise<void>;
7
8
  export declare function execCrowdinAsync(cmd: string, ...args: string[]): Promise<void>;
package/built/crowdin.js CHANGED
@@ -1,107 +1,78 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.execCrowdinAsync = exports.buildAllTranslationsAsync = exports.downloadTargetTranslationsAsync = exports.internalUploadTargetTranslationsAsync = exports.uploadTargetTranslationsAsync = void 0;
3
+ exports.execCrowdinAsync = exports.buildAllTranslationsAsync = exports.downloadTargetTranslationsAsync = exports.uploadBuiltStringsAsync = exports.internalUploadTargetTranslationsAsync = exports.uploadTargetTranslationsAsync = void 0;
4
4
  const nodeutil = require("./nodeutil");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
- function crowdinCredentialsAsync() {
8
- const prj = pxt.appTarget.appTheme.crowdinProject;
9
- const branch = pxt.appTarget.appTheme.crowdinBranch;
10
- if (!prj) {
11
- pxt.log(`crowdin upload skipped, Crowdin project missing in target theme`);
12
- return Promise.resolve(undefined);
13
- }
14
- let key;
15
- if (pxt.crowdin.testMode)
16
- key = pxt.crowdin.TEST_KEY;
17
- else
18
- key = process.env[pxt.crowdin.KEY_VARIABLE];
19
- if (!key) {
20
- pxt.log(`Crowdin operation skipped: '${pxt.crowdin.KEY_VARIABLE}' variable is missing`);
21
- return Promise.resolve(undefined);
22
- }
23
- return Promise.resolve({ prj, key, branch });
24
- }
7
+ const crowdinApi_1 = require("./crowdinApi");
25
8
  function uploadTargetTranslationsAsync(parsed) {
26
9
  const uploadDocs = parsed && !!parsed.flags["docs"];
27
10
  const uploadApiStrings = parsed && !!parsed.flags["apis"];
28
- if (parsed && !!parsed.flags["test"])
11
+ if (parsed && !!parsed.flags["test"]) {
29
12
  pxt.crowdin.setTestMode();
13
+ }
30
14
  return internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs);
31
15
  }
32
16
  exports.uploadTargetTranslationsAsync = uploadTargetTranslationsAsync;
33
- function internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs) {
17
+ async function internalUploadTargetTranslationsAsync(uploadApiStrings, uploadDocs) {
34
18
  pxt.log(`uploading translations (apis ${uploadApiStrings ? "yes" : "no"}, docs ${uploadDocs ? "yes" : "no"})...`);
35
- return crowdinCredentialsAsync()
36
- .then(cred => {
37
- if (!cred)
38
- return Promise.resolve();
39
- pxt.log("got Crowdin credentials");
40
- const crowdinDir = pxt.appTarget.id;
41
- if (crowdinDir == "core") {
42
- if (!uploadDocs) {
43
- pxt.log('missing --docs flag, skipping');
44
- return Promise.resolve();
19
+ const crowdinDir = pxt.appTarget.id;
20
+ if (crowdinDir == "core") {
21
+ if (!uploadDocs) {
22
+ pxt.log('missing --docs flag, skipping');
23
+ return;
24
+ }
25
+ await uploadDocsTranslationsAsync("docs", crowdinDir);
26
+ await uploadDocsTranslationsAsync("common-docs", crowdinDir);
27
+ }
28
+ else {
29
+ if (uploadApiStrings) {
30
+ await uploadBuiltStringsAsync("built/target-strings.json", crowdinDir);
31
+ if (fs.existsSync("built/sim-strings.json")) {
32
+ await uploadBuiltStringsAsync("built/sim-strings.json", crowdinDir);
45
33
  }
46
- return uploadDocsTranslationsAsync("docs", crowdinDir, cred.branch, cred.prj, cred.key)
47
- .then(() => uploadDocsTranslationsAsync("common-docs", crowdinDir, cred.branch, cred.prj, cred.key));
34
+ await uploadBundledTranslationsAsync(crowdinDir);
48
35
  }
49
36
  else {
50
- let p = Promise.resolve();
51
- if (uploadApiStrings)
52
- p = p.then(() => execCrowdinAsync("upload", "built/target-strings.json", crowdinDir))
53
- .then(() => fs.existsSync("built/sim-strings.json") ? execCrowdinAsync("upload", "built/sim-strings.json", crowdinDir) : Promise.resolve())
54
- .then(() => uploadBundledTranslationsAsync(crowdinDir, cred.branch, cred.prj, cred.key));
55
- else
56
- p = p.then(() => pxt.log(`translations: skipping api strings upload`));
57
- if (uploadDocs)
58
- p = p.then(() => uploadDocsTranslationsAsync("docs", crowdinDir, cred.branch, cred.prj, cred.key))
59
- // scan for docs in bundled packages
60
- .then(() => Promise.all(pxt.appTarget.bundleddirs
61
- // there must be a folder under .../docs
62
- .filter(pkgDir => nodeutil.existsDirSync(path.join(pkgDir, "docs")))
63
- // upload to crowdin
64
- .map(pkgDir => uploadDocsTranslationsAsync(path.join(pkgDir, "docs"), crowdinDir, cred.branch, cred.prj, cred.key))).then(() => {
65
- pxt.log("docs uploaded");
66
- }));
67
- else
68
- p = p.then(() => pxt.log(`translations: skipping docs upload`));
69
- return p;
37
+ pxt.log(`translations: skipping api strings upload`);
70
38
  }
71
- });
39
+ if (uploadDocs) {
40
+ await uploadDocsTranslationsAsync("docs", crowdinDir);
41
+ await Promise.all(pxt.appTarget.bundleddirs
42
+ .filter(pkgDir => nodeutil.existsDirSync(path.join(pkgDir, "docs")))
43
+ .map(pkgDir => uploadDocsTranslationsAsync(path.join(pkgDir, "docs"), crowdinDir)));
44
+ pxt.log("docs uploaded");
45
+ }
46
+ else {
47
+ pxt.log(`translations: skipping docs upload`);
48
+ }
49
+ }
72
50
  }
73
51
  exports.internalUploadTargetTranslationsAsync = internalUploadTargetTranslationsAsync;
74
- function uploadDocsTranslationsAsync(srcDir, crowdinDir, branch, prj, key) {
75
- pxt.log(`uploading from ${srcDir} to ${crowdinDir} under project ${prj}/${branch || ""}`);
52
+ async function uploadBuiltStringsAsync(filename, crowdinDir) {
53
+ const baseName = path.basename(filename);
54
+ const crowdinFile = crowdinDir ? path.join(crowdinDir, baseName) : baseName;
55
+ const contents = fs.readFileSync(filename, "utf8");
56
+ pxt.log(`Uploading ${filename} to ${crowdinFile}`);
57
+ await (0, crowdinApi_1.uploadFileAsync)(crowdinFile, contents);
58
+ }
59
+ exports.uploadBuiltStringsAsync = uploadBuiltStringsAsync;
60
+ async function uploadDocsTranslationsAsync(srcDir, crowdinDir) {
61
+ pxt.log(`Uploading from ${srcDir} to ${crowdinDir}`);
76
62
  const ignoredDirectoriesList = getIgnoredDirectories(srcDir);
77
63
  const todo = nodeutil.allFiles(srcDir).filter(f => /\.md$/.test(f) && !/_locales/.test(f)).reverse();
78
- const knownFolders = {};
79
- const ensureFolderAsync = (crowdd) => {
80
- if (!knownFolders[crowdd]) {
81
- knownFolders[crowdd] = true;
82
- pxt.log(`creating folder ${crowdd}`);
83
- return pxt.crowdin.createDirectoryAsync(branch, prj, key, crowdd);
84
- }
85
- return Promise.resolve();
86
- };
87
- const nextFileAsync = (f) => {
88
- if (!f)
89
- return Promise.resolve();
90
- const crowdf = path.join(crowdinDir, f);
91
- const crowdd = path.dirname(crowdf);
64
+ for (const file of todo) {
65
+ if (!file)
66
+ continue;
67
+ const crowdinFile = path.join(crowdinDir, file);
92
68
  // check if file should be ignored
93
- if (ignoredDirectoriesList.filter(d => path.dirname(f).indexOf(d) == 0).length > 0) {
94
- pxt.log(`skipping ${f} because of .crowdinignore file`);
95
- return nextFileAsync(todo.pop());
69
+ if (ignoredDirectoriesList.filter(d => path.dirname(file).indexOf(d) == 0).length > 0) {
70
+ pxt.log(`skipping ${file} because of .crowdinignore file`);
71
+ continue;
96
72
  }
97
- const data = fs.readFileSync(f, 'utf8');
98
- pxt.log(`uploading ${f} to ${crowdf}`);
99
- return ensureFolderAsync(crowdd)
100
- .then(() => pxt.crowdin.uploadTranslationAsync(branch, prj, key, crowdf, data))
101
- .then(() => nextFileAsync(todo.pop()));
102
- };
103
- return ensureFolderAsync(path.join(crowdinDir, srcDir))
104
- .then(() => nextFileAsync(todo.pop()));
73
+ pxt.log(`Uploading ${file} to ${crowdinFile}`);
74
+ await (0, crowdinApi_1.uploadFileAsync)(crowdinFile, fs.readFileSync(file, "utf8"));
75
+ }
105
76
  }
106
77
  function getIgnoredDirectories(srcDir) {
107
78
  const ignoredDirectories = {};
@@ -117,40 +88,39 @@ function getIgnoredDirectories(srcDir) {
117
88
  });
118
89
  return Object.keys(ignoredDirectories).filter(d => ignoredDirectories[d]);
119
90
  }
120
- function uploadBundledTranslationsAsync(crowdinDir, branch, prj, key) {
91
+ async function uploadBundledTranslationsAsync(crowdinDir) {
121
92
  const todo = [];
122
- pxt.appTarget.bundleddirs.forEach(dir => {
93
+ for (const dir of pxt.appTarget.bundleddirs) {
123
94
  const locdir = path.join(dir, "_locales");
124
- if (fs.existsSync(locdir))
125
- fs.readdirSync(locdir)
126
- .filter(f => /strings\.json$/i.test(f))
127
- .forEach(f => todo.push(path.join(locdir, f)));
128
- });
95
+ if (fs.existsSync(locdir)) {
96
+ const stringsFiles = fs.readdirSync(locdir).filter(f => /strings\.json$/i.test(f));
97
+ for (const file of stringsFiles) {
98
+ todo.unshift(path.join(locdir, file));
99
+ }
100
+ }
101
+ }
129
102
  pxt.log(`uploading bundled translations to Crowdin (${todo.length} files)`);
130
- const nextFileAsync = () => {
131
- const f = todo.pop();
132
- if (!f)
133
- return Promise.resolve();
134
- const data = JSON.parse(fs.readFileSync(f, 'utf8'));
135
- const crowdf = path.join(crowdinDir, path.basename(f));
136
- pxt.log(`uploading ${f} to ${crowdf}`);
137
- return pxt.crowdin.uploadTranslationAsync(branch, prj, key, crowdf, JSON.stringify(data))
138
- .then(nextFileAsync);
139
- };
140
- return nextFileAsync();
103
+ for (const file of todo) {
104
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'));
105
+ const crowdinFile = path.join(crowdinDir, path.basename(file));
106
+ pxt.log(`Uploading ${file} to ${crowdinFile}`);
107
+ await (0, crowdinApi_1.uploadFileAsync)(crowdinFile, JSON.stringify(data));
108
+ }
141
109
  }
142
110
  async function downloadTargetTranslationsAsync(parsed) {
143
111
  const name = parsed === null || parsed === void 0 ? void 0 : parsed.args[0];
144
- const cred = await crowdinCredentialsAsync();
145
- if (!cred)
146
- return;
147
112
  await buildAllTranslationsAsync(async (fileName) => {
148
113
  pxt.log(`downloading ${fileName}`);
149
- return pxt.crowdin.downloadTranslationsAsync(cred.branch, cred.prj, cred.key, fileName, { translatedOnly: true, validatedOnly: true });
114
+ const translations = await (0, crowdinApi_1.downloadFileTranslationsAsync)(fileName);
115
+ const parsed = {};
116
+ for (const file of Object.keys(translations)) {
117
+ parsed[file] = JSON.parse(translations[file]);
118
+ }
119
+ return parsed;
150
120
  }, name);
151
121
  }
152
122
  exports.downloadTargetTranslationsAsync = downloadTargetTranslationsAsync;
153
- async function buildAllTranslationsAsync(langToStringsHandlerAsync, singleDir) {
123
+ async function buildAllTranslationsAsync(fetchFileTranslationAsync, singleDir) {
154
124
  await buildTranslationFilesAsync(["sim-strings.json"], "sim-strings.json");
155
125
  await buildTranslationFilesAsync(["target-strings.json"], "target-strings.json");
156
126
  await buildTranslationFilesAsync(["strings.json"], "strings.json", true);
@@ -176,7 +146,7 @@ async function buildAllTranslationsAsync(langToStringsHandlerAsync, singleDir) {
176
146
  const locdir = path.dirname(filePath);
177
147
  const projectdir = path.dirname(locdir);
178
148
  pxt.debug(`projectdir: ${projectdir}`);
179
- const data = await langToStringsHandlerAsync(crowdf);
149
+ const data = await fetchFileTranslationAsync(crowdf);
180
150
  for (const lang of Object.keys(data)) {
181
151
  const dataLang = data[lang];
182
152
  if (!dataLang || !stringifyTranslations(dataLang))
@@ -212,120 +182,125 @@ function stringifyTranslations(strings) {
212
182
  else
213
183
  return JSON.stringify(trg, null, 2);
214
184
  }
215
- function execCrowdinAsync(cmd, ...args) {
185
+ async function execCrowdinAsync(cmd, ...args) {
216
186
  pxt.log(`executing Crowdin command ${cmd}...`);
217
- const prj = pxt.appTarget.appTheme.crowdinProject;
218
- if (!prj) {
219
- console.log(`crowdin operation skipped, crowdin project not specified in pxtarget.json`);
220
- return Promise.resolve();
221
- }
222
- const branch = pxt.appTarget.appTheme.crowdinBranch;
223
- return crowdinCredentialsAsync()
224
- .then(crowdinCredentials => {
225
- if (!crowdinCredentials)
226
- return Promise.resolve();
227
- const key = crowdinCredentials.key;
228
- cmd = cmd.toLowerCase();
229
- if (!args[0] && (cmd != "clean" && cmd != "stats"))
230
- throw new Error(cmd == "status" ? "language missing" : "filename missing");
231
- switch (cmd) {
232
- case "stats": return statsCrowdinAsync(prj, key, args[0]);
233
- case "clean": return cleanCrowdinAsync(prj, key, args[0] || "docs");
234
- case "upload": return uploadCrowdinAsync(branch, prj, key, args[0], args[1]);
235
- case "download": {
236
- if (!args[1])
237
- throw new Error("output path missing");
238
- const fn = path.basename(args[0]);
239
- return pxt.crowdin.downloadTranslationsAsync(branch, prj, key, args[0], { translatedOnly: true, validatedOnly: true })
240
- .then(r => {
241
- Object.keys(r).forEach(k => {
242
- const rtranslations = stringifyTranslations(r[k]);
243
- if (!rtranslations)
244
- return;
245
- nodeutil.mkdirP(path.join(args[1], k));
246
- const outf = path.join(args[1], k, fn);
247
- console.log(`writing ${outf}`);
248
- nodeutil.writeFileSync(outf, rtranslations, { encoding: "utf8" });
249
- });
250
- });
187
+ switch (cmd.toLowerCase()) {
188
+ case "stats":
189
+ execStatsAsync(args[0]);
190
+ break;
191
+ case "clean":
192
+ await execCleanAsync(args[0] || "docs");
193
+ break;
194
+ case "upload":
195
+ if (!args[0]) {
196
+ throw new Error("filename missing");
251
197
  }
252
- default: throw new Error("unknown command");
253
- }
254
- });
198
+ await uploadBuiltStringsAsync(args[0], args[1]);
199
+ break;
200
+ case "download":
201
+ if (!args[1]) {
202
+ throw new Error("output path missing");
203
+ }
204
+ await execDownloadAsync(args[0], args[1]);
205
+ break;
206
+ default:
207
+ throw new Error("unknown command");
208
+ }
255
209
  }
256
210
  exports.execCrowdinAsync = execCrowdinAsync;
257
- function cleanCrowdinAsync(prj, key, dir) {
258
- const p = pxt.appTarget.id + "/" + dir;
259
- return pxt.crowdin.listFilesAsync(prj, key, p)
260
- .then(files => {
261
- files.filter(f => !nodeutil.fileExistsSync(f.fullName.substring(pxt.appTarget.id.length + 1)))
262
- .forEach(f => pxt.log(`crowdin: dead file: ${f.branch ? f.branch + "/" : ""}${f.fullName}`));
263
- });
211
+ async function execDownloadAsync(filename, outputDir) {
212
+ const basename = path.basename(filename);
213
+ pxt.log("Downloading translations");
214
+ const translations = await (0, crowdinApi_1.downloadFileTranslationsAsync)(filename);
215
+ for (const language of Object.keys(translations)) {
216
+ const langTranslations = stringifyTranslations(JSON.parse(translations[language]));
217
+ if (!langTranslations)
218
+ continue;
219
+ nodeutil.mkdirP(path.join(outputDir, language));
220
+ const outFilename = path.join(outputDir, language, basename);
221
+ console.log(`Writing ${outFilename}`);
222
+ nodeutil.writeFileSync(outFilename, langTranslations, { encoding: "utf8" });
223
+ }
264
224
  }
265
- function statsCrowdinAsync(prj, key, preferredLang) {
266
- pxt.log(`collecting crowdin stats for ${prj} ${preferredLang ? `for language ${preferredLang}` : `all languages`}`);
267
- console.log(`context\t language\t translated%\t approved%\t phrases\t translated\t approved`);
268
- const fn = `crowdinstats.csv`;
269
- let headers = 'sep=\t\r\n';
270
- headers += `id\t file\t language\t phrases\t translated\t approved\r\n`;
271
- nodeutil.writeFileSync(fn, headers, { encoding: "utf8" });
272
- return pxt.crowdin.projectInfoAsync(prj, key)
273
- .then(info => {
274
- if (!info)
275
- throw new Error("info failed");
276
- let languages = info.languages;
277
- // remove in-context language
278
- languages = languages.filter(l => l.code != ts.pxtc.Util.TRANSLATION_LOCALE);
279
- if (preferredLang)
280
- languages = languages.filter(lang => lang.code.toLowerCase() == preferredLang.toLowerCase());
281
- return Promise.all(languages.map(lang => langStatsCrowdinAsync(prj, key, lang.code)));
282
- }).then(() => {
283
- console.log(`stats written to ${fn}`);
284
- });
285
- function langStatsCrowdinAsync(prj, key, lang) {
286
- return pxt.crowdin.languageStatsAsync(prj, key, lang)
287
- .then(stats => {
288
- let uiphrases = 0;
289
- let uitranslated = 0;
290
- let uiapproved = 0;
291
- let corephrases = 0;
292
- let coretranslated = 0;
293
- let coreapproved = 0;
294
- let phrases = 0;
295
- let translated = 0;
296
- let approved = 0;
297
- let r = '';
298
- stats.forEach(stat => {
299
- const cfn = `${stat.branch ? stat.branch + "/" : ""}${stat.fullName}`;
300
- r += `${stat.id}\t ${cfn}\t ${lang}\t ${stat.phrases}\t ${stat.translated}\t ${stat.approved}\r\n`;
301
- if (stat.fullName == "strings.json") {
302
- uiapproved += Number(stat.approved);
303
- uitranslated += Number(stat.translated);
304
- uiphrases += Number(stat.phrases);
305
- }
306
- else if (/core-strings\.json$/.test(stat.fullName)) {
307
- coreapproved += Number(stat.approved);
308
- coretranslated += Number(stat.translated);
309
- corephrases += Number(stat.phrases);
310
- }
311
- else if (/-strings\.json$/.test(stat.fullName)) {
312
- approved += Number(stat.approved);
313
- translated += Number(stat.translated);
314
- phrases += Number(stat.phrases);
315
- }
316
- });
317
- fs.appendFileSync(fn, r, { encoding: "utf8" });
318
- console.log(`ui\t ${lang}\t ${(uitranslated / uiphrases * 100) >> 0}%\t ${(uiapproved / uiphrases * 100) >> 0}%\t ${uiphrases}\t ${uitranslated}\t ${uiapproved}`);
319
- console.log(`core\t ${lang}\t ${(coretranslated / corephrases * 100) >> 0}%\t ${(coreapproved / corephrases * 100) >> 0}%\t ${corephrases}\t ${coretranslated}\t ${coreapproved}`);
320
- console.log(`blocks\t ${lang}\t ${(translated / phrases * 100) >> 0}%\t ${(approved / phrases * 100) >> 0}%\t ${phrases}\t ${translated}\t ${approved}`);
321
- });
225
+ async function execCleanAsync(dir) {
226
+ const directoryPath = pxt.appTarget.id + "/" + dir;
227
+ const files = await (0, crowdinApi_1.listFilesAsync)(directoryPath);
228
+ for (const file of files) {
229
+ if (!nodeutil.fileExistsSync(file.substring(pxt.appTarget.id.length + 1))) {
230
+ pxt.log(`crowdin: dead file: ${file}`);
231
+ }
322
232
  }
323
233
  }
324
- function uploadCrowdinAsync(branch, prj, key, p, dir) {
325
- let fn = path.basename(p);
326
- if (dir)
327
- fn = dir.replace(/[\\/]*$/g, '') + '/' + fn;
328
- const data = JSON.parse(fs.readFileSync(p, "utf8"));
329
- pxt.log(`upload ${fn} (${Object.keys(data).length} strings) to https://crowdin.com/project/${prj}${branch ? `?branch=${branch}` : ''}`);
330
- return pxt.crowdin.uploadTranslationAsync(branch, prj, key, fn, JSON.stringify(data));
234
+ async function execStatsAsync(language) {
235
+ const crowdinDir = pxt.appTarget.id;
236
+ // If this is run inside pxt-core, give results for all targets
237
+ const isCore = crowdinDir === "core";
238
+ pxt.log(`collecting crowdin stats for ${isCore ? "all targets" : crowdinDir} ${language ? `for language ${language}` : `all languages`}`);
239
+ const files = await (0, crowdinApi_1.listFilesAsync)();
240
+ const stats = {};
241
+ const outputCsvFile = `crowdinstats.csv`;
242
+ let headers = 'sep=\t\r\n';
243
+ headers += `file\t language\t phrases\t translated\t approved\r\n`;
244
+ nodeutil.writeFileSync(outputCsvFile, headers, { encoding: "utf8" });
245
+ for (const file of files) {
246
+ pxt.debug("Processing file: " + file + "...");
247
+ // We only care about strings files
248
+ if (!file.endsWith("-strings.json"))
249
+ continue;
250
+ // Files for core are in the top-level of the crowdin project
251
+ const isCoreFile = file.indexOf("/") === -1;
252
+ // Only include files for the current target and core
253
+ if (!isCore && !isCoreFile && !file.startsWith(crowdinDir + "/"))
254
+ continue;
255
+ pxt.debug(`Downloading progress`);
256
+ const progress = await (0, crowdinApi_1.getFileProgressAsync)(file, language && [language]);
257
+ let fileCsvRows = "";
258
+ for (const language of progress) {
259
+ if (!stats[language.languageId]) {
260
+ stats[language.languageId] = {
261
+ uiphrases: 0,
262
+ uitranslated: 0,
263
+ uiapproved: 0,
264
+ corephrases: 0,
265
+ coretranslated: 0,
266
+ coreapproved: 0,
267
+ phrases: 0,
268
+ translated: 0,
269
+ approved: 0
270
+ };
271
+ }
272
+ const fileCsvColumns = [
273
+ file,
274
+ language.languageId,
275
+ language.phrases.total,
276
+ language.phrases.translated,
277
+ language.phrases.approved
278
+ ];
279
+ fileCsvRows += `${fileCsvColumns.join("\t ")}\r\n`;
280
+ const langStats = stats[language.languageId];
281
+ if (file === "strings.json") {
282
+ langStats.uiapproved += language.phrases.approved;
283
+ langStats.uitranslated += language.phrases.translated;
284
+ langStats.uiphrases += language.phrases.total;
285
+ }
286
+ else if (/core-strings\.json$/.test(file)) {
287
+ langStats.coreapproved += language.phrases.approved;
288
+ langStats.coretranslated += language.phrases.translated;
289
+ langStats.corephrases += language.phrases.total;
290
+ }
291
+ else {
292
+ langStats.approved += language.phrases.approved;
293
+ langStats.translated += language.phrases.translated;
294
+ langStats.phrases += language.phrases.total;
295
+ }
296
+ }
297
+ fs.appendFileSync(outputCsvFile, fileCsvRows, { encoding: "utf8" });
298
+ }
299
+ console.log(`context\t language\t translated%\t approved%\t phrases\t translated\t approved`);
300
+ for (const language of Object.keys(stats)) {
301
+ const { uiphrases, uitranslated, uiapproved, corephrases, coretranslated, coreapproved, phrases, translated, approved, } = stats[language];
302
+ console.log(`ui\t ${language}\t ${(uitranslated / uiphrases * 100) >> 0}%\t ${(uiapproved / uiphrases * 100) >> 0}%\t ${uiphrases}\t ${uitranslated}\t ${uiapproved}`);
303
+ console.log(`core\t ${language}\t ${(coretranslated / corephrases * 100) >> 0}%\t ${(coreapproved / corephrases * 100) >> 0}%\t ${corephrases}\t ${coretranslated}\t ${coreapproved}`);
304
+ console.log(`blocks\t ${language}\t ${(translated / phrases * 100) >> 0}%\t ${(approved / phrases * 100) >> 0}%\t ${phrases}\t ${translated}\t ${approved}`);
305
+ }
331
306
  }
@@ -0,0 +1,9 @@
1
+ export declare function setProjectId(id: number): void;
2
+ export declare function uploadFileAsync(fileName: string, fileContent: string): Promise<void>;
3
+ export declare function getProjectInfoAsync(): Promise<import("@crowdin/crowdin-api-client").ProjectsGroupsModel.Project | import("@crowdin/crowdin-api-client").ProjectsGroupsModel.ProjectSettings>;
4
+ export declare function getProjectProgressAsync(languages?: string[]): Promise<import("@crowdin/crowdin-api-client").TranslationStatusModel.LanguageProgress[]>;
5
+ export declare function getDirectoryProgressAsync(directory: string, languages?: string[]): Promise<import("@crowdin/crowdin-api-client").TranslationStatusModel.LanguageProgress[]>;
6
+ export declare function getFileProgressAsync(file: string, languages?: string[]): Promise<import("@crowdin/crowdin-api-client").TranslationStatusModel.LanguageProgress[]>;
7
+ export declare function listFilesAsync(directory?: string): Promise<string[]>;
8
+ export declare function downloadTranslationsAsync(directory?: string): Promise<pxt.Map<string>>;
9
+ export declare function downloadFileTranslationsAsync(fileName: string): Promise<pxt.Map<string>>;