sanity-plugin-transifex 4.0.2 → 5.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/LICENSE +1 -1
- package/README.md +10 -13
- package/dist/index.d.ts +12 -56
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +124 -147
- package/dist/index.js.map +1 -1
- package/package.json +33 -80
- package/dist/index.d.mts +0 -57
- package/dist/index.mjs +0 -183
- package/dist/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/index.ts +0 -60
- package/src/transifexAdapter/createTask.ts +0 -82
- package/src/transifexAdapter/getLocales.ts +0 -20
- package/src/transifexAdapter/getTranslation.ts +0 -90
- package/src/transifexAdapter/getTranslationTask.ts +0 -51
- package/src/transifexAdapter/helpers.ts +0 -11
- package/src/transifexAdapter/index.ts +0 -13
- package/v2-incompatible.js +0 -11
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
## Installation
|
|
3
2
|
|
|
4
3
|
```sh
|
|
@@ -7,7 +6,6 @@ npm install sanity-plugin-transifex
|
|
|
7
6
|
|
|
8
7
|

|
|
9
8
|
|
|
10
|
-
|
|
11
9
|
# Sanity + Transifex = 🌍
|
|
12
10
|
|
|
13
11
|
This plugin provides an in-studio integration with [Transifex](https://transifex.com). It allows your editors to send any document to Transifex with the click of a button, monitor ongoing translations, and import partial or complete translations back into the studio.
|
|
@@ -31,7 +29,7 @@ _Recent updates for v4:_ We've added support for the new document internationali
|
|
|
31
29
|
npm install sanity-plugin-transifex
|
|
32
30
|
```
|
|
33
31
|
|
|
34
|
-
2. Ensure the plugin has access to your Transifex secrets. You'll want to create a document that includes your project name, organization name, and a token with appropriate access.
|
|
32
|
+
2. Ensure the plugin has access to your Transifex secrets. You'll want to create a document that includes your project name, organization name, and a token with appropriate access.
|
|
35
33
|
|
|
36
34
|
[Please refer to the Transifex documentation on creating a token if you don't have one already.](https://docs.transifex.com/account/authentication)
|
|
37
35
|
|
|
@@ -53,30 +51,30 @@ client.createOrReplace({
|
|
|
53
51
|
// Replace these with your values
|
|
54
52
|
organization: 'YOUR_TRANSIFEX_ORG_HERE',
|
|
55
53
|
project: 'YOUR_TRANSIFEX_PROJECT_HERE',
|
|
56
|
-
token: 'YOUR_TRANSIFEX_TOKEN_HERE'
|
|
54
|
+
token: 'YOUR_TRANSIFEX_TOKEN_HERE',
|
|
57
55
|
})
|
|
58
56
|
```
|
|
59
57
|
|
|
60
|
-
On the command line, run the file:
|
|
58
|
+
On the command line, run the file:
|
|
61
59
|
|
|
62
60
|
```sh
|
|
63
61
|
npx sanity exec populateTransifexSecrets.js --with-user-token
|
|
64
62
|
```
|
|
65
|
-
|
|
63
|
+
|
|
66
64
|
Verify that the document was created using the Vision Tool in the Studio and query `*[_id == 'transifex.secrets']`. Note: If you have multiple datasets, you'll have to do this across all of them.
|
|
67
65
|
|
|
68
|
-
If the document was found in your dataset(s), delete `populateTransifexSecrets.js`.
|
|
66
|
+
If the document was found in your dataset(s), delete `populateTransifexSecrets.js`.
|
|
69
67
|
|
|
70
68
|
If you have concerns about this being exposed to authenticated users of your studio, you can control access to this path with [role-based access control](https://www.sanity.io/docs/access-control).
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
3. Get the Transifex tab on your desired document type, using whatever pattern you like. You'll use the [desk structure](https://www.sanity.io/docs/structure-builder-introduction) for this. The options for translation will be nested under this desired document type's views. Here's an example:
|
|
73
71
|
|
|
74
72
|
```javascript
|
|
75
73
|
import {DefaultDocumentNodeResolver} from 'sanity/desk'
|
|
76
74
|
//...your other desk structure imports...
|
|
77
75
|
import {TranslationsTab, defaultDocumentLevelConfig} from 'sanity-plugin-transifex'
|
|
78
76
|
//if you are using field-level translations, you can import the field-level config instead:
|
|
79
|
-
//import {TranslationsTab, defaultFieldLevelConfig} from 'sanity-plugin-
|
|
77
|
+
//import {TranslationsTab, defaultFieldLevelConfig} from 'sanity-plugin-transifex'
|
|
80
78
|
//if you're not sure which, please look at the document-level and field-level sections below
|
|
81
79
|
|
|
82
80
|
export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
|
|
@@ -140,7 +138,7 @@ There are several reasons to override these functions. More general cases are of
|
|
|
140
138
|
|
|
141
139
|
## Migrating to Sanity Studio v3
|
|
142
140
|
|
|
143
|
-
You should not have to do anything to migrate to Sanity Studio v3. If you are using the default configs, you should be able to upgrade without any changes. If you are using custom serialization, you may need to update how `BaseDocumentSerializer` receives your schema.
|
|
141
|
+
You should not have to do anything to migrate to Sanity Studio v3. If you are using the default configs, you should be able to upgrade without any changes. If you are using custom serialization, you may need to update how `BaseDocumentSerializer` receives your schema.
|
|
144
142
|
|
|
145
143
|
These are outlined in the serializer README [here](https://github.com/sanity-io/sanity-naive-html-serializer#v2-to-v3-changes).
|
|
146
144
|
|
|
@@ -160,7 +158,6 @@ on how to run this plugin with hotreload in the studio.
|
|
|
160
158
|
|
|
161
159
|
### Release new version
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
Make sure to select the main branch and check "Release new version".
|
|
161
|
+
Releases are handled from the [`sanity-io/plugins`](https://github.com/sanity-io/plugins) monorepo using Changesets.
|
|
165
162
|
|
|
166
|
-
|
|
163
|
+
Add a changeset in your PR, then follow the monorepo release workflow to publish from `main`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,57 +1,13 @@
|
|
|
1
|
-
import {Adapter} from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {fieldLevelPatch} from 'sanity-translations-tab'
|
|
9
|
-
import {findLatestDraft} from 'sanity-translations-tab'
|
|
10
|
-
import {legacyDocumentLevelPatch} from 'sanity-translations-tab'
|
|
11
|
-
import {TranslationFunctionContext} from 'sanity-translations-tab'
|
|
12
|
-
import {TranslationsTab} from 'sanity-translations-tab'
|
|
13
|
-
|
|
14
|
-
export {BaseDocumentDeserializer}
|
|
15
|
-
|
|
16
|
-
export {BaseDocumentMerger}
|
|
17
|
-
|
|
18
|
-
export {BaseDocumentSerializer}
|
|
19
|
-
|
|
20
|
-
declare interface ConfigOptions {
|
|
21
|
-
adapter: Adapter
|
|
22
|
-
secretsNamespace: string | null
|
|
23
|
-
exportForTranslation: (
|
|
24
|
-
id: string,
|
|
25
|
-
context: TranslationFunctionContext,
|
|
26
|
-
) => Promise<Record<string, any>>
|
|
27
|
-
importTranslation: (
|
|
28
|
-
id: string,
|
|
29
|
-
localeId: string,
|
|
30
|
-
doc: string,
|
|
31
|
-
context: TranslationFunctionContext,
|
|
32
|
-
) => Promise<void>
|
|
1
|
+
import { Adapter, BaseDocumentDeserializer, BaseDocumentMerger, BaseDocumentSerializer, TranslationFunctionContext, TranslationsTab, customSerializers, defaultStopTypes, documentLevelPatch, fieldLevelPatch, findLatestDraft, legacyDocumentLevelPatch } from "sanity-translations-tab";
|
|
2
|
+
declare const TransifexAdapter: Adapter;
|
|
3
|
+
interface ConfigOptions {
|
|
4
|
+
adapter: Adapter;
|
|
5
|
+
secretsNamespace: string | null;
|
|
6
|
+
exportForTranslation: (id: string, context: TranslationFunctionContext) => Promise<Record<string, any>>;
|
|
7
|
+
importTranslation: (id: string, localeId: string, doc: string, context: TranslationFunctionContext) => Promise<void>;
|
|
33
8
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
export declare const defaultFieldLevelConfig: ConfigOptions
|
|
40
|
-
|
|
41
|
-
export {defaultStopTypes}
|
|
42
|
-
|
|
43
|
-
export {documentLevelPatch}
|
|
44
|
-
|
|
45
|
-
export {fieldLevelPatch}
|
|
46
|
-
|
|
47
|
-
export {findLatestDraft}
|
|
48
|
-
|
|
49
|
-
export declare const legacyDocumentLevelConfig: ConfigOptions
|
|
50
|
-
|
|
51
|
-
export {legacyDocumentLevelPatch}
|
|
52
|
-
|
|
53
|
-
export declare const TransifexAdapter: Adapter
|
|
54
|
-
|
|
55
|
-
export {TranslationsTab}
|
|
56
|
-
|
|
57
|
-
export {}
|
|
9
|
+
declare const defaultDocumentLevelConfig: ConfigOptions;
|
|
10
|
+
declare const legacyDocumentLevelConfig: ConfigOptions;
|
|
11
|
+
declare const defaultFieldLevelConfig: ConfigOptions;
|
|
12
|
+
export { BaseDocumentDeserializer, BaseDocumentMerger, BaseDocumentSerializer, TransifexAdapter, TranslationsTab, customSerializers, defaultDocumentLevelConfig, defaultFieldLevelConfig, defaultStopTypes, documentLevelPatch, fieldLevelPatch, findLatestDraft, legacyDocumentLevelConfig, legacyDocumentLevelPatch };
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/transifexAdapter/index.ts","../src/index.ts"],"mappings":";cAOa,gBAAA,EAAkB,OAAA;AAAA,UCerB,aAAA;EACR,OAAA,EAAS,OAAA;EACT,gBAAA;EACA,oBAAA,GACE,EAAA,UACA,OAAA,EAAS,0BAAA,KACN,OAAA,CAAQ,MAAA;EACb,iBAAA,GACE,EAAA,UACA,QAAA,UACA,GAAA,UACA,OAAA,EAAS,0BAAA,KACN,OAAA;AAAA;AAAA,cAED,0BAAA,EAA4B,aAAA;AAAA,cAM5B,yBAAA,EAA2B,aAAA;AAAA,cAM3B,uBAAA,EAAyB,aAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var sanityTranslationsTab = require("sanity-translations-tab");
|
|
1
|
+
import { baseDocumentLevelConfig, baseFieldLevelConfig, legacyDocumentLevelConfig as legacyDocumentLevelConfig$1 } from "sanity-translations-tab";
|
|
2
|
+
import { BaseDocumentDeserializer, BaseDocumentMerger, BaseDocumentSerializer, TranslationsTab, customSerializers, defaultStopTypes, documentLevelPatch, fieldLevelPatch, findLatestDraft, legacyDocumentLevelPatch } from "sanity-translations-tab";
|
|
4
3
|
const baseTransifexUrl = "https://rest.api.transifex.com", getHeaders = (secrets) => ({
|
|
5
4
|
Authorization: `Bearer ${secrets?.token}`,
|
|
6
5
|
"Content-Type": "application/vnd.api+json"
|
|
7
6
|
}), projOrgSlug = (secrets) => `o:${secrets?.organization}:p:${secrets?.project}`, getLocales = async (secrets) => {
|
|
8
7
|
let locales = [];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
if (secrets) {
|
|
9
|
+
const response = await fetch(`${baseTransifexUrl}/projects/${projOrgSlug(secrets)}/languages`, {
|
|
10
|
+
headers: getHeaders(secrets)
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok)
|
|
13
|
+
throw Error(`Failed to retrieve locales from Transifex. Status: ${response.status}`);
|
|
14
|
+
locales = await response.json().then((res) => res.data.map((lang) => ({
|
|
13
15
|
enabled: !0,
|
|
14
16
|
description: lang.attributes.name,
|
|
15
17
|
localeId: lang.attributes.code
|
|
16
|
-
}))
|
|
17
|
-
|
|
18
|
+
})));
|
|
19
|
+
}
|
|
20
|
+
return locales;
|
|
18
21
|
}, getTranslationTask = async (documentId, secrets) => {
|
|
19
22
|
if (!documentId || !secrets)
|
|
20
23
|
return {
|
|
@@ -22,74 +25,29 @@ const baseTransifexUrl = "https://rest.api.transifex.com", getHeaders = (secrets
|
|
|
22
25
|
documentId,
|
|
23
26
|
locales: []
|
|
24
27
|
};
|
|
25
|
-
const projectFilter = `filter[project]=${projOrgSlug(secrets)}`, resourceFilter = `filter[resource]=${projOrgSlug(secrets)}:r:${documentId}`, task = await fetch(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
).then((res) => {
|
|
28
|
+
const projectFilter = `filter[project]=${projOrgSlug(secrets)}`, resourceFilter = `filter[resource]=${projOrgSlug(secrets)}:r:${documentId}`, task = await fetch(`${baseTransifexUrl}/resource_language_stats?${projectFilter}&${resourceFilter}`, {
|
|
29
|
+
headers: getHeaders(secrets)
|
|
30
|
+
}).then((res) => {
|
|
29
31
|
if (res.ok)
|
|
30
32
|
return res.json();
|
|
31
33
|
if (res.status === 404)
|
|
32
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
data: []
|
|
36
|
+
};
|
|
33
37
|
throw Error(`Failed to retrieve tasks from Transifex. Status: ${res.status}`);
|
|
34
38
|
}).then((res) => ({
|
|
35
39
|
taskId: `${projOrgSlug(secrets)}:r:${documentId}`,
|
|
36
40
|
documentId,
|
|
37
|
-
locales: res.data.map((locale) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
41
|
+
locales: res.data.map((locale) => {
|
|
42
|
+
const reviewedStrings = Number(locale.attributes.reviewed_strings), totalStrings = Number(locale.attributes.total_strings);
|
|
43
|
+
return {
|
|
44
|
+
localeId: locale.relationships.language.data.id.split(":")[1],
|
|
45
|
+
progress: Number.isFinite(reviewedStrings) && Number.isFinite(totalStrings) && totalStrings > 0 ? Math.floor(100 * reviewedStrings / totalStrings) : 0
|
|
46
|
+
};
|
|
47
|
+
})
|
|
48
|
+
})), localeIds = (await getLocales(secrets)).map((l) => l.localeId), validLocales = task.locales.filter((locale) => localeIds.find((id) => id === locale.localeId));
|
|
46
49
|
return task.locales = validLocales, task;
|
|
47
|
-
},
|
|
48
|
-
const response = await fetch(`${resourceDownloadUrl}/${translationDownloadId}`, {
|
|
49
|
-
headers
|
|
50
|
-
});
|
|
51
|
-
return response.status === 500 ? (console.info(
|
|
52
|
-
`Transifex plugin message: Received 500 for translation download ID ${translationDownloadId}. Trying to reconnect...`
|
|
53
|
-
), await new Promise((resolve) => setTimeout(resolve, 3e3)), pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers)) : response.redirected ? (console.info(
|
|
54
|
-
`Transifex plugin message: Received redirect for translation download ID ${translationDownloadId}. Following redirect now for file download.`
|
|
55
|
-
), response.url) : response.status === 200 ? (console.info(
|
|
56
|
-
`Transifex plugin message: Requested download location for translation download ID ${translationDownloadId}. Location is still pending, trying again.`
|
|
57
|
-
), await new Promise((resolve) => setTimeout(resolve, 3e3)), pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers)) : (console.error(
|
|
58
|
-
`Transifex plugin message: Requested download location for translation download ID ${translationDownloadId} but received error code ${response.status}. Waiting and trying again.`
|
|
59
|
-
), await new Promise((resolve) => setTimeout(resolve, 3e3)), pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers));
|
|
60
|
-
}, handleFileDownload = (url) => fetch(url).then((res) => res.text()), getTranslation = async (taskId, localeId, secrets) => {
|
|
61
|
-
const resourceDownloadBody = {
|
|
62
|
-
data: {
|
|
63
|
-
attributes: {
|
|
64
|
-
content_encoding: "text"
|
|
65
|
-
},
|
|
66
|
-
relationships: {
|
|
67
|
-
language: {
|
|
68
|
-
data: {
|
|
69
|
-
id: `l:${localeId}`,
|
|
70
|
-
type: "languages"
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
resource: {
|
|
74
|
-
data: {
|
|
75
|
-
id: taskId,
|
|
76
|
-
type: "resources"
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
type: "resource_translations_async_downloads"
|
|
81
|
-
}
|
|
82
|
-
}, resourceDownloadUrl = `${baseTransifexUrl}/resource_translations_async_downloads`, translationDownloadId = await fetch(resourceDownloadUrl, {
|
|
83
|
-
headers: getHeaders(secrets),
|
|
84
|
-
method: "POST",
|
|
85
|
-
body: JSON.stringify(resourceDownloadBody)
|
|
86
|
-
}).then((res) => res.json()).then((res) => res.data.id), headers = getHeaders(secrets), location = await pollForFileDownloadLocation(
|
|
87
|
-
resourceDownloadUrl,
|
|
88
|
-
translationDownloadId,
|
|
89
|
-
headers
|
|
90
|
-
);
|
|
91
|
-
return handleFileDownload(location);
|
|
92
|
-
}, createResource = (doc, documentId, secrets) => {
|
|
50
|
+
}, createResource = async (doc, documentId, secrets) => {
|
|
93
51
|
const resourceCreateBody = {
|
|
94
52
|
data: {
|
|
95
53
|
attributes: {
|
|
@@ -113,17 +71,31 @@ const baseTransifexUrl = "https://rest.api.transifex.com", getHeaders = (secrets
|
|
|
113
71
|
},
|
|
114
72
|
type: "resources"
|
|
115
73
|
}
|
|
116
|
-
}
|
|
117
|
-
return fetch(`${baseTransifexUrl}/resources`, {
|
|
74
|
+
}, response = await fetch(`${baseTransifexUrl}/resources`, {
|
|
118
75
|
headers: getHeaders(secrets),
|
|
119
76
|
method: "POST",
|
|
120
77
|
body: JSON.stringify(resourceCreateBody)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok)
|
|
80
|
+
throw Error(`Failed to create Transifex resource. Status: ${response.status}`);
|
|
81
|
+
const resourceId = (await response.json())?.data?.id;
|
|
82
|
+
if (!resourceId)
|
|
83
|
+
throw Error("Failed to create Transifex resource. Missing resource id in response.");
|
|
84
|
+
return resourceId;
|
|
85
|
+
}, createTask = async (documentId, document, _localeIds, secrets) => {
|
|
86
|
+
if (!documentId || !secrets)
|
|
87
|
+
throw Error("Missing documentId or Transifex secrets.");
|
|
88
|
+
const resourceResponse = await fetch(`${baseTransifexUrl}/resources/${projOrgSlug(secrets)}:r:${documentId}`, {
|
|
89
|
+
headers: getHeaders(secrets)
|
|
90
|
+
});
|
|
91
|
+
let resourceId = null;
|
|
92
|
+
if (resourceResponse.status === 404)
|
|
93
|
+
resourceId = null;
|
|
94
|
+
else if (resourceResponse.ok) {
|
|
95
|
+
const resource = await resourceResponse.json();
|
|
96
|
+
resourceId = resource.data ? resource.data.id : null;
|
|
97
|
+
} else
|
|
98
|
+
throw Error(`Failed to retrieve Transifex resource. Status: ${resourceResponse.status}`);
|
|
127
99
|
resourceId || (resourceId = await createResource(document, documentId, secrets));
|
|
128
100
|
const resourceUploadUrl = `${baseTransifexUrl}/resource_strings_async_uploads`, resourceUploadBody = {
|
|
129
101
|
data: {
|
|
@@ -141,92 +113,97 @@ const baseTransifexUrl = "https://rest.api.transifex.com", getHeaders = (secrets
|
|
|
141
113
|
},
|
|
142
114
|
type: "resource_strings_async_uploads"
|
|
143
115
|
}
|
|
144
|
-
}
|
|
145
|
-
return fetch(resourceUploadUrl, {
|
|
116
|
+
}, uploadResponse = await fetch(resourceUploadUrl, {
|
|
146
117
|
method: "POST",
|
|
147
118
|
body: JSON.stringify(resourceUploadBody),
|
|
148
119
|
headers: getHeaders(secrets)
|
|
149
|
-
})
|
|
120
|
+
});
|
|
121
|
+
if (!uploadResponse.ok)
|
|
122
|
+
throw Error(`Failed to upload resource strings to Transifex. Status: ${uploadResponse.status}`);
|
|
123
|
+
return getTranslationTask(documentId, secrets);
|
|
124
|
+
}, pollForFileDownloadLocation = async (resourceDownloadUrl, translationDownloadId, headers, retryCount = 0, maxRetries = 20) => {
|
|
125
|
+
const response = await fetch(`${resourceDownloadUrl}/${translationDownloadId}`, {
|
|
126
|
+
headers
|
|
127
|
+
});
|
|
128
|
+
if (retryCount >= maxRetries)
|
|
129
|
+
throw Error(`Failed to retrieve download location for translation download ID ${translationDownloadId} after ${maxRetries} retries.`);
|
|
130
|
+
if (response.status === 500)
|
|
131
|
+
return console.info(`Transifex plugin message: Received 500 for translation download ID ${translationDownloadId}. Trying to reconnect...`), await new Promise((resolve) => setTimeout(resolve, 3e3)), pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers, retryCount + 1, maxRetries);
|
|
132
|
+
if (response.redirected)
|
|
133
|
+
return console.info(`Transifex plugin message: Received redirect for translation download ID ${translationDownloadId}. Following redirect now for file download.`), response.url;
|
|
134
|
+
if (response.status === 200)
|
|
135
|
+
return console.info(`Transifex plugin message: Requested download location for translation download ID ${translationDownloadId}. Location is still pending, trying again.`), await new Promise((resolve) => setTimeout(resolve, 3e3)), pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers, retryCount + 1, maxRetries);
|
|
136
|
+
if (response.status === 401 || response.status === 403)
|
|
137
|
+
throw Error(`Failed to retrieve download location for translation download ID ${translationDownloadId}. Status: ${response.status}`);
|
|
138
|
+
return console.error(`Transifex plugin message: Requested download location for translation download ID ${translationDownloadId} but received error code ${response.status}. Waiting and trying again.`), await new Promise((resolve) => setTimeout(resolve, 3e3)), pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers, retryCount + 1, maxRetries);
|
|
139
|
+
}, handleFileDownload = (url) => fetch(url).then((res) => res.text()), getTranslation = async (taskId, localeId, secrets) => {
|
|
140
|
+
if (!secrets)
|
|
141
|
+
throw Error("Missing Transifex secrets.");
|
|
142
|
+
const resourceDownloadBody = {
|
|
143
|
+
data: {
|
|
144
|
+
attributes: {
|
|
145
|
+
content_encoding: "text"
|
|
146
|
+
},
|
|
147
|
+
relationships: {
|
|
148
|
+
language: {
|
|
149
|
+
data: {
|
|
150
|
+
id: `l:${localeId}`,
|
|
151
|
+
type: "languages"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
resource: {
|
|
155
|
+
data: {
|
|
156
|
+
id: taskId,
|
|
157
|
+
type: "resources"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
type: "resource_translations_async_downloads"
|
|
162
|
+
}
|
|
163
|
+
}, resourceDownloadUrl = `${baseTransifexUrl}/resource_translations_async_downloads`, downloadResponse = await fetch(resourceDownloadUrl, {
|
|
164
|
+
headers: getHeaders(secrets),
|
|
165
|
+
method: "POST",
|
|
166
|
+
body: JSON.stringify(resourceDownloadBody)
|
|
167
|
+
});
|
|
168
|
+
if (!downloadResponse.ok)
|
|
169
|
+
throw Error(`Failed to create translation download request in Transifex. Status: ${downloadResponse.status}`);
|
|
170
|
+
const translationDownloadId = (await downloadResponse.json())?.data?.id;
|
|
171
|
+
if (!translationDownloadId)
|
|
172
|
+
throw Error("Failed to create translation download request in Transifex. Missing download id.");
|
|
173
|
+
const headers = getHeaders(secrets), location = await pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers);
|
|
174
|
+
return handleFileDownload(location);
|
|
150
175
|
}, TransifexAdapter = {
|
|
151
176
|
getLocales,
|
|
152
177
|
getTranslationTask,
|
|
153
178
|
createTask,
|
|
154
179
|
getTranslation
|
|
155
180
|
}, defaultDocumentLevelConfig = {
|
|
156
|
-
...
|
|
181
|
+
...baseDocumentLevelConfig,
|
|
157
182
|
adapter: TransifexAdapter,
|
|
158
183
|
secretsNamespace: "transifex"
|
|
159
184
|
}, legacyDocumentLevelConfig = {
|
|
160
|
-
...
|
|
185
|
+
...legacyDocumentLevelConfig$1,
|
|
161
186
|
adapter: TransifexAdapter,
|
|
162
187
|
secretsNamespace: "transifex"
|
|
163
188
|
}, defaultFieldLevelConfig = {
|
|
164
|
-
...
|
|
189
|
+
...baseFieldLevelConfig,
|
|
165
190
|
adapter: TransifexAdapter,
|
|
166
191
|
secretsNamespace: "transifex"
|
|
167
192
|
};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
Object.defineProperty(exports, "TranslationsTab", {
|
|
187
|
-
enumerable: !0,
|
|
188
|
-
get: function() {
|
|
189
|
-
return sanityTranslationsTab.TranslationsTab;
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
Object.defineProperty(exports, "customSerializers", {
|
|
193
|
-
enumerable: !0,
|
|
194
|
-
get: function() {
|
|
195
|
-
return sanityTranslationsTab.customSerializers;
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
Object.defineProperty(exports, "defaultStopTypes", {
|
|
199
|
-
enumerable: !0,
|
|
200
|
-
get: function() {
|
|
201
|
-
return sanityTranslationsTab.defaultStopTypes;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
Object.defineProperty(exports, "documentLevelPatch", {
|
|
205
|
-
enumerable: !0,
|
|
206
|
-
get: function() {
|
|
207
|
-
return sanityTranslationsTab.documentLevelPatch;
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
Object.defineProperty(exports, "fieldLevelPatch", {
|
|
211
|
-
enumerable: !0,
|
|
212
|
-
get: function() {
|
|
213
|
-
return sanityTranslationsTab.fieldLevelPatch;
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
Object.defineProperty(exports, "findLatestDraft", {
|
|
217
|
-
enumerable: !0,
|
|
218
|
-
get: function() {
|
|
219
|
-
return sanityTranslationsTab.findLatestDraft;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
Object.defineProperty(exports, "legacyDocumentLevelPatch", {
|
|
223
|
-
enumerable: !0,
|
|
224
|
-
get: function() {
|
|
225
|
-
return sanityTranslationsTab.legacyDocumentLevelPatch;
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
exports.TransifexAdapter = TransifexAdapter;
|
|
229
|
-
exports.defaultDocumentLevelConfig = defaultDocumentLevelConfig;
|
|
230
|
-
exports.defaultFieldLevelConfig = defaultFieldLevelConfig;
|
|
231
|
-
exports.legacyDocumentLevelConfig = legacyDocumentLevelConfig;
|
|
193
|
+
export {
|
|
194
|
+
BaseDocumentDeserializer,
|
|
195
|
+
BaseDocumentMerger,
|
|
196
|
+
BaseDocumentSerializer,
|
|
197
|
+
TransifexAdapter,
|
|
198
|
+
TranslationsTab,
|
|
199
|
+
customSerializers,
|
|
200
|
+
defaultDocumentLevelConfig,
|
|
201
|
+
defaultFieldLevelConfig,
|
|
202
|
+
defaultStopTypes,
|
|
203
|
+
documentLevelPatch,
|
|
204
|
+
fieldLevelPatch,
|
|
205
|
+
findLatestDraft,
|
|
206
|
+
legacyDocumentLevelConfig,
|
|
207
|
+
legacyDocumentLevelPatch
|
|
208
|
+
};
|
|
232
209
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/transifexAdapter/helpers.ts","../src/transifexAdapter/getLocales.ts","../src/transifexAdapter/getTranslationTask.ts","../src/transifexAdapter/getTranslation.ts","../src/transifexAdapter/createTask.ts","../src/transifexAdapter/index.ts","../src/index.ts"],"sourcesContent":["import {Secrets} from 'sanity-translations-tab'\n\nexport const baseTransifexUrl = 'https://rest.api.transifex.com'\n\nexport const getHeaders = (secrets: Secrets | null): Record<string, string> => ({\n Authorization: `Bearer ${secrets?.token}`,\n 'Content-Type': 'application/vnd.api+json',\n})\n\nexport const projOrgSlug = (secrets: Secrets | null): string =>\n `o:${secrets?.organization}:p:${secrets?.project}`\n","import {Adapter, Secrets} from 'sanity-translations-tab'\nimport {baseTransifexUrl, projOrgSlug, getHeaders} from './helpers'\n\nexport const getLocales: Adapter['getLocales'] = async (secrets: Secrets | null) => {\n let locales = []\n if (secrets) {\n locales = await fetch(`${baseTransifexUrl}/projects/${projOrgSlug(secrets)}/languages`, {\n headers: getHeaders(secrets),\n })\n .then((res) => res.json())\n .then((res) =>\n res.data.map((lang: Record<string, any>) => ({\n enabled: true,\n description: lang.attributes.name,\n localeId: lang.attributes.code,\n }))\n )\n }\n return locales\n}\n","import {Adapter, Secrets} from 'sanity-translations-tab'\nimport {baseTransifexUrl, projOrgSlug, getHeaders} from './helpers'\nimport {getLocales} from './getLocales'\n\nexport const getTranslationTask: Adapter['getTranslationTask'] = async (\n documentId: string,\n secrets: Secrets | null\n) => {\n if (!documentId || !secrets) {\n return {\n taskId: documentId,\n documentId: documentId,\n locales: [],\n }\n }\n const projectFilter = `filter[project]=${projOrgSlug(secrets)}`\n const resourceFilter = `filter[resource]=${projOrgSlug(secrets)}:r:${documentId}`\n const task = await fetch(\n `${baseTransifexUrl}/resource_language_stats?${projectFilter}&${resourceFilter}`,\n {headers: getHeaders(secrets)}\n )\n .then((res) => {\n if (res.ok) {\n return res.json()\n }\n //normal -- just means that this task doesn't exist yet.\n else if (res.status === 404) {\n return {data: []}\n }\n throw Error(`Failed to retrieve tasks from Transifex. Status: ${res.status}`)\n })\n .then((res) => ({\n taskId: `${projOrgSlug(secrets)}:r:${documentId}`,\n documentId: documentId,\n locales: res.data.map((locale: Record<string, any>) => ({\n localeId: locale.relationships.language.data.id.split(':')[1],\n progress: Math.floor(\n 100 * (locale.attributes.reviewed_strings / parseFloat(locale.attributes.total_strings))\n ),\n })),\n }))\n\n const locales = await getLocales(secrets)\n const localeIds = locales.map((l: Record<string, any>) => l.localeId)\n const validLocales = task.locales.filter((locale: Record<string, any>) =>\n localeIds.find((id: string) => id === locale.localeId)\n )\n task.locales = validLocales\n\n return task\n}\n","import {Adapter, Secrets} from 'sanity-translations-tab'\nimport {baseTransifexUrl, getHeaders} from './helpers'\n\nconst pollForFileDownloadLocation = async (\n resourceDownloadUrl: string,\n translationDownloadId: string,\n headers: Record<string, any>\n): Promise<string> => {\n const response = await fetch(`${resourceDownloadUrl}/${translationDownloadId}`, {\n headers: headers,\n })\n\n if (response.status === 500) {\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.info(\n `Transifex plugin message: Received 500 for translation download ID ${translationDownloadId}. Trying to reconnect...`\n )\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers)\n } else if (response.redirected) {\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.info(\n `Transifex plugin message: Received redirect for translation download ID ${translationDownloadId}. Following redirect now for file download.`\n )\n return response.url\n } else if (response.status === 200) {\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.info(\n `Transifex plugin message: Requested download location for translation download ID ${translationDownloadId}. Location is still pending, trying again.`\n )\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers)\n }\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.error(\n `Transifex plugin message: Requested download location for translation download ID ${translationDownloadId} but received error code ${response.status}. Waiting and trying again.`\n )\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers)\n}\n\nconst handleFileDownload = (url: string) => {\n return fetch(url).then((res) => res.text())\n}\n\nexport const getTranslation: Adapter['getTranslation'] = async (\n taskId: string,\n localeId: string,\n secrets: Secrets | null\n) => {\n const resourceDownloadBody = {\n data: {\n attributes: {\n content_encoding: 'text',\n },\n relationships: {\n language: {\n data: {\n id: `l:${localeId}`,\n type: 'languages',\n },\n },\n resource: {\n data: {\n id: taskId,\n type: 'resources',\n },\n },\n },\n type: 'resource_translations_async_downloads',\n },\n }\n\n const resourceDownloadUrl = `${baseTransifexUrl}/resource_translations_async_downloads`\n const translationDownloadId = await fetch(resourceDownloadUrl, {\n headers: getHeaders(secrets),\n method: 'POST',\n body: JSON.stringify(resourceDownloadBody),\n })\n .then((res) => res.json())\n .then((res) => res.data.id)\n\n const headers = getHeaders(secrets)\n const location = await pollForFileDownloadLocation(\n resourceDownloadUrl,\n translationDownloadId,\n headers\n )\n return handleFileDownload(location)\n}\n","import {Adapter, Secrets} from 'sanity-translations-tab'\nimport {baseTransifexUrl, projOrgSlug, getHeaders} from './helpers'\nimport {getTranslationTask} from './getTranslationTask'\n\nconst createResource = (doc: Record<string, any>, documentId: string, secrets: Secrets | null) => {\n const resourceCreateBody = {\n data: {\n attributes: {\n accept_translations: true,\n name: doc.name,\n slug: documentId,\n },\n relationships: {\n i18n_format: {\n data: {\n id: 'HTML_FRAGMENT',\n type: 'i18n_formats',\n },\n },\n project: {\n data: {\n id: projOrgSlug(secrets),\n type: 'projects',\n },\n },\n },\n type: 'resources',\n },\n }\n\n return fetch(`${baseTransifexUrl}/resources`, {\n headers: getHeaders(secrets),\n method: 'POST',\n body: JSON.stringify(resourceCreateBody),\n })\n .then((res) => res.json())\n .then((res) => res.data.id)\n}\n\n//@ts-ignore until we resolve the TranslationTask return type\nexport const createTask: Adapter['createTask'] = async (\n documentId: string,\n document: Record<string, any>,\n localeIds: string[],\n secrets: Secrets | null\n) => {\n let resourceId = await fetch(\n `${baseTransifexUrl}/resources/${projOrgSlug(secrets)}:r:${documentId}`,\n {headers: getHeaders(secrets)}\n )\n .then((res) => res.json())\n .then((res) => (res.data ? res.data.id : null))\n\n if (!resourceId) {\n resourceId = await createResource(document, documentId, secrets)\n }\n\n const resourceUploadUrl = `${baseTransifexUrl}/resource_strings_async_uploads`\n const resourceUploadBody = {\n data: {\n attributes: {\n content: document.content,\n content_encoding: 'text',\n },\n relationships: {\n resource: {\n data: {\n id: resourceId,\n type: 'resources',\n },\n },\n },\n type: 'resource_strings_async_uploads',\n },\n }\n\n return fetch(resourceUploadUrl, {\n method: 'POST',\n body: JSON.stringify(resourceUploadBody),\n headers: getHeaders(secrets),\n }).then(() => getTranslationTask(documentId, secrets))\n}\n","import {Adapter} from 'sanity-translations-tab'\n\nimport {getLocales} from './getLocales'\nimport {getTranslationTask} from './getTranslationTask'\nimport {getTranslation} from './getTranslation'\nimport {createTask} from './createTask'\n\nexport const TransifexAdapter: Adapter = {\n getLocales,\n getTranslationTask,\n createTask,\n getTranslation,\n}\n","import {\n baseDocumentLevelConfig,\n legacyDocumentLevelConfig as baseLegacyDocumentLevelConfig,\n baseFieldLevelConfig,\n Adapter,\n TranslationFunctionContext,\n} from 'sanity-translations-tab'\nimport {TransifexAdapter} from './transifexAdapter'\n\nexport {\n findLatestDraft,\n BaseDocumentDeserializer,\n BaseDocumentSerializer,\n BaseDocumentMerger,\n defaultStopTypes,\n customSerializers,\n legacyDocumentLevelPatch,\n documentLevelPatch,\n fieldLevelPatch,\n TranslationsTab,\n} from 'sanity-translations-tab'\n\ninterface ConfigOptions {\n adapter: Adapter\n secretsNamespace: string | null\n exportForTranslation: (\n id: string,\n context: TranslationFunctionContext\n ) => Promise<Record<string, any>>\n importTranslation: (\n id: string,\n localeId: string,\n doc: string,\n context: TranslationFunctionContext\n ) => Promise<void>\n}\nconst defaultDocumentLevelConfig: ConfigOptions = {\n ...baseDocumentLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nconst legacyDocumentLevelConfig: ConfigOptions = {\n ...baseLegacyDocumentLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nconst defaultFieldLevelConfig: ConfigOptions = {\n ...baseFieldLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nexport {\n TransifexAdapter,\n defaultDocumentLevelConfig,\n defaultFieldLevelConfig,\n legacyDocumentLevelConfig,\n}\n"],"names":["baseDocumentLevelConfig","baseLegacyDocumentLevelConfig","baseFieldLevelConfig"],"mappings":";;;AAEO,MAAM,mBAAmB,kCAEnB,aAAa,CAAC,aAAqD;AAAA,EAC9E,eAAe,UAAU,SAAS,KAAK;AAAA,EACvC,gBAAgB;AAClB,IAEa,cAAc,CAAC,YAC1B,KAAK,SAAS,YAAY,MAAM,SAAS,OAAO,ICPrC,aAAoC,OAAO,YAA4B;AAClF,MAAI,UAAU,CAAA;AACd,SAAI,YACF,UAAU,MAAM,MAAM,GAAG,gBAAgB,aAAa,YAAY,OAAO,CAAC,cAAc;AAAA,IACtF,SAAS,WAAW,OAAO;AAAA,EAAA,CAC5B,EACE,KAAK,CAAC,QAAQ,IAAI,KAAA,CAAM,EACxB;AAAA,IAAK,CAAC,QACL,IAAI,KAAK,IAAI,CAAC,UAA+B;AAAA,MAC3C,SAAS;AAAA,MACT,aAAa,KAAK,WAAW;AAAA,MAC7B,UAAU,KAAK,WAAW;AAAA,IAAA,EAC1B;AAAA,EAAA,IAGD;AACT,GCfa,qBAAoD,OAC/D,YACA,YACG;AACH,MAAI,CAAC,cAAc,CAAC;AAClB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,SAAS,CAAA;AAAA,IAAC;AAGd,QAAM,gBAAgB,mBAAmB,YAAY,OAAO,CAAC,IACvD,iBAAiB,oBAAoB,YAAY,OAAO,CAAC,MAAM,UAAU,IACzE,OAAO,MAAM;AAAA,IACjB,GAAG,gBAAgB,4BAA4B,aAAa,IAAI,cAAc;AAAA,IAC9E,EAAC,SAAS,WAAW,OAAO,EAAA;AAAA,EAAC,EAE5B,KAAK,CAAC,QAAQ;AACb,QAAI,IAAI;AACN,aAAO,IAAI,KAAA;AAGR,QAAI,IAAI,WAAW;AACtB,aAAO,EAAC,MAAM,GAAC;AAEjB,UAAM,MAAM,oDAAoD,IAAI,MAAM,EAAE;AAAA,EAC9E,CAAC,EACA,KAAK,CAAC,SAAS;AAAA,IACd,QAAQ,GAAG,YAAY,OAAO,CAAC,MAAM,UAAU;AAAA,IAC/C;AAAA,IACA,SAAS,IAAI,KAAK,IAAI,CAAC,YAAiC;AAAA,MACtD,UAAU,OAAO,cAAc,SAAS,KAAK,GAAG,MAAM,GAAG,EAAE,CAAC;AAAA,MAC5D,UAAU,KAAK;AAAA,QACb,OAAO,OAAO,WAAW,mBAAmB,WAAW,OAAO,WAAW,aAAa;AAAA,MAAA;AAAA,IACxF,EACA;AAAA,EAAA,EACF,GAGE,aADU,MAAM,WAAW,OAAO,GACd,IAAI,CAAC,MAA2B,EAAE,QAAQ,GAC9D,eAAe,KAAK,QAAQ;AAAA,IAAO,CAAC,WACxC,UAAU,KAAK,CAAC,OAAe,OAAO,OAAO,QAAQ;AAAA,EAAA;AAEvD,SAAA,KAAK,UAAU,cAER;AACT,GC/CM,8BAA8B,OAClC,qBACA,uBACA,YACoB;AACpB,QAAM,WAAW,MAAM,MAAM,GAAG,mBAAmB,IAAI,qBAAqB,IAAI;AAAA,IAC9E;AAAA,EAAA,CACD;AAED,SAAI,SAAS,WAAW,OAEtB,QAAQ;AAAA,IACN,sEAAsE,qBAAqB;AAAA,EAAA,GAE7F,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC,GACjD,4BAA4B,qBAAqB,uBAAuB,OAAO,KAC7E,SAAS,cAElB,QAAQ;AAAA,IACN,2EAA2E,qBAAqB;AAAA,EAAA,GAE3F,SAAS,OACP,SAAS,WAAW,OAE7B,QAAQ;AAAA,IACN,qFAAqF,qBAAqB;AAAA,EAAA,GAE5G,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC,GACjD,4BAA4B,qBAAqB,uBAAuB,OAAO,MAGxF,QAAQ;AAAA,IACN,qFAAqF,qBAAqB,4BAA4B,SAAS,MAAM;AAAA,EAAA,GAEvJ,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC,GACjD,4BAA4B,qBAAqB,uBAAuB,OAAO;AACxF,GAEM,qBAAqB,CAAC,QACnB,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ,IAAI,MAAM,GAG/B,iBAA4C,OACvD,QACA,UACA,YACG;AACH,QAAM,uBAAuB;AAAA,IAC3B,MAAM;AAAA,MACJ,YAAY;AAAA,QACV,kBAAkB;AAAA,MAAA;AAAA,MAEpB,eAAe;AAAA,QACb,UAAU;AAAA,UACR,MAAM;AAAA,YACJ,IAAI,KAAK,QAAQ;AAAA,YACjB,MAAM;AAAA,UAAA;AAAA,QACR;AAAA,QAEF,UAAU;AAAA,UACR,MAAM;AAAA,YACJ,IAAI;AAAA,YACJ,MAAM;AAAA,UAAA;AAAA,QACR;AAAA,MACF;AAAA,MAEF,MAAM;AAAA,IAAA;AAAA,EACR,GAGI,sBAAsB,GAAG,gBAAgB,0CACzC,wBAAwB,MAAM,MAAM,qBAAqB;AAAA,IAC7D,SAAS,WAAW,OAAO;AAAA,IAC3B,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,oBAAoB;AAAA,EAAA,CAC1C,EACE,KAAK,CAAC,QAAQ,IAAI,KAAA,CAAM,EACxB,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,GAEtB,UAAU,WAAW,OAAO,GAC5B,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,SAAO,mBAAmB,QAAQ;AACpC,GCrFM,iBAAiB,CAAC,KAA0B,YAAoB,YAA4B;AAChG,QAAM,qBAAqB;AAAA,IACzB,MAAM;AAAA,MACJ,YAAY;AAAA,QACV,qBAAqB;AAAA,QACrB,MAAM,IAAI;AAAA,QACV,MAAM;AAAA,MAAA;AAAA,MAER,eAAe;AAAA,QACb,aAAa;AAAA,UACX,MAAM;AAAA,YACJ,IAAI;AAAA,YACJ,MAAM;AAAA,UAAA;AAAA,QACR;AAAA,QAEF,SAAS;AAAA,UACP,MAAM;AAAA,YACJ,IAAI,YAAY,OAAO;AAAA,YACvB,MAAM;AAAA,UAAA;AAAA,QACR;AAAA,MACF;AAAA,MAEF,MAAM;AAAA,IAAA;AAAA,EACR;AAGF,SAAO,MAAM,GAAG,gBAAgB,cAAc;AAAA,IAC5C,SAAS,WAAW,OAAO;AAAA,IAC3B,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,kBAAkB;AAAA,EAAA,CACxC,EACE,KAAK,CAAC,QAAQ,IAAI,KAAA,CAAM,EACxB,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE;AAC9B,GAGa,aAAoC,OAC/C,YACA,UACA,WACA,YACG;AACH,MAAI,aAAa,MAAM;AAAA,IACrB,GAAG,gBAAgB,cAAc,YAAY,OAAO,CAAC,MAAM,UAAU;AAAA,IACrE,EAAC,SAAS,WAAW,OAAO,EAAA;AAAA,EAAC,EAE5B,KAAK,CAAC,QAAQ,IAAI,MAAM,EACxB,KAAK,CAAC,QAAS,IAAI,OAAO,IAAI,KAAK,KAAK,IAAK;AAE3C,iBACH,aAAa,MAAM,eAAe,UAAU,YAAY,OAAO;AAGjE,QAAM,oBAAoB,GAAG,gBAAgB,mCACvC,qBAAqB;AAAA,IACzB,MAAM;AAAA,MACJ,YAAY;AAAA,QACV,SAAS,SAAS;AAAA,QAClB,kBAAkB;AAAA,MAAA;AAAA,MAEpB,eAAe;AAAA,QACb,UAAU;AAAA,UACR,MAAM;AAAA,YACJ,IAAI;AAAA,YACJ,MAAM;AAAA,UAAA;AAAA,QACR;AAAA,MACF;AAAA,MAEF,MAAM;AAAA,IAAA;AAAA,EACR;AAGF,SAAO,MAAM,mBAAmB;AAAA,IAC9B,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,kBAAkB;AAAA,IACvC,SAAS,WAAW,OAAO;AAAA,EAAA,CAC5B,EAAE,KAAK,MAAM,mBAAmB,YAAY,OAAO,CAAC;AACvD,GC1Ea,mBAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GCwBM,6BAA4C;AAAA,EAChD,GAAGA,sBAAAA;AAAAA,EACH,SAAS;AAAA,EACT,kBAAkB;AACpB,GAEM,4BAA2C;AAAA,EAC/C,GAAGC,sBAAAA;AAAAA,EACH,SAAS;AAAA,EACT,kBAAkB;AACpB,GAEM,0BAAyC;AAAA,EAC7C,GAAGC,sBAAAA;AAAAA,EACH,SAAS;AAAA,EACT,kBAAkB;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/transifexAdapter/helpers.ts","../src/transifexAdapter/getLocales.ts","../src/transifexAdapter/getTranslationTask.ts","../src/transifexAdapter/createTask.ts","../src/transifexAdapter/getTranslation.ts","../src/transifexAdapter/index.ts","../src/index.ts"],"sourcesContent":["import type {Secrets} from 'sanity-translations-tab'\n\nexport const baseTransifexUrl = 'https://rest.api.transifex.com'\n\nexport const getHeaders = (secrets: Secrets | null): Record<string, string> => ({\n 'Authorization': `Bearer ${secrets?.token}`,\n 'Content-Type': 'application/vnd.api+json',\n})\n\nexport const projOrgSlug = (secrets: Secrets | null): string =>\n `o:${secrets?.organization}:p:${secrets?.project}`\n","import type {Adapter, Secrets} from 'sanity-translations-tab'\n\nimport {baseTransifexUrl, projOrgSlug, getHeaders} from './helpers'\n\nexport const getLocales: Adapter['getLocales'] = async (secrets: Secrets | null) => {\n let locales = []\n if (secrets) {\n const response = await fetch(`${baseTransifexUrl}/projects/${projOrgSlug(secrets)}/languages`, {\n headers: getHeaders(secrets),\n })\n\n if (!response.ok) {\n throw Error(`Failed to retrieve locales from Transifex. Status: ${response.status}`)\n }\n\n locales = await response.json().then((res) =>\n res.data.map((lang: Record<string, any>) => ({\n enabled: true,\n description: lang['attributes']['name'],\n localeId: lang['attributes']['code'],\n })),\n )\n }\n return locales\n}\n","import type {Adapter, Secrets} from 'sanity-translations-tab'\n\nimport {getLocales} from './getLocales'\nimport {baseTransifexUrl, projOrgSlug, getHeaders} from './helpers'\n\nexport const getTranslationTask: Adapter['getTranslationTask'] = async (\n documentId: string,\n secrets: Secrets | null,\n) => {\n if (!documentId || !secrets) {\n return {\n taskId: documentId,\n documentId: documentId,\n locales: [],\n }\n }\n const projectFilter = `filter[project]=${projOrgSlug(secrets)}`\n const resourceFilter = `filter[resource]=${projOrgSlug(secrets)}:r:${documentId}`\n const task = await fetch(\n `${baseTransifexUrl}/resource_language_stats?${projectFilter}&${resourceFilter}`,\n {headers: getHeaders(secrets)},\n )\n .then((res) => {\n if (res.ok) {\n return res.json()\n }\n //normal -- just means that this task doesn't exist yet.\n else if (res.status === 404) {\n return {data: []}\n }\n throw Error(`Failed to retrieve tasks from Transifex. Status: ${res.status}`)\n })\n .then((res) => ({\n taskId: `${projOrgSlug(secrets)}:r:${documentId}`,\n documentId: documentId,\n locales: res.data.map((locale: Record<string, any>) => {\n const reviewedStrings = Number(locale['attributes']['reviewed_strings'])\n const totalStrings = Number(locale['attributes']['total_strings'])\n\n return {\n localeId: locale['relationships']['language']['data']['id'].split(':')[1],\n progress:\n Number.isFinite(reviewedStrings) && Number.isFinite(totalStrings) && totalStrings > 0\n ? Math.floor((100 * reviewedStrings) / totalStrings)\n : 0,\n }\n }),\n }))\n\n const locales = await getLocales(secrets)\n const localeIds = locales.map((l: Record<string, any>) => l['localeId'])\n const validLocales = task.locales.filter((locale: Record<string, any>) =>\n localeIds.find((id: string) => id === locale['localeId']),\n )\n task.locales = validLocales\n\n return task\n}\n","import type {Adapter, Secrets} from 'sanity-translations-tab'\n\nimport {getTranslationTask} from './getTranslationTask'\nimport {baseTransifexUrl, projOrgSlug, getHeaders} from './helpers'\n\nconst createResource = async (\n doc: Record<string, any>,\n documentId: string,\n secrets: Secrets | null,\n) => {\n const resourceCreateBody = {\n data: {\n attributes: {\n accept_translations: true,\n name: doc['name'],\n slug: documentId,\n },\n relationships: {\n i18n_format: {\n data: {\n id: 'HTML_FRAGMENT',\n type: 'i18n_formats',\n },\n },\n project: {\n data: {\n id: projOrgSlug(secrets),\n type: 'projects',\n },\n },\n },\n type: 'resources',\n },\n }\n\n const response = await fetch(`${baseTransifexUrl}/resources`, {\n headers: getHeaders(secrets),\n method: 'POST',\n body: JSON.stringify(resourceCreateBody),\n })\n\n if (!response.ok) {\n throw Error(`Failed to create Transifex resource. Status: ${response.status}`)\n }\n\n const res = await response.json()\n const resourceId = res?.data?.id\n\n if (!resourceId) {\n throw Error('Failed to create Transifex resource. Missing resource id in response.')\n }\n\n return resourceId\n}\n\nexport const createTask: Adapter['createTask'] = async (\n documentId: string,\n document: Record<string, any>,\n _localeIds: string[],\n secrets: Secrets | null,\n) => {\n if (!documentId || !secrets) {\n throw Error('Missing documentId or Transifex secrets.')\n }\n\n const resourceResponse = await fetch(\n `${baseTransifexUrl}/resources/${projOrgSlug(secrets)}:r:${documentId}`,\n {headers: getHeaders(secrets)},\n )\n let resourceId: string | null = null\n\n if (resourceResponse.status === 404) {\n resourceId = null\n } else if (!resourceResponse.ok) {\n throw Error(`Failed to retrieve Transifex resource. Status: ${resourceResponse.status}`)\n } else {\n const resource = await resourceResponse.json()\n resourceId = resource.data ? resource.data.id : null\n }\n\n if (!resourceId) {\n resourceId = await createResource(document, documentId, secrets)\n }\n\n const resourceUploadUrl = `${baseTransifexUrl}/resource_strings_async_uploads`\n const resourceUploadBody = {\n data: {\n attributes: {\n content: document['content'],\n content_encoding: 'text',\n },\n relationships: {\n resource: {\n data: {\n id: resourceId,\n type: 'resources',\n },\n },\n },\n type: 'resource_strings_async_uploads',\n },\n }\n\n const uploadResponse = await fetch(resourceUploadUrl, {\n method: 'POST',\n body: JSON.stringify(resourceUploadBody),\n headers: getHeaders(secrets),\n })\n\n if (!uploadResponse.ok) {\n throw Error(`Failed to upload resource strings to Transifex. Status: ${uploadResponse.status}`)\n }\n\n return getTranslationTask(documentId, secrets)\n}\n","import type {Adapter, Secrets} from 'sanity-translations-tab'\n\nimport {baseTransifexUrl, getHeaders} from './helpers'\n\nconst pollForFileDownloadLocation = async (\n resourceDownloadUrl: string,\n translationDownloadId: string,\n headers: Record<string, any>,\n retryCount = 0,\n maxRetries = 20,\n): Promise<string> => {\n const response = await fetch(`${resourceDownloadUrl}/${translationDownloadId}`, {\n headers: headers,\n })\n\n if (retryCount >= maxRetries) {\n throw Error(\n `Failed to retrieve download location for translation download ID ${translationDownloadId} after ${maxRetries} retries.`,\n )\n }\n\n if (response.status === 500) {\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.info(\n `Transifex plugin message: Received 500 for translation download ID ${translationDownloadId}. Trying to reconnect...`,\n )\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return pollForFileDownloadLocation(\n resourceDownloadUrl,\n translationDownloadId,\n headers,\n retryCount + 1,\n maxRetries,\n )\n } else if (response.redirected) {\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.info(\n `Transifex plugin message: Received redirect for translation download ID ${translationDownloadId}. Following redirect now for file download.`,\n )\n return response.url\n } else if (response.status === 200) {\n //eslint-disable-next-line no-console -- this is for developer feedback/debugging\n console.info(\n `Transifex plugin message: Requested download location for translation download ID ${translationDownloadId}. Location is still pending, trying again.`,\n )\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return pollForFileDownloadLocation(\n resourceDownloadUrl,\n translationDownloadId,\n headers,\n retryCount + 1,\n maxRetries,\n )\n } else if (response.status === 401 || response.status === 403) {\n throw Error(\n `Failed to retrieve download location for translation download ID ${translationDownloadId}. Status: ${response.status}`,\n )\n }\n console.error(\n `Transifex plugin message: Requested download location for translation download ID ${translationDownloadId} but received error code ${response.status}. Waiting and trying again.`,\n )\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return pollForFileDownloadLocation(\n resourceDownloadUrl,\n translationDownloadId,\n headers,\n retryCount + 1,\n maxRetries,\n )\n}\n\nconst handleFileDownload = (url: string) => {\n return fetch(url).then((res) => res.text())\n}\n\nexport const getTranslation: Adapter['getTranslation'] = async (\n taskId: string,\n localeId: string,\n secrets: Secrets | null,\n) => {\n if (!secrets) {\n throw Error('Missing Transifex secrets.')\n }\n\n const resourceDownloadBody = {\n data: {\n attributes: {\n content_encoding: 'text',\n },\n relationships: {\n language: {\n data: {\n id: `l:${localeId}`,\n type: 'languages',\n },\n },\n resource: {\n data: {\n id: taskId,\n type: 'resources',\n },\n },\n },\n type: 'resource_translations_async_downloads',\n },\n }\n\n const resourceDownloadUrl = `${baseTransifexUrl}/resource_translations_async_downloads`\n const downloadResponse = await fetch(resourceDownloadUrl, {\n headers: getHeaders(secrets),\n method: 'POST',\n body: JSON.stringify(resourceDownloadBody),\n })\n\n if (!downloadResponse.ok) {\n throw Error(\n `Failed to create translation download request in Transifex. Status: ${downloadResponse.status}`,\n )\n }\n\n const download = await downloadResponse.json()\n const translationDownloadId = download?.data?.id\n\n if (!translationDownloadId) {\n throw Error('Failed to create translation download request in Transifex. Missing download id.')\n }\n\n const headers = getHeaders(secrets)\n const location = await pollForFileDownloadLocation(\n resourceDownloadUrl,\n translationDownloadId,\n headers,\n )\n return handleFileDownload(location)\n}\n","import type {Adapter} from 'sanity-translations-tab'\n\nimport {createTask} from './createTask'\nimport {getLocales} from './getLocales'\nimport {getTranslation} from './getTranslation'\nimport {getTranslationTask} from './getTranslationTask'\n\nexport const TransifexAdapter: Adapter = {\n getLocales,\n getTranslationTask,\n createTask,\n getTranslation,\n}\n","import {\n baseDocumentLevelConfig,\n legacyDocumentLevelConfig as baseLegacyDocumentLevelConfig,\n baseFieldLevelConfig,\n} from 'sanity-translations-tab'\nimport type {Adapter, TranslationFunctionContext} from 'sanity-translations-tab'\n\nimport {TransifexAdapter} from './transifexAdapter'\n\nexport {\n findLatestDraft,\n BaseDocumentDeserializer,\n BaseDocumentSerializer,\n BaseDocumentMerger,\n defaultStopTypes,\n customSerializers,\n legacyDocumentLevelPatch,\n documentLevelPatch,\n fieldLevelPatch,\n TranslationsTab,\n} from 'sanity-translations-tab'\n\ninterface ConfigOptions {\n adapter: Adapter\n secretsNamespace: string | null\n exportForTranslation: (\n id: string,\n context: TranslationFunctionContext,\n ) => Promise<Record<string, any>>\n importTranslation: (\n id: string,\n localeId: string,\n doc: string,\n context: TranslationFunctionContext,\n ) => Promise<void>\n}\nconst defaultDocumentLevelConfig: ConfigOptions = {\n ...baseDocumentLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nconst legacyDocumentLevelConfig: ConfigOptions = {\n ...baseLegacyDocumentLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nconst defaultFieldLevelConfig: ConfigOptions = {\n ...baseFieldLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nexport {\n TransifexAdapter,\n defaultDocumentLevelConfig,\n defaultFieldLevelConfig,\n legacyDocumentLevelConfig,\n}\n"],"names":["baseTransifexUrl","getHeaders","secrets","token","projOrgSlug","organization","project","getLocales","locales","response","fetch","headers","ok","Error","status","json","then","res","data","map","lang","enabled","description","localeId","getTranslationTask","documentId","taskId","projectFilter","resourceFilter","task","locale","reviewedStrings","Number","totalStrings","split","progress","isFinite","Math","floor","localeIds","l","validLocales","filter","find","id","createResource","doc","resourceCreateBody","attributes","accept_translations","name","slug","relationships","i18n_format","type","method","body","JSON","stringify","resourceId","createTask","document","_localeIds","resourceResponse","resource","resourceUploadUrl","resourceUploadBody","content","content_encoding","uploadResponse","pollForFileDownloadLocation","resourceDownloadUrl","translationDownloadId","retryCount","maxRetries","console","info","Promise","resolve","setTimeout","redirected","url","error","handleFileDownload","text","getTranslation","resourceDownloadBody","language","downloadResponse","location","TransifexAdapter","defaultDocumentLevelConfig","baseDocumentLevelConfig","adapter","secretsNamespace","legacyDocumentLevelConfig","baseLegacyDocumentLevelConfig","defaultFieldLevelConfig","baseFieldLevelConfig"],"mappings":"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"}
|