gt-sanity 0.0.5 → 0.0.6
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.md +1 -8
- package/README.md +5 -5
- package/dist/index.d.mts +35 -30
- package/dist/index.d.ts +35 -30
- package/dist/index.js +234 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +237 -124
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
- package/src/adapter/core.ts +72 -7
- package/src/adapter/createTask.ts +1 -1
- package/src/adapter/types.ts +9 -0
- package/src/components/LanguageStatus.tsx +2 -0
- package/src/components/NewTask.tsx +2 -0
- package/src/components/ProgressBar.tsx +2 -0
- package/src/components/TaskView.tsx +2 -0
- package/src/components/TranslationContext.tsx +5 -0
- package/src/components/TranslationView.tsx +34 -2
- package/src/components/TranslationsTab.tsx +4 -0
- package/src/components/page/TranslationsTool.tsx +876 -0
- package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +23 -10
- package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +77 -24
- package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +51 -8
- package/src/configuration/baseDocumentLevelConfig/index.ts +6 -37
- package/src/configuration/baseFieldLevelConfig.ts +4 -1
- package/src/configuration/utils/checkSerializationVersion.ts +2 -0
- package/src/configuration/utils/findDocumentAtRevision.ts +2 -0
- package/src/configuration/utils/findLatestDraft.ts +2 -0
- package/src/hooks/useClient.ts +3 -1
- package/src/hooks/useSecrets.ts +2 -0
- package/src/index.ts +70 -32
- package/src/translation/checkTranslationStatus.ts +42 -0
- package/src/translation/createJobs.ts +16 -0
- package/src/translation/downloadTranslations.ts +68 -0
- package/src/translation/importDocument.ts +24 -0
- package/src/translation/initProject.ts +61 -0
- package/src/translation/uploadFiles.ts +32 -0
- package/src/types.ts +4 -1
- package/src/utils/applyDocuments.ts +72 -0
- package/src/utils/serialize.ts +32 -0
- package/src/utils/shared.ts +1 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +0 -5
- package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +0 -69
- package/src/configuration/index.ts +0 -18
- package/src/configuration/utils/index.ts +0 -3
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { gtConfig } from '../adapter/core';
|
|
2
|
+
import { documentLevelPatch } from '../configuration/baseDocumentLevelConfig/documentLevelPatch';
|
|
3
|
+
import type { GTFile, TranslationFunctionContext } from '../types';
|
|
4
|
+
import { deserializeDocument } from '../utils/serialize';
|
|
5
|
+
|
|
6
|
+
export async function importDocument(
|
|
7
|
+
docInfo: GTFile,
|
|
8
|
+
localeId: string,
|
|
9
|
+
document: string,
|
|
10
|
+
context: TranslationFunctionContext,
|
|
11
|
+
publish: boolean = false
|
|
12
|
+
) {
|
|
13
|
+
const { client } = context;
|
|
14
|
+
const deserialized = deserializeDocument(document);
|
|
15
|
+
return documentLevelPatch(
|
|
16
|
+
docInfo, // versionId is not used here, since we just use the _rev id in the deserialized HTML itself
|
|
17
|
+
deserialized,
|
|
18
|
+
localeId,
|
|
19
|
+
client,
|
|
20
|
+
gtConfig.getLanguageField(),
|
|
21
|
+
false,
|
|
22
|
+
publish
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { gt, overrideConfig } from '../adapter/core';
|
|
2
|
+
import type { Secrets } from '../types';
|
|
3
|
+
|
|
4
|
+
export async function initProject(
|
|
5
|
+
uploadResult: Awaited<ReturnType<typeof gt.uploadSourceFiles>>,
|
|
6
|
+
options: { timeout?: number },
|
|
7
|
+
secrets: Secrets
|
|
8
|
+
): Promise<boolean> {
|
|
9
|
+
overrideConfig(secrets);
|
|
10
|
+
const setupDecision = await Promise.resolve(gt.shouldSetupProject?.())
|
|
11
|
+
.then((v: any) => v)
|
|
12
|
+
.catch(() => ({ shouldSetupProject: false }));
|
|
13
|
+
const shouldSetupProject = Boolean(setupDecision?.shouldSetupProject);
|
|
14
|
+
|
|
15
|
+
// Step 2: Setup if needed and poll until complete
|
|
16
|
+
if (shouldSetupProject) {
|
|
17
|
+
// Calculate timeout once for setup fetching
|
|
18
|
+
// Accept number or numeric string, default to 600s
|
|
19
|
+
const timeoutVal =
|
|
20
|
+
options?.timeout !== undefined ? Number(options.timeout) : 600;
|
|
21
|
+
const setupTimeoutMs =
|
|
22
|
+
(Number.isFinite(timeoutVal) ? timeoutVal : 600) * 1000;
|
|
23
|
+
|
|
24
|
+
const { setupJobId } = await gt.setupProject(uploadResult.uploadedFiles);
|
|
25
|
+
|
|
26
|
+
const start = Date.now();
|
|
27
|
+
const pollInterval = 2000;
|
|
28
|
+
|
|
29
|
+
let setupCompleted = false;
|
|
30
|
+
let setupFailedMessage: string | null = null;
|
|
31
|
+
|
|
32
|
+
while (true) {
|
|
33
|
+
const status = await gt.checkSetupStatus(setupJobId);
|
|
34
|
+
|
|
35
|
+
if (status.status === 'completed') {
|
|
36
|
+
setupCompleted = true;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (status.status === 'failed') {
|
|
40
|
+
setupFailedMessage = status.error?.message || 'Unknown error';
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (Date.now() - start > setupTimeoutMs) {
|
|
44
|
+
setupFailedMessage = 'Timed out while waiting for setup generation';
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (setupCompleted) {
|
|
51
|
+
console.log('Setup successfully completed');
|
|
52
|
+
return true;
|
|
53
|
+
} else {
|
|
54
|
+
console.log(`Setup ${setupFailedMessage ? 'failed' : 'timed out'} `);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log('Setup not needed');
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { GTFile, Secrets } from '../types';
|
|
2
|
+
import { gt, overrideConfig } from '../adapter/core';
|
|
3
|
+
import { libraryDefaultLocale } from 'generaltranslation/internal';
|
|
4
|
+
import type { SerializedDocument } from 'sanity-naive-html-serializer';
|
|
5
|
+
|
|
6
|
+
// note: this function is used to create a new translation task
|
|
7
|
+
// uploads files & calls the getTranslationTask function
|
|
8
|
+
export async function uploadFiles(
|
|
9
|
+
documents: {
|
|
10
|
+
info: GTFile;
|
|
11
|
+
serializedDocument: SerializedDocument;
|
|
12
|
+
}[],
|
|
13
|
+
secrets: Secrets | null
|
|
14
|
+
): Promise<Awaited<ReturnType<typeof gt.uploadSourceFiles>>> {
|
|
15
|
+
overrideConfig(secrets);
|
|
16
|
+
const uploadResult = await gt.uploadSourceFiles(
|
|
17
|
+
documents.map(({ info, serializedDocument }) => ({
|
|
18
|
+
source: {
|
|
19
|
+
content: serializedDocument.content,
|
|
20
|
+
fileName: `sanity/${info.documentId}`,
|
|
21
|
+
fileId: info.documentId,
|
|
22
|
+
fileFormat: 'HTML',
|
|
23
|
+
locale: gt.sourceLocale || libraryDefaultLocale,
|
|
24
|
+
versionId: info.versionId || undefined,
|
|
25
|
+
},
|
|
26
|
+
})),
|
|
27
|
+
{
|
|
28
|
+
sourceLocale: gt.sourceLocale || libraryDefaultLocale,
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
return uploadResult;
|
|
32
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
+
|
|
1
3
|
import { SanityClient, Schema, TypedObject } from 'sanity';
|
|
2
4
|
import { SerializedDocument } from 'sanity-naive-html-serializer';
|
|
3
5
|
import { PortableTextTypeComponent } from '@portabletext/to-html';
|
|
@@ -96,7 +98,8 @@ export type ImportTranslation = (
|
|
|
96
98
|
additionalBlockDeserializers?: DeserializerRule[];
|
|
97
99
|
},
|
|
98
100
|
languageField?: string,
|
|
99
|
-
mergeWithTargetLocale?: boolean
|
|
101
|
+
mergeWithTargetLocale?: boolean,
|
|
102
|
+
publish?: boolean
|
|
100
103
|
) => Promise<void>;
|
|
101
104
|
|
|
102
105
|
export type TranslationsTabConfigOptions = {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { JSONPath } from 'jsonpath-plus';
|
|
2
|
+
import JSONPointer from 'jsonpointer';
|
|
3
|
+
import type { IgnoreFields } from '../adapter/types';
|
|
4
|
+
|
|
5
|
+
export function applyDocuments(
|
|
6
|
+
documentId: string,
|
|
7
|
+
sourceDocument: Record<string, any>,
|
|
8
|
+
targetDocument: Record<string, any>,
|
|
9
|
+
ignore: IgnoreFields[]
|
|
10
|
+
) {
|
|
11
|
+
const ignoreFields = ignore.filter(
|
|
12
|
+
(field) =>
|
|
13
|
+
field.documentId === documentId ||
|
|
14
|
+
field.documentId === undefined ||
|
|
15
|
+
field.documentId === null
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Start with a shallow copy of the source document
|
|
19
|
+
const mergedDocument = { ...sourceDocument };
|
|
20
|
+
|
|
21
|
+
// Merge top-level properties of targetDocument
|
|
22
|
+
for (const [key, value] of Object.entries(targetDocument)) {
|
|
23
|
+
mergedDocument[key] = value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Process ignored fields and restore them from source document
|
|
27
|
+
for (const ignoreField of ignoreFields) {
|
|
28
|
+
if (!ignoreField.fields) continue;
|
|
29
|
+
|
|
30
|
+
for (const field of ignoreField.fields) {
|
|
31
|
+
const { property, type } = field;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Use JSONPath to find matching paths, then JSONPointer to get/set values
|
|
35
|
+
const sourceResults = JSONPath({
|
|
36
|
+
json: sourceDocument,
|
|
37
|
+
path: property,
|
|
38
|
+
resultType: 'all',
|
|
39
|
+
flatten: true,
|
|
40
|
+
wrap: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (sourceResults && sourceResults.length > 0) {
|
|
44
|
+
// Process each matching path
|
|
45
|
+
sourceResults.forEach((result: { pointer: string; value: any }) => {
|
|
46
|
+
const sourceValue = result.value;
|
|
47
|
+
|
|
48
|
+
// If type is specified, check if it matches the object's _type property
|
|
49
|
+
if (type !== undefined) {
|
|
50
|
+
if (
|
|
51
|
+
typeof sourceValue === 'object' &&
|
|
52
|
+
sourceValue !== null &&
|
|
53
|
+
sourceValue._type === type
|
|
54
|
+
) {
|
|
55
|
+
// Type matches, restore the entire object using JSONPointer
|
|
56
|
+
JSONPointer.set(mergedDocument, result.pointer, sourceValue);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// No type specified, restore the value using JSONPointer
|
|
60
|
+
JSONPointer.set(mergedDocument, result.pointer, sourceValue);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Invalid JSONPath, skip this field
|
|
66
|
+
console.warn(`Invalid JSONPath: ${property}`, error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return mergedDocument;
|
|
72
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SanityDocument, Schema } from 'sanity';
|
|
2
|
+
import {
|
|
3
|
+
BaseDocumentDeserializer,
|
|
4
|
+
BaseDocumentSerializer,
|
|
5
|
+
defaultStopTypes,
|
|
6
|
+
customSerializers,
|
|
7
|
+
customBlockDeserializers,
|
|
8
|
+
} from 'sanity-naive-html-serializer';
|
|
9
|
+
|
|
10
|
+
export function deserializeDocument(document: string) {
|
|
11
|
+
const deserialized = BaseDocumentDeserializer.deserializeDocument(
|
|
12
|
+
document,
|
|
13
|
+
{ types: {} },
|
|
14
|
+
customBlockDeserializers
|
|
15
|
+
) as SanityDocument;
|
|
16
|
+
return deserialized;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function serializeDocument(
|
|
20
|
+
document: SanityDocument,
|
|
21
|
+
schema: Schema,
|
|
22
|
+
baseLanguage: string
|
|
23
|
+
) {
|
|
24
|
+
const serialized = BaseDocumentSerializer(schema).serializeDocument(
|
|
25
|
+
document,
|
|
26
|
+
'document',
|
|
27
|
+
baseLanguage,
|
|
28
|
+
defaultStopTypes,
|
|
29
|
+
customSerializers
|
|
30
|
+
);
|
|
31
|
+
return serialized;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SECRETS_NAMESPACE = 'generaltranslation';
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { createI18nDocAndPatchMetadata } from './createI18nDocAndPatchMetadata';
|
|
2
|
-
export { createTranslationMetadata } from './createTranslationMetadata';
|
|
3
|
-
export { getTranslationMetadata } from './getTranslationMetadata';
|
|
4
|
-
export { getOrCreateTranslationMetadata } from './getOrCreateTranslationMetadata';
|
|
5
|
-
export { patchI18nDoc } from './patchI18nDoc';
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { SanityClient, SanityDocument, SanityDocumentLike } from 'sanity';
|
|
2
|
-
import { BaseDocumentMerger } from 'sanity-naive-html-serializer';
|
|
3
|
-
|
|
4
|
-
import { findLatestDraft, findDocumentAtRevision } from '../utils';
|
|
5
|
-
import type { GTFile } from '../../types';
|
|
6
|
-
|
|
7
|
-
export const legacyDocumentLevelPatch = async (
|
|
8
|
-
docInfo: GTFile,
|
|
9
|
-
translatedFields: SanityDocument,
|
|
10
|
-
localeId: string,
|
|
11
|
-
client: SanityClient
|
|
12
|
-
): Promise<void> => {
|
|
13
|
-
let baseDoc: SanityDocument | null = null;
|
|
14
|
-
|
|
15
|
-
/*
|
|
16
|
-
* we send over the _rev with our translation file so we can
|
|
17
|
-
* accurately coalesce the translations in case something has
|
|
18
|
-
* changed in the base document since translating
|
|
19
|
-
*/
|
|
20
|
-
if (docInfo.documentId && docInfo.versionId) {
|
|
21
|
-
baseDoc = await findDocumentAtRevision(
|
|
22
|
-
docInfo.documentId,
|
|
23
|
-
docInfo.versionId,
|
|
24
|
-
client
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
if (!baseDoc) {
|
|
28
|
-
baseDoc = await findLatestDraft(docInfo.documentId, client);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
* we then merge the translation with the base document
|
|
33
|
-
* to create a document that contains the translation and everything
|
|
34
|
-
* that wasn't sent over for translation
|
|
35
|
-
*/
|
|
36
|
-
const merged = BaseDocumentMerger.documentLevelMerge(
|
|
37
|
-
translatedFields,
|
|
38
|
-
baseDoc
|
|
39
|
-
) as SanityDocumentLike;
|
|
40
|
-
|
|
41
|
-
/* we now need to check if we have a translated document
|
|
42
|
-
* if not, we create it
|
|
43
|
-
*/
|
|
44
|
-
const targetId = `drafts.${docInfo.documentId}__i18n_${localeId}`;
|
|
45
|
-
const i18nDoc = await findLatestDraft(targetId, client);
|
|
46
|
-
if (i18nDoc) {
|
|
47
|
-
const cleanedMerge: Record<string, any> = {};
|
|
48
|
-
//don't overwrite any existing system values on the i18n doc
|
|
49
|
-
Object.entries(merged).forEach(([key, value]) => {
|
|
50
|
-
if (
|
|
51
|
-
Object.keys(translatedFields).includes(key) &&
|
|
52
|
-
!['_id', '_rev', '_updatedAt'].includes(key)
|
|
53
|
-
) {
|
|
54
|
-
cleanedMerge[key] = value;
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
await client
|
|
59
|
-
.transaction()
|
|
60
|
-
//@ts-ignore
|
|
61
|
-
.patch(i18nDoc._id, (p) => p.set(cleanedMerge))
|
|
62
|
-
.commit();
|
|
63
|
-
} else {
|
|
64
|
-
merged._id = targetId;
|
|
65
|
-
|
|
66
|
-
merged.__i18n_lang = localeId;
|
|
67
|
-
await client.create(merged);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
baseDocumentLevelConfig,
|
|
3
|
-
documentLevelPatch,
|
|
4
|
-
legacyDocumentLevelConfig,
|
|
5
|
-
legacyDocumentLevelPatch,
|
|
6
|
-
} from './baseDocumentLevelConfig';
|
|
7
|
-
import { baseFieldLevelConfig, fieldLevelPatch } from './baseFieldLevelConfig';
|
|
8
|
-
import { findLatestDraft } from './utils';
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
baseDocumentLevelConfig,
|
|
12
|
-
legacyDocumentLevelConfig,
|
|
13
|
-
baseFieldLevelConfig,
|
|
14
|
-
findLatestDraft,
|
|
15
|
-
legacyDocumentLevelPatch,
|
|
16
|
-
documentLevelPatch,
|
|
17
|
-
fieldLevelPatch,
|
|
18
|
-
};
|