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.
Files changed (48) hide show
  1. package/LICENSE.md +1 -8
  2. package/README.md +5 -5
  3. package/dist/index.d.mts +35 -30
  4. package/dist/index.d.ts +35 -30
  5. package/dist/index.js +234 -123
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +237 -124
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +5 -3
  10. package/src/adapter/core.ts +72 -7
  11. package/src/adapter/createTask.ts +1 -1
  12. package/src/adapter/types.ts +9 -0
  13. package/src/components/LanguageStatus.tsx +2 -0
  14. package/src/components/NewTask.tsx +2 -0
  15. package/src/components/ProgressBar.tsx +2 -0
  16. package/src/components/TaskView.tsx +2 -0
  17. package/src/components/TranslationContext.tsx +5 -0
  18. package/src/components/TranslationView.tsx +34 -2
  19. package/src/components/TranslationsTab.tsx +4 -0
  20. package/src/components/page/TranslationsTool.tsx +876 -0
  21. package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +23 -10
  22. package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +77 -24
  23. package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +2 -0
  24. package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +2 -0
  25. package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +2 -0
  26. package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +51 -8
  27. package/src/configuration/baseDocumentLevelConfig/index.ts +6 -37
  28. package/src/configuration/baseFieldLevelConfig.ts +4 -1
  29. package/src/configuration/utils/checkSerializationVersion.ts +2 -0
  30. package/src/configuration/utils/findDocumentAtRevision.ts +2 -0
  31. package/src/configuration/utils/findLatestDraft.ts +2 -0
  32. package/src/hooks/useClient.ts +3 -1
  33. package/src/hooks/useSecrets.ts +2 -0
  34. package/src/index.ts +70 -32
  35. package/src/translation/checkTranslationStatus.ts +42 -0
  36. package/src/translation/createJobs.ts +16 -0
  37. package/src/translation/downloadTranslations.ts +68 -0
  38. package/src/translation/importDocument.ts +24 -0
  39. package/src/translation/initProject.ts +61 -0
  40. package/src/translation/uploadFiles.ts +32 -0
  41. package/src/types.ts +4 -1
  42. package/src/utils/applyDocuments.ts +72 -0
  43. package/src/utils/serialize.ts +32 -0
  44. package/src/utils/shared.ts +1 -0
  45. package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +0 -5
  46. package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +0 -69
  47. package/src/configuration/index.ts +0 -18
  48. 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
- };
@@ -1,3 +0,0 @@
1
- export * from './findDocumentAtRevision';
2
- export * from './findLatestDraft';
3
- export * from './checkSerializationVersion';