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
@@ -0,0 +1,221 @@
|
|
1
|
+
/**
|
2
|
+
* @file
|
3
|
+
* Helper functions for syncing Freshdesk knowledge base articles with Transifex
|
4
|
+
*/
|
5
|
+
import { promises as fsPromises } from 'fs'
|
6
|
+
import { mkdirp } from 'mkdirp'
|
7
|
+
import FreshdeskApi, { FreshdeskArticleCreate, FreshdeskArticleStatus } from './freshdesk-api.mts'
|
8
|
+
import { TransifexStringKeyValueJson, TransifexStringsKeyValueJson, TransifexStrings } from './transifex-formats.mts'
|
9
|
+
import { TransifexResourceObject } from './transifex-objects.mts'
|
10
|
+
import { txPull, txResourcesObjects, txAvailableLanguages } from './transifex.mts'
|
11
|
+
|
12
|
+
const FD = new FreshdeskApi('https://mitscratch.freshdesk.com', process.env.FRESHDESK_TOKEN ?? '')
|
13
|
+
const TX_PROJECT = 'scratch-help'
|
14
|
+
|
15
|
+
const freshdeskLocale = (locale: string): string => {
|
16
|
+
// map between Transifex locale and Freshdesk. Two letter codes are usually fine
|
17
|
+
const localeMap: Record<string, string> = {
|
18
|
+
es_419: 'es-LA',
|
19
|
+
ja: 'ja-JP',
|
20
|
+
'ja-Hira': 'ja-JP',
|
21
|
+
lv: 'lv-LV',
|
22
|
+
nb: 'nb-NO',
|
23
|
+
nn: 'nb-NO',
|
24
|
+
pt: 'pt-PT',
|
25
|
+
pt_BR: 'pt-BR',
|
26
|
+
ru: 'ru-RU',
|
27
|
+
sv: 'sv-SE',
|
28
|
+
zh_CN: 'zh-CN',
|
29
|
+
zh_TW: 'zh-TW',
|
30
|
+
}
|
31
|
+
return localeMap[locale] || locale
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Parse a string into an integer.
|
36
|
+
* If converting the integer back to a string does not result in the same string, throw.
|
37
|
+
* @param str - The (allegedly) numeric string to parse
|
38
|
+
* @param radix - Interpret the string as a number in this base. For example, use 10 for decimal values.
|
39
|
+
* @returns The numeric value of the string
|
40
|
+
*/
|
41
|
+
const parseIntOrThrow = (str: string, radix: number) => {
|
42
|
+
const num = parseInt(str, radix)
|
43
|
+
if (str != num.toString(radix)) {
|
44
|
+
throw new Error(`Could not parse int safely: ${str}`)
|
45
|
+
}
|
46
|
+
return num
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Pull metadata from Transifex for the scratch-help project
|
51
|
+
* @returns Promise for a results object containing:
|
52
|
+
* languages - array of supported languages
|
53
|
+
* folders - array of tx resources corresponding to Freshdesk folders
|
54
|
+
* names - array of tx resources corresponding to the Freshdesk metadata
|
55
|
+
*/
|
56
|
+
export const getInputs = async () => {
|
57
|
+
const resourcesPromise = txResourcesObjects(TX_PROJECT)
|
58
|
+
const languagesPromise = txAvailableLanguages(TX_PROJECT)
|
59
|
+
|
60
|
+
// there are three types of resources differentiated by the file type
|
61
|
+
const foldersPromise = resourcesPromise.then(resources =>
|
62
|
+
resources.filter(resource => resource.attributes.i18n_type === 'STRUCTURED_JSON'),
|
63
|
+
)
|
64
|
+
const namesPromise = resourcesPromise.then(resources =>
|
65
|
+
resources.filter(resource => resource.attributes.i18n_type === 'KEYVALUEJSON'),
|
66
|
+
)
|
67
|
+
// ignore the yaml type because it's not possible to update via API
|
68
|
+
|
69
|
+
const [languages, folders, names] = await Promise.all([languagesPromise, foldersPromise, namesPromise])
|
70
|
+
|
71
|
+
return {
|
72
|
+
languages,
|
73
|
+
folders,
|
74
|
+
names,
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* internal function to serialize saving category and folder name translations to avoid Freshdesk rate limit
|
80
|
+
* @param strings - the string data pulled from Transifex
|
81
|
+
* @param resource - the `attributes` property of the resource object which contains these strings
|
82
|
+
* @param locale - the Transifex locale code corresponding to these strings
|
83
|
+
*/
|
84
|
+
const serializeNameSave = async (
|
85
|
+
strings: TransifexStringsKeyValueJson,
|
86
|
+
resource: TransifexResourceObject,
|
87
|
+
locale: string,
|
88
|
+
): Promise<void> => {
|
89
|
+
for (const [key, value] of Object.entries(strings)) {
|
90
|
+
// key is of the form <name>_<id>
|
91
|
+
const words = key.split('_')
|
92
|
+
const id = parseIntOrThrow(words[words.length - 1], 10)
|
93
|
+
let status
|
94
|
+
if (resource.attributes.name === 'categoryNames_json') {
|
95
|
+
status = await FD.updateCategoryTranslation(id, freshdeskLocale(locale), { name: value })
|
96
|
+
}
|
97
|
+
if (resource.attributes.name === 'folderNames_json') {
|
98
|
+
status = await FD.updateFolderTranslation(id, freshdeskLocale(locale), { name: value })
|
99
|
+
}
|
100
|
+
if (status === -1) {
|
101
|
+
process.exitCode = 1
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* We use this specific structure in the `STRUCTUREDJSON` resources associated with our Freshdesk folders.
|
108
|
+
* This should be compatible with (and stricter than) `TransifexStringStructuredJson`.
|
109
|
+
*/
|
110
|
+
interface FreshdeskFolderInTransifex {
|
111
|
+
title: { string: string }
|
112
|
+
description: { string: string }
|
113
|
+
tags: { string: string }
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Internal function serialize Freshdesk requests to avoid getting rate limited
|
118
|
+
* @param json object with keys corresponding to article ids
|
119
|
+
* @param locale language code
|
120
|
+
* @returns a numeric status code
|
121
|
+
*/
|
122
|
+
const serializeFolderSave = async (json: TransifexStrings<FreshdeskFolderInTransifex>, locale: string) => {
|
123
|
+
for (const [idString, value] of Object.entries(json)) {
|
124
|
+
const id = parseIntOrThrow(idString, 10)
|
125
|
+
const body: FreshdeskArticleCreate = {
|
126
|
+
title: value.title.string,
|
127
|
+
description: value.description.string,
|
128
|
+
status: FreshdeskArticleStatus.published,
|
129
|
+
}
|
130
|
+
if (Object.prototype.hasOwnProperty.call(value, 'tags')) {
|
131
|
+
const tags = value.tags.string.split(',')
|
132
|
+
const validTags = tags.filter(tag => tag.length < 33)
|
133
|
+
if (validTags.length !== tags.length) {
|
134
|
+
process.stdout.write(`Warning: tags too long in ${id} for ${locale}\n`)
|
135
|
+
}
|
136
|
+
body.tags = validTags
|
137
|
+
}
|
138
|
+
const status = await FD.updateArticleTranslation(id, freshdeskLocale(locale), body)
|
139
|
+
if (status === -1) {
|
140
|
+
// eslint-disable-next-line require-atomic-updates -- `process` will not change across `await`
|
141
|
+
process.exitCode = 1
|
142
|
+
}
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
/**
|
147
|
+
* Process Transifex resource corresponding to a Knowledge base folder on Freshdesk
|
148
|
+
* @param folderAttributes Transifex resource json corresponding to a KB folder
|
149
|
+
* @param locale locale to pull and submit to Freshdesk
|
150
|
+
*/
|
151
|
+
export const localizeFolder = async (folderAttributes: TransifexResourceObject, locale: string) => {
|
152
|
+
try {
|
153
|
+
const data = await txPull<FreshdeskFolderInTransifex>(
|
154
|
+
TX_PROJECT,
|
155
|
+
folderAttributes.attributes.slug,
|
156
|
+
locale,
|
157
|
+
'default',
|
158
|
+
)
|
159
|
+
await serializeFolderSave(data, locale)
|
160
|
+
} catch (e) {
|
161
|
+
process.stdout.write(`Error processing ${folderAttributes.attributes.slug}, ${locale}: ${(e as Error).message}\n`)
|
162
|
+
process.exitCode = 1 // not ok
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Save Transifex resource corresponding to a Knowledge base folder locally for debugging
|
168
|
+
* @param folderAttributes Transifex resource json corresponding to a KB folder
|
169
|
+
* @param locale locale to pull and save
|
170
|
+
*/
|
171
|
+
export const debugFolder = async (folderAttributes: TransifexResourceObject, locale: string) => {
|
172
|
+
await mkdirp('tmpDebug')
|
173
|
+
await txPull(TX_PROJECT, folderAttributes.attributes.slug, locale, 'default')
|
174
|
+
.then(data =>
|
175
|
+
fsPromises.writeFile(
|
176
|
+
`tmpDebug/${folderAttributes.attributes.slug}_${locale}.json`,
|
177
|
+
JSON.stringify(data, null, 2),
|
178
|
+
),
|
179
|
+
)
|
180
|
+
.catch(e => {
|
181
|
+
process.stdout.write(
|
182
|
+
`Error processing ${folderAttributes.attributes.slug}, ${locale}: ${(e as Error).message}\n`,
|
183
|
+
)
|
184
|
+
process.exitCode = 1 // not ok
|
185
|
+
})
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Process KEYVALUEJSON resources from scratch-help on transifex
|
190
|
+
* Category and Folder names are stored as plain json
|
191
|
+
* @param resource Transifex resource json for either CategoryNames or FolderNames
|
192
|
+
* @param locale locale to pull and submit to Freshdesk
|
193
|
+
*/
|
194
|
+
export const localizeNames = async (resource: TransifexResourceObject, locale: string): Promise<void> => {
|
195
|
+
await txPull<TransifexStringKeyValueJson>(TX_PROJECT, resource.attributes.slug, locale, 'default')
|
196
|
+
.then(data => serializeNameSave(data, resource, locale))
|
197
|
+
.catch(e => {
|
198
|
+
process.stdout.write(`Error saving ${resource.attributes.slug}, ${locale}: ${(e as Error).message}\n`)
|
199
|
+
process.exitCode = 1 // not ok
|
200
|
+
})
|
201
|
+
}
|
202
|
+
|
203
|
+
const BATCH_SIZE = 2
|
204
|
+
|
205
|
+
type SaveFn = (item: TransifexResourceObject, language: string) => Promise<void>
|
206
|
+
|
207
|
+
/**
|
208
|
+
* save resource items in batches to reduce rate limiting errors
|
209
|
+
* @param item Transifex resource json, used for 'slug'
|
210
|
+
* @param languages Array of languages to save
|
211
|
+
* @param saveFn Async function to use to save the item
|
212
|
+
*/
|
213
|
+
export const saveItem = async (item: TransifexResourceObject, languages: string[], saveFn: SaveFn) => {
|
214
|
+
const saveLanguages = languages.filter(l => l !== 'en') // exclude English from update
|
215
|
+
for (let i = 0; i < saveLanguages.length; i += BATCH_SIZE) {
|
216
|
+
await Promise.all(saveLanguages.slice(i, i + BATCH_SIZE).map(l => saveFn(item, l))).catch(err => {
|
217
|
+
process.stdout.write(`Error saving item:${(err as Error).message}\n${JSON.stringify(item, null, 2)}\n`)
|
218
|
+
process.exitCode = 1 // not ok
|
219
|
+
})
|
220
|
+
}
|
221
|
+
}
|
@@ -2,19 +2,24 @@
|
|
2
2
|
* Helper class to log progress.
|
3
3
|
*/
|
4
4
|
export class ProgressLogger {
|
5
|
+
total?: number
|
6
|
+
completed: number
|
7
|
+
percent?: number
|
8
|
+
|
5
9
|
/**
|
6
|
-
* @param
|
10
|
+
* @param [total] - Optional: expected total number of items to process.
|
7
11
|
*/
|
8
|
-
constructor(total) {
|
12
|
+
constructor(total?: number) {
|
9
13
|
this.total = total
|
10
14
|
this.completed = 0
|
15
|
+
this.percent = 0
|
11
16
|
}
|
12
17
|
|
13
18
|
/**
|
14
19
|
* Set the expected total number of items to process.
|
15
|
-
* @param
|
20
|
+
* @param total - Total number of items to process.
|
16
21
|
*/
|
17
|
-
setTotal(total) {
|
22
|
+
setTotal(total: number) {
|
18
23
|
if (this.total !== total) {
|
19
24
|
this.total = total
|
20
25
|
delete this.percent
|
@@ -25,7 +30,7 @@ export class ProgressLogger {
|
|
25
30
|
* Increment the number of items processed and log progress.
|
26
31
|
* If a total is set, progress is logged as a percentage and only when the percentage changes.
|
27
32
|
* If no total is set, progress is logged as a count.
|
28
|
-
* @param
|
33
|
+
* @param [count] - Number of items processed.
|
29
34
|
*/
|
30
35
|
increment(count = 1) {
|
31
36
|
this.completed += count
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/**
|
2
|
+
* A set of strings from a Transifex resource.
|
3
|
+
*/
|
4
|
+
export type TransifexStrings<T> = Record<string, T>
|
5
|
+
|
6
|
+
/**
|
7
|
+
* A single string from a resource that uses the "CHROME" file type.
|
8
|
+
* Scratch: used for most (but not all) Scratch Editor resources.
|
9
|
+
*/
|
10
|
+
export interface TransifexStringChrome {
|
11
|
+
/** The source or translation text */
|
12
|
+
message: string
|
13
|
+
/** Description or context information for translators */
|
14
|
+
description?: string
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* A set of strings from a resource that uses the "CHROME" file type.
|
19
|
+
* Scratch: used for most (but not all) Scratch Editor resources.
|
20
|
+
*/
|
21
|
+
export type TransifexStringsChrome = TransifexStrings<TransifexStringChrome>
|
22
|
+
|
23
|
+
/**
|
24
|
+
* A single string from a resource that uses the "KEYVALUEJSON" file type.
|
25
|
+
* Scratch: used for the Scratch Website project, the Scratch Editor blocks resource, and some Scratch Help resources.
|
26
|
+
*/
|
27
|
+
export type TransifexStringKeyValueJson = string
|
28
|
+
|
29
|
+
/**
|
30
|
+
* A set of strings from a resource that uses the "KEYVALUEJSON" file type.
|
31
|
+
* Scratch: used for the Scratch Website project, the Scratch Editor blocks resource, and some Scratch Help resources.
|
32
|
+
*/
|
33
|
+
export type TransifexStringsKeyValueJson = TransifexStrings<TransifexStringKeyValueJson>
|
34
|
+
|
35
|
+
/**
|
36
|
+
* A single string from a resource that uses the "STRUCTUREDJSON" file type.
|
37
|
+
* Scratch: used for most (but not all) Scratch Help resources.
|
38
|
+
*/
|
39
|
+
export interface TransifexStringStructuredJson {
|
40
|
+
string?: string
|
41
|
+
context?: string
|
42
|
+
developer_comment?: string
|
43
|
+
character_limit?: number
|
44
|
+
plurals?: object
|
45
|
+
|
46
|
+
[key: string]: TransifexStringStructuredJson | string | number | object | undefined
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* A set of strings from a resource that uses the "STRUCTUREDJSON" file type.
|
51
|
+
* Scratch: used for most (but not all) Scratch Help resources.
|
52
|
+
*/
|
53
|
+
export type TransifexStringsStructuredJson = TransifexStrings<TransifexStringStructuredJson>
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import { JsonApiResource } from '@transifex/api'
|
2
|
+
|
3
|
+
// Writing these types is very manual, so I've only written the ones used by our scripts. The types are adapted from
|
4
|
+
// the documentation of the Transifex API, usually from the description of the `data` field in a 200 response to some
|
5
|
+
// "list" or "get details" kind of call.
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Properties common to all Transifex API objects. I chose the generic term "Object" for what JSON API calls a
|
9
|
+
* "Resource" since "Resource" has a specific meaning in a Transifex context.
|
10
|
+
*/
|
11
|
+
export interface TransifexObject extends JsonApiResource {
|
12
|
+
/** The type of object. Use this to determine the specific type to use for this object. */
|
13
|
+
type: string
|
14
|
+
/** Unique identifier for this object. */
|
15
|
+
id: string
|
16
|
+
/** The attributes of this object. */
|
17
|
+
attributes: Record<string, unknown>
|
18
|
+
/** The relationships of this object. */
|
19
|
+
relationships: Record<string, unknown>
|
20
|
+
/** The URL links of the object. */
|
21
|
+
links: Record<string, string>
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Transifex object representing a Language.
|
26
|
+
* @see https://developers.transifex.com/reference/get_languages-language-id
|
27
|
+
*/
|
28
|
+
export interface TransifexLanguageObject extends TransifexObject {
|
29
|
+
/** The type of the resource.*/
|
30
|
+
type: 'languages'
|
31
|
+
/** Language identifier. Example: `l:en_US` */
|
32
|
+
id: `l:${string}`
|
33
|
+
attributes: {
|
34
|
+
/** The language code as defined in CLDR. Example: `en`. */
|
35
|
+
code: string
|
36
|
+
/** The name of the language as defined in CLDR. Example: `English`. */
|
37
|
+
name: string
|
38
|
+
/** Whether the language is right-to-left. */
|
39
|
+
rtl: boolean
|
40
|
+
/** The language plural rule equation as defined in CLDR. Example: `(n != 1)`. */
|
41
|
+
plural_equation: string
|
42
|
+
/** Object of plural rules for Language as defined in CLDR. */
|
43
|
+
plural_rules: object
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Transifex object representing a Project.
|
49
|
+
* @see https://developers.transifex.com/reference/get_projects-project-id
|
50
|
+
*/
|
51
|
+
export interface TransifexProjectObject extends TransifexObject {
|
52
|
+
/** The type of the resource.*/
|
53
|
+
type: 'projects'
|
54
|
+
/** Project identifier. Example: `o:org_slug:p:project_slug` */
|
55
|
+
id: `o:${string}:p:${string}`
|
56
|
+
attributes: {
|
57
|
+
/**
|
58
|
+
* If the project is archived or not.
|
59
|
+
* If a project is archived the pricing will be lower but no action will be available.
|
60
|
+
*/
|
61
|
+
archived: boolean
|
62
|
+
/** The date and time the project was created. */
|
63
|
+
datetime_created: string
|
64
|
+
/** The date and time the project was last modified. */
|
65
|
+
datetime_modified: string
|
66
|
+
/** A description of the project. */
|
67
|
+
description: string
|
68
|
+
/** The homepage of the project. */
|
69
|
+
homepage_url: string
|
70
|
+
/**
|
71
|
+
* A web page containing documentation or instructions for translators, or localization tips for your
|
72
|
+
* community.
|
73
|
+
*/
|
74
|
+
instructions_url: string
|
75
|
+
/** The license of the project. */
|
76
|
+
license: string
|
77
|
+
/** The URL of the project's logo. */
|
78
|
+
logo_url: string
|
79
|
+
/** A long description of the project. */
|
80
|
+
long_description: string
|
81
|
+
/** If the resources of the project will be filled up from a machine translation. */
|
82
|
+
machine_translation_fillup: boolean
|
83
|
+
/** The name of the project. */
|
84
|
+
name: string
|
85
|
+
/** Whether the project is private. A private project is visible only by you and your team. */
|
86
|
+
private: boolean
|
87
|
+
/** The URL of the public source code repository. */
|
88
|
+
repository_url: string
|
89
|
+
/** The slug of the project. Example: `project_slug`. */
|
90
|
+
slug: string
|
91
|
+
/** List of tags for the project. */
|
92
|
+
tags: string[]
|
93
|
+
/** If the resources of the project will be filled up from common translation memory. */
|
94
|
+
translation_memory_fillup: boolean
|
95
|
+
/** The type of the project. */
|
96
|
+
type: 'live' | 'file'
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Transifex object representing a Resource.
|
102
|
+
* @see https://developers.transifex.com/reference/get_resources-resource-id
|
103
|
+
*/
|
104
|
+
export interface TransifexResourceObject extends TransifexObject {
|
105
|
+
/** The type of the resource.*/
|
106
|
+
type: 'resources'
|
107
|
+
/** Resource identifier. Example: `o:org_slug:p:project_slug:r:resource_slug` */
|
108
|
+
id: `o:${string}:p:${string}:r:${string}`
|
109
|
+
attributes: {
|
110
|
+
/** The slug of the resource. Example: `resource_slug`. */
|
111
|
+
slug: string
|
112
|
+
/** The name of the resource. */
|
113
|
+
name: string
|
114
|
+
/** The priority of the resource. */
|
115
|
+
priority: 'normal' | 'high' | 'urgent'
|
116
|
+
/** The format of the resource. Example: `STRUCTURED_JSON`. */
|
117
|
+
i18n_type: string
|
118
|
+
/** File format type version. */
|
119
|
+
i18n_version: number
|
120
|
+
/** Whether the resource should accept translations or not. */
|
121
|
+
accept_translations: boolean
|
122
|
+
/** The number of strings in the resource content. */
|
123
|
+
string_count: number
|
124
|
+
/** The number of words in the resource content. */
|
125
|
+
word_count: string
|
126
|
+
/** The date and time the resource was created. */
|
127
|
+
datetime_created: string
|
128
|
+
/** The date and time the resource was last modified. */
|
129
|
+
datetime_modified: string
|
130
|
+
/** List of categories to associate similar resources. */
|
131
|
+
categories: string[]
|
132
|
+
/** Options that determine how the resource will be parsed and compiled. */
|
133
|
+
i18n_options: Record<string, unknown>
|
134
|
+
/** A (public) URL to provide an MP4 video file for subtitle translation. */
|
135
|
+
mp4_url: string
|
136
|
+
/** A (public) URL to provide an OGG video file for subtitle translation. */
|
137
|
+
ogg_url: string
|
138
|
+
/** A (public) URL to provide a YouTube video file for subtitle translation. */
|
139
|
+
youtube_url: string
|
140
|
+
/** A (public) URL to provide a WEBM video file for subtitle translation. */
|
141
|
+
webm_url: string
|
142
|
+
}
|
143
|
+
}
|