sanity-plugin-transifex 2.0.5 → 3.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 +2 -2
- package/README.md +74 -200
- package/dist/index.d.ts +52 -11
- package/dist/index.esm.js +192 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +251 -7
- package/dist/index.js.map +1 -0
- package/package.json +87 -48
- package/sanity.json +8 -0
- package/src/index.ts +14 -19
- package/src/transifexAdapter/createTask.ts +12 -17
- package/src/transifexAdapter/getLocales.ts +10 -11
- package/src/transifexAdapter/getTranslation.ts +48 -60
- package/src/transifexAdapter/getTranslationTask.ts +12 -19
- package/src/transifexAdapter/helpers.ts +3 -3
- package/src/transifexAdapter/index.ts +5 -5
- package/v2-incompatible.js +11 -0
- package/.github/workflows/main.yml +0 -29
- package/.github/workflows/npm-publish.yml +0 -19
- package/dist/sanity-plugin-transifex.cjs.development.js +0 -903
- package/dist/sanity-plugin-transifex.cjs.development.js.map +0 -1
- package/dist/sanity-plugin-transifex.cjs.production.min.js +0 -2
- package/dist/sanity-plugin-transifex.cjs.production.min.js.map +0 -1
- package/dist/sanity-plugin-transifex.esm.js +0 -844
- package/dist/sanity-plugin-transifex.esm.js.map +0 -1
- package/dist/transifexAdapter/createTask.d.ts +0 -6
- package/dist/transifexAdapter/getLocales.d.ts +0 -2
- package/dist/transifexAdapter/getTranslation.d.ts +0 -2
- package/dist/transifexAdapter/getTranslationTask.d.ts +0 -6
- package/dist/transifexAdapter/helpers.d.ts +0 -7
- package/dist/transifexAdapter/index.d.ts +0 -2
- package/test/directives.test.ts +0 -155
- package/test/localeId.test.ts +0 -17
- package/test/mergeTranslation.test.ts +0 -113
- package/tsconfig.json +0 -35
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
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,260 +1,134 @@
|
|
|
1
|
-
|
|
1
|
+
> This is a **Sanity Studio v3** plugin.
|
|
2
2
|
|
|
3
|
+
## Installation
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
```sh
|
|
6
|
+
npm install sanity-plugin-transifex
|
|
7
|
+
```
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* A new tab in your studio for the documents you want to translate
|
|
9
|
-
* An adapter that communicates with the Transifex file API
|
|
10
|
-
* Customizable HTML serialization and deserialization tooling
|
|
11
|
-
* Customizable document patching tooling
|
|
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.
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
# Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Quickstart](#quickstart)
|
|
16
|
+
- [Assumptions](#assumptions)
|
|
17
|
+
- [Studio experience](#studio-experience)
|
|
18
|
+
- [Overriding defaults](#overriding-defaults)
|
|
19
|
+
- [License](#license)
|
|
20
|
+
- [Develop and test](#develop-and-test)
|
|
16
21
|
|
|
17
22
|
## Quickstart
|
|
18
23
|
|
|
19
24
|
1. In your studio folder, run `npm install sanity-plugin-transifex`.
|
|
20
25
|
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)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
- In your studio, create a file called `populateTransifexSecrets.js`.
|
|
27
|
+
- Place the following in the file and fill out the correct values (those in all-caps).
|
|
28
|
+
|
|
24
29
|
```javascript
|
|
25
|
-
import
|
|
30
|
+
import {getCliClient} from 'sanity/cli'
|
|
26
31
|
|
|
27
|
-
const client =
|
|
32
|
+
const client = getCliClient({apiVersion: '2023-02-15'})
|
|
28
33
|
|
|
29
34
|
client.createOrReplace({
|
|
30
|
-
_id: 'transifex.secrets',
|
|
31
|
-
_type: 'transifexSettings',
|
|
32
|
-
organization: '
|
|
33
|
-
project: '
|
|
34
|
-
token: '
|
|
35
|
+
_id: 'transifex.secrets',
|
|
36
|
+
_type: 'transifexSettings',
|
|
37
|
+
organization: 'YOUR_TRANSIFEX_ORG_HERE',
|
|
38
|
+
project: 'YOUR_TRANSIFEX_PROJECT_HERE',
|
|
39
|
+
token: 'YOUR_TRANSIFEX_TOKEN_HERE'
|
|
35
40
|
})
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
- On the command line, run the file with `sanity exec populateTransifexSecrets.js --with-user-token`.
|
|
44
|
+
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!)
|
|
45
|
+
- If everything looks good, go ahead and delete `populateTransifexSecrets.js` so you don't commit it.
|
|
46
|
+
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).
|
|
47
|
+
|
|
43
48
|
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:
|
|
44
49
|
|
|
45
50
|
```javascript
|
|
46
|
-
import
|
|
51
|
+
import {DefaultDocumentNodeResolver} from 'sanity/desk'
|
|
47
52
|
//...your other desk structure imports...
|
|
48
|
-
import {
|
|
49
|
-
|
|
53
|
+
import {TranslationTab, defaultFieldLevelConfig} from 'sanity-plugin-transifex'
|
|
50
54
|
|
|
51
|
-
export const getDefaultDocumentNode = (
|
|
52
|
-
if (
|
|
55
|
+
export const getDefaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
|
|
56
|
+
if (schemaType === 'myTranslatableDocumentType') {
|
|
53
57
|
return S.document().views([
|
|
54
58
|
S.view.form(),
|
|
55
59
|
//...my other views -- for example, live preview, the i18n plugin, etc.,
|
|
56
|
-
S.view.component(TranslationTab).title('Transifex').options(
|
|
57
|
-
defaultDocumentLevelConfig
|
|
58
|
-
)
|
|
60
|
+
S.view.component(TranslationTab).title('Transifex').options(defaultDocumentLevelConfig)
|
|
59
61
|
])
|
|
60
62
|
}
|
|
61
|
-
return S.document()
|
|
62
|
-
}
|
|
63
|
+
return S.document()
|
|
64
|
+
}
|
|
63
65
|
```
|
|
64
66
|
|
|
65
67
|
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!
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
<br />
|
|
69
|
-
|
|
70
|
-
### Important note on defaults
|
|
71
|
-
|
|
72
|
-
`defaultDocumentLevelConfig` and `defaultFieldLevelConfig` make a few assumptions that can be overridden (see the below section). These assumptions are based on [Sanity's existing recommendations on localization](https://www.sanity.io/docs/localization):
|
|
73
|
-
* `defaultDocumentLevelConfig`:
|
|
74
|
-
* You want _any_ fields containing text or text arrays to be translated.
|
|
75
|
-
* You're storing documents in different languages along a path pattern like `i18n.{id-of-base-language-document}.{locale}`.
|
|
76
|
-
* `defaultFieldLevelConfig`:
|
|
77
|
-
* Your base language is English.
|
|
78
|
-
* Any fields you want translated exist in the multi-locale object form we recommend.
|
|
79
|
-
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
|
|
80
|
-
```
|
|
81
|
-
{ title: {
|
|
82
|
-
en: 'My title is here.',
|
|
83
|
-
es: 'Mi título está aquí.',
|
|
84
|
-
etc...
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
This config will look for the English values on all fields that look like this, and place translated values into their appropriate fields.
|
|
89
|
-
|
|
90
|
-
If your content models don't look like this, you can still run the defaults as an experiment -- you'll just likely get some funky results on import!
|
|
69
|
+
## Assumptions
|
|
91
70
|
|
|
92
|
-
|
|
93
|
-
<br />
|
|
71
|
+
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).
|
|
94
72
|
|
|
95
|
-
|
|
73
|
+
### Field-level translations
|
|
96
74
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<br />
|
|
100
|
-
|
|
101
|
-
### Scenario: Some fields or objects in my document are serializing /deserializing strangely.
|
|
102
|
-
First: this is often caused by not declaring types at the top level of your schema. Serialization introspects your schema files and can get a much better sense of what to do when objects are not "anonymous" (this is similar to how our GraphQL functions work -- more info on "strict" schemas [here](https://www.sanity.io/docs/graphql#33ec7103289a)) You can save yourself some development time by trying this first.
|
|
103
|
-
|
|
104
|
-
If that's still not doing the trick, you can add on to the serializer to ensure you have complete say over how an object gets serialized and deserialized. Under the hood, serialization is using Sanity's [blocks-to-html](https://github.com/sanity-io/block-content-to-html), and the same principles apply here. We strongly recommend you check that documentation to understand how to use these serialization rules. Here's how you might declare and use some custom serialization.
|
|
105
|
-
|
|
106
|
-
First, write your serialization rules:
|
|
75
|
+
If you are using field-level translation, we assume any fields you want translated exist in the multi-locale object form we recommend.
|
|
76
|
+
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
|
|
107
77
|
|
|
108
78
|
```javascript
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const innerElements = //do things with the props
|
|
116
|
-
//className and id is VERY important!! don't forget them!!
|
|
117
|
-
return h('div', { className: props.node._type, id: props.node._key }, innerElements)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const myCustomSerializers = customSerializers
|
|
122
|
-
myCustomSerializers.types = myCustomSerializerTypes
|
|
123
|
-
|
|
124
|
-
const myCustomDeserializer = {
|
|
125
|
-
types: {
|
|
126
|
-
myType: (htmlString) => {
|
|
127
|
-
//parse it back out!
|
|
128
|
-
}
|
|
79
|
+
{
|
|
80
|
+
//...other document fields,
|
|
81
|
+
title: {
|
|
82
|
+
en: 'My title is here.',
|
|
83
|
+
es: 'Mi título está aquí.',
|
|
84
|
+
etc...
|
|
129
85
|
}
|
|
130
86
|
}
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
If your object is inline, then you may need to use the deserialization rules in Sanity's [block-tools](https://github.com/sanity-io/sanity/tree/next/packages/@sanity/block-tools) (also used in deserialzation. So you might declare something like this:
|
|
135
|
-
|
|
136
|
-
```javascript
|
|
137
|
-
const myBlockDeserializationRules = [
|
|
138
|
-
{
|
|
139
|
-
deserialize(el, next, block) {
|
|
140
|
-
if (el.className.toLowerCase() != myType.toLowerCase()) {
|
|
141
|
-
return undefined
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
//do stuff with the HTML string
|
|
145
|
-
return {
|
|
146
|
-
_type: 'myType',
|
|
147
|
-
//all my other fields
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
]
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Now, to bring it all together:
|
|
154
|
-
|
|
155
|
-
```javascript
|
|
156
|
-
import { TranslationTab, defaultDocumentLevelConfig, BaseDocumentSerializer, BaseDocumentDeserializer, BaseDocumentPatcher, defaultStopTypes } from "sanity-plugin-transifex"
|
|
157
|
-
|
|
158
|
-
const myCustomConfig = {
|
|
159
|
-
...defaultDocumentLevelConfig,
|
|
160
|
-
exportForTranslation: (id) =>
|
|
161
|
-
BaseDocumentSerializer.serializeDocument(
|
|
162
|
-
id,
|
|
163
|
-
'document',
|
|
164
|
-
'en',
|
|
165
|
-
defaultStopTypes,
|
|
166
|
-
myCustomSerializers),
|
|
167
|
-
importTranslation: (id, localeId, document) => {
|
|
168
|
-
return BaseDocumentDeserializer.deserializeDocument(
|
|
169
|
-
id,
|
|
170
|
-
document,
|
|
171
|
-
myCustomDeserializer,
|
|
172
|
-
myBlockDeserializationRules).then(
|
|
173
|
-
deserialized =>
|
|
174
|
-
BaseDocumentPatcher.documentLevelPatch(deserialized, id, localeId)
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
87
|
```
|
|
180
88
|
|
|
181
|
-
|
|
89
|
+
### Document level translations
|
|
182
90
|
|
|
183
|
-
|
|
184
|
-
S.view.component(TranslationTab).title('Transifex').options(
|
|
185
|
-
myCustomConfig
|
|
186
|
-
)
|
|
187
|
-
```
|
|
91
|
+
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}`
|
|
188
92
|
|
|
189
|
-
|
|
190
|
-
<br />
|
|
93
|
+
### Final note
|
|
191
94
|
|
|
192
|
-
|
|
95
|
+
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).
|
|
193
96
|
|
|
194
|
-
|
|
97
|
+
## Studio experience
|
|
195
98
|
|
|
196
|
-
|
|
197
|
-
import { TranslationTab, defaultDocumentLevelConfig, BaseDocumentDeserializer } from "sanity-plugin-transifex"
|
|
198
|
-
|
|
199
|
-
const myCustomConfig = {
|
|
200
|
-
...defaultDocumentLevelConfig,
|
|
201
|
-
importTranslation: (id, localeId, document) => {
|
|
202
|
-
return BaseDocumentDeserializer.deserializeDocument(id,document).then(
|
|
203
|
-
deserialized =>
|
|
204
|
-
//you should have an object of translated values here. Do things with them!
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
```
|
|
99
|
+
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.
|
|
209
100
|
|
|
210
|
-
|
|
211
|
-
<br />
|
|
101
|
+
## Overriding defaults
|
|
212
102
|
|
|
213
|
-
|
|
214
|
-
The serializer actually introspects your schema files. You can set `localize: false` on a schema and that field should not be sent off. Example:
|
|
215
|
-
```javascript
|
|
216
|
-
fields: [{
|
|
217
|
-
name: 'categories',
|
|
218
|
-
type: 'array',
|
|
219
|
-
localize: false,
|
|
220
|
-
...
|
|
221
|
-
}]
|
|
222
|
-
```
|
|
103
|
+
To personalize this configuration it's useful to know what arguments go into `TranslationsTab` as options (the `defaultConfigs` are just wrappers for these):
|
|
223
104
|
|
|
224
|
-
|
|
225
|
-
|
|
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!
|
|
226
108
|
|
|
227
|
-
|
|
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 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.
|
|
228
110
|
|
|
229
|
-
|
|
111
|
+
## Migrating to V3
|
|
230
112
|
|
|
231
|
-
|
|
232
|
-
import { TranslationTab, defaultDocumentLevelConfig, defaultStopTypes, BaseDocumentSerializer } from "sanity-plugin-transifex"
|
|
113
|
+
You should not have to do anything to migrate to 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. These is oulined in the serializer README [here](https://github.com/sanity-io/sanity-naive-html-serializer#v2-to-v3-changes).
|
|
233
114
|
|
|
234
|
-
|
|
235
|
-
...defaultStopTypes,
|
|
236
|
-
'listItem'
|
|
237
|
-
]
|
|
115
|
+
## License
|
|
238
116
|
|
|
239
|
-
|
|
240
|
-
...defaultDocumentLevelConfig,
|
|
241
|
-
exportForTranslation: (id) => BaseDocumentSerializer.serializeDocument(
|
|
242
|
-
id, 'document', 'en', myCustomStopTypes)
|
|
243
|
-
}
|
|
244
|
-
```
|
|
117
|
+
[MIT](LICENSE) © Sanity.io
|
|
245
118
|
|
|
246
|
-
|
|
119
|
+
## Develop & test
|
|
247
120
|
|
|
248
|
-
|
|
121
|
+
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!
|
|
249
122
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
)
|
|
123
|
+
This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit)
|
|
124
|
+
with default configuration for build & watch scripts.
|
|
253
125
|
|
|
254
|
-
|
|
126
|
+
See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio)
|
|
127
|
+
on how to run this plugin with hotreload in the studio.
|
|
255
128
|
|
|
256
|
-
|
|
129
|
+
### Release new version
|
|
257
130
|
|
|
258
|
-
|
|
131
|
+
Run ["CI & Release" workflow](https://github.com/sanity-io/sanity-plugin-transifex/actions/workflows/main.yml).
|
|
132
|
+
Make sure to select the main branch and check "Release new version".
|
|
259
133
|
|
|
260
|
-
|
|
134
|
+
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,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 {TranslationFunctionContext} from 'sanity-translations-tab'
|
|
11
|
+
import {TranslationsTab} from 'sanity-translations-tab'
|
|
12
|
+
|
|
13
|
+
export {BaseDocumentDeserializer}
|
|
14
|
+
|
|
15
|
+
export {BaseDocumentMerger}
|
|
16
|
+
|
|
17
|
+
export {BaseDocumentSerializer}
|
|
18
|
+
|
|
19
|
+
declare interface ConfigOptions {
|
|
20
|
+
adapter: Adapter
|
|
21
|
+
secretsNamespace: string | null
|
|
22
|
+
exportForTranslation: (
|
|
23
|
+
id: string,
|
|
24
|
+
context: TranslationFunctionContext
|
|
25
|
+
) => Promise<Record<string, any>>
|
|
26
|
+
importTranslation: (
|
|
27
|
+
id: string,
|
|
28
|
+
localeId: string,
|
|
29
|
+
doc: string,
|
|
30
|
+
context: TranslationFunctionContext
|
|
31
|
+
) => Promise<void>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {customSerializers}
|
|
35
|
+
|
|
36
|
+
export declare const defaultDocumentLevelConfig: ConfigOptions
|
|
37
|
+
|
|
38
|
+
export declare const defaultFieldLevelConfig: ConfigOptions
|
|
39
|
+
|
|
40
|
+
export {defaultStopTypes}
|
|
41
|
+
|
|
42
|
+
export {documentLevelPatch}
|
|
43
|
+
|
|
44
|
+
export {fieldLevelPatch}
|
|
45
|
+
|
|
46
|
+
export {findLatestDraft}
|
|
47
|
+
|
|
48
|
+
export declare const TransifexAdapter: Adapter
|
|
49
|
+
|
|
50
|
+
export {TranslationsTab}
|
|
51
|
+
|
|
52
|
+
export {}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { baseDocumentLevelConfig, baseFieldLevelConfig } from 'sanity-translations-tab';
|
|
2
|
+
export { BaseDocumentDeserializer, BaseDocumentMerger, BaseDocumentSerializer, TranslationsTab, customSerializers, defaultStopTypes, documentLevelPatch, fieldLevelPatch, findLatestDraft } 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 defaultFieldLevelConfig = {
|
|
187
|
+
...baseFieldLevelConfig,
|
|
188
|
+
adapter: TransifexAdapter,
|
|
189
|
+
secretsNamespace: "transifex"
|
|
190
|
+
};
|
|
191
|
+
export { TransifexAdapter, defaultDocumentLevelConfig, defaultFieldLevelConfig };
|
|
192
|
+
//# 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 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 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 defaultFieldLevelConfig: ConfigOptions = {\n ...baseFieldLevelConfig,\n adapter: TransifexAdapter,\n secretsNamespace: 'transifex',\n}\n\nexport {TransifexAdapter, defaultDocumentLevelConfig, defaultFieldLevelConfig}\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","defaultFieldLevelConfig","baseFieldLevelConfig"],"mappings":"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"}
|