scratch-l10n 3.18.357 → 4.0.0
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/.github/PULL_REQUEST_TEMPLATE.md +4 -4
- package/.nvmrc +1 -1
- package/CHANGELOG.md +22 -0
- package/dist/l10n.js +237 -380
- package/dist/l10n.js.map +1 -1
- package/dist/localeData.js +225 -369
- package/dist/localeData.js.map +1 -1
- package/dist/supportedLocales.js +139 -289
- package/dist/supportedLocales.js.map +1 -1
- package/lib/progress-logger.mjs +42 -0
- package/lib/transifex.js +70 -22
- package/package.json +15 -15
- package/scripts/{build-data.js → build-data.mjs} +3 -3
- package/scripts/{tx-pull-editor.js → tx-pull-editor.mjs} +3 -3
- package/scripts/{tx-pull-www.js → tx-pull-www.mjs} +42 -26
- package/scripts/{tx-push-help.js → tx-push-help.mjs} +1 -1
- package/scripts/{validate-extension-inputs.js → validate-extension-inputs.mjs} +2 -2
- package/scripts/{validate-translations.js → validate-translations.mjs} +4 -4
- package/scripts/{validate-www.js → validate-www.mjs} +4 -4
- package/src/index.mjs +3 -0
- package/src/{locale-data.js → locale-data.mjs} +1 -1
- package/src/index.js +0 -3
- /package/lib/{validate.js → validate.mjs} +0 -0
- /package/src/{supported-locales.js → supported-locales.mjs} +0 -0
package/lib/transifex.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @fileoverview
|
@@ -8,6 +8,10 @@
|
|
8
8
|
const transifexApi = require('@transifex/api').transifexApi;
|
9
9
|
const download = require('download');
|
10
10
|
|
11
|
+
/**
|
12
|
+
* @import {Collection, JsonApiResource} from '@transifex/api';
|
13
|
+
*/
|
14
|
+
|
11
15
|
const ORG_NAME = 'llk';
|
12
16
|
const SOURCE_LOCALE = 'en';
|
13
17
|
|
@@ -22,15 +26,44 @@ try {
|
|
22
26
|
throw err;
|
23
27
|
}
|
24
28
|
|
29
|
+
/*
|
30
|
+
* The Transifex JS API wraps the Transifex JSON API, and is built around the concept of a `Collection`.
|
31
|
+
* A `Collection` begins as a URL builder: methods like `filter` and `sort` add query parameters to the URL.
|
32
|
+
* The `download` method doesn't actually download anything: it returns the built URL. It seems to be intended
|
33
|
+
* primarily for internal use, but shows up in the documentation despite not being advertised in the .d.ts file.
|
34
|
+
* The `download` method is mainly used to skip the `fetch` method in favor of downloading the resource yourself.
|
35
|
+
* The `fetch` method sends a request to the URL and returns a promise that resolves to the first page of results.
|
36
|
+
* If there's only one page of results, the `data` property of the collection object will be an array of all results.
|
37
|
+
* However, if there are multiple pages of results, the `data` property will only contain the first page of results.
|
38
|
+
* Previous versions of this code would unsafely assume that the `data` property contained all results.
|
39
|
+
* The `all` method returns an async iterator that yields all results, fetching additional pages as needed.
|
40
|
+
*/
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Collects all resources from all pages of a potentially-paginated Transifex collection.
|
44
|
+
* It's not necessary, but also not harmful, to call `fetch()` on the collection before calling this function.
|
45
|
+
* @param {Collection} collection A collection of Transifex resources.
|
46
|
+
* @returns {Promise<JsonApiResource[]>} An array of all resources in the collection.
|
47
|
+
*/
|
48
|
+
const collectAll = async function (collection) {
|
49
|
+
await collection.fetch(); // fetch the first page if it hasn't already been fetched
|
50
|
+
const collected = [];
|
51
|
+
for await (const item of collection.all()) {
|
52
|
+
collected.push(item);
|
53
|
+
}
|
54
|
+
return collected;
|
55
|
+
};
|
56
|
+
|
25
57
|
/**
|
26
58
|
* Creates a download event for a specific project, resource, and locale.
|
59
|
+
* Returns the URL to download the resource.
|
27
60
|
* @param {string} projectSlug - project slug (for example, "scratch-editor")
|
28
61
|
* @param {string} resourceSlug - resource slug (for example, "blocks")
|
29
62
|
* @param {string} localeCode - language code (for example, "ko")
|
30
63
|
* @param {string} mode - translation status of strings to include
|
31
|
-
* @returns {Promise<string>} -
|
64
|
+
* @returns {Promise<string>} - URL to download the resource
|
32
65
|
*/
|
33
|
-
const
|
66
|
+
const getResourceLocation = async function (projectSlug, resourceSlug, localeCode, mode = 'default') {
|
34
67
|
const resource = {
|
35
68
|
data: {
|
36
69
|
id: `o:${ORG_NAME}:p:${projectSlug}:r:${resourceSlug}`,
|
@@ -66,21 +99,38 @@ const downloadResource = async function (projectSlug, resourceSlug, localeCode,
|
|
66
99
|
* @param {string} resource - resource slug (for example, "blocks")
|
67
100
|
* @param {string} locale - language code (for example, "ko")
|
68
101
|
* @param {string} mode - translation status of strings to include
|
69
|
-
* @returns {Promise<object>} - JSON object of translated resource strings (or, of the original
|
102
|
+
* @returns {Promise<object>} - JSON object of translated resource strings (or, of the original resource
|
70
103
|
* strings, if the local is the source language)
|
71
104
|
*/
|
72
105
|
const txPull = async function (project, resource, locale, mode = 'default') {
|
73
|
-
const url = await downloadResource(project, resource, locale, mode);
|
74
106
|
let buffer;
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
107
|
+
try {
|
108
|
+
const url = await getResourceLocation(project, resource, locale, mode);
|
109
|
+
for (let i = 0; i < 5; i++) {
|
110
|
+
if (i > 0) {
|
111
|
+
console.log(`Retrying txPull download after ${i} failed attempt(s)`);
|
112
|
+
}
|
113
|
+
try {
|
114
|
+
buffer = await download(url); // might throw(?)
|
115
|
+
break;
|
116
|
+
} catch (e) {
|
117
|
+
console.error(e, {project, resource, locale, buffer});
|
118
|
+
}
|
119
|
+
}
|
120
|
+
if (!buffer) {
|
121
|
+
throw Error(`txPull download failed after 5 retries: ${url}`);
|
81
122
|
}
|
123
|
+
buffer = buffer.toString();
|
124
|
+
return JSON.parse(buffer);
|
125
|
+
} catch (e) {
|
126
|
+
e.cause = {
|
127
|
+
project,
|
128
|
+
resource,
|
129
|
+
locale,
|
130
|
+
buffer
|
131
|
+
};
|
132
|
+
throw e;
|
82
133
|
}
|
83
|
-
throw Error('failed to pull after 5 retries');
|
84
134
|
};
|
85
135
|
|
86
136
|
/**
|
@@ -89,13 +139,13 @@ const txPull = async function (project, resource, locale, mode = 'default') {
|
|
89
139
|
* @returns {Promise<array>} - array of strings, slugs identifying each resource in the project
|
90
140
|
*/
|
91
141
|
const txResources = async function (project) {
|
92
|
-
const resources =
|
142
|
+
const resources = transifexApi.Resource.filter({
|
93
143
|
project: `o:${ORG_NAME}:p:${project}`
|
94
144
|
});
|
95
145
|
|
96
|
-
await resources
|
146
|
+
const resourcesData = await collectAll(resources);
|
97
147
|
|
98
|
-
const slugs =
|
148
|
+
const slugs = resourcesData.map(r =>
|
99
149
|
// r.id is a longer id string, like "o:llk:p:scratch-website:r:about-l10njson"
|
100
150
|
// We just want the slug that comes after ":r:" ("about-l10njson")
|
101
151
|
r.id.split(':r:')[1]
|
@@ -105,15 +155,14 @@ const txResources = async function (project) {
|
|
105
155
|
|
106
156
|
/**
|
107
157
|
* @param {string} project - project slug (for example)
|
108
|
-
* @returns {
|
158
|
+
* @returns {Promise<JsonApiResource[]>} - array of resource objects
|
109
159
|
*/
|
110
160
|
const txResourcesObjects = async function (project) {
|
111
|
-
const resources =
|
161
|
+
const resources = transifexApi.Resource.filter({
|
112
162
|
project: `o:${ORG_NAME}:p:${project}`
|
113
163
|
});
|
114
164
|
|
115
|
-
|
116
|
-
return resources.data;
|
165
|
+
return collectAll(resources);
|
117
166
|
};
|
118
167
|
|
119
168
|
/**
|
@@ -128,9 +177,8 @@ const txAvailableLanguages = async function (slug) {
|
|
128
177
|
});
|
129
178
|
|
130
179
|
const languages = await project.fetch('languages');
|
131
|
-
await languages
|
132
|
-
return
|
133
|
-
|
180
|
+
const languagesData = await collectAll(languages);
|
181
|
+
return languagesData.map(l => l.attributes.code);
|
134
182
|
};
|
135
183
|
|
136
184
|
/**
|
package/package.json
CHANGED
@@ -1,40 +1,40 @@
|
|
1
1
|
{
|
2
2
|
"name": "scratch-l10n",
|
3
|
-
"version": "
|
3
|
+
"version": "4.0.0",
|
4
4
|
"description": "Localization for the Scratch 3.0 components",
|
5
5
|
"main": "./dist/l10n.js",
|
6
|
-
"browser": "./src/index.
|
6
|
+
"browser": "./src/index.mjs",
|
7
7
|
"bin": {
|
8
8
|
"build-i18n-src": "./scripts/build-i18n-src.js",
|
9
9
|
"tx-push-src": "./scripts/tx-push-src.js"
|
10
10
|
},
|
11
11
|
"scripts": {
|
12
12
|
"build": "npm run clean && npm run build:data && webpack --progress --colors --bail",
|
13
|
-
"build:data": "
|
13
|
+
"build:data": "node scripts/build-data.mjs",
|
14
14
|
"clean": "rimraf ./dist ./locales && mkdirp dist locales",
|
15
15
|
"lint": "npm run lint:js && npm run lint:json",
|
16
|
-
"lint:js": "eslint . --ext .js",
|
16
|
+
"lint:js": "eslint . --ext .js,.mjs,.cjs",
|
17
17
|
"lint:json": "jshint -e .json www editor/blocks editor/extensions editor/interface editor/paint-editor",
|
18
18
|
"prepare": "husky install",
|
19
|
-
"pull:blocks": "
|
19
|
+
"pull:blocks": "node scripts/tx-pull-editor.mjs scratch-editor blocks ./editor/blocks/",
|
20
20
|
"pull:editor": "npm run pull:blocks && npm run pull:extensions && npm run pull:paint && npm run pull:interface",
|
21
|
-
"pull:extensions": "
|
21
|
+
"pull:extensions": "node scripts/tx-pull-editor.mjs scratch-editor extensions ./editor/extensions/",
|
22
22
|
"pull:help": "npm run pull:help:names && npm run pull:help:articles",
|
23
23
|
"pull:help:articles": "./scripts/tx-pull-help-articles.js",
|
24
24
|
"pull:help:names": "./scripts/tx-pull-help-names.js",
|
25
|
-
"pull:interface": "
|
26
|
-
"pull:paint": "
|
27
|
-
"pull:www": "
|
28
|
-
"push:help": "./scripts/tx-push-help.
|
25
|
+
"pull:interface": "node scripts/tx-pull-editor.mjs scratch-editor interface ./editor/interface/",
|
26
|
+
"pull:paint": "node scripts/tx-pull-editor.mjs scratch-editor paint-editor ./editor/paint-editor/",
|
27
|
+
"pull:www": "node scripts/tx-pull-www.mjs ./www",
|
28
|
+
"push:help": "./scripts/tx-push-help.mjs",
|
29
29
|
"sync:help": "npm run push:help && npm run pull:help",
|
30
30
|
"test": "npm run lint:js && npm run validate:editor && npm run validate:www && npm run build && npm run lint:json",
|
31
31
|
"update": "scripts/update-translations.sh",
|
32
|
-
"validate:blocks": "
|
32
|
+
"validate:blocks": "node scripts/validate-translations.mjs ./editor/blocks/",
|
33
33
|
"validate:editor": "npm run validate:blocks && npm run validate:extensions && npm run validate:interface && npm run validate:paint",
|
34
|
-
"validate:extensions": "
|
35
|
-
"validate:interface": "
|
36
|
-
"validate:paint": "
|
37
|
-
"validate:www": "
|
34
|
+
"validate:extensions": "node scripts/validate-translations.mjs ./editor/extensions/ && node scripts/validate-extension-inputs.mjs",
|
35
|
+
"validate:interface": "node scripts/validate-translations.mjs ./editor/interface/",
|
36
|
+
"validate:paint": "node scripts/validate-translations.mjs ./editor/paint-editor/",
|
37
|
+
"validate:www": "node scripts/validate-www.mjs ./www"
|
38
38
|
},
|
39
39
|
"repository": {
|
40
40
|
"type": "git",
|
@@ -40,12 +40,12 @@ Missing locales are ignored, react-intl will use the default messages for them.
|
|
40
40
|
*/
|
41
41
|
import * as fs from 'fs';
|
42
42
|
import * as path from 'path';
|
43
|
-
import
|
43
|
+
import mkdirp from 'mkdirp';
|
44
44
|
import defaultsDeep from 'lodash.defaultsdeep';
|
45
|
-
import locales from '../src/supported-locales.
|
45
|
+
import locales from '../src/supported-locales.mjs';
|
46
46
|
|
47
47
|
const MSGS_DIR = './locales/';
|
48
|
-
|
48
|
+
mkdirp.sync(MSGS_DIR);
|
49
49
|
let missingLocales = [];
|
50
50
|
|
51
51
|
const combineJson = (component) => {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @fileoverview
|
@@ -29,8 +29,8 @@ if (!process.env.TX_TOKEN || args.length < 3) {
|
|
29
29
|
import fs from 'fs';
|
30
30
|
import path from 'path';
|
31
31
|
import {txPull} from '../lib/transifex.js';
|
32
|
-
import {validateTranslations} from '../lib/validate.
|
33
|
-
import locales, {localeMap} from '../src/supported-locales.
|
32
|
+
import {validateTranslations} from '../lib/validate.mjs';
|
33
|
+
import locales, {localeMap} from '../src/supported-locales.mjs';
|
34
34
|
import {batchMap} from '../lib/batch.js';
|
35
35
|
|
36
36
|
// Globals
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @fileoverview
|
@@ -27,12 +27,13 @@ if (!process.env.TX_TOKEN || args.length < 1) {
|
|
27
27
|
process.exit(1);
|
28
28
|
}
|
29
29
|
|
30
|
-
import fs from 'fs';
|
30
|
+
import fs from 'fs/promises';
|
31
31
|
import path from 'path';
|
32
32
|
import mkdirp from 'mkdirp';
|
33
33
|
import {txPull, txResources} from '../lib/transifex.js';
|
34
|
-
import locales, {localeMap} from '../src/supported-locales.
|
34
|
+
import locales, {localeMap} from '../src/supported-locales.mjs';
|
35
35
|
import {batchMap} from '../lib/batch.js';
|
36
|
+
import {ProgressLogger} from '../lib/progress-logger.mjs';
|
36
37
|
|
37
38
|
// Globals
|
38
39
|
const PROJECT = 'scratch-website';
|
@@ -46,27 +47,34 @@ const getLocaleData = async function (item) {
|
|
46
47
|
const locale = item.locale;
|
47
48
|
const resource = item.resource;
|
48
49
|
let txLocale = localeMap[locale] || locale;
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
50
|
+
|
51
|
+
const translations = await txPull(PROJECT, resource, txLocale);
|
52
|
+
|
53
|
+
const txOutdir = `${OUTPUT_DIR}/${PROJECT}.${resource}`;
|
54
|
+
const fileName = `${txOutdir}/${locale}.json`;
|
55
|
+
|
56
|
+
try {
|
57
|
+
mkdirp.sync(txOutdir);
|
58
|
+
await fs.writeFile(
|
59
|
+
fileName,
|
60
|
+
JSON.stringify(translations, null, 4)
|
61
|
+
);
|
62
|
+
|
63
|
+
return {
|
64
|
+
resource,
|
65
|
+
locale,
|
66
|
+
fileName
|
67
|
+
};
|
68
|
+
} catch (e) {
|
69
|
+
e.cause = {
|
70
|
+
resource,
|
71
|
+
locale,
|
72
|
+
translations,
|
73
|
+
txOutdir,
|
74
|
+
fileName
|
75
|
+
};
|
76
|
+
throw e;
|
68
77
|
}
|
69
|
-
throw Error('failed to pull translations after 5 retries');
|
70
78
|
};
|
71
79
|
|
72
80
|
const expandResourceFiles = (resources) => {
|
@@ -84,13 +92,21 @@ const expandResourceFiles = (resources) => {
|
|
84
92
|
};
|
85
93
|
|
86
94
|
const pullTranslations = async function () {
|
87
|
-
const resources = await txResources(
|
95
|
+
const resources = await txResources(PROJECT);
|
88
96
|
const allFiles = expandResourceFiles(resources);
|
89
97
|
|
98
|
+
const progress = new ProgressLogger(allFiles.length);
|
99
|
+
|
90
100
|
try {
|
91
|
-
await batchMap(allFiles, CONCURRENCY_LIMIT,
|
101
|
+
await batchMap(allFiles, CONCURRENCY_LIMIT, async item => {
|
102
|
+
try {
|
103
|
+
await getLocaleData(item);
|
104
|
+
} finally {
|
105
|
+
progress.increment();
|
106
|
+
}
|
107
|
+
});
|
92
108
|
} catch (err) {
|
93
|
-
console.error(err);
|
109
|
+
console.error(err);
|
94
110
|
process.exit(1);
|
95
111
|
}
|
96
112
|
};
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @fileoverview
|
@@ -9,7 +9,7 @@ import fs from 'fs';
|
|
9
9
|
import path from 'path';
|
10
10
|
import async from 'async';
|
11
11
|
import assert from 'assert';
|
12
|
-
import locales from '../src/supported-locales.
|
12
|
+
import locales from '../src/supported-locales.mjs';
|
13
13
|
|
14
14
|
// Globals
|
15
15
|
const JSON_DIR = path.join(process.cwd(), '/editor/extensions');
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @fileoverview
|
@@ -8,7 +8,7 @@
|
|
8
8
|
const args = process.argv.slice(2);
|
9
9
|
const usage = `
|
10
10
|
Validate translation json. Usage:
|
11
|
-
|
11
|
+
node validate_translations.mjs path
|
12
12
|
path: where to find the downloaded json files
|
13
13
|
`;
|
14
14
|
// Fail immediately if the TX_TOKEN is not defined
|
@@ -19,8 +19,8 @@ if (args.length < 1) {
|
|
19
19
|
import fs from 'fs';
|
20
20
|
import path from 'path';
|
21
21
|
import async from 'async';
|
22
|
-
import {validateTranslations} from '../lib/validate.
|
23
|
-
import locales from '../src/supported-locales.
|
22
|
+
import {validateTranslations} from '../lib/validate.mjs';
|
23
|
+
import locales from '../src/supported-locales.mjs';
|
24
24
|
|
25
25
|
// Globals
|
26
26
|
const JSON_DIR = path.resolve(args[0]);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env node
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @fileoverview
|
@@ -8,7 +8,7 @@
|
|
8
8
|
const args = process.argv.slice(2);
|
9
9
|
const usage = `
|
10
10
|
Validate translation json. Usage:
|
11
|
-
|
11
|
+
node validate_www.mjs path
|
12
12
|
path: root folder for all the www resource folders
|
13
13
|
`;
|
14
14
|
if (args.length < 1) {
|
@@ -19,8 +19,8 @@ import fs from 'fs';
|
|
19
19
|
import path from 'path';
|
20
20
|
import glob from 'glob';
|
21
21
|
import async from 'async';
|
22
|
-
import {validateTranslations} from '../lib/validate.
|
23
|
-
import locales from '../src/supported-locales.
|
22
|
+
import {validateTranslations} from '../lib/validate.mjs';
|
23
|
+
import locales from '../src/supported-locales.mjs';
|
24
24
|
|
25
25
|
// Globals
|
26
26
|
const WWW_DIR = path.resolve(args[0]);
|
package/src/index.mjs
ADDED
@@ -72,7 +72,7 @@ import xh from './locale-data/xh';
|
|
72
72
|
import zh from './locale-data/zh';
|
73
73
|
import zu from './locale-data/zu';
|
74
74
|
|
75
|
-
import {customLocales} from './supported-locales.
|
75
|
+
import {customLocales} from './supported-locales.mjs';
|
76
76
|
|
77
77
|
let localeData = [].concat(
|
78
78
|
en,
|
package/src/index.js
DELETED
File without changes
|
File without changes
|