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/lib/transifex.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env babel-node
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>} - id of the created download event
64
+ * @returns {Promise<string>} - URL to download the resource
32
65
  */
33
- const downloadResource = async function (projectSlug, resourceSlug, localeCode, mode = 'default') {
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 resourse
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
- for (let i = 0; i < 5; i++) {
76
- try {
77
- buffer = await download(url);
78
- return JSON.parse(buffer.toString());
79
- } catch (e) {
80
- process.stdout.write(`got ${e.message}, retrying after ${i + 1} failed attempt(s)\n`);
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 = await transifexApi.Resource.filter({
142
+ const resources = transifexApi.Resource.filter({
93
143
  project: `o:${ORG_NAME}:p:${project}`
94
144
  });
95
145
 
96
- await resources.fetch();
146
+ const resourcesData = await collectAll(resources);
97
147
 
98
- const slugs = resources.data.map(r =>
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 {object[]} - array of resource objects
158
+ * @returns {Promise<JsonApiResource[]>} - array of resource objects
109
159
  */
110
160
  const txResourcesObjects = async function (project) {
111
- const resources = await transifexApi.Resource.filter({
161
+ const resources = transifexApi.Resource.filter({
112
162
  project: `o:${ORG_NAME}:p:${project}`
113
163
  });
114
164
 
115
- await resources.fetch();
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.fetch();
132
- return languages.data.map(l => l.attributes.code);
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.18.357",
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.js",
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": "babel-node scripts/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": "babel-node scripts/tx-pull-editor scratch-editor blocks ./editor/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": "babel-node scripts/tx-pull-editor scratch-editor extensions ./editor/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": "babel-node scripts/tx-pull-editor scratch-editor interface ./editor/interface/",
26
- "pull:paint": "babel-node scripts/tx-pull-editor scratch-editor paint-editor ./editor/paint-editor/",
27
- "pull:www": "babel-node scripts/tx-pull-www ./www",
28
- "push:help": "./scripts/tx-push-help.js",
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": "babel-node scripts/validate-translations ./editor/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": "babel-node scripts/validate-translations ./editor/extensions/ && babel-node scripts/validate-extension-inputs",
35
- "validate:interface": "babel-node scripts/validate-translations ./editor/interface/",
36
- "validate:paint": "babel-node scripts/validate-translations ./editor/paint-editor/",
37
- "validate:www": "babel-node scripts/validate-www ./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 {sync as mkdirpSync} from 'mkdirp';
43
+ import mkdirp from 'mkdirp';
44
44
  import defaultsDeep from 'lodash.defaultsdeep';
45
- import locales from '../src/supported-locales.js';
45
+ import locales from '../src/supported-locales.mjs';
46
46
 
47
47
  const MSGS_DIR = './locales/';
48
- mkdirpSync(MSGS_DIR);
48
+ mkdirp.sync(MSGS_DIR);
49
49
  let missingLocales = [];
50
50
 
51
51
  const combineJson = (component) => {
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env babel-node
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.js';
33
- import locales, {localeMap} from '../src/supported-locales.js';
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 babel-node
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.js';
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
- for (let i = 0; i < 5; i++) {
50
- try {
51
- const translations = await txPull(PROJECT, resource, txLocale);
52
-
53
- const txOutdir = `${OUTPUT_DIR}/${PROJECT}.${resource}`;
54
- mkdirp.sync(txOutdir);
55
- const fileName = `${txOutdir}/${locale}.json`;
56
- fs.writeFileSync(
57
- fileName,
58
- JSON.stringify(translations, null, 4)
59
- );
60
- return {
61
- resource: resource,
62
- locale: locale,
63
- file: fileName
64
- };
65
- } catch (e) {
66
- process.stdout.write(`got ${e.message}, retrying after ${i + 1} attempt(s)\n`);
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('scratch-website');
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, getLocaleData);
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); // eslint-disable-line no-console
109
+ console.error(err);
94
110
  process.exit(1);
95
111
  }
96
112
  };
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env babel-node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * @fileoverview
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env babel-node
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.js';
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 babel-node
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
- babel-node validate_translations.js path
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.js';
23
- import locales from '../src/supported-locales.js';
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 babel-node
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
- babel-node validate_www.js path
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.js';
23
- import locales from '../src/supported-locales.js';
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
@@ -0,0 +1,3 @@
1
+ import localeData from './locale-data.mjs';
2
+ import locales, {localeMap, isRtl} from './supported-locales.mjs';
3
+ export {locales as default, localeData, localeMap, isRtl};
@@ -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.js';
75
+ import {customLocales} from './supported-locales.mjs';
76
76
 
77
77
  let localeData = [].concat(
78
78
  en,
package/src/index.js DELETED
@@ -1,3 +0,0 @@
1
- import localeData from './locale-data.js';
2
- import locales, {localeMap, isRtl} from './supported-locales.js';
3
- export {locales as default, localeData, localeMap, isRtl};
File without changes