sanity-plugin-transifex 2.0.8-beta.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +117 -85
  3. package/dist/index.d.ts +57 -11
  4. package/dist/index.esm.js +197 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +263 -7
  7. package/dist/index.js.map +1 -0
  8. package/package.json +87 -48
  9. package/sanity.json +8 -0
  10. package/src/index.ts +21 -14
  11. package/src/transifexAdapter/createTask.ts +12 -17
  12. package/src/transifexAdapter/getLocales.ts +10 -11
  13. package/src/transifexAdapter/getTranslation.ts +48 -60
  14. package/src/transifexAdapter/getTranslationTask.ts +12 -19
  15. package/src/transifexAdapter/helpers.ts +3 -3
  16. package/src/transifexAdapter/index.ts +5 -5
  17. package/v2-incompatible.js +11 -0
  18. package/.github/workflows/main.yml +0 -29
  19. package/.github/workflows/npm-publish.yml +0 -19
  20. package/dist/sanity-plugin-transifex.cjs.development.js +0 -903
  21. package/dist/sanity-plugin-transifex.cjs.development.js.map +0 -1
  22. package/dist/sanity-plugin-transifex.cjs.production.min.js +0 -2
  23. package/dist/sanity-plugin-transifex.cjs.production.min.js.map +0 -1
  24. package/dist/sanity-plugin-transifex.esm.js +0 -844
  25. package/dist/sanity-plugin-transifex.esm.js.map +0 -1
  26. package/dist/transifexAdapter/createTask.d.ts +0 -6
  27. package/dist/transifexAdapter/getLocales.d.ts +0 -2
  28. package/dist/transifexAdapter/getTranslation.d.ts +0 -2
  29. package/dist/transifexAdapter/getTranslationTask.d.ts +0 -6
  30. package/dist/transifexAdapter/helpers.d.ts +0 -7
  31. package/dist/transifexAdapter/index.d.ts +0 -2
  32. package/test/directives.test.ts +0 -155
  33. package/test/localeId.test.ts +0 -17
  34. package/test/mergeTranslation.test.ts +0 -113
  35. package/tsconfig.json +0 -35
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Rune Botten, Carolina Gonzalez
3
+ Copyright (c) 2023 Sanity.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,135 +1,167 @@
1
- # Sanity + Transifex = 🌍
1
+ > This is a **Sanity Studio v3** plugin.
2
+
3
+ ## Installation
4
+
5
+ ```sh
6
+ npm install sanity-plugin-transifex
7
+ ```
2
8
 
3
- This is the Studio V2 version of the Transifex plugin.
9
+ ![2023-02-15 10 06 06](https://user-images.githubusercontent.com/3969996/219130146-8d34c5a6-f11c-4647-826d-e3c07eaf7144.gif)
10
+
11
+
12
+ # Sanity + Transifex = 🌍
4
13
 
5
14
  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.
6
15
 
16
+ _Recent updates for v4:_ We've added support for the new document internationalization plugin pattern. Please read the [Document level translations](#document-level-translations) section for more information.
17
+
7
18
  # Table of Contents
8
- - [Plugin features](#plugin-features)
9
- - [Assumptions](#assumptions)
19
+
10
20
  - [Quickstart](#quickstart)
21
+ - [Assumptions](#assumptions)
11
22
  - [Studio experience](#studio-experience)
12
23
  - [Overriding defaults](#overriding-defaults)
13
- - [v1 to v2 changes](#v1-to-v2-changes)
14
-
24
+ - [License](#license)
25
+ - [Develop and test](#develop-and-test)
15
26
 
16
- ## Plugin features
17
-
18
- This plugin comes with (and exposes to the developer) the following items:
19
- - An `Adapter` that connects to the Transifex API with methods to create a new translation job, upload and assign a file to that translation job, check the progress of an ongoing translation, and retrieve a translated file.
20
- - A `Serializer` that transforms your content into HTML (we found this was the most efficient way to maintain your document structure, no matter how deeply nested, while remaining readable to translators in Transifex). The `Serializer` takes in optional arguments: `stopTypes`, which prevents certain types from being sent to your translatiors and `customSerializers`, which are rules you can use to have full control over how individual fields on your document get serialized.
21
- - A `Deserializer` that deserializes translated text back to Sanity's format.
22
- - A `Patcher` which determines how your content gets patched back into its destination document or field.
23
- - A `TranslationsTab`, a React element that allows a non-technical user to import, export, and monitor Transifex progress.
24
-
25
- To make life easier, we also include `defaultFieldLevelConfig` and `defaultDocumentLevelConfig`, which bundles all of the above up to get you up and running quickly.
26
-
27
- ## Assumptions
28
- To use the default config mentioned above, we assume that you are following the conventions we outline in [our documentation on localization](https://www.sanity.io/docs/localization).
27
+ ## Quickstart
29
28
 
29
+ 1. In your studio folder, run:
30
30
 
31
- ### Field-level translations
32
- If you are using field-level translation, we assume any fields you want translated exist in the multi-locale object form we recommend.
33
- For example, on a document you don't want to be translated, you may have a "title" field that's a flat string: `title: 'My title is here.'` For a field you want to include many languages for, your title may look like
34
- ```
35
- { title: {
36
- en: 'My title is here.',
37
- es: 'Mi título está aquí.',
38
- etc...
39
- }
40
- }
41
- ```
31
+ ```sh
32
+ npm install sanity-plugin-transifex
33
+ ```
42
34
 
43
- ### Document level translations
44
- Since we often find users want to use the [Document internationalization plugin](https://www.sanity.io/plugins/document-internationalization) if they're using document-level translations, we assume that any documents you want in different languages will follow the pattern `{id-of-base-language-document}__i18n_{locale}`
35
+ 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.
45
36
 
46
- ### Final note
47
- It's okay if your data doesn't follow these patterns and you don't want to change them! You will simply have to override how the plugin gets and patches back information from your documents. Please see [Overriding defaults](#overriding-defaults).
37
+ [Please refer to the Transifex documentation on creating a token if you don't have one already.](https://docs.transifex.com/account/authentication)
48
38
 
49
- ## Quickstart
39
+ In your Studio folder, create a file called `populateTransifexSecrets.js` with the following contents:
50
40
 
51
- 1. In your studio folder, run `npm install sanity-plugin-transifex`.
52
- 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. [Please refer to the Transifex documentation on creating a token if you don't have one already.](https://docs.transifex.com/account/authentication)
53
- * In your studio, create a file called `populateTransifexSecrets.js`.
54
- * Place the following in the file and fill out the correct values (those in all-caps).
55
-
56
41
  ```javascript
57
- import sanityClient from 'part:@sanity/base/client'
42
+ // ./populateTransifexSecrets.js
43
+ // Do not commit this file to your repository
44
+
45
+ import {getCliClient} from 'sanity/cli'
58
46
 
59
- const client = sanityClient.withConfig({ apiVersion: '2021-03-25' })
47
+ const client = getCliClient({apiVersion: '2023-02-15'})
60
48
 
61
49
  client.createOrReplace({
62
- _id: 'transifex.secrets',
63
- _type: 'transifexSettings',
64
- organization: 'YOUR_ORG_HERE',
65
- project: 'YOUR_PROJECT_HERE',
66
- token: 'YOUR_TOKEN_HERE',
50
+ // The `.` in this _id will ensure the document is private
51
+ // even in a public dataset!
52
+ _id: 'transifex.secrets',
53
+ _type: 'transifexSettings',
54
+ // Replace these with your values
55
+ organization: 'YOUR_TRANSIFEX_ORG_HERE',
56
+ project: 'YOUR_TRANSIFEX_PROJECT_HERE',
57
+ token: 'YOUR_TRANSIFEX_TOKEN_HERE'
67
58
  })
68
59
  ```
69
60
 
70
- * On the command line, run the file with `sanity exec populateTransifexSecrets.js --with-user-token`.
71
- Verify that everything went well by using Vision in the studio to query `*[_id == 'transifex.secrets']`. (NOTE: If you have multiple datasets, you'll have to do this across all of them, since it's a document!)
72
- * If everything looks good, go ahead and delete `populateTransifexSecrets.js` so you don't commit it.
73
- Because the document's `_id` is on a path (`transifex`), it won't be exposed to the outside world, even in a public dataset. 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).
74
-
75
- 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:
61
+ On the command line, run the file:
62
+
63
+ ```sh
64
+ npx sanity exec populateTransifexSecrets.js --with-user-token
65
+ ```
66
+
67
+ 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.
68
+
69
+ If the document was found in your dataset(s), delete `populateTransifexSecrets.js`.
70
+
71
+ 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).
72
+
73
+ 4. 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:
76
74
 
77
75
  ```javascript
78
- import S from '@sanity/desk-tool/structure-builder'
76
+ import {DefaultDocumentNodeResolver} from 'sanity/desk'
79
77
  //...your other desk structure imports...
80
- import { TranslationTab, defaultDocumentLevelConfig, defaultFieldLevelConfig } from 'sanity-plugin-transifex'
81
-
78
+ import {TranslationsTab, defaultDocumentLevelConfig} from 'sanity-plugin-transifex'
79
+ //if you are using field-level translations, you can import the field-level config instead:
80
+ //import {TranslationsTab, defaultFieldLevelConfig} from 'sanity-plugin-studio-smartling'
81
+ //if you're not sure which, please look at the document-level and field-level sections below
82
82
 
83
- export const getDefaultDocumentNode = (props) => {
84
- if (props.schemaType === 'myTranslatableDocumentType') {
83
+ export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
84
+ if (schemaType === 'myTranslatableDocumentType') {
85
85
  return S.document().views([
86
86
  S.view.form(),
87
- //...my other views -- for example, live preview, the i18n plugin, etc.,
88
- S.view.component(TranslationTab).title('Transifex').options(
89
- defaultDocumentLevelConfig
90
- )
87
+ //...my other views -- for example, live preview, document pane, etc.,
88
+ S.view.component(TranslationsTab).title('Transifex').options(defaultDocumentLevelConfig)
89
+ //again, if you're using field-level translations, you can use the field-level config instead:
91
90
  ])
92
91
  }
93
- return S.document();
94
- };
92
+ }
95
93
  ```
96
94
 
97
95
  And that should do it! Go into your studio, click around, and check the document in Transifex (it should be under its Sanity `_id`). Once it's translated, check the import by clicking the `Import` button on your Transifex tab!
98
96
 
97
+ ## Assumptions
98
+
99
+ To use the default config mentioned above, we assume that you are following the conventions we outline in [our documentation on localization](https://www.sanity.io/docs/localization).
100
+
101
+ ### Field-level translations
102
+
103
+ If you are using field-level translation and the `defaultFieldLevelConfig` configuration, we assume any fields you want translated exist in the multi-locale object form we recommend.
104
+
105
+ For example, on a document you don't want to be translated, you may have a "title" field that's a flat string: `title: 'My title is here.'` For a field you want to include many languages for your title may look like:
106
+
107
+ ```javascript
108
+ {
109
+ //...other document fields,
110
+ title: {
111
+ en: 'My title is here.',
112
+ es: 'Mi título está aquí.',
113
+ etc...
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Document level translations
119
+
120
+ Since we often find users want to use the [Document internationalization plugin](https://www.sanity.io/plugins/document-internationalization) if they're using document-level translations, we assume that any documents you want in different languages will be present in a `translation.metadata` document.
121
+
122
+ _Important_: The above is true if you are using the Document Internationalization Plugin at version 2 or above. If you are using version 1 please use the `legacyDocumentLevelConfig` configuration exported from this plugin. This configuration assumes your translations follow the pattern `{id-of-base-language-document}__i18n_{locale}`
123
+
124
+ ### Final note
125
+
126
+ It's okay if your data doesn't follow these patterns and you don't want to change them! You will simply have to override how the plugin gets and patches back information from your documents. Please see [Overriding defaults](#overriding-defaults).
127
+
99
128
  ## Studio experience
129
+
100
130
  By adding the `TranslationsTab` to your desk structure, your users should now have an additional view. The boxes at the top of the tab can be used to send translations off to Transifex, and once those jobs are started, they should see progress bars monitoring the progress of the jobs. They can import a partial or complete job back.
101
131
 
102
132
  ## Overriding defaults
103
133
 
104
134
  To personalize this configuration it's useful to know what arguments go into `TranslationsTab` as options (the `defaultConfigs` are just wrappers for these):
105
- * `exportForTranslation`: a function that takes your document id and returns an object with `name`: the field you want to use identify your doc in Transifex (by default this is `_id` and `content`: a serialized HTML string of all the fields in your document to be translated.
106
- * `importTranslation`: a function that takes in `id` (your document id) `localeId` (the locale of the imported language) and `document` the translated HTML from Transifex. It will deserialize your document back into an object that can be patched into your Sanity data, and then executes that patch.
107
- * `Adapter`: An interface with methods to send things over to Transifex. You likely don't want to override this!
108
135
 
109
- There are a number of reasons to override these functions. More general cases are often around ensuring documents serialize and deserialize correctly. Since the serialization fucntions are used across all our translation plugins currently, you can find some frequently encountered scenarios at [their repository here](https://github.com/sanity-io/sanity-naive-html-serializer), along with code examples for new config.
136
+ - `exportForTranslation`: a function that takes your document id and returns an object with `name`: the field you want to use identify your doc in Transifex (by default this is `_id` and `content`: a serialized HTML string of all the fields in your document to be translated.
137
+ - `importTranslation`: a function that takes in `id` (your document id) `localeId` (the locale of the imported language) and `document` the translated HTML from Transifex. It will deserialize your document back into an object that can be patched into your Sanity data, and then executes that patch.
138
+ - `Adapter`: An interface with methods to send things over to Transifex. You likely don't want to override this!
110
139
 
111
- ## V1 to V2 changes
140
+ There are several reasons to override these functions. More general cases are often around ensuring documents serialize and deserialize correctly. Since the serialization functions are used across all our translation plugins currently, you can find some frequently encountered scenarios at [their repository here](https://github.com/sanity-io/sanity-naive-html-serializer), along with code examples for new config.
112
141
 
113
- Most users will not encounter issues in upgrading to v2. The breaking changes are as follows:
142
+ ## Migrating to Sanity Studio v3
114
143
 
115
- 1. **Change to document-level localization id structure.** Since the [Internationalization input plugin](https://www.sanity.io/plugins/sanity-plugin-intl-input) was deprecated, the default pattern `i18n.{id-of-base-language-document}.{locale}` was deprecated in favor of `{id-of-base-language-document}__i18n_{locale}`. If you would like to maintain that pattern, please add the `idStructure` param to your tab config, like:
116
- ```javascript
117
- S.view.component(TranslationsTab).title('Transifex').options(
118
- {...defaultDocumentLevelConfig, idStructure: 'subpath'}
119
- )
120
- ```
121
- 2. **Underlying changes in serializers.** Serializers were updated to a) take advantage of the newer [Portable Text to HTML package](https://github.com/portabletext/to-html) and allow for explicit schema closures. If you were overriding serialization methods, that means invocation of `BaseDocumentSerializer` will change from:
122
- ```javascript
123
- BaseDocumentSerializer.serializeDocument(id, 'serialization-level')
124
- ```
144
+ 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.
125
145
 
126
- to:
127
- ```javascript
128
- import schemas from 'part:@sanity/base/schema'
146
+ These are outlined in the serializer README [here](https://github.com/sanity-io/sanity-naive-html-serializer#v2-to-v3-changes).
129
147
 
130
- BaseDocumentSerializer(schemas).serializeDocument(id, 'serialization-level')
131
- ```
148
+ ## License
132
149
 
150
+ [MIT](LICENSE) © Sanity.io
151
+
152
+ ## Develop & test
133
153
 
134
154
  This plugin is in early stages. We plan on improving some of the user-facing chrome, sorting out some quiet bugs, figuring out where things don't fail elegantly, etc. Please be a part of our development process!
135
155
 
156
+ This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit)
157
+ with default configuration for build & watch scripts.
158
+
159
+ See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio)
160
+ on how to run this plugin with hotreload in the studio.
161
+
162
+ ### Release new version
163
+
164
+ Run ["CI & Release" workflow](https://github.com/sanity-io/sanity-plugin-transifex/actions/workflows/main.yml).
165
+ Make sure to select the main branch and check "Release new version".
166
+
167
+ Semantic release will only release on configured branches, so it is safe to run release on any branch.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,57 @@
1
- import { TranslationsTab, findLatestDraft, BaseDocumentDeserializer, BaseDocumentSerializer, BaseDocumentMerger, defaultStopTypes, customSerializers, Adapter, documentLevelPatch, fieldLevelPatch } from 'sanity-translations-tab';
2
- import { TransifexAdapter } from './transifexAdapter';
3
- interface ConfigOptions {
4
- adapter: Adapter;
5
- secretsNamespace: string | null;
6
- exportForTranslation: (id: string) => Promise<Record<string, any>>;
7
- importTranslation: (id: string, localeId: string, doc: string, _idStructure: undefined) => Promise<void>;
8
- }
9
- declare const defaultDocumentLevelConfig: ConfigOptions;
10
- declare const defaultFieldLevelConfig: ConfigOptions;
11
- export { TranslationsTab, findLatestDraft, documentLevelPatch, fieldLevelPatch, BaseDocumentDeserializer, BaseDocumentSerializer, BaseDocumentMerger, defaultStopTypes, customSerializers, TransifexAdapter, defaultDocumentLevelConfig, defaultFieldLevelConfig, };
1
+ import {Adapter} from 'sanity-translations-tab'
2
+ import {BaseDocumentDeserializer} from 'sanity-translations-tab'
3
+ import {BaseDocumentMerger} from 'sanity-translations-tab'
4
+ import {BaseDocumentSerializer} from 'sanity-translations-tab'
5
+ import {customSerializers} from 'sanity-translations-tab'
6
+ import {defaultStopTypes} from 'sanity-translations-tab'
7
+ import {documentLevelPatch} from 'sanity-translations-tab'
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>
33
+ }
34
+
35
+ export {customSerializers}
36
+
37
+ export declare const defaultDocumentLevelConfig: ConfigOptions
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 {}
@@ -0,0 +1,197 @@
1
+ import { baseDocumentLevelConfig, legacyDocumentLevelConfig as legacyDocumentLevelConfig$1, baseFieldLevelConfig } from 'sanity-translations-tab';
2
+ export { BaseDocumentDeserializer, BaseDocumentMerger, BaseDocumentSerializer, TranslationsTab, customSerializers, defaultStopTypes, documentLevelPatch, fieldLevelPatch, findLatestDraft, legacyDocumentLevelPatch } from 'sanity-translations-tab';
3
+ const baseTransifexUrl = "https://rest.api.transifex.com";
4
+ const getHeaders = secrets => ({
5
+ Authorization: "Bearer ".concat(secrets == null ? void 0 : secrets.token),
6
+ "Content-Type": "application/vnd.api+json"
7
+ });
8
+ const projOrgSlug = secrets => "o:".concat(secrets == null ? void 0 : secrets.organization, ":p:").concat(secrets == null ? void 0 : secrets.project);
9
+ const getLocales = async secrets => {
10
+ let locales = [];
11
+ if (secrets) {
12
+ locales = await fetch("".concat(baseTransifexUrl, "/projects/").concat(projOrgSlug(secrets), "/languages"), {
13
+ headers: getHeaders(secrets)
14
+ }).then(res => res.json()).then(res => res.data.map(lang => ({
15
+ enabled: true,
16
+ description: lang.attributes.name,
17
+ localeId: lang.attributes.code
18
+ })));
19
+ }
20
+ return locales;
21
+ };
22
+ const getTranslationTask = async (documentId, secrets) => {
23
+ if (!documentId || !secrets) {
24
+ return {
25
+ taskId: documentId,
26
+ documentId,
27
+ locales: []
28
+ };
29
+ }
30
+ const projectFilter = "filter[project]=".concat(projOrgSlug(secrets));
31
+ const resourceFilter = "filter[resource]=".concat(projOrgSlug(secrets), ":r:").concat(documentId);
32
+ const task = await fetch("".concat(baseTransifexUrl, "/resource_language_stats?").concat(projectFilter, "&").concat(resourceFilter), {
33
+ headers: getHeaders(secrets)
34
+ }).then(res => {
35
+ if (res.ok) {
36
+ return res.json();
37
+ } else if (res.status === 404) {
38
+ return {
39
+ data: []
40
+ };
41
+ }
42
+ throw Error("Failed to retrieve tasks from Transifex. Status: ".concat(res.status));
43
+ }).then(res => ({
44
+ taskId: "".concat(projOrgSlug(secrets), ":r:").concat(documentId),
45
+ documentId,
46
+ locales: res.data.map(locale => ({
47
+ localeId: locale.relationships.language.data.id.split(":")[1],
48
+ progress: Math.floor(100 * (locale.attributes.reviewed_strings / parseFloat(locale.attributes.total_strings)))
49
+ }))
50
+ }));
51
+ const locales = await getLocales(secrets);
52
+ const localeIds = locales.map(l => l.localeId);
53
+ const validLocales = task.locales.filter(locale => localeIds.find(id => id === locale.localeId));
54
+ task.locales = validLocales;
55
+ return task;
56
+ };
57
+ const pollForFileDownloadLocation = async (resourceDownloadUrl, translationDownloadId, headers) => {
58
+ const response = await fetch("".concat(resourceDownloadUrl, "/").concat(translationDownloadId), {
59
+ headers
60
+ });
61
+ if (response.status === 500) {
62
+ console.info("Transifex plugin message: Received 500 for translation download ID ".concat(translationDownloadId, ". Trying to reconnect..."));
63
+ await new Promise(resolve => setTimeout(resolve, 3e3));
64
+ return pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers);
65
+ } else if (response.redirected) {
66
+ console.info("Transifex plugin message: Received redirect for translation download ID ".concat(translationDownloadId, ". Following redirect now for file download."));
67
+ return response.url;
68
+ } else if (response.status === 200) {
69
+ console.info("Transifex plugin message: Requested download location for translation download ID ".concat(translationDownloadId, ". Location is still pending, trying again."));
70
+ await new Promise(resolve => setTimeout(resolve, 3e3));
71
+ return pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers);
72
+ }
73
+ console.error("Transifex plugin message: Requested download location for translation download ID ".concat(translationDownloadId, " but received error code ").concat(response.status, ". Waiting and trying again."));
74
+ await new Promise(resolve => setTimeout(resolve, 3e3));
75
+ return pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers);
76
+ };
77
+ const handleFileDownload = url => {
78
+ return fetch(url).then(res => res.text());
79
+ };
80
+ const getTranslation = async (taskId, localeId, secrets) => {
81
+ const resourceDownloadBody = {
82
+ data: {
83
+ attributes: {
84
+ content_encoding: "text"
85
+ },
86
+ relationships: {
87
+ language: {
88
+ data: {
89
+ id: "l:".concat(localeId),
90
+ type: "languages"
91
+ }
92
+ },
93
+ resource: {
94
+ data: {
95
+ id: taskId,
96
+ type: "resources"
97
+ }
98
+ }
99
+ },
100
+ type: "resource_translations_async_downloads"
101
+ }
102
+ };
103
+ const resourceDownloadUrl = "".concat(baseTransifexUrl, "/resource_translations_async_downloads");
104
+ const translationDownloadId = await fetch(resourceDownloadUrl, {
105
+ headers: getHeaders(secrets),
106
+ method: "POST",
107
+ body: JSON.stringify(resourceDownloadBody)
108
+ }).then(res => res.json()).then(res => res.data.id);
109
+ const headers = getHeaders(secrets);
110
+ const location = await pollForFileDownloadLocation(resourceDownloadUrl, translationDownloadId, headers);
111
+ return handleFileDownload(location);
112
+ };
113
+ const createResource = (doc, documentId, secrets) => {
114
+ const resourceCreateBody = {
115
+ data: {
116
+ attributes: {
117
+ accept_translations: true,
118
+ name: doc.name,
119
+ slug: documentId
120
+ },
121
+ relationships: {
122
+ i18n_format: {
123
+ data: {
124
+ id: "HTML_FRAGMENT",
125
+ type: "i18n_formats"
126
+ }
127
+ },
128
+ project: {
129
+ data: {
130
+ id: projOrgSlug(secrets),
131
+ type: "projects"
132
+ }
133
+ }
134
+ },
135
+ type: "resources"
136
+ }
137
+ };
138
+ return fetch("".concat(baseTransifexUrl, "/resources"), {
139
+ headers: getHeaders(secrets),
140
+ method: "POST",
141
+ body: JSON.stringify(resourceCreateBody)
142
+ }).then(res => res.json()).then(res => res.data.id);
143
+ };
144
+ const createTask = async (documentId, document, localeIds, secrets) => {
145
+ let resourceId = await fetch("".concat(baseTransifexUrl, "/resources/").concat(projOrgSlug(secrets), ":r:").concat(documentId), {
146
+ headers: getHeaders(secrets)
147
+ }).then(res => res.json()).then(res => res.data ? res.data.id : null);
148
+ if (!resourceId) {
149
+ resourceId = await createResource(document, documentId, secrets);
150
+ }
151
+ const resourceUploadUrl = "".concat(baseTransifexUrl, "/resource_strings_async_uploads");
152
+ const resourceUploadBody = {
153
+ data: {
154
+ attributes: {
155
+ content: document.content,
156
+ content_encoding: "text"
157
+ },
158
+ relationships: {
159
+ resource: {
160
+ data: {
161
+ id: resourceId,
162
+ type: "resources"
163
+ }
164
+ }
165
+ },
166
+ type: "resource_strings_async_uploads"
167
+ }
168
+ };
169
+ return fetch(resourceUploadUrl, {
170
+ method: "POST",
171
+ body: JSON.stringify(resourceUploadBody),
172
+ headers: getHeaders(secrets)
173
+ }).then(() => getTranslationTask(documentId, secrets));
174
+ };
175
+ const TransifexAdapter = {
176
+ getLocales,
177
+ getTranslationTask,
178
+ createTask,
179
+ getTranslation
180
+ };
181
+ const defaultDocumentLevelConfig = {
182
+ ...baseDocumentLevelConfig,
183
+ adapter: TransifexAdapter,
184
+ secretsNamespace: "transifex"
185
+ };
186
+ const legacyDocumentLevelConfig = {
187
+ ...legacyDocumentLevelConfig$1,
188
+ adapter: TransifexAdapter,
189
+ secretsNamespace: "transifex"
190
+ };
191
+ const defaultFieldLevelConfig = {
192
+ ...baseFieldLevelConfig,
193
+ adapter: TransifexAdapter,
194
+ secretsNamespace: "transifex"
195
+ };
196
+ export { TransifexAdapter, defaultDocumentLevelConfig, defaultFieldLevelConfig, legacyDocumentLevelConfig };
197
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.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":["baseTransifexUrl","getHeaders","secrets","Authorization","token","projOrgSlug","organization","project","getLocales","locales","fetch","headers","then","res","json","data","map","lang","enabled","description","attributes","name","localeId","code","getTranslationTask","documentId","taskId","projectFilter","resourceFilter","task","ok","status","Error","locale","relationships","language","id","split","progress","Math","floor","reviewed_strings","parseFloat","total_strings","localeIds","l","validLocales","filter","find","pollForFileDownloadLocation","resourceDownloadUrl","translationDownloadId","response","console","info","Promise","resolve","setTimeout","redirected","url","error","handleFileDownload","text","getTranslation","resourceDownloadBody","content_encoding","type","resource","method","body","JSON","stringify","location","createResource","doc","resourceCreateBody","accept_translations","slug","i18n_format","createTask","document","resourceId","resourceUploadUrl","resourceUploadBody","content","TransifexAdapter","defaultDocumentLevelConfig","baseDocumentLevelConfig","adapter","secretsNamespace","legacyDocumentLevelConfig","baseLegacyDocumentLevelConfig","defaultFieldLevelConfig","baseFieldLevelConfig"],"mappings":";;AAEO,MAAMA,gBAAmB,GAAA,gCAAA;AAEnB,MAAAC,UAAA,GAAcC,OAAqD,KAAA;EAC9EC,aAAA,mBAAyBD,OAAS,IAAA,IAAA,GAAA,KAAA,CAAA,GAAAA,OAAA,CAAAE,KAAA,CAAA;EAClC,cAAgB,EAAA;AAClB,CAAA,CAAA;AAEO,MAAMC,cAAeH,OAAA,gBACrBA,OAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAAA,OAAA,CAASI,4BAAkBJ,OAAS,IAAA,IAAA,GAAA,KAAA,CAAA,GAAAA,OAAA,CAAAK,OAAA,CAAA;ACP9B,MAAAC,UAAA,GAAoC,MAAON,OAA4B,IAAA;EAClF,IAAIO,UAAU,EAAC;EACf,IAAIP,OAAS,EAAA;IACXO,OAAA,GAAU,MAAMC,KAAM,WAAGV,gBAA6B,uBAAAK,WAAA,CAAYH,OAAO,CAAe,iBAAA;MACtFS,OAAA,EAASV,WAAWC,OAAO;IAAA,CAC5B,EACEU,IAAK,CAACC,OAAQA,GAAI,CAAAC,IAAA,EAAM,CACxB,CAAAF,IAAA,CAAMC,GACL,IAAAA,GAAA,CAAIE,IAAK,CAAAC,GAAA,CAAKC,IAA+B,KAAA;MAC3CC,OAAS,EAAA,IAAA;MACTC,WAAA,EAAaF,KAAKG,UAAW,CAAAC,IAAA;MAC7BC,QAAA,EAAUL,KAAKG,UAAW,CAAAG;IAAA,CAC1B,CAAA,CAAA,CACJ;EACJ;EACO,OAAAd,OAAA;AACT,CAAA;ACfa,MAAAe,kBAAA,GAAoD,OAC/DC,UAAA,EACAvB,OACG,KAAA;EACC,IAAA,CAACuB,UAAc,IAAA,CAACvB,OAAS,EAAA;IACpB,OAAA;MACLwB,MAAQ,EAAAD,UAAA;MACRA,UAAA;MACAhB,SAAS;IAAC,CACZ;EACF;EACM,MAAAkB,aAAA,6BAAmCtB,WAAA,CAAYH,OAAO,CAAA,CAAA;EAC5D,MAAM0B,cAAiB,8BAAoBvB,WAAY,CAAAH,OAAO,CAAO,gBAAAuB,UAAA,CAAA;EACrE,MAAMI,OAAO,MAAMnB,KAAA,WACdV,sDAA4C2B,aAAiB,cAAAC,cAAA,GAChE;IAACjB,OAAA,EAASV,UAAW,CAAAC,OAAO;EAAC,CAAA,CAC/B,CACGU,IAAK,CAACC,GAAQ,IAAA;IACb,IAAIA,IAAIiB,EAAI,EAAA;MACV,OAAOjB,IAAIC,IAAK,EAAA;IAAA,CAClB,MAAA,IAESD,GAAI,CAAAkB,MAAA,KAAW,GAAK,EAAA;MACpB,OAAA;QAAChB,IAAM,EAAA;OAAE;IAClB;IACM,MAAAiB,KAAA,4DAA0DnB,GAAA,CAAIkB,MAAQ,EAAA;EAAA,CAC7E,CAAA,CACAnB,IAAK,CAACC,GAAS,KAAA;IACda,MAAQ,YAAGrB,WAAY,CAAAH,OAAO,CAAO,gBAAAuB,UAAA,CAAA;IACrCA,UAAA;IACAhB,OAAS,EAAAI,GAAA,CAAIE,IAAK,CAAAC,GAAA,CAAKiB,MAAiC,KAAA;MACtDX,QAAA,EAAUW,OAAOC,aAAc,CAAAC,QAAA,CAASpB,KAAKqB,EAAG,CAAAC,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA;MAC5DC,UAAUC,IAAK,CAAAC,KAAA,CACb,OAAOP,MAAO,CAAAb,UAAA,CAAWqB,mBAAmBC,UAAW,CAAAT,MAAA,CAAOb,WAAWuB,aAAa,CAAA,CAAA;IACxF,CACA,CAAA;EACF,CAAA,CAAA,CAAA;EAEE,MAAAlC,OAAA,GAAU,MAAMD,UAAA,CAAWN,OAAO,CAAA;EACxC,MAAM0C,YAAYnC,OAAQ,CAAAO,GAAA,CAAK6B,CAAA,IAA2BA,EAAEvB,QAAQ,CAAA;EAC9D,MAAAwB,YAAA,GAAejB,KAAKpB,OAAQ,CAAAsC,MAAA,CAAQd,UACxCW,SAAU,CAAAI,IAAA,CAAMZ,EAAe,IAAAA,EAAA,KAAOH,OAAOX,QAAQ,CAAA,CACvD;EACAO,IAAA,CAAKpB,OAAU,GAAAqC,YAAA;EAER,OAAAjB,IAAA;AACT,CAAA;AC/CA,MAAMoB,2BAA8B,GAAA,OAClCC,mBACA,EAAAC,qBAAA,EACAxC,OACoB,KAAA;EACpB,MAAMyC,QAAW,GAAA,MAAM1C,KAAM,WAAGwC,iCAAuBC,qBAAyB,GAAA;IAC9ExC;EAAA,CACD,CAAA;EAEG,IAAAyC,QAAA,CAASrB,WAAW,GAAK,EAAA;IAEnBsB,OAAA,CAAAC,IAAA,8EACgEH,qBAAA,8BACxE;IACA,MAAM,IAAII,OAAQ,CAACC,WAAYC,UAAW,CAAAD,OAAA,EAAS,GAAI,CAAC,CAAA;IACjD,OAAAP,2BAAA,CAA4BC,mBAAqB,EAAAC,qBAAA,EAAuBxC,OAAO,CAAA;EAAA,CACxF,MAAA,IAAWyC,SAASM,UAAY,EAAA;IAEtBL,OAAA,CAAAC,IAAA,mFACqEH,qBAAA,iDAC7E;IACA,OAAOC,QAAS,CAAAO,GAAA;EAAA,CAClB,MAAA,IAAWP,QAAS,CAAArB,MAAA,KAAW,GAAK,EAAA;IAE1BsB,OAAA,CAAAC,IAAA,6FAC+EH,qBAAA,gDACvF;IACA,MAAM,IAAII,OAAQ,CAACC,WAAYC,UAAW,CAAAD,OAAA,EAAS,GAAI,CAAC,CAAA;IACjD,OAAAP,2BAAA,CAA4BC,mBAAqB,EAAAC,qBAAA,EAAuBxC,OAAO,CAAA;EACxF;EAEQ0C,OAAA,CAAAO,KAAA,6FAC+ET,2DAAiDC,QAAS,CAAArB,MAAA,iCACjJ;EACA,MAAM,IAAIwB,OAAQ,CAACC,WAAYC,UAAW,CAAAD,OAAA,EAAS,GAAI,CAAC,CAAA;EACjD,OAAAP,2BAAA,CAA4BC,mBAAqB,EAAAC,qBAAA,EAAuBxC,OAAO,CAAA;AACxF,CAAA;AAEA,MAAMkD,kBAAA,GAAsBF,GAAgB,IAAA;EACnC,OAAAjD,KAAA,CAAMiD,GAAG,CAAE,CAAA/C,IAAA,CAAMC,GAAQ,IAAAA,GAAA,CAAIiD,MAAM,CAAA;AAC5C,CAAA;AAEO,MAAMC,cAA4C,GAAA,OACvDrC,MACA,EAAAJ,QAAA,EACApB,OACG,KAAA;EACH,MAAM8D,oBAAuB,GAAA;IAC3BjD,IAAM,EAAA;MACJK,UAAY,EAAA;QACV6C,gBAAkB,EAAA;MACpB,CAAA;MACA/B,aAAe,EAAA;QACbC,QAAU,EAAA;UACRpB,IAAM,EAAA;YACJqB,gBAASd,QAAA,CAAA;YACT4C,IAAM,EAAA;UACR;QACF,CAAA;QACAC,QAAU,EAAA;UACRpD,IAAM,EAAA;YACJqB,EAAI,EAAAV,MAAA;YACJwC,IAAM,EAAA;UACR;QACF;MACF,CAAA;MACAA,IAAM,EAAA;IACR;EAAA,CACF;EAEA,MAAMhB,gCAAyBlD,gBAAA,2CAAA;EACzB,MAAAmD,qBAAA,GAAwB,MAAMzC,KAAA,CAAMwC,mBAAqB,EAAA;IAC7DvC,OAAA,EAASV,WAAWC,OAAO,CAAA;IAC3BkE,MAAQ,EAAA,MAAA;IACRC,IAAA,EAAMC,IAAK,CAAAC,SAAA,CAAUP,oBAAoB;EAC1C,CAAA,CAAA,CACEpD,IAAK,CAACC,OAAQA,GAAI,CAAAC,IAAA,EAAM,CAAA,CACxBF,IAAK,CAACC,GAAQ,IAAAA,GAAA,CAAIE,KAAKqB,EAAE,CAAA;EAEtB,MAAAzB,OAAA,GAAUV,WAAWC,OAAO,CAAA;EAClC,MAAMsE,WAAW,MAAMvB,2BAAA,CACrBC,mBAAA,EACAC,qBAAA,EACAxC,OAAA,CACF;EACA,OAAOkD,mBAAmBW,QAAQ,CAAA;AACpC,CAAA;ACrFA,MAAMC,cAAiB,GAAA,CAACC,GAA0B,EAAAjD,UAAA,EAAoBvB,OAA4B,KAAA;EAChG,MAAMyE,kBAAqB,GAAA;IACzB5D,IAAM,EAAA;MACJK,UAAY,EAAA;QACVwD,mBAAqB,EAAA,IAAA;QACrBvD,MAAMqD,GAAI,CAAArD,IAAA;QACVwD,IAAM,EAAApD;MACR,CAAA;MACAS,aAAe,EAAA;QACb4C,WAAa,EAAA;UACX/D,IAAM,EAAA;YACJqB,EAAI,EAAA,eAAA;YACJ8B,IAAM,EAAA;UACR;QACF,CAAA;QACA3D,OAAS,EAAA;UACPQ,IAAM,EAAA;YACJqB,EAAA,EAAI/B,YAAYH,OAAO,CAAA;YACvBgE,IAAM,EAAA;UACR;QACF;MACF,CAAA;MACAA,IAAM,EAAA;IACR;EAAA,CACF;EAEO,OAAAxD,KAAA,WAASV,gBAA8B,iBAAA;IAC5CW,OAAA,EAASV,WAAWC,OAAO,CAAA;IAC3BkE,MAAQ,EAAA,MAAA;IACRC,IAAA,EAAMC,IAAK,CAAAC,SAAA,CAAUI,kBAAkB;EACxC,CAAA,CAAA,CACE/D,IAAK,CAACC,OAAQA,GAAI,CAAAC,IAAA,EAAM,CAAA,CACxBF,IAAK,CAACC,GAAQ,IAAAA,GAAA,CAAIE,KAAKqB,EAAE,CAAA;AAC9B,CAAA;AAGO,MAAM2C,UAAoC,GAAA,OAC/CtD,UACA,EAAAuD,QAAA,EACApC,WACA1C,OACG,KAAA;EACH,IAAI+E,aAAa,MAAMvE,KAAA,WAClBV,gBAAA,wBAA8BK,WAAY,CAAAH,OAAO,CAAO,gBAAAuB,UAAA,GAC3D;IAACd,OAAA,EAASV,UAAW,CAAAC,OAAO;EAAC,CAAA,EAE5BU,IAAK,CAACC,GAAQ,IAAAA,GAAA,CAAIC,MAAM,CAAA,CACxBF,IAAK,CAACC,OAASA,GAAI,CAAAE,IAAA,GAAOF,GAAI,CAAAE,IAAA,CAAKqB,KAAK,IAAK,CAAA;EAEhD,IAAI,CAAC6C,UAAY,EAAA;IACfA,UAAA,GAAa,MAAMR,cAAA,CAAeO,QAAU,EAAAvD,UAAA,EAAYvB,OAAO,CAAA;EACjE;EAEA,MAAMgF,8BAAuBlF,gBAAA,oCAAA;EAC7B,MAAMmF,kBAAqB,GAAA;IACzBpE,IAAM,EAAA;MACJK,UAAY,EAAA;QACVgE,SAASJ,QAAS,CAAAI,OAAA;QAClBnB,gBAAkB,EAAA;MACpB,CAAA;MACA/B,aAAe,EAAA;QACbiC,QAAU,EAAA;UACRpD,IAAM,EAAA;YACJqB,EAAI,EAAA6C,UAAA;YACJf,IAAM,EAAA;UACR;QACF;MACF,CAAA;MACAA,IAAM,EAAA;IACR;EAAA,CACF;EAEA,OAAOxD,MAAMwE,iBAAmB,EAAA;IAC9Bd,MAAQ,EAAA,MAAA;IACRC,IAAA,EAAMC,IAAK,CAAAC,SAAA,CAAUY,kBAAkB,CAAA;IACvCxE,OAAA,EAASV,WAAWC,OAAO;EAAA,CAC5B,CAAE,CAAAU,IAAA,CAAK,MAAMY,kBAAmB,CAAAC,UAAA,EAAYvB,OAAO,CAAC,CAAA;AACvD,CAAA;AC1EO,MAAMmF,gBAA4B,GAAA;EACvC7E,UAAA;EACAgB,kBAAA;EACAuD,UAAA;EACAhB;AACF,CAAA;ACwBA,MAAMuB,0BAA4C,GAAA;EAChD,GAAGC,uBAAA;EACHC,OAAS,EAAAH,gBAAA;EACTI,gBAAkB,EAAA;AACpB,CAAA;AAEA,MAAMC,yBAA2C,GAAA;EAC/C,GAAGC,2BAAA;EACHH,OAAS,EAAAH,gBAAA;EACTI,gBAAkB,EAAA;AACpB,CAAA;AAEA,MAAMG,uBAAyC,GAAA;EAC7C,GAAGC,oBAAA;EACHL,OAAS,EAAAH,gBAAA;EACTI,gBAAkB,EAAA;AACpB,CAAA;"}