scratch-l10n 5.0.309 → 6.0.1
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/CHANGELOG.md +57 -0
- package/README.md +6 -0
- package/dist/l10n.js +3 -0
- package/dist/l10n.js.map +1 -1
- package/dist/localeData.js +3 -0
- package/dist/localeData.js.map +1 -1
- package/dist/supportedLocales.js +3 -0
- package/dist/supportedLocales.js.map +1 -1
- package/package.json +23 -21
- package/scripts/{build-data.mjs → build-data.mts} +15 -6
- package/scripts/{build-i18n-src.js → build-i18n-src.mts} +16 -11
- package/scripts/lib/concurrent.mts +37 -0
- package/scripts/lib/freshdesk-api.mts +322 -0
- package/scripts/lib/help-utils.mts +221 -0
- package/{lib/progress-logger.mjs → scripts/lib/progress-logger.mts} +10 -5
- package/scripts/lib/transifex-formats.mts +53 -0
- package/scripts/lib/transifex-objects.mts +143 -0
- package/scripts/lib/transifex.mts +284 -0
- package/scripts/lib/validate.mts +107 -0
- package/scripts/tsconfig.json +20 -0
- package/scripts/tx-pull-editor.mts +74 -0
- package/scripts/{tx-pull-help-articles.js → tx-pull-help-articles.mts} +5 -13
- package/scripts/{tx-pull-help-names.js → tx-pull-help-names.mts} +5 -13
- package/scripts/{tx-pull-locale-articles.js → tx-pull-locale-articles.mts} +5 -13
- package/scripts/{tx-pull-www.mjs → tx-pull-www.mts} +16 -29
- package/scripts/{tx-push-help.mjs → tx-push-help.mts} +39 -37
- package/scripts/{tx-push-src.js → tx-push-src.mts} +13 -20
- package/scripts/update-translations.sh +2 -2
- package/scripts/{validate-extension-inputs.mjs → validate-extension-inputs.mts} +20 -10
- package/scripts/{validate-translations.mjs → validate-translations.mts} +7 -12
- package/scripts/{validate-www.mjs → validate-www.mts} +15 -13
- package/src/supported-locales.mjs +3 -0
- package/www/scratch-website.about-l10njson/nn.json +1 -1
- package/www/scratch-website.splash-l10njson/mi.json +2 -2
- package/www/scratch-website.teacher-faq-l10njson/cy.json +1 -1
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -75
- package/.github/workflows/ci-cd.yml +0 -55
- package/.github/workflows/commitlint.yml +0 -12
- package/.github/workflows/daily-help-update.yml +0 -40
- package/.github/workflows/daily-tx-pull.yml +0 -54
- package/.github/workflows/signature-assistant.yml +0 -31
- package/lib/batch.js +0 -15
- package/lib/transifex.js +0 -242
- package/lib/validate.mjs +0 -48
- package/scripts/freshdesk-api.js +0 -149
- package/scripts/help-utils.js +0 -190
- package/scripts/tx-pull-editor.mjs +0 -83
package/scripts/help-utils.js
DELETED
@@ -1,190 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
|
-
/**
|
4
|
-
* @file
|
5
|
-
* Helper functions for syncing Freshdesk knowledge base articles with Transifex
|
6
|
-
*/
|
7
|
-
|
8
|
-
const FreshdeskApi = require('./freshdesk-api.js')
|
9
|
-
const fs = require('fs')
|
10
|
-
const fsPromises = fs.promises
|
11
|
-
const mkdirp = require('mkdirp')
|
12
|
-
const { txPull, txResourcesObjects, txAvailableLanguages } = require('../lib/transifex.js')
|
13
|
-
|
14
|
-
const FD = new FreshdeskApi('https://mitscratch.freshdesk.com', process.env.FRESHDESK_TOKEN)
|
15
|
-
const TX_PROJECT = 'scratch-help'
|
16
|
-
|
17
|
-
const freshdeskLocale = locale => {
|
18
|
-
// map between Transifex locale and Freshdesk. Two letter codes are usually fine
|
19
|
-
const localeMap = {
|
20
|
-
es_419: 'es-LA',
|
21
|
-
ja: 'ja-JP',
|
22
|
-
'ja-Hira': 'ja-JP',
|
23
|
-
lv: 'lv-LV',
|
24
|
-
nb: 'nb-NO',
|
25
|
-
nn: 'nb-NO',
|
26
|
-
pt: 'pt-PT',
|
27
|
-
pt_BR: 'pt-BR',
|
28
|
-
ru: 'ru-RU',
|
29
|
-
sv: 'sv-SE',
|
30
|
-
zh_CN: 'zh-CN',
|
31
|
-
zh_TW: 'zh-TW',
|
32
|
-
}
|
33
|
-
return localeMap[locale] || locale
|
34
|
-
}
|
35
|
-
|
36
|
-
/**
|
37
|
-
* Pull metadata from Transifex for the scratch-help project
|
38
|
-
* @returns {Promise} results array containing:
|
39
|
-
* languages: array of supported languages
|
40
|
-
* folders: array of tx resources corresponding to Freshdesk folders
|
41
|
-
* names: array of tx resources corresponding to the Freshdesk metadata
|
42
|
-
*/
|
43
|
-
exports.getInputs = async () => {
|
44
|
-
const resources = await txResourcesObjects(TX_PROJECT)
|
45
|
-
const languages = await txAvailableLanguages(TX_PROJECT)
|
46
|
-
// there are three types of resources differentiated by the file type
|
47
|
-
const folders = resources.filter(resource => resource.i18n_type === 'STRUCTURED_JSON')
|
48
|
-
const names = resources.filter(resource => resource.i18n_type === 'KEYVALUEJSON')
|
49
|
-
// ignore the yaml type because it's not possible to update via API
|
50
|
-
|
51
|
-
return Promise.all([languages, folders, names])
|
52
|
-
}
|
53
|
-
|
54
|
-
/*
|
55
|
-
* internal function to serialize saving category and folder name translations to avoid Freshdesk rate limit
|
56
|
-
*/
|
57
|
-
const serializeNameSave = async (json, resource, locale) => {
|
58
|
-
for (const [key, value] of Object.entries(json)) {
|
59
|
-
// key is of the form <name>_<id>
|
60
|
-
const words = key.split('_')
|
61
|
-
const id = words[words.length - 1]
|
62
|
-
let status = 0
|
63
|
-
if (resource.name === 'categoryNames_json') {
|
64
|
-
status = await FD.updateCategoryTranslation(id, freshdeskLocale(locale), { name: value })
|
65
|
-
}
|
66
|
-
if (resource.name === 'folderNames_json') {
|
67
|
-
status = await FD.updateFolderTranslation(id, freshdeskLocale(locale), { name: value })
|
68
|
-
}
|
69
|
-
if (status === -1) {
|
70
|
-
process.exitCode = 1
|
71
|
-
}
|
72
|
-
}
|
73
|
-
}
|
74
|
-
|
75
|
-
/**
|
76
|
-
* Internal function serialize Freshdesk requests to avoid getting rate limited
|
77
|
-
* @param {object} json object with keys corresponding to article ids
|
78
|
-
* @param {string} locale language code
|
79
|
-
* @returns {Promise} [description]
|
80
|
-
*/
|
81
|
-
const serializeFolderSave = async (json, locale) => {
|
82
|
-
// json is a map of articles:
|
83
|
-
// {
|
84
|
-
// <id>: {
|
85
|
-
// title: {string: <title-value>},
|
86
|
-
// description: {string: <description-value>},
|
87
|
-
// tags: {string: <comma separated strings} // optional
|
88
|
-
// },
|
89
|
-
// <id>: {
|
90
|
-
// title: {string: <title-value>},
|
91
|
-
// description: {string: <description-value>},
|
92
|
-
// tags: {string: <comma separated strings} // optional
|
93
|
-
// }
|
94
|
-
// }
|
95
|
-
for (const [id, value] of Object.entries(json)) {
|
96
|
-
const body = {
|
97
|
-
title: value.title.string,
|
98
|
-
description: value.description.string,
|
99
|
-
status: 2, // set status to published
|
100
|
-
}
|
101
|
-
if (Object.prototype.hasOwnProperty.call(value, 'tags')) {
|
102
|
-
const tags = value.tags.string.split(',')
|
103
|
-
const validTags = tags.filter(tag => tag.length < 33)
|
104
|
-
if (validTags.length !== tags.length) {
|
105
|
-
process.stdout.write(`Warning: tags too long in ${id} for ${locale}\n`)
|
106
|
-
}
|
107
|
-
body.tags = validTags
|
108
|
-
}
|
109
|
-
const status = await FD.updateArticleTranslation(id, freshdeskLocale(locale), body)
|
110
|
-
if (status === -1) {
|
111
|
-
// eslint-disable-next-line require-atomic-updates -- I promise that `process` won't change across `await`
|
112
|
-
process.exitCode = 1
|
113
|
-
}
|
114
|
-
}
|
115
|
-
return 0
|
116
|
-
}
|
117
|
-
|
118
|
-
/**
|
119
|
-
* Process Transifex resource corresponding to a Knowledge base folder on Freshdesk
|
120
|
-
* @param {object} folder Transifex resource json corresponding to a KB folder
|
121
|
-
* @param {string} locale locale to pull and submit to Freshdesk
|
122
|
-
* @returns {Promise} [description]
|
123
|
-
*/
|
124
|
-
exports.localizeFolder = async (folder, locale) => {
|
125
|
-
txPull(TX_PROJECT, folder.slug, locale, { mode: 'default' })
|
126
|
-
.then(data => {
|
127
|
-
serializeFolderSave(data, locale)
|
128
|
-
})
|
129
|
-
.catch(e => {
|
130
|
-
process.stdout.write(`Error processing ${folder.slug}, ${locale}: ${e.message}\n`)
|
131
|
-
process.exitCode = 1 // not ok
|
132
|
-
})
|
133
|
-
}
|
134
|
-
|
135
|
-
/**
|
136
|
-
* Save Transifex resource corresponding to a Knowledge base folder locally for debugging
|
137
|
-
* @param {object} folder Transifex resource json corresponding to a KB folder
|
138
|
-
* @param {string} locale locale to pull and save
|
139
|
-
* @returns {Promise} [description]
|
140
|
-
*/
|
141
|
-
exports.debugFolder = async (folder, locale) => {
|
142
|
-
mkdirp.sync('tmpDebug')
|
143
|
-
txPull(TX_PROJECT, folder.slug, locale, { mode: 'default' })
|
144
|
-
.then(data => {
|
145
|
-
fsPromises.writeFile(`tmpDebug/${folder.slug}_${locale}.json`, JSON.stringify(data, null, 2))
|
146
|
-
})
|
147
|
-
.catch(e => {
|
148
|
-
process.stdout.write(`Error processing ${folder.slug}, ${locale}: ${e.message}\n`)
|
149
|
-
process.exitCode = 1 // not ok
|
150
|
-
})
|
151
|
-
}
|
152
|
-
|
153
|
-
/**
|
154
|
-
* Process KEYVALUEJSON resources from scratch-help on transifex
|
155
|
-
* Category and Folder names are stored as plain json
|
156
|
-
* @param {object} resource Transifex resource json for either CategoryNames or FolderNames
|
157
|
-
* @param {string} locale locale to pull and submit to Freshdesk
|
158
|
-
* @returns {Promise} [description]
|
159
|
-
*/
|
160
|
-
exports.localizeNames = async (resource, locale) => {
|
161
|
-
txPull(TX_PROJECT, resource.slug, locale, { mode: 'default' })
|
162
|
-
.then(data => {
|
163
|
-
serializeNameSave(data, resource, locale)
|
164
|
-
})
|
165
|
-
.catch(e => {
|
166
|
-
process.stdout.write(`Error saving ${resource.slug}, ${locale}: ${e.message}\n`)
|
167
|
-
process.exitCode = 1 // not ok
|
168
|
-
})
|
169
|
-
}
|
170
|
-
|
171
|
-
const BATCH_SIZE = 2
|
172
|
-
/*
|
173
|
-
* save resource items in batches to reduce rate limiting errors
|
174
|
-
* @param {object} item Transifex resource json, used for 'slug'
|
175
|
-
* @param {array} languages Array of languages to save
|
176
|
-
* @param {function} saveFn Async function to use to save the item
|
177
|
-
* @return {Promise}
|
178
|
-
*/
|
179
|
-
exports.saveItem = async (item, languages, saveFn) => {
|
180
|
-
const saveLanguages = languages.filter(l => l !== 'en') // exclude English from update
|
181
|
-
let batchedPromises = Promise.resolve()
|
182
|
-
for (let i = 0; i < saveLanguages.length; i += BATCH_SIZE) {
|
183
|
-
batchedPromises = batchedPromises
|
184
|
-
.then(() => Promise.all(saveLanguages.slice(i, i + BATCH_SIZE).map(l => saveFn(item, l))))
|
185
|
-
.catch(err => {
|
186
|
-
process.stdout.write(`Error saving item:${err.message}\n${JSON.stringify(item, null, 2)}\n`)
|
187
|
-
process.exitCode = 1 // not ok
|
188
|
-
})
|
189
|
-
}
|
190
|
-
}
|
@@ -1,83 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
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 { batchMap } from '../lib/batch.js'
|
12
|
-
import { txPull } from '../lib/transifex.js'
|
13
|
-
import { validateTranslations } from '../lib/validate.mjs'
|
14
|
-
import locales, { localeMap } from '../src/supported-locales.mjs'
|
15
|
-
|
16
|
-
/**
|
17
|
-
* @file
|
18
|
-
* Script to pull translations from transifex and generate the editor-msgs file.
|
19
|
-
* Expects that the project and resource have already been defined in Transifex, and that
|
20
|
-
* the person running the script has the the TX_TOKEN environment variable set to an api
|
21
|
-
* token that has developer access.
|
22
|
-
*/
|
23
|
-
|
24
|
-
const args = process.argv.slice(2)
|
25
|
-
|
26
|
-
const usage = `
|
27
|
-
Pull supported language translations from Transifex. Usage:
|
28
|
-
node tx-pull-editor.js tx-project tx-resource path
|
29
|
-
tx-project: project on Transifex (e.g., scratch-editor)
|
30
|
-
tx-resource: resource within the project (e.g., interface)
|
31
|
-
path: where to put the downloaded json files
|
32
|
-
NOTE: TX_TOKEN environment variable needs to be set with a Transifex API token. See
|
33
|
-
the Localization page on the GUI wiki for information about setting up Transifex.
|
34
|
-
`
|
35
|
-
|
36
|
-
// Fail immediately if the TX_TOKEN is not defined
|
37
|
-
if (!process.env.TX_TOKEN || args.length < 3) {
|
38
|
-
process.stdout.write(usage)
|
39
|
-
process.exit(1)
|
40
|
-
}
|
41
|
-
|
42
|
-
// Globals
|
43
|
-
const PROJECT = args[0]
|
44
|
-
const RESOURCE = args[1]
|
45
|
-
const OUTPUT_DIR = path.resolve(args[2])
|
46
|
-
const MODE = 'reviewed'
|
47
|
-
const CONCURRENCY_LIMIT = 36
|
48
|
-
|
49
|
-
const getLocaleData = async function (locale) {
|
50
|
-
const txLocale = localeMap[locale] || locale
|
51
|
-
const data = await txPull(PROJECT, RESOURCE, txLocale, MODE)
|
52
|
-
return {
|
53
|
-
locale: locale,
|
54
|
-
translations: data,
|
55
|
-
}
|
56
|
-
}
|
57
|
-
|
58
|
-
const pullTranslations = async function () {
|
59
|
-
try {
|
60
|
-
const values = await batchMap(Object.keys(locales), CONCURRENCY_LIMIT, getLocaleData)
|
61
|
-
const source = values.find(elt => elt.locale === 'en').translations
|
62
|
-
values.forEach(translation => {
|
63
|
-
validateTranslations({ locale: translation.locale, translations: translation.translations }, source)
|
64
|
-
// if translation has message & description, we only want the message
|
65
|
-
const txs = {}
|
66
|
-
for (const key of Object.keys(translation.translations)) {
|
67
|
-
const tx = translation.translations[key]
|
68
|
-
if (tx.message) {
|
69
|
-
txs[key] = tx.message
|
70
|
-
} else {
|
71
|
-
txs[key] = tx
|
72
|
-
}
|
73
|
-
}
|
74
|
-
const file = JSON.stringify(txs, null, 4)
|
75
|
-
fs.writeFileSync(`${OUTPUT_DIR}/${translation.locale}.json`, file)
|
76
|
-
})
|
77
|
-
} catch (err) {
|
78
|
-
process.stdout.write(err.message)
|
79
|
-
process.exit(1)
|
80
|
-
}
|
81
|
-
}
|
82
|
-
|
83
|
-
pullTranslations()
|