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 +44 -45
- package/built/crowdin.d.ts +2 -1
- package/built/crowdin.js +189 -214
- package/built/crowdinApi.d.ts +9 -0
- package/built/crowdinApi.js +310 -0
- package/built/pxt.js +44 -285
- package/built/pxtlib.d.ts +0 -35
- package/built/pxtlib.js +0 -240
- package/built/target.js +1 -1
- package/built/web/ai.2.min.js +2 -2
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtembed.js +1 -1
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/common-docs/teachertool/catalog-shared.json +20 -11
- package/common-docs/teachertool/test/catalog-shared.json +52 -9
- package/common-docs/teachertool/test/validator-plans-shared.json +18 -3
- package/common-docs/teachertool/validator-plans-shared.json +153 -30
- package/localtypings/pxtarget.d.ts +1 -0
- package/package.json +5 -1
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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, "
|
|
6313
|
+
advancedCommand("downloadtrgtranslations", "download translations from bundled projects", crowdin.downloadTargetTranslationsAsync, "[package]");
|
|
6315
6314
|
p.defineCommand({
|
|
6316
6315
|
name: "checkdocs",
|
|
6317
6316
|
onlineHelp: true,
|
package/built/crowdin.d.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
if (!
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
.then(() => uploadDocsTranslationsAsync("common-docs", crowdinDir, cred.branch, cred.prj, cred.key));
|
|
34
|
+
await uploadBundledTranslationsAsync(crowdinDir);
|
|
48
35
|
}
|
|
49
36
|
else {
|
|
50
|
-
|
|
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
|
|
75
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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(
|
|
94
|
-
pxt.log(`skipping ${
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
91
|
+
async function uploadBundledTranslationsAsync(crowdinDir) {
|
|
121
92
|
const todo = [];
|
|
122
|
-
pxt.appTarget.bundleddirs
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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>>;
|