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.
- 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 +24 -22
- 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
@@ -1,54 +0,0 @@
|
|
1
|
-
name: Daily TX Pull
|
2
|
-
|
3
|
-
on:
|
4
|
-
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
|
5
|
-
schedule:
|
6
|
-
# daily-tx-pull (e.g., daily at 3 AM UTC)
|
7
|
-
- cron: '0 3 * * *'
|
8
|
-
|
9
|
-
concurrency:
|
10
|
-
group: '${{ github.workflow }}'
|
11
|
-
cancel-in-progress: true
|
12
|
-
|
13
|
-
permissions:
|
14
|
-
contents: write # publish a GitHub release
|
15
|
-
pages: write # deploy to GitHub Pages
|
16
|
-
issues: write # comment on released issues
|
17
|
-
pull-requests: write # comment on released pull requests
|
18
|
-
|
19
|
-
jobs:
|
20
|
-
daily-tx-pull:
|
21
|
-
runs-on: ubuntu-latest
|
22
|
-
|
23
|
-
env:
|
24
|
-
# Organization-wide secrets
|
25
|
-
TX_TOKEN: ${{ secrets.TX_TOKEN }}
|
26
|
-
|
27
|
-
steps:
|
28
|
-
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
29
|
-
- uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3
|
30
|
-
with:
|
31
|
-
cache: 'npm'
|
32
|
-
node-version-file: '.nvmrc'
|
33
|
-
|
34
|
-
- name: Install dependencies
|
35
|
-
run: npm ci
|
36
|
-
|
37
|
-
- name: Pull editor and www translations
|
38
|
-
run: |
|
39
|
-
npm run pull:editor
|
40
|
-
npm run pull:www
|
41
|
-
npm run test
|
42
|
-
|
43
|
-
- name: Commit translation updates
|
44
|
-
id: commit
|
45
|
-
run: |
|
46
|
-
git config --global user.email $(git log --pretty=format:"%ae" -n1)
|
47
|
-
git config --global user.name $(git log --pretty=format:"%an" -n1)
|
48
|
-
git add .
|
49
|
-
if git diff --cached --exit-code --quiet; then
|
50
|
-
echo "Nothing to commit."
|
51
|
-
else
|
52
|
-
git commit -m "fix: pull new editor translations from Transifex"
|
53
|
-
git push
|
54
|
-
fi
|
@@ -1,31 +0,0 @@
|
|
1
|
-
name: 'Signature Assistant'
|
2
|
-
on:
|
3
|
-
issue_comment:
|
4
|
-
types: [created]
|
5
|
-
pull_request_target:
|
6
|
-
types: [opened, closed, synchronize]
|
7
|
-
|
8
|
-
permissions:
|
9
|
-
actions: write
|
10
|
-
contents: read
|
11
|
-
pull-requests: write
|
12
|
-
statuses: write
|
13
|
-
|
14
|
-
jobs:
|
15
|
-
CLA-Assistant:
|
16
|
-
runs-on: ubuntu-latest
|
17
|
-
steps:
|
18
|
-
- name: 'CLA Assistant'
|
19
|
-
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
20
|
-
uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1
|
21
|
-
env:
|
22
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
23
|
-
# the below token should have repo scope and must be manually added by you in the repository's secrets
|
24
|
-
PERSONAL_ACCESS_TOKEN: ${{ secrets.GHA_AGREEMENTS_PAT }}
|
25
|
-
with:
|
26
|
-
remote-organization-name: 'scratchfoundation'
|
27
|
-
remote-repository-name: 'scratch-agreements'
|
28
|
-
path-to-signatures: 'signatures/version1/cla.json'
|
29
|
-
path-to-document: 'https://github.com/scratchfoundation/scratch-agreements/blob/main/CLA.md'
|
30
|
-
branch: 'main'
|
31
|
-
allowlist: semantic-release-bot,*[bot]
|
package/lib/batch.js
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Maps each value of an array into an async function, and returns an array of the results
|
3
|
-
* @param {Array} arr - array of values
|
4
|
-
* @param {number} batchSize - number of calls to `func` to do at one time
|
5
|
-
* @param {Function} func - async function to apply to all items in `arr`. Function should take one argument.
|
6
|
-
* @returns {Promise<Array>} - results of `func` applied to each item in `arr`
|
7
|
-
*/
|
8
|
-
exports.batchMap = async (arr, batchSize, func) => {
|
9
|
-
const results = []
|
10
|
-
for (let i = 0; i < arr.length; i += batchSize) {
|
11
|
-
const result = await Promise.all(arr.slice(i, i + batchSize).map(func))
|
12
|
-
results.push(...result)
|
13
|
-
}
|
14
|
-
return results
|
15
|
-
}
|
package/lib/transifex.js
DELETED
@@ -1,242 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
|
-
/**
|
4
|
-
* @file
|
5
|
-
* Utilities for interfacing with Transifex API 3.
|
6
|
-
*/
|
7
|
-
|
8
|
-
const transifexApi = require('@transifex/api').transifexApi
|
9
|
-
const download = require('download')
|
10
|
-
|
11
|
-
/**
|
12
|
-
* @import {Collection, JsonApiResource} from '@transifex/api';
|
13
|
-
*/
|
14
|
-
|
15
|
-
const ORG_NAME = 'llk'
|
16
|
-
const SOURCE_LOCALE = 'en'
|
17
|
-
|
18
|
-
try {
|
19
|
-
transifexApi.setup({
|
20
|
-
auth: process.env.TX_TOKEN,
|
21
|
-
})
|
22
|
-
} catch (err) {
|
23
|
-
if (!process.env.TX_TOKEN) {
|
24
|
-
throw new Error('TX_TOKEN is not defined.')
|
25
|
-
}
|
26
|
-
throw err
|
27
|
-
}
|
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
|
-
|
57
|
-
/**
|
58
|
-
* Creates a download event for a specific project, resource, and locale.
|
59
|
-
* Returns the URL to download the resource.
|
60
|
-
* @param {string} projectSlug - project slug (for example, "scratch-editor")
|
61
|
-
* @param {string} resourceSlug - resource slug (for example, "blocks")
|
62
|
-
* @param {string} localeCode - language code (for example, "ko")
|
63
|
-
* @param {string} mode - translation status of strings to include
|
64
|
-
* @returns {Promise<string>} - URL to download the resource
|
65
|
-
*/
|
66
|
-
const getResourceLocation = async function (projectSlug, resourceSlug, localeCode, mode = 'default') {
|
67
|
-
const resource = {
|
68
|
-
data: {
|
69
|
-
id: `o:${ORG_NAME}:p:${projectSlug}:r:${resourceSlug}`,
|
70
|
-
type: 'resources',
|
71
|
-
},
|
72
|
-
}
|
73
|
-
|
74
|
-
// if locale is English, create a download event of the source file
|
75
|
-
if (localeCode === SOURCE_LOCALE) {
|
76
|
-
return await transifexApi.ResourceStringsAsyncDownload.download({
|
77
|
-
resource,
|
78
|
-
})
|
79
|
-
}
|
80
|
-
|
81
|
-
const language = {
|
82
|
-
data: {
|
83
|
-
id: `l:${localeCode}`,
|
84
|
-
type: 'languages',
|
85
|
-
},
|
86
|
-
}
|
87
|
-
|
88
|
-
// if locale is not English, create a download event of the translation file
|
89
|
-
return await transifexApi.ResourceTranslationsAsyncDownload.download({
|
90
|
-
mode,
|
91
|
-
resource,
|
92
|
-
language,
|
93
|
-
})
|
94
|
-
}
|
95
|
-
|
96
|
-
/**
|
97
|
-
* Pulls a translation json from transifex, for a specific project, resource, and locale.
|
98
|
-
* @param {string} project - project slug (for example, "scratch-editor")
|
99
|
-
* @param {string} resource - resource slug (for example, "blocks")
|
100
|
-
* @param {string} locale - language code (for example, "ko")
|
101
|
-
* @param {string} mode - translation status of strings to include
|
102
|
-
* @returns {Promise<object>} - JSON object of translated resource strings (or, of the original resource
|
103
|
-
* strings, if the local is the source language)
|
104
|
-
*/
|
105
|
-
const txPull = async function (project, resource, locale, mode = 'default') {
|
106
|
-
let buffer
|
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}`)
|
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
|
133
|
-
}
|
134
|
-
}
|
135
|
-
|
136
|
-
/**
|
137
|
-
* Given a project, returns a list of the slugs of all resources in the project
|
138
|
-
* @param {string} project - project slug (for example, "scratch-website")
|
139
|
-
* @returns {Promise<Array>} - array of strings, slugs identifying each resource in the project
|
140
|
-
*/
|
141
|
-
const txResources = async function (project) {
|
142
|
-
const resources = transifexApi.Resource.filter({
|
143
|
-
project: `o:${ORG_NAME}:p:${project}`,
|
144
|
-
})
|
145
|
-
|
146
|
-
const resourcesData = await collectAll(resources)
|
147
|
-
|
148
|
-
const slugs = resourcesData.map(
|
149
|
-
r =>
|
150
|
-
// r.id is a longer id string, like "o:llk:p:scratch-website:r:about-l10njson"
|
151
|
-
// We just want the slug that comes after ":r:" ("about-l10njson")
|
152
|
-
r.id.split(':r:')[1],
|
153
|
-
)
|
154
|
-
return slugs
|
155
|
-
}
|
156
|
-
|
157
|
-
/**
|
158
|
-
* @param {string} project - project slug (for example)
|
159
|
-
* @returns {Promise<JsonApiResource[]>} - array of resource objects
|
160
|
-
*/
|
161
|
-
const txResourcesObjects = async function (project) {
|
162
|
-
const resources = transifexApi.Resource.filter({
|
163
|
-
project: `o:${ORG_NAME}:p:${project}`,
|
164
|
-
})
|
165
|
-
|
166
|
-
return collectAll(resources)
|
167
|
-
}
|
168
|
-
|
169
|
-
/**
|
170
|
-
* Gets available languages for a project
|
171
|
-
* @param {string} slug - project slug (for example, "scratch-editor")
|
172
|
-
* @returns {Promise<string[]>} - list of language codes
|
173
|
-
*/
|
174
|
-
const txAvailableLanguages = async function (slug) {
|
175
|
-
const project = await transifexApi.Project.get({
|
176
|
-
organization: `o:${ORG_NAME}`,
|
177
|
-
slug: slug,
|
178
|
-
})
|
179
|
-
|
180
|
-
const languages = await project.fetch('languages')
|
181
|
-
const languagesData = await collectAll(languages)
|
182
|
-
return languagesData.map(l => l.attributes.code)
|
183
|
-
}
|
184
|
-
|
185
|
-
/**
|
186
|
-
* Uploads English source strings to a resource in transifex
|
187
|
-
* @param {string} project - project slug (for example, "scratch-editor")
|
188
|
-
* @param {string} resource - resource slug (for example, "blocks")
|
189
|
-
* @param {object} sourceStrings - json of source strings
|
190
|
-
*/
|
191
|
-
const txPush = async function (project, resource, sourceStrings) {
|
192
|
-
const resourceObj = {
|
193
|
-
data: {
|
194
|
-
id: `o:${ORG_NAME}:p:${project}:r:${resource}`,
|
195
|
-
type: 'resources',
|
196
|
-
},
|
197
|
-
}
|
198
|
-
|
199
|
-
await transifexApi.ResourceStringsAsyncUpload.upload({
|
200
|
-
resource: resourceObj,
|
201
|
-
content: JSON.stringify(sourceStrings),
|
202
|
-
})
|
203
|
-
}
|
204
|
-
|
205
|
-
/**
|
206
|
-
* Creates a new resource, and then uploads source strings to it if they are provided
|
207
|
-
* @param {string} project - project slug (for example, "scratch-editor")
|
208
|
-
* @param {object} resource - object of resource information
|
209
|
-
* @param {string} resource.slug - resource slug (for example, "blocks")
|
210
|
-
* @param {string} resource.name - human-readable name for the resource
|
211
|
-
* @param {string} resource.i18nType - i18n format id
|
212
|
-
* @param {object} resource.sourceStrings - json object of source strings
|
213
|
-
*/
|
214
|
-
const txCreateResource = async function (project, { slug, name, i18nType, sourceStrings }) {
|
215
|
-
const i18nFormat = {
|
216
|
-
data: {
|
217
|
-
id: i18nType || 'KEYVALUEJSON',
|
218
|
-
type: 'i18n_formats',
|
219
|
-
},
|
220
|
-
}
|
221
|
-
|
222
|
-
const projectObj = {
|
223
|
-
data: {
|
224
|
-
id: `o:${ORG_NAME}:p:${project}`,
|
225
|
-
type: 'projects',
|
226
|
-
},
|
227
|
-
}
|
228
|
-
|
229
|
-
await transifexApi.Resource.create({
|
230
|
-
attributes: { slug: slug, name: name },
|
231
|
-
relationships: {
|
232
|
-
i18n_format: i18nFormat,
|
233
|
-
project: projectObj,
|
234
|
-
},
|
235
|
-
})
|
236
|
-
|
237
|
-
if (sourceStrings) {
|
238
|
-
await txPush(project, slug, sourceStrings)
|
239
|
-
}
|
240
|
-
}
|
241
|
-
|
242
|
-
module.exports = { txPull, txPush, txResources, txResourcesObjects, txCreateResource, txAvailableLanguages }
|
package/lib/validate.mjs
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
import assert from 'assert'
|
2
|
-
import parse from 'format-message-parse'
|
3
|
-
|
4
|
-
// filter placeholders out of a message
|
5
|
-
// parse('a message with a {value} and {count, plural, one {one} other {more}}.')
|
6
|
-
// returns an array:
|
7
|
-
// [ 'a message with a ',
|
8
|
-
// [ 'value' ],
|
9
|
-
// ' and ',
|
10
|
-
// [ 'count', 'plural', 0, { one: [Array], other: [Array] } ],
|
11
|
-
// '.'
|
12
|
-
// ]
|
13
|
-
// placeholders are always an array, so filter for array elements to find the placeholders
|
14
|
-
const placeholders = message =>
|
15
|
-
// this will throw an error if the message is not valid ICU
|
16
|
-
// single quote (as in French l'année) messes up the parse and is not
|
17
|
-
// relevant for this check, so strip them out
|
18
|
-
parse(message.replace(/'/g, '')).filter(item => Array.isArray(item))
|
19
|
-
|
20
|
-
const validMessage = (message, source) => {
|
21
|
-
const transPlaceholders = placeholders(message.toString())
|
22
|
-
const srcPlaceholders = placeholders(source.toString())
|
23
|
-
// different number of placeholders
|
24
|
-
if (transPlaceholders.length !== srcPlaceholders.length) {
|
25
|
-
return false
|
26
|
-
}
|
27
|
-
// TODO: Add checking to make sure placeholders in source have not been translated
|
28
|
-
// TODO: Add validation of scratch-blocks placeholders
|
29
|
-
return true
|
30
|
-
}
|
31
|
-
|
32
|
-
const validateTranslations = (translation, source) => {
|
33
|
-
const locale = translation.locale
|
34
|
-
const translations = translation.translations
|
35
|
-
const transKeys = Object.keys(translations)
|
36
|
-
const sourceKeys = Object.keys(source)
|
37
|
-
assert.strictEqual(transKeys.length, sourceKeys.length, `locale ${locale} has a different number of message keys`)
|
38
|
-
transKeys.map(item => assert(sourceKeys.includes(item), `locale ${locale} has key ${item} not in the source`))
|
39
|
-
sourceKeys.map(item => assert(transKeys.includes(item), `locale ${locale} is missing key ${item}`))
|
40
|
-
sourceKeys.map(item =>
|
41
|
-
assert(
|
42
|
-
validMessage(translations[item], source[item]),
|
43
|
-
`locale ${locale}: "${translations[item]}" is not a valid translation for "${source[item]}"`,
|
44
|
-
),
|
45
|
-
)
|
46
|
-
}
|
47
|
-
|
48
|
-
export { validateTranslations, validMessage }
|
package/scripts/freshdesk-api.js
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
// interface to FreshDesk Solutions (knowledge base) api
|
2
|
-
|
3
|
-
const fetch = require('node-fetch')
|
4
|
-
class FreshdeskApi {
|
5
|
-
constructor(baseUrl, apiKey) {
|
6
|
-
this.baseUrl = baseUrl
|
7
|
-
this._auth = 'Basic ' + new Buffer(`${apiKey}:X`).toString('base64')
|
8
|
-
this.defaultHeaders = {
|
9
|
-
'Content-Type': 'application/json',
|
10
|
-
Authorization: this._auth,
|
11
|
-
}
|
12
|
-
this.rateLimited = false
|
13
|
-
}
|
14
|
-
|
15
|
-
/**
|
16
|
-
* Checks the status of a response. If status is not ok, or the body is not json raise exception
|
17
|
-
* @param {object} res The response object
|
18
|
-
* @returns {object} the response if it is ok
|
19
|
-
*/
|
20
|
-
checkStatus(res) {
|
21
|
-
if (res.ok) {
|
22
|
-
if (res.headers.get('content-type').indexOf('application/json') !== -1) {
|
23
|
-
return res
|
24
|
-
}
|
25
|
-
throw new Error(`response not json: ${res.headers.get('content-type')}`)
|
26
|
-
}
|
27
|
-
const err = new Error(`response ${res.statusText}`)
|
28
|
-
err.code = res.status
|
29
|
-
if (res.status === 429) {
|
30
|
-
err.retryAfter = res.headers.get('Retry-After')
|
31
|
-
}
|
32
|
-
throw err
|
33
|
-
}
|
34
|
-
|
35
|
-
listCategories() {
|
36
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/categories`, { headers: this.defaultHeaders })
|
37
|
-
.then(this.checkStatus)
|
38
|
-
.then(res => res.json())
|
39
|
-
}
|
40
|
-
|
41
|
-
listFolders(category) {
|
42
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/categories/${category.id}/folders`, {
|
43
|
-
headers: this.defaultHeaders,
|
44
|
-
})
|
45
|
-
.then(this.checkStatus)
|
46
|
-
.then(res => res.json())
|
47
|
-
}
|
48
|
-
|
49
|
-
listArticles(folder) {
|
50
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/folders/${folder.id}/articles`, { headers: this.defaultHeaders })
|
51
|
-
.then(this.checkStatus)
|
52
|
-
.then(res => res.json())
|
53
|
-
}
|
54
|
-
|
55
|
-
updateCategoryTranslation(id, locale, body) {
|
56
|
-
if (this.rateLimited) {
|
57
|
-
process.stdout.write(`Rate limited, skipping id: ${id} for ${locale}\n`)
|
58
|
-
return -1
|
59
|
-
}
|
60
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/categories/${id}/${locale}`, {
|
61
|
-
method: 'put',
|
62
|
-
body: JSON.stringify(body),
|
63
|
-
headers: this.defaultHeaders,
|
64
|
-
})
|
65
|
-
.then(this.checkStatus)
|
66
|
-
.then(res => res.json())
|
67
|
-
.catch(err => {
|
68
|
-
if (err.code === 404) {
|
69
|
-
// not found, try create instead
|
70
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/categories/${id}/${locale}`, {
|
71
|
-
method: 'post',
|
72
|
-
body: JSON.stringify(body),
|
73
|
-
headers: this.defaultHeaders,
|
74
|
-
})
|
75
|
-
.then(this.checkStatus)
|
76
|
-
.then(res => res.json())
|
77
|
-
}
|
78
|
-
if (err.code === 429) {
|
79
|
-
this.rateLimited = true
|
80
|
-
}
|
81
|
-
process.stdout.write(`Error processing id ${id} for locale ${locale}: ${err.message}\n`)
|
82
|
-
throw err
|
83
|
-
})
|
84
|
-
}
|
85
|
-
|
86
|
-
updateFolderTranslation(id, locale, body) {
|
87
|
-
if (this.rateLimited) {
|
88
|
-
process.stdout.write(`Rate limited, skipping id: ${id} for ${locale}\n`)
|
89
|
-
return -1
|
90
|
-
}
|
91
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/folders/${id}/${locale}`, {
|
92
|
-
method: 'put',
|
93
|
-
body: JSON.stringify(body),
|
94
|
-
headers: this.defaultHeaders,
|
95
|
-
})
|
96
|
-
.then(this.checkStatus)
|
97
|
-
.then(res => res.json())
|
98
|
-
.catch(err => {
|
99
|
-
if (err.code === 404) {
|
100
|
-
// not found, try create instead
|
101
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/folders/${id}/${locale}`, {
|
102
|
-
method: 'post',
|
103
|
-
body: JSON.stringify(body),
|
104
|
-
headers: this.defaultHeaders,
|
105
|
-
})
|
106
|
-
.then(this.checkStatus)
|
107
|
-
.then(res => res.json())
|
108
|
-
}
|
109
|
-
if (err.code === 429) {
|
110
|
-
this.rateLimited = true
|
111
|
-
}
|
112
|
-
process.stdout.write(`Error processing id ${id} for locale ${locale}: ${err.message}\n`)
|
113
|
-
throw err
|
114
|
-
})
|
115
|
-
}
|
116
|
-
|
117
|
-
updateArticleTranslation(id, locale, body) {
|
118
|
-
if (this.rateLimited) {
|
119
|
-
process.stdout.write(`Rate limited, skipping id: ${id} for ${locale}\n`)
|
120
|
-
return -1
|
121
|
-
}
|
122
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/articles/${id}/${locale}`, {
|
123
|
-
method: 'put',
|
124
|
-
body: JSON.stringify(body),
|
125
|
-
headers: this.defaultHeaders,
|
126
|
-
})
|
127
|
-
.then(this.checkStatus)
|
128
|
-
.then(res => res.json())
|
129
|
-
.catch(err => {
|
130
|
-
if (err.code === 404) {
|
131
|
-
// not found, try create instead
|
132
|
-
return fetch(`${this.baseUrl}/api/v2/solutions/articles/${id}/${locale}`, {
|
133
|
-
method: 'post',
|
134
|
-
body: JSON.stringify(body),
|
135
|
-
headers: this.defaultHeaders,
|
136
|
-
})
|
137
|
-
.then(this.checkStatus)
|
138
|
-
.then(res => res.json())
|
139
|
-
}
|
140
|
-
if (err.code === 429) {
|
141
|
-
this.rateLimited = true
|
142
|
-
}
|
143
|
-
process.stdout.write(`Error processing id ${id} for locale ${locale}: ${err.message}\n`)
|
144
|
-
throw err
|
145
|
-
})
|
146
|
-
}
|
147
|
-
}
|
148
|
-
|
149
|
-
module.exports = FreshdeskApi
|