scratch-l10n 5.0.231 → 5.0.232

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.
Files changed (43) hide show
  1. package/.editorconfig +9 -0
  2. package/.github/PULL_REQUEST_TEMPLATE.md +53 -46
  3. package/.github/workflows/ci-cd.yml +5 -5
  4. package/.github/workflows/commitlint.yml +1 -1
  5. package/.github/workflows/daily-help-update.yml +4 -4
  6. package/.github/workflows/daily-tx-pull.yml +4 -4
  7. package/.github/workflows/signature-assistant.yml +3 -3
  8. package/.prettierignore +11 -0
  9. package/CHANGELOG.md +7 -0
  10. package/README.md +12 -12
  11. package/commitlint.config.js +3 -3
  12. package/dist/l10n.js +224 -232
  13. package/dist/l10n.js.map +1 -1
  14. package/dist/localeData.js +223 -232
  15. package/dist/localeData.js.map +1 -1
  16. package/dist/supportedLocales.js +123 -130
  17. package/dist/supportedLocales.js.map +1 -1
  18. package/eslint.config.mjs +13 -0
  19. package/lib/batch.js +10 -12
  20. package/lib/progress-logger.mjs +33 -33
  21. package/lib/transifex.js +145 -144
  22. package/lib/validate.mjs +32 -31
  23. package/package.json +7 -8
  24. package/prettier.config.mjs +3 -0
  25. package/release.config.js +13 -13
  26. package/renovate.json5 +3 -5
  27. package/scripts/build-data.mjs +33 -49
  28. package/scripts/build-i18n-src.js +29 -28
  29. package/scripts/freshdesk-api.js +129 -144
  30. package/scripts/help-utils.js +129 -139
  31. package/scripts/tx-pull-editor.mjs +54 -51
  32. package/scripts/tx-pull-help-articles.js +14 -14
  33. package/scripts/tx-pull-help-names.js +14 -14
  34. package/scripts/tx-pull-locale-articles.js +19 -19
  35. package/scripts/tx-pull-www.mjs +79 -76
  36. package/scripts/tx-push-help.mjs +87 -96
  37. package/scripts/tx-push-src.js +65 -65
  38. package/scripts/validate-extension-inputs.mjs +65 -68
  39. package/scripts/validate-translations.mjs +33 -29
  40. package/scripts/validate-www.mjs +45 -43
  41. package/src/index.mjs +4 -3
  42. package/src/locale-data.mjs +148 -150
  43. package/src/supported-locales.mjs +124 -131
@@ -1,14 +1,28 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * @file
4
+ * Script to pull www translations from transifex for all resources.
5
+ * Expects that the project and that the person running the script
6
+ * has the the TX_TOKEN environment variable set to an api
7
+ * token that has developer access.
8
+ */
9
+ import fs from 'fs/promises'
10
+ import mkdirp from 'mkdirp'
11
+ import path from 'path'
12
+ import { batchMap } from '../lib/batch.js'
13
+ import { ProgressLogger } from '../lib/progress-logger.mjs'
14
+ import { txPull, txResources } from '../lib/transifex.js'
15
+ import locales, { localeMap } from '../src/supported-locales.mjs'
2
16
 
3
17
  /**
4
- * @fileoverview
18
+ * @file
5
19
  * Script to pull www translations from transifex for all resources.
6
20
  * Expects that the project and that the person running the script
7
21
  * has the the TX_TOKEN environment variable set to an api
8
22
  * token that has developer access.
9
23
  */
10
24
 
11
- const args = process.argv.slice(2);
25
+ const args = process.argv.slice(2)
12
26
 
13
27
  const usage = `
14
28
  Pull supported language translations from Transifex for the 'scratch-website' project.
@@ -18,97 +32,86 @@ const usage = `
18
32
  path: root for the translated resources.
19
33
  Each resource will be a subdirectory containing language json files.
20
34
  lang: optional language code - will only pull resources for that language
21
- NOTE: TX_TOKEN environment variable needs to be set with a Transifex API token.
35
+ NOTE: TX_TOKEN environment variable needs to be set with a Transifex API token.
22
36
  See the Localization page on the GUI wiki for information about setting up Transifex.
23
- `;
37
+ `
24
38
  // Fail immediately if the TX_TOKEN is not defined
25
39
  if (!process.env.TX_TOKEN || args.length < 1) {
26
- process.stdout.write(usage);
27
- process.exit(1);
40
+ process.stdout.write(usage)
41
+ process.exit(1)
28
42
  }
29
43
 
30
- import fs from 'fs/promises';
31
- import path from 'path';
32
- import mkdirp from 'mkdirp';
33
- import {txPull, txResources} from '../lib/transifex.js';
34
- import locales, {localeMap} from '../src/supported-locales.mjs';
35
- import {batchMap} from '../lib/batch.js';
36
- import {ProgressLogger} from '../lib/progress-logger.mjs';
37
-
38
44
  // Globals
39
- const PROJECT = 'scratch-website';
40
- const OUTPUT_DIR = path.resolve(args[0]);
45
+ const PROJECT = 'scratch-website'
46
+ const OUTPUT_DIR = path.resolve(args[0])
41
47
  // const MODE = {mode: 'reviewed'}; // default is everything for www
42
- const CONCURRENCY_LIMIT = 36;
48
+ const CONCURRENCY_LIMIT = 36
43
49
 
44
- const lang = args.length === 2 ? args[1] : '';
50
+ const lang = args.length === 2 ? args[1] : ''
45
51
 
46
52
  const getLocaleData = async function (item) {
47
- const locale = item.locale;
48
- const resource = item.resource;
49
- let txLocale = localeMap[locale] || locale;
53
+ const locale = item.locale
54
+ const resource = item.resource
55
+ const txLocale = localeMap[locale] || locale
50
56
 
51
- const translations = await txPull(PROJECT, resource, txLocale);
57
+ const translations = await txPull(PROJECT, resource, txLocale)
52
58
 
53
- const txOutdir = `${OUTPUT_DIR}/${PROJECT}.${resource}`;
54
- const fileName = `${txOutdir}/${locale}.json`;
59
+ const txOutdir = `${OUTPUT_DIR}/${PROJECT}.${resource}`
60
+ const fileName = `${txOutdir}/${locale}.json`
55
61
 
56
- try {
57
- mkdirp.sync(txOutdir);
58
- await fs.writeFile(
59
- fileName,
60
- JSON.stringify(translations, null, 4)
61
- );
62
+ try {
63
+ mkdirp.sync(txOutdir)
64
+ await fs.writeFile(fileName, JSON.stringify(translations, null, 4))
62
65
 
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;
66
+ return {
67
+ resource,
68
+ locale,
69
+ fileName,
77
70
  }
78
- };
71
+ } catch (e) {
72
+ e.cause = {
73
+ resource,
74
+ locale,
75
+ translations,
76
+ txOutdir,
77
+ fileName,
78
+ }
79
+ throw e
80
+ }
81
+ }
79
82
 
80
- const expandResourceFiles = (resources) => {
81
- let items = [];
82
- for (let resource of resources) {
83
- if (lang) {
84
- items.push({resource: resource, locale: lang});
85
- } else {
86
- for (let locale of Object.keys(locales)) {
87
- items.push({resource: resource, locale: locale});
88
- }
89
- }
83
+ const expandResourceFiles = resources => {
84
+ const items = []
85
+ for (const resource of resources) {
86
+ if (lang) {
87
+ items.push({ resource: resource, locale: lang })
88
+ } else {
89
+ for (const locale of Object.keys(locales)) {
90
+ items.push({ resource: resource, locale: locale })
91
+ }
90
92
  }
91
- return items;
92
- };
93
+ }
94
+ return items
95
+ }
93
96
 
94
97
  const pullTranslations = async function () {
95
- const resources = await txResources(PROJECT);
96
- const allFiles = expandResourceFiles(resources);
97
-
98
- const progress = new ProgressLogger(allFiles.length);
99
-
100
- try {
101
- await batchMap(allFiles, CONCURRENCY_LIMIT, async item => {
102
- try {
103
- await getLocaleData(item);
104
- } finally {
105
- progress.increment();
106
- }
107
- });
108
- } catch (err) {
109
- console.error(err);
110
- process.exit(1);
111
- }
112
- };
98
+ const resources = await txResources(PROJECT)
99
+ const allFiles = expandResourceFiles(resources)
100
+
101
+ const progress = new ProgressLogger(allFiles.length)
102
+
103
+ try {
104
+ await batchMap(allFiles, CONCURRENCY_LIMIT, async item => {
105
+ try {
106
+ await getLocaleData(item)
107
+ } finally {
108
+ progress.increment()
109
+ }
110
+ })
111
+ } catch (err) {
112
+ console.error(err)
113
+ process.exit(1)
114
+ }
115
+ }
113
116
 
114
- pullTranslations();
117
+ pullTranslations()
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  /**
4
- * @fileoverview
3
+ * @file
5
4
  * Script get Knowledge base articles from Freshdesk and push them to transifex.
6
5
  */
6
+ import { txPush, txCreateResource } from '../lib/transifex.js'
7
+ import FreshdeskApi from './freshdesk-api.js'
7
8
 
8
- import {txPush, txCreateResource} from '../lib/transifex.js';
9
-
10
- const args = process.argv.slice(2);
9
+ const args = process.argv.slice(2)
11
10
 
12
11
  const usage = `
13
12
  Pull knowledge base articles from Freshdesk and push to scratch-help project on transifex. Usage:
@@ -17,129 +16,121 @@ const usage = `
17
16
  access to the Knowledge Base.
18
17
  TX_TOKEN environment variable needs to be set with a Transifex API token. See
19
18
  the Localization page on the GUI wiki for information about setting up Transifex.
20
- `;
19
+ `
21
20
  // Fail immediately if the API tokens are not defined, or there any argument
22
21
  if (!process.env.TX_TOKEN || !process.env.FRESHDESK_TOKEN || args.length > 0) {
23
- process.stdout.write(usage);
24
- process.exit(1);
22
+ process.stdout.write(usage)
23
+ process.exit(1)
25
24
  }
26
25
 
27
- import FreshdeskApi from './freshdesk-api.js';
26
+ const FD = new FreshdeskApi('https://mitscratch.freshdesk.com', process.env.FRESHDESK_TOKEN)
27
+ const TX_PROJECT = 'scratch-help'
28
28
 
29
- const FD = new FreshdeskApi('https://mitscratch.freshdesk.com', process.env.FRESHDESK_TOKEN);
30
- const TX_PROJECT = 'scratch-help';
31
-
32
- const categoryNames = {};
33
- const folderNames = {};
29
+ const categoryNames = {}
30
+ const folderNames = {}
34
31
 
35
32
  /**
36
33
  * Generate a transifex id from the name and id field of an objects. Remove spaces and '/'
37
34
  * from the name and append '.<id>' Transifex ids (slugs) have a max length of 50. Use at most
38
35
  * 30 characters of the name to allow for Freshdesk id, and a suffix like '_json'
39
36
  * @param {object} item data from Freshdesk that includes the name and id of a category or folder
40
- * @return {string} generated transifex id
37
+ * @returns {string} generated transifex id
41
38
  */
42
- const makeTxId = item => {
43
- return `${item.name.replace(/[ /]/g, '').slice(0, 30)}_${item.id}`;
44
- };
39
+ const makeTxId = item => `${item.name.replace(/[ /]/g, '').slice(0, 30)}_${item.id}`
45
40
 
46
41
  const txPushResource = async (name, articles, type) => {
47
- const resourceData = {
48
- slug: name,
49
- name: name,
50
- i18n_type: type,
51
- priority: 0, // default to normal priority
52
- content: articles
53
- };
54
-
55
- try {
56
- await txPush(TX_PROJECT, name, articles);
57
- } catch (err) {
58
- if (err.statusCode !== 404) {
59
- process.stdout.write(`Transifex Error: ${err.message}\n`);
60
- process.stdout.write(
61
- `Transifex Error ${err.response.statusCode.toString()}: ${err.response.body}\n`);
62
- process.exitCode = 1;
63
- return;
64
- }
42
+ const resourceData = {
43
+ slug: name,
44
+ name: name,
45
+ i18n_type: type,
46
+ priority: 0, // default to normal priority
47
+ content: articles,
48
+ }
49
+
50
+ try {
51
+ await txPush(TX_PROJECT, name, articles)
52
+ } catch (err) {
53
+ if (err.statusCode !== 404) {
54
+ process.stdout.write(`Transifex Error: ${err.message}\n`)
55
+ process.stdout.write(`Transifex Error ${err.response.statusCode.toString()}: ${err.response.body}\n`)
56
+ process.exitCode = 1
57
+ return
58
+ }
65
59
 
66
- // file not found - create it, but also give message
67
- process.stdout.write(`Transifex Resource not found, creating: ${name}\n`);
68
- if (err.statusCode === 404) {
69
- await txCreateResource(TX_PROJECT, resourceData);
70
- }
60
+ // file not found - create it, but also give message
61
+ process.stdout.write(`Transifex Resource not found, creating: ${name}\n`)
62
+ if (err.statusCode === 404) {
63
+ await txCreateResource(TX_PROJECT, resourceData)
71
64
  }
72
- };
65
+ }
66
+ }
73
67
 
74
68
  /**
75
69
  * get a flattened list of folders associated with the specified categories
76
70
  * @param {object[]} categories array of categories the folders belong to
77
- * @return {Promise<object[]>} flattened list of folders from all requested categories
71
+ * @returns {Promise<object[]>} flattened list of folders from all requested categories
78
72
  */
79
- const getFolders = async (categories) => {
80
- const categoryFolders = await Promise.all(
81
- categories.map(category => FD.listFolders(category))
82
- );
83
- return [].concat(...categoryFolders);
84
- };
73
+ const getFolders = async categories => {
74
+ const categoryFolders = await Promise.all(categories.map(category => FD.listFolders(category)))
75
+ return [].concat(...categoryFolders)
76
+ }
85
77
 
86
- const PUBLISHED = 2; // in Freshdesk, draft status = 1, and published = 2
78
+ const PUBLISHED = 2 // in Freshdesk, draft status = 1, and published = 2
87
79
 
88
80
  /**
89
81
  * Save articles in a particular folder
90
82
  * @param {object} folder The folder object
91
83
  */
92
84
  const saveArticles = async folder => {
93
- await FD.listArticles(folder)
94
- .then(json => {
95
- const txArticles = json.reduce((strings, current) => {
96
- if (current.status === PUBLISHED) {
97
- strings[`${current.id}`] = {
98
- title: {
99
- string: current.title
100
- },
101
- description: {
102
- string: current.description
103
- }
104
- };
105
- if (current.tags.length > 0) {
106
- strings[`${current.id}`].tags = {string: current.tags.toString()};
107
- }
108
- }
109
- return strings;
110
- }, {});
111
- process.stdout.write(`Push ${folder.name} articles to Transifex\n`);
112
- txPushResource(`${makeTxId(folder)}_json`, txArticles, 'STRUCTURED_JSON');
113
- });
114
- };
85
+ await FD.listArticles(folder).then(json => {
86
+ const txArticles = json.reduce((strings, current) => {
87
+ if (current.status === PUBLISHED) {
88
+ strings[`${current.id}`] = {
89
+ title: {
90
+ string: current.title,
91
+ },
92
+ description: {
93
+ string: current.description,
94
+ },
95
+ }
96
+ if (current.tags.length > 0) {
97
+ strings[`${current.id}`].tags = { string: current.tags.toString() }
98
+ }
99
+ }
100
+ return strings
101
+ }, {})
102
+ process.stdout.write(`Push ${folder.name} articles to Transifex\n`)
103
+ txPushResource(`${makeTxId(folder)}_json`, txArticles, 'STRUCTURED_JSON')
104
+ })
105
+ }
115
106
 
116
107
  /**
117
108
  * @param {object[]} folders Array of folders containing articles to be saved
118
109
  */
119
- const saveArticleFolders = async (folders) => {
120
- await Promise.all(folders.map(folder => saveArticles(folder)));
121
- };
110
+ const saveArticleFolders = async folders => {
111
+ await Promise.all(folders.map(folder => saveArticles(folder)))
112
+ }
122
113
 
123
114
  const syncSources = async () => {
124
- await FD.listCategories()
125
- .then(json => {
126
- // save category names for translation
127
- for (let cat of json.values()) {
128
- categoryNames[`${makeTxId(cat)}`] = cat.name;
129
- }
130
- return json;
131
- })
132
- .then(getFolders)
133
- .then(data => {
134
- data.forEach(item => {
135
- folderNames[`${makeTxId(item)}`] = item.name;
136
- });
137
- process.stdout.write('Push category and folder names to Transifex\n');
138
- txPushResource('categoryNames_json', categoryNames, 'KEYVALUEJSON');
139
- txPushResource('folderNames_json', folderNames, 'KEYVALUEJSON');
140
- return data;
141
- })
142
- .then(saveArticleFolders);
143
- };
115
+ await FD.listCategories()
116
+ .then(json => {
117
+ // save category names for translation
118
+ for (const cat of json.values()) {
119
+ categoryNames[`${makeTxId(cat)}`] = cat.name
120
+ }
121
+ return json
122
+ })
123
+ .then(getFolders)
124
+ .then(data => {
125
+ data.forEach(item => {
126
+ folderNames[`${makeTxId(item)}`] = item.name
127
+ })
128
+ process.stdout.write('Push category and folder names to Transifex\n')
129
+ txPushResource('categoryNames_json', categoryNames, 'KEYVALUEJSON')
130
+ txPushResource('folderNames_json', folderNames, 'KEYVALUEJSON')
131
+ return data
132
+ })
133
+ .then(saveArticleFolders)
134
+ }
144
135
 
145
- syncSources();
136
+ syncSources()
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * @fileoverview
4
+ * @file
5
5
  * Script to upload a source en.json file to a particular transifex project resource.
6
6
  * Expects that the project and resource have already been defined in Transifex, and that
7
7
  * the person running the script has the the TX_TOKEN environment variable set to an api
8
8
  * token that has developer access.
9
9
  */
10
10
 
11
- const fs = require('fs');
12
- const path = require('path');
13
- const {txPush, txCreateResource} = require('../lib/transifex.js');
11
+ const fs = require('fs')
12
+ const path = require('path')
13
+ const { txPush, txCreateResource } = require('../lib/transifex.js')
14
14
 
15
- const args = process.argv.slice(2);
15
+ const args = process.argv.slice(2)
16
16
 
17
17
  const usage = `
18
18
  Push English source strings to Transifex. Usage:
@@ -22,79 +22,79 @@ Push English source strings to Transifex. Usage:
22
22
  english-json-file: path to the en.json source
23
23
  NOTE: TX_TOKEN environment variable needs to be set with a Transifex API token. See
24
24
  the Localization page on the GUI wiki for information about setting up Transifex.
25
- `;
25
+ `
26
26
 
27
27
  // Exit if missing arguments or TX_TOKEN
28
28
  if (args.length < 3 || !process.env.TX_TOKEN) {
29
- process.stdout.write(usage);
30
- process.exit(1);
29
+ process.stdout.write(usage)
30
+ process.exit(1)
31
31
  }
32
32
 
33
33
  // Globals
34
- const PROJECT = args[0];
35
- const RESOURCE = args[1];
34
+ const PROJECT = args[0]
35
+ const RESOURCE = args[1]
36
36
 
37
- let en = fs.readFileSync(path.resolve(args[2]));
38
- en = JSON.parse(en);
37
+ let en = fs.readFileSync(path.resolve(args[2]))
38
+ en = JSON.parse(en)
39
39
 
40
40
  // get the correct resource file type based on transifex project/repo and resource
41
41
  const getResourceType = (project, resource) => {
42
- if (project === 'scratch-website') {
43
- // all the resources are KEYVALUEJSON
44
- return 'KEYVALUEJSON';
42
+ if (project === 'scratch-website') {
43
+ // all the resources are KEYVALUEJSON
44
+ return 'KEYVALUEJSON'
45
+ }
46
+ if (project === 'scratch-legacy') {
47
+ // all the resources are po files
48
+ return 'PO'
49
+ }
50
+ if (project === 'scratch-editor') {
51
+ if (resource === 'blocks') {
52
+ return 'KEYVALUEJSON'
45
53
  }
46
- if (project === 'scratch-legacy') {
47
- // all the resources are po files
48
- return 'PO';
49
- }
50
- if (project === 'scratch-editor') {
51
- if (resource === 'blocks') {
52
- return 'KEYVALUEJSON';
53
- }
54
- // everything else is CHROME I18N JSON
55
- return 'CHROME';
56
- }
57
- if (project === 'scratch-videos') {
58
- // all the resources are srt files
59
- return 'SRT';
60
- }
61
- if (project === 'scratch-android') {
62
- // all the resources are android xml files
63
- return 'ANDROID';
64
- }
65
- if (project === 'scratch-resources') {
66
- // all the resources are Chrome format json files
67
- return 'CHROME';
68
- }
69
- process.stdout.write(`Error - Unknown resource type for:\n`);
70
- process.stdout.write(` Project: ${project}, resource: ${resource}\n`);
71
- process.exit(1);
72
- };
54
+ // everything else is CHROME I18N JSON
55
+ return 'CHROME'
56
+ }
57
+ if (project === 'scratch-videos') {
58
+ // all the resources are srt files
59
+ return 'SRT'
60
+ }
61
+ if (project === 'scratch-android') {
62
+ // all the resources are android xml files
63
+ return 'ANDROID'
64
+ }
65
+ if (project === 'scratch-resources') {
66
+ // all the resources are Chrome format json files
67
+ return 'CHROME'
68
+ }
69
+ process.stdout.write(`Error - Unknown resource type for:\n`)
70
+ process.stdout.write(` Project: ${project}, resource: ${resource}\n`)
71
+ process.exit(1)
72
+ }
73
73
 
74
74
  // update Transifex with English source
75
75
  const pushSource = async function () {
76
- try {
77
- await txPush(PROJECT, RESOURCE, en);
78
- } catch (err) {
79
- if (err.statusCode !== 404) {
80
- process.stdout.write(`Transifex Error: ${err.message}\n`);
81
- process.stdout.write(
82
- `Transifex Error ${err.response.statusCode.toString()}: ${err.response.body}\n`);
83
- process.exitCode = 1;
84
- return;
85
- }
86
- // file not found - create it, but also give message
87
- process.stdout.write(`Transifex Resource not found, creating: ${RESOURCE}\n`);
88
- const resourceData = {
89
- slug: RESOURCE,
90
- name: RESOURCE,
91
- priority: 0, // default to normal priority
92
- i18nType: getResourceType(PROJECT, RESOURCE),
93
- content: en
94
- };
95
- await txCreateResource(PROJECT, resourceData);
96
- process.exitCode = 0;
76
+ try {
77
+ await txPush(PROJECT, RESOURCE, en)
78
+ } catch (err) {
79
+ if (err.statusCode !== 404) {
80
+ process.stdout.write(`Transifex Error: ${err.message}\n`)
81
+ process.stdout.write(`Transifex Error ${err.response.statusCode.toString()}: ${err.response.body}\n`)
82
+ process.exitCode = 1
83
+ return
97
84
  }
98
- };
85
+ // file not found - create it, but also give message
86
+ process.stdout.write(`Transifex Resource not found, creating: ${RESOURCE}\n`)
87
+ const resourceData = {
88
+ slug: RESOURCE,
89
+ name: RESOURCE,
90
+ priority: 0, // default to normal priority
91
+ i18nType: getResourceType(PROJECT, RESOURCE),
92
+ content: en,
93
+ }
94
+ await txCreateResource(PROJECT, resourceData)
95
+ // eslint-disable-next-line require-atomic-updates -- I promise that `process` won't change across `await`
96
+ process.exitCode = 0
97
+ }
98
+ }
99
99
 
100
- pushSource();
100
+ pushSource()