scratch-l10n 5.0.308 → 6.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +6 -0
  3. package/dist/l10n.js +3 -0
  4. package/dist/l10n.js.map +1 -1
  5. package/dist/localeData.js +3 -0
  6. package/dist/localeData.js.map +1 -1
  7. package/dist/supportedLocales.js +3 -0
  8. package/dist/supportedLocales.js.map +1 -1
  9. package/package.json +24 -22
  10. package/scripts/{build-data.mjs → build-data.mts} +15 -6
  11. package/scripts/{build-i18n-src.js → build-i18n-src.mts} +16 -11
  12. package/scripts/lib/concurrent.mts +37 -0
  13. package/scripts/lib/freshdesk-api.mts +322 -0
  14. package/scripts/lib/help-utils.mts +221 -0
  15. package/{lib/progress-logger.mjs → scripts/lib/progress-logger.mts} +10 -5
  16. package/scripts/lib/transifex-formats.mts +53 -0
  17. package/scripts/lib/transifex-objects.mts +143 -0
  18. package/scripts/lib/transifex.mts +284 -0
  19. package/scripts/lib/validate.mts +107 -0
  20. package/scripts/tsconfig.json +20 -0
  21. package/scripts/tx-pull-editor.mts +74 -0
  22. package/scripts/{tx-pull-help-articles.js → tx-pull-help-articles.mts} +5 -13
  23. package/scripts/{tx-pull-help-names.js → tx-pull-help-names.mts} +5 -13
  24. package/scripts/{tx-pull-locale-articles.js → tx-pull-locale-articles.mts} +5 -13
  25. package/scripts/{tx-pull-www.mjs → tx-pull-www.mts} +16 -29
  26. package/scripts/{tx-push-help.mjs → tx-push-help.mts} +39 -37
  27. package/scripts/{tx-push-src.js → tx-push-src.mts} +13 -20
  28. package/scripts/update-translations.sh +2 -2
  29. package/scripts/{validate-extension-inputs.mjs → validate-extension-inputs.mts} +20 -10
  30. package/scripts/{validate-translations.mjs → validate-translations.mts} +7 -12
  31. package/scripts/{validate-www.mjs → validate-www.mts} +15 -13
  32. package/src/supported-locales.mjs +3 -0
  33. package/www/scratch-website.about-l10njson/nn.json +1 -1
  34. package/www/scratch-website.splash-l10njson/mi.json +2 -2
  35. package/www/scratch-website.teacher-faq-l10njson/cy.json +1 -1
  36. package/.github/PULL_REQUEST_TEMPLATE.md +0 -75
  37. package/.github/workflows/ci-cd.yml +0 -55
  38. package/.github/workflows/commitlint.yml +0 -12
  39. package/.github/workflows/daily-help-update.yml +0 -40
  40. package/.github/workflows/daily-tx-pull.yml +0 -54
  41. package/.github/workflows/signature-assistant.yml +0 -31
  42. package/lib/batch.js +0 -15
  43. package/lib/transifex.js +0 -242
  44. package/lib/validate.mjs +0 -48
  45. package/scripts/freshdesk-api.js +0 -149
  46. package/scripts/help-utils.js +0 -190
  47. package/scripts/tx-pull-editor.mjs +0 -83
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @file
3
+ * Utilities for interfacing with Transifex API 3.
4
+ */
5
+ import { transifexApi, Collection, JsonApiResource } from '@transifex/api'
6
+ import { TransifexStrings } from './transifex-formats.mts'
7
+ import { TransifexLanguageObject, TransifexResourceObject } from './transifex-objects.mts'
8
+
9
+ const ORG_NAME = 'llk'
10
+ const SOURCE_LOCALE = 'en'
11
+
12
+ if (!process.env.TX_TOKEN) {
13
+ throw new Error('TX_TOKEN is not defined.')
14
+ }
15
+
16
+ transifexApi.setup({
17
+ auth: process.env.TX_TOKEN,
18
+ })
19
+
20
+ /*
21
+ * The Transifex JS API wraps the Transifex JSON API, and is built around the concept of a `Collection`.
22
+ * A `Collection` begins as a URL builder: methods like `filter` and `sort` add query parameters to the URL.
23
+ * The `download` method doesn't actually download anything: it returns the built URL. It seems to be intended
24
+ * primarily for internal use, but shows up in the documentation despite not being advertised in the .d.ts file.
25
+ * The `download` method is mainly used to skip the `fetch` method in favor of downloading the resource yourself.
26
+ * The `fetch` method sends a request to the URL and returns a promise that resolves to the first page of results.
27
+ * If there's only one page of results, the `data` property of the collection object will be an array of all results.
28
+ * However, if there are multiple pages of results, the `data` property will only contain the first page of results.
29
+ * Previous versions of this code would unsafely assume that the `data` property contained all results.
30
+ * The `all` method returns an async iterator that yields all results, fetching additional pages as needed.
31
+ */
32
+
33
+ /**
34
+ * Collects all resources from all pages of a potentially-paginated JSON API collection.
35
+ * It's not necessary, but also not harmful, to call `fetch()` on the collection before calling this function.
36
+ * @param collection A collection of JSON API resources.
37
+ * @returns An array of all resources in the collection.
38
+ * @todo This seems necessary with the latest Transifex API..?
39
+ */
40
+ const collectAll = async function <T extends JsonApiResource>(collection: Collection): Promise<T[]> {
41
+ await collection.fetch() // fetch the first page if it hasn't already been fetched
42
+ const collected: T[] = []
43
+ for (const item of collection.all()) {
44
+ collected.push(item as T)
45
+ }
46
+ return collected
47
+ }
48
+
49
+ /**
50
+ * Creates a download event for a specific project, resource, and locale.
51
+ * Returns the URL to download the resource.
52
+ * @param projectSlug - project slug (for example, "scratch-editor")
53
+ * @param resourceSlug - resource slug (for example, "blocks")
54
+ * @param localeCode - language code (for example, "ko")
55
+ * @param mode - translation status of strings to include
56
+ * @returns URL to download the resource
57
+ */
58
+ const getResourceLocation = async function (
59
+ projectSlug: string,
60
+ resourceSlug: string,
61
+ localeCode: string,
62
+ mode = 'default',
63
+ ): Promise<string> {
64
+ const resource = {
65
+ data: {
66
+ id: `o:${ORG_NAME}:p:${projectSlug}:r:${resourceSlug}`,
67
+ type: 'resources',
68
+ },
69
+ }
70
+
71
+ // if locale is English, create a download event of the source file
72
+ if (localeCode === SOURCE_LOCALE) {
73
+ return (await transifexApi.ResourceStringsAsyncDownload.download({
74
+ resource,
75
+ })) as string
76
+ }
77
+
78
+ const language = {
79
+ data: {
80
+ id: `l:${localeCode}`,
81
+ type: 'languages',
82
+ },
83
+ }
84
+
85
+ // if locale is not English, create a download event of the translation file
86
+ return (await transifexApi.ResourceTranslationsAsyncDownload.download({
87
+ mode,
88
+ resource,
89
+ language,
90
+ })) as string
91
+ }
92
+
93
+ /**
94
+ * Pulls a translation JSON from transifex, for a specific project, resource, and locale.
95
+ * @template T - resource file type, such as `TransifexStringsKeyValueJson`
96
+ * @param project - project slug (for example, `scratch-editor`)
97
+ * @param resource - resource slug (for example, `blocks`)
98
+ * @param locale - language code (for example, `ko`)
99
+ * @param mode - translation status of strings to include
100
+ * @returns JSON object of translated resource strings (or, of the original resource strings, if the local is the
101
+ * source language)
102
+ */
103
+ export const txPull = async function <T>(
104
+ project: string,
105
+ resource: string,
106
+ locale: string,
107
+ mode = 'default',
108
+ ): Promise<TransifexStrings<T>> {
109
+ let buffer: string | null = null
110
+ try {
111
+ const url = await getResourceLocation(project, resource, locale, mode)
112
+ for (let i = 0; i < 5; i++) {
113
+ if (i > 0) {
114
+ console.log(`Retrying txPull download after ${i} failed attempt(s)`)
115
+ }
116
+ try {
117
+ const response = await fetch(url)
118
+ if (!response.ok) {
119
+ throw new Error(`Failed to download resource: ${response.statusText}`)
120
+ }
121
+ buffer = await response.text()
122
+ break
123
+ } catch (e) {
124
+ console.error(e, { project, resource, locale, buffer })
125
+ }
126
+ }
127
+ if (!buffer) {
128
+ throw Error(`txPull download failed after 5 retries: ${url}`)
129
+ }
130
+ return JSON.parse(buffer) as TransifexStrings<T>
131
+ } catch (e) {
132
+ ;(e as Error).cause = {
133
+ project,
134
+ resource,
135
+ locale,
136
+ buffer,
137
+ }
138
+ throw e
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Given a project, returns a list of the slugs of all resources in the project
144
+ * @param project - project slug (for example, "scratch-website")
145
+ * @returns - array of strings, slugs identifying each resource in the project
146
+ */
147
+ export const txResources = async function (project: string): Promise<string[]> {
148
+ const resources = transifexApi.Resource.filter({
149
+ project: `o:${ORG_NAME}:p:${project}`,
150
+ })
151
+
152
+ const resourcesData = await collectAll<TransifexResourceObject>(resources)
153
+
154
+ const slugs = resourcesData.map(
155
+ r =>
156
+ // r.id is a longer id string, like "o:llk:p:scratch-website:r:about-l10njson"
157
+ // We just want the slug that comes after ":r:" ("about-l10njson")
158
+ r.id.split(':r:')[1],
159
+ )
160
+ return slugs
161
+ }
162
+
163
+ /**
164
+ * @param project - project slug (for example)
165
+ * @returns - array of resource objects
166
+ */
167
+ export const txResourcesObjects = async function (project: string): Promise<TransifexResourceObject[]> {
168
+ const resources = transifexApi.Resource.filter({
169
+ project: `o:${ORG_NAME}:p:${project}`,
170
+ })
171
+
172
+ return collectAll<TransifexResourceObject>(resources)
173
+ }
174
+
175
+ /**
176
+ * Gets available languages for a project
177
+ * @param slug - project slug (for example, "scratch-editor")
178
+ * @returns - list of language codes
179
+ */
180
+ export const txAvailableLanguages = async function (slug: string): Promise<string[]> {
181
+ const project = await transifexApi.Project.get({
182
+ organization: `o:${ORG_NAME}`,
183
+ slug: slug,
184
+ })
185
+
186
+ const languages = (await project.fetch('languages', false)) as Collection
187
+ const languagesData = await collectAll<TransifexLanguageObject>(languages)
188
+ return languagesData.map(l => l.attributes.code)
189
+ }
190
+
191
+ /**
192
+ * Uploads English source strings to a resource in transifex
193
+ * @param project - project slug (for example, "scratch-editor")
194
+ * @param resource - resource slug (for example, "blocks")
195
+ * @param sourceStrings - json of source strings
196
+ */
197
+ export const txPush = async function (project: string, resource: string, sourceStrings: TransifexStrings<unknown>) {
198
+ const resourceObj = {
199
+ data: {
200
+ id: `o:${ORG_NAME}:p:${project}:r:${resource}`,
201
+ type: 'resources',
202
+ },
203
+ }
204
+
205
+ await transifexApi.ResourceStringsAsyncUpload.upload({
206
+ resource: resourceObj,
207
+ content: JSON.stringify(sourceStrings),
208
+ })
209
+ }
210
+
211
+ /**
212
+ * Creates a new resource, and then uploads source strings to it if they are provided
213
+ * @param project - project slug (for example, "scratch-editor")
214
+ * @param resource - object of resource information
215
+ * @param resource.slug - resource slug (for example, "blocks")
216
+ * @param resource.name - human-readable name for the resource
217
+ * @param resource.i18nType - i18n format id
218
+ * @param resource.sourceStrings - json object of source strings
219
+ */
220
+ export const txCreateResource = async function (
221
+ project: string,
222
+ {
223
+ slug,
224
+ name,
225
+ i18nType,
226
+ sourceStrings,
227
+ }: {
228
+ slug: string
229
+ name: string
230
+ i18nType: string
231
+ sourceStrings?: TransifexStrings<unknown>
232
+ },
233
+ ) {
234
+ const i18nFormat = {
235
+ data: {
236
+ id: i18nType || 'KEYVALUEJSON',
237
+ type: 'i18n_formats',
238
+ },
239
+ }
240
+
241
+ const projectObj = {
242
+ data: {
243
+ id: `o:${ORG_NAME}:p:${project}`,
244
+ type: 'projects',
245
+ },
246
+ }
247
+
248
+ // @ts-expect-error This omits "required" props but has been like this for ages and I'm not sure how to best fix it
249
+ await transifexApi.Resource.create({
250
+ attributes: { slug: slug, name: name },
251
+ relationships: {
252
+ i18n_format: i18nFormat,
253
+ project: projectObj,
254
+ },
255
+ })
256
+
257
+ if (sourceStrings) {
258
+ await txPush(project, slug, sourceStrings)
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Information about an error condition generated by Transifex's JSON API
264
+ * @see https://github.com/transifex/transifex-api-python/blob/master/src/jsonapi/exceptions.py
265
+ * @see https://github.com/transifex/transifex-javascript/blob/master/packages/jsonapi/src/errors.js
266
+ */
267
+ export interface JsonApiError {
268
+ status: number
269
+ code: string
270
+ title: string
271
+ detail: string
272
+ source?: string
273
+ }
274
+
275
+ /**
276
+ * A JS `Error` thrown by Transifex's JSON API
277
+ * @see https://github.com/transifex/transifex-api-python/blob/master/src/jsonapi/exceptions.py
278
+ * @see https://github.com/transifex/transifex-javascript/blob/master/packages/jsonapi/src/errors.js
279
+ */
280
+ export interface JsonApiException extends Error {
281
+ statusCode: number
282
+ errors: JsonApiError[]
283
+ message: string
284
+ }
@@ -0,0 +1,107 @@
1
+ import assert from 'assert'
2
+ import parse from 'format-message-parse'
3
+ import {
4
+ TransifexStringChrome,
5
+ TransifexStringKeyValueJson,
6
+ TransifexStringsChrome,
7
+ TransifexStringsKeyValueJson,
8
+ } from './transifex-formats.mts'
9
+
10
+ export type TransifexEditorString = TransifexStringChrome | TransifexStringKeyValueJson
11
+ export type TransifexEditorStrings = TransifexStringsChrome | TransifexStringsKeyValueJson
12
+
13
+ /**
14
+ * filter placeholders out of a message
15
+ * @param message - the message to parse
16
+ * @returns an array of placeholder information
17
+ * @example
18
+ * parse('a message with a {value} and {count, plural, one {one} other {more}}.')
19
+ * returns an array:
20
+ * [ 'a message with a ',
21
+ * [ 'value' ],
22
+ * ' and ',
23
+ * [ 'count', 'plural', 0, { one: [Array], other: [Array] } ],
24
+ * '.'
25
+ * ]
26
+ * placeholders are always an array, so filter for array elements to find the placeholders
27
+ */
28
+ const placeholders = (message: string): parse.Placeholder[] =>
29
+ // this will throw an error if the message is not valid ICU
30
+ // single quote (as in French l'année) messes up the parse and is not
31
+ // relevant for this check, so strip them out
32
+ parse(message.replace(/'/g, '')).filter(item => Array.isArray(item))
33
+
34
+ const getMessageText = (m: TransifexStringKeyValueJson | TransifexStringChrome): string =>
35
+ typeof m === 'string' ? m : m.message
36
+
37
+ /**
38
+ * @param a - one array of items
39
+ * @param b - another array of items
40
+ * @returns true if the two arrays contain the same items, without consideration of order and duplicates, judged by
41
+ * shallow equality.
42
+ * @example
43
+ * sameItems(['a', 'b'], ['a', 'b']) === true
44
+ * sameItems(['a', 'b'], ['b', 'a']) === true
45
+ * sameItems(['a', 'b'], ['b', 'a', 'b']) === true
46
+ * sameItems(['a', 'b'], ['a']) === false
47
+ */
48
+ function sameItems<T>(a: T[], b: T[]): boolean {
49
+ if (!a.every(x => b.includes(x))) {
50
+ return false
51
+ }
52
+ if (!b.every(x => a.includes(x))) {
53
+ return false
54
+ }
55
+ return true
56
+ }
57
+
58
+ /**
59
+ * @param message - the translated message to validate
60
+ * @param source - the source string for this translated message
61
+ * @returns `false` if the message definitely has a problem, or `true` if the message might be OK.
62
+ */
63
+ export const validMessage = (message: TransifexEditorString, source: TransifexEditorString): boolean => {
64
+ const msgText = getMessageText(message)
65
+ const srcText = getMessageText(source)
66
+
67
+ // Check ICU placeholder names (but not extended plural info)
68
+ const msgPlaceholderNamesICU = placeholders(msgText).map(x => x[0])
69
+ const srcPlaceholderNamesICU = placeholders(srcText).map(x => x[0])
70
+ if (!sameItems(msgPlaceholderNamesICU, srcPlaceholderNamesICU)) {
71
+ return false
72
+ }
73
+
74
+ // Check scratch-blocks numeric placeholders like '%1'
75
+ // TODO: apply this only for resources that use numeric placeholders.
76
+ // Otherwise, sentences with percentages can cause failures in some languages. Example: "göre %48'lik bir artış"
77
+ // const msgPlaceholdersNumeric: string[] = msgText.match(/%[0-9]+/g) ?? []
78
+ // const srcPlaceholdersNumeric: string[] = srcText.match(/%[0-9]+/g) ?? []
79
+ // if (!sameItems(msgPlaceholdersNumeric, srcPlaceholdersNumeric)) {
80
+ // return false
81
+ // }
82
+
83
+ return true
84
+ }
85
+
86
+ /**
87
+ * @param translation - the translations to validate and their corresponding source strings
88
+ * @param translation.locale - the Transifex locale, for error reporting
89
+ * @param translation.translations - the translations to validate
90
+ * @param source - the source strings for the translations
91
+ */
92
+ export const validateTranslations = (
93
+ { locale, translations }: { locale: string; translations: TransifexEditorStrings },
94
+ source: TransifexEditorStrings,
95
+ ) => {
96
+ const transKeys = Object.keys(translations)
97
+ const sourceKeys = Object.keys(source)
98
+ assert.strictEqual(transKeys.length, sourceKeys.length, `locale ${locale} has a different number of message keys`)
99
+ transKeys.forEach(item => assert(sourceKeys.includes(item), `locale ${locale} has key ${item} not in the source`))
100
+ sourceKeys.forEach(item => assert(transKeys.includes(item), `locale ${locale} is missing key ${item}`))
101
+ sourceKeys.forEach(item =>
102
+ assert(
103
+ validMessage(translations[item], source[item]),
104
+ `locale ${locale} / item ${item}: message validation failed:\n msg: ${getMessageText(translations[item])}\n src: ${getMessageText(source[item])}`,
105
+ ),
106
+ )
107
+ }
@@ -0,0 +1,20 @@
1
+ // tsconfig.json
2
+ {
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "allowJs": true,
8
+ "checkJs": true,
9
+ "allowImportingTsExtensions": true,
10
+ "esModuleInterop": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "strict": true,
13
+ "noEmit": true,
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "incremental": true
17
+ },
18
+ "include": ["**/*.js", "**/*.mjs", "**/*.ts", "**/*.mts"],
19
+ "exclude": ["node_modules"]
20
+ }
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * @file
4
+ * Script to pull translations from transifex and generate the editor-msgs file.
5
+ * Expects that the project and resource have already been defined in Transifex, and that
6
+ * the person running the script has the the TX_TOKEN environment variable set to an api
7
+ * token that has developer access.
8
+ */
9
+ import fs from 'fs'
10
+ import path from 'path'
11
+ import locales, { localeMap } from '../src/supported-locales.mjs'
12
+ import { poolMap } from './lib/concurrent.mts'
13
+ import { TransifexStringsKeyValueJson } from './lib/transifex-formats.mts'
14
+ import { txPull } from './lib/transifex.mts'
15
+ import { TransifexEditorStrings, validateTranslations } from './lib/validate.mts'
16
+
17
+ const args = process.argv.slice(2)
18
+
19
+ const usage = `
20
+ Pull supported language translations from Transifex. Usage:
21
+ node tx-pull-editor.js tx-project tx-resource path
22
+ tx-project: project on Transifex (e.g., scratch-editor)
23
+ tx-resource: resource within the project (e.g., interface)
24
+ path: where to put the downloaded json files
25
+ NOTE: TX_TOKEN environment variable needs to be set with a Transifex API token. See
26
+ the Localization page on the GUI wiki for information about setting up Transifex.
27
+ `
28
+
29
+ // Fail immediately if the TX_TOKEN is not defined
30
+ if (!process.env.TX_TOKEN || args.length < 3) {
31
+ process.stdout.write(usage)
32
+ process.exit(1)
33
+ }
34
+
35
+ // Globals
36
+ const PROJECT = args[0]
37
+ const RESOURCE = args[1]
38
+ const OUTPUT_DIR = path.resolve(args[2])
39
+ const MODE = 'reviewed'
40
+ const CONCURRENCY_LIMIT = 36
41
+
42
+ const getLocaleData = async function (locale: string) {
43
+ const txLocale = localeMap[locale] || locale
44
+ const data = (await txPull(PROJECT, RESOURCE, txLocale, MODE)) as TransifexEditorStrings
45
+ return {
46
+ locale: locale,
47
+ translations: data,
48
+ }
49
+ }
50
+
51
+ const pullTranslations = async function () {
52
+ const values = await poolMap(Object.keys(locales), CONCURRENCY_LIMIT, getLocaleData)
53
+ const source = values.find(elt => elt.locale === 'en')?.translations
54
+ if (!source) {
55
+ throw new Error('Could not find source strings')
56
+ }
57
+ values.forEach(translation => {
58
+ validateTranslations({ locale: translation.locale, translations: translation.translations }, source)
59
+ // if translation has message & description, we only want the message
60
+ const txs: TransifexStringsKeyValueJson = {}
61
+ for (const key of Object.keys(translation.translations)) {
62
+ const tx = translation.translations[key]
63
+ if (typeof tx === 'string') {
64
+ txs[key] = tx
65
+ } else {
66
+ txs[key] = tx.message
67
+ }
68
+ }
69
+ const file = JSON.stringify(txs, null, 4)
70
+ fs.writeFileSync(`${OUTPUT_DIR}/${translation.locale}.json`, file)
71
+ })
72
+ }
73
+
74
+ await pullTranslations()
@@ -1,9 +1,9 @@
1
- #!/usr/bin/env node
2
-
1
+ #!/usr/bin/env tsx
3
2
  /**
4
3
  * @file
5
4
  * Script to pull scratch-help translations from transifex and push to FreshDesk.
6
5
  */
6
+ import { getInputs, saveItem, localizeFolder } from './lib/help-utils.mts'
7
7
 
8
8
  const args = process.argv.slice(2)
9
9
  const usage = `
@@ -21,14 +21,6 @@ if (!process.env.TX_TOKEN || !process.env.FRESHDESK_TOKEN || args.length > 0) {
21
21
  process.exit(1)
22
22
  }
23
23
 
24
- const { getInputs, saveItem, localizeFolder } = require('./help-utils.js')
25
-
26
- getInputs()
27
- .then(([languages, folders]) => {
28
- process.stdout.write('Processing articles pulled from Transifex\n')
29
- return folders.map(item => saveItem(item, languages, localizeFolder))
30
- })
31
- .catch(e => {
32
- process.stdout.write(`Error: ${e.message}\n`)
33
- process.exitCode = 1 // not ok
34
- })
24
+ const { languages, folders } = await getInputs()
25
+ console.log('Processing articles pulled from Transifex')
26
+ await Promise.all(folders.map(item => saveItem(item, languages, localizeFolder)))
@@ -1,9 +1,9 @@
1
- #!/usr/bin/env node
2
-
1
+ #!/usr/bin/env tsx
3
2
  /**
4
3
  * @file
5
4
  * Script to pull scratch-help translations from transifex and push to FreshDesk.
6
5
  */
6
+ import { getInputs, saveItem, localizeNames } from './lib/help-utils.mts'
7
7
 
8
8
  const args = process.argv.slice(2)
9
9
 
@@ -22,14 +22,6 @@ if (!process.env.TX_TOKEN || !process.env.FRESHDESK_TOKEN || args.length > 0) {
22
22
  process.exit(1)
23
23
  }
24
24
 
25
- const { getInputs, saveItem, localizeNames } = require('./help-utils.js')
26
-
27
- getInputs()
28
- .then(([languages, , names]) => {
29
- process.stdout.write('Process Category and Folder Names pulled from Transifex\n')
30
- return names.map(item => saveItem(item, languages, localizeNames))
31
- })
32
- .catch(e => {
33
- process.stdout.write(`Error: ${e.message}\n`)
34
- process.exitCode = 1 // not ok
35
- })
25
+ const { languages, names } = await getInputs()
26
+ console.log('Process Category and Folder Names pulled from Transifex')
27
+ await Promise.all(names.map(item => saveItem(item, languages, localizeNames)))
@@ -1,9 +1,9 @@
1
- #!/usr/bin/env node
2
-
1
+ #!/usr/bin/env tsx
3
2
  /**
4
3
  * @file
5
4
  * Script to pull scratch-help translations from transifex and push to FreshDesk.
6
5
  */
6
+ import { getInputs, saveItem, localizeFolder, debugFolder } from './lib/help-utils.mts'
7
7
 
8
8
  const args = process.argv.slice(2)
9
9
  const usage = `
@@ -21,8 +21,6 @@ if (!process.env.TX_TOKEN || !process.env.FRESHDESK_TOKEN || args.length === 0)
21
21
  process.exit(1)
22
22
  }
23
23
 
24
- const { getInputs, saveItem, localizeFolder, debugFolder } = require('./help-utils.js')
25
-
26
24
  let locale = args[0]
27
25
  let debug = false
28
26
  if (locale === '-d') {
@@ -31,12 +29,6 @@ if (locale === '-d') {
31
29
  }
32
30
  const saveFn = debug ? debugFolder : localizeFolder
33
31
 
34
- getInputs()
35
- .then(([, folders]) => {
36
- process.stdout.write('Processing articles pulled from Transifex\n')
37
- return folders.map(item => saveItem(item, [locale], saveFn))
38
- })
39
- .catch(e => {
40
- process.stdout.write(`Error: ${e.message}\n`)
41
- process.exitCode = 1 // not ok
42
- })
32
+ const { folders } = await getInputs()
33
+ console.log('Processing articles pulled from Transifex')
34
+ await Promise.all(folders.map(item => saveItem(item, [locale], saveFn)))
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env tsx
2
2
  /**
3
3
  * @file
4
4
  * Script to pull www translations from transifex for all resources.
@@ -7,20 +7,12 @@
7
7
  * token that has developer access.
8
8
  */
9
9
  import fs from 'fs/promises'
10
- import mkdirp from 'mkdirp'
10
+ import { mkdirp } from 'mkdirp'
11
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
12
  import locales, { localeMap } from '../src/supported-locales.mjs'
16
-
17
- /**
18
- * @file
19
- * Script to pull www translations from transifex for all resources.
20
- * Expects that the project and that the person running the script
21
- * has the the TX_TOKEN environment variable set to an api
22
- * token that has developer access.
23
- */
13
+ import { poolMap } from './lib/concurrent.mts'
14
+ import { ProgressLogger } from './lib/progress-logger.mjs'
15
+ import { txPull, txResources } from './lib/transifex.mts'
24
16
 
25
17
  const args = process.argv.slice(2)
26
18
 
@@ -49,7 +41,7 @@ const CONCURRENCY_LIMIT = 36
49
41
 
50
42
  const lang = args.length === 2 ? args[1] : ''
51
43
 
52
- const getLocaleData = async function (item) {
44
+ const getLocaleData = async function (item: { locale: string; resource: string }) {
53
45
  const locale = item.locale
54
46
  const resource = item.resource
55
47
  const txLocale = localeMap[locale] || locale
@@ -69,7 +61,7 @@ const getLocaleData = async function (item) {
69
61
  fileName,
70
62
  }
71
63
  } catch (e) {
72
- e.cause = {
64
+ ;(e as Error).cause = {
73
65
  resource,
74
66
  locale,
75
67
  translations,
@@ -80,7 +72,7 @@ const getLocaleData = async function (item) {
80
72
  }
81
73
  }
82
74
 
83
- const expandResourceFiles = resources => {
75
+ const expandResourceFiles = (resources: string[]) => {
84
76
  const items = []
85
77
  for (const resource of resources) {
86
78
  if (lang) {
@@ -100,18 +92,13 @@ const pullTranslations = async function () {
100
92
 
101
93
  const progress = new ProgressLogger(allFiles.length)
102
94
 
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
- }
95
+ await poolMap(allFiles, CONCURRENCY_LIMIT, async item => {
96
+ try {
97
+ await getLocaleData(item)
98
+ } finally {
99
+ progress.increment()
100
+ }
101
+ })
115
102
  }
116
103
 
117
- pullTranslations()
104
+ await pullTranslations()