pxt-core 9.2.9 → 9.2.11

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/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,155 @@ 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
+ case "restore":
207
+ if (!args[0]) {
208
+ throw new Error("Time missing");
209
+ }
210
+ if (args[1] !== "force" && !pxt.crowdin.testMode) {
211
+ throw new Error(`Refusing to run restore command without 'force' argument. Re-run as 'pxt crowdin restore <date> force' to proceed or use --test flag to test.`);
212
+ }
213
+ execRestoreFiles(args[0]);
214
+ break;
215
+ default:
216
+ throw new Error("unknown command");
217
+ }
255
218
  }
256
219
  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
- });
220
+ async function execDownloadAsync(filename, outputDir) {
221
+ const basename = path.basename(filename);
222
+ pxt.log("Downloading translations");
223
+ const translations = await (0, crowdinApi_1.downloadFileTranslationsAsync)(filename);
224
+ for (const language of Object.keys(translations)) {
225
+ const langTranslations = stringifyTranslations(JSON.parse(translations[language]));
226
+ if (!langTranslations)
227
+ continue;
228
+ nodeutil.mkdirP(path.join(outputDir, language));
229
+ const outFilename = path.join(outputDir, language, basename);
230
+ console.log(`Writing ${outFilename}`);
231
+ nodeutil.writeFileSync(outFilename, langTranslations, { encoding: "utf8" });
232
+ }
264
233
  }
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`;
234
+ async function execCleanAsync(dir) {
235
+ const directoryPath = pxt.appTarget.id + "/" + dir;
236
+ const files = await (0, crowdinApi_1.listFilesAsync)(directoryPath);
237
+ for (const file of files) {
238
+ if (!nodeutil.fileExistsSync(file.substring(pxt.appTarget.id.length + 1))) {
239
+ pxt.log(`crowdin: dead file: ${file}`);
240
+ }
241
+ }
242
+ }
243
+ async function execStatsAsync(language) {
244
+ const crowdinDir = pxt.appTarget.id;
245
+ // If this is run inside pxt-core, give results for all targets
246
+ const isCore = crowdinDir === "core";
247
+ pxt.log(`collecting crowdin stats for ${isCore ? "all targets" : crowdinDir} ${language ? `for language ${language}` : `all languages`}`);
248
+ const files = await (0, crowdinApi_1.listFilesAsync)();
249
+ const stats = {};
250
+ const outputCsvFile = `crowdinstats.csv`;
269
251
  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
- });
252
+ headers += `file\t language\t phrases\t translated\t approved\r\n`;
253
+ nodeutil.writeFileSync(outputCsvFile, headers, { encoding: "utf8" });
254
+ for (const file of files) {
255
+ pxt.debug("Processing file: " + file + "...");
256
+ // We only care about strings files
257
+ if (!file.endsWith("-strings.json"))
258
+ continue;
259
+ // Files for core are in the top-level of the crowdin project
260
+ const isCoreFile = file.indexOf("/") === -1;
261
+ // Only include files for the current target and core
262
+ if (!isCore && !isCoreFile && !file.startsWith(crowdinDir + "/"))
263
+ continue;
264
+ pxt.debug(`Downloading progress`);
265
+ const progress = await (0, crowdinApi_1.getFileProgressAsync)(file, language && [language]);
266
+ let fileCsvRows = "";
267
+ for (const language of progress) {
268
+ if (!stats[language.languageId]) {
269
+ stats[language.languageId] = {
270
+ uiphrases: 0,
271
+ uitranslated: 0,
272
+ uiapproved: 0,
273
+ corephrases: 0,
274
+ coretranslated: 0,
275
+ coreapproved: 0,
276
+ phrases: 0,
277
+ translated: 0,
278
+ approved: 0
279
+ };
280
+ }
281
+ const fileCsvColumns = [
282
+ file,
283
+ language.languageId,
284
+ language.phrases.total,
285
+ language.phrases.translated,
286
+ language.phrases.approved
287
+ ];
288
+ fileCsvRows += `${fileCsvColumns.join("\t ")}\r\n`;
289
+ const langStats = stats[language.languageId];
290
+ if (file === "strings.json") {
291
+ langStats.uiapproved += language.phrases.approved;
292
+ langStats.uitranslated += language.phrases.translated;
293
+ langStats.uiphrases += language.phrases.total;
294
+ }
295
+ else if (/core-strings\.json$/.test(file)) {
296
+ langStats.coreapproved += language.phrases.approved;
297
+ langStats.coretranslated += language.phrases.translated;
298
+ langStats.corephrases += language.phrases.total;
299
+ }
300
+ else {
301
+ langStats.approved += language.phrases.approved;
302
+ langStats.translated += language.phrases.translated;
303
+ langStats.phrases += language.phrases.total;
304
+ }
305
+ }
306
+ fs.appendFileSync(outputCsvFile, fileCsvRows, { encoding: "utf8" });
307
+ }
308
+ console.log(`context\t language\t translated%\t approved%\t phrases\t translated\t approved`);
309
+ for (const language of Object.keys(stats)) {
310
+ const { uiphrases, uitranslated, uiapproved, corephrases, coretranslated, coreapproved, phrases, translated, approved, } = stats[language];
311
+ console.log(`ui\t ${language}\t ${(uitranslated / uiphrases * 100) >> 0}%\t ${(uiapproved / uiphrases * 100) >> 0}%\t ${uiphrases}\t ${uitranslated}\t ${uiapproved}`);
312
+ console.log(`core\t ${language}\t ${(coretranslated / corephrases * 100) >> 0}%\t ${(coreapproved / corephrases * 100) >> 0}%\t ${corephrases}\t ${coretranslated}\t ${coreapproved}`);
313
+ console.log(`blocks\t ${language}\t ${(translated / phrases * 100) >> 0}%\t ${(approved / phrases * 100) >> 0}%\t ${phrases}\t ${translated}\t ${approved}`);
322
314
  }
323
315
  }
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));
316
+ async function execRestoreFiles(time) {
317
+ let cutoffTime;
318
+ if (!isNaN(parseInt(time + ""))) {
319
+ cutoffTime = parseInt(time + "");
320
+ }
321
+ else {
322
+ cutoffTime = new Date(time).getTime();
323
+ }
324
+ const crowdinDir = pxt.appTarget.id;
325
+ // If this is run inside pxt-core, give results for all targets
326
+ const isCore = crowdinDir === "core";
327
+ const files = await (0, crowdinApi_1.listFilesAsync)();
328
+ for (const file of files) {
329
+ pxt.debug("Processing file: " + file + "...");
330
+ // Files for core are in the top-level of the crowdin project
331
+ const isCoreFile = file.indexOf("/") === -1;
332
+ if ((isCore && !isCoreFile) || !file.startsWith(crowdinDir + "/"))
333
+ continue;
334
+ await (0, crowdinApi_1.restoreFileBefore)(file, cutoffTime);
335
+ }
331
336
  }
@@ -0,0 +1,10 @@
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>>;
10
+ export declare function restoreFileBefore(filename: string, cutoffTime: number): Promise<void>;