@smartcat/sanity-plugin 1.0.0 → 1.0.1

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 (121) hide show
  1. package/README.md +294 -0
  2. package/dist/_chunks-cjs/constants.cjs +13 -0
  3. package/dist/_chunks-cjs/constants.cjs.map +1 -0
  4. package/dist/_chunks-cjs/index.cjs +168 -0
  5. package/dist/_chunks-cjs/index.cjs.map +1 -0
  6. package/dist/_chunks-cjs/index2.cjs +311 -0
  7. package/dist/_chunks-cjs/index2.cjs.map +1 -0
  8. package/dist/_chunks-cjs/workflow.cjs +16 -0
  9. package/dist/_chunks-cjs/workflow.cjs.map +1 -0
  10. package/dist/_chunks-es/constants.js +14 -0
  11. package/dist/_chunks-es/constants.js.map +1 -0
  12. package/dist/_chunks-es/index.js +169 -0
  13. package/dist/_chunks-es/index.js.map +1 -0
  14. package/dist/_chunks-es/index2.js +313 -0
  15. package/dist/_chunks-es/index2.js.map +1 -0
  16. package/dist/_chunks-es/workflow.js +17 -0
  17. package/dist/_chunks-es/workflow.js.map +1 -0
  18. package/dist/export.cjs +160 -0
  19. package/dist/export.cjs.map +1 -0
  20. package/dist/export.d.cts +162 -0
  21. package/dist/export.d.ts +162 -0
  22. package/dist/export.js +162 -0
  23. package/dist/export.js.map +1 -0
  24. package/dist/import.cjs +169 -0
  25. package/dist/import.cjs.map +1 -0
  26. package/dist/import.d.cts +155 -0
  27. package/dist/import.d.ts +155 -0
  28. package/dist/import.js +169 -0
  29. package/dist/import.js.map +1 -0
  30. package/dist/index.cjs +2103 -0
  31. package/dist/index.cjs.map +1 -0
  32. package/dist/index.d.cts +122 -0
  33. package/dist/index.d.ts +122 -0
  34. package/dist/index.js +2110 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/locjson.cjs +12 -0
  37. package/dist/locjson.cjs.map +1 -0
  38. package/dist/locjson.d.cts +287 -0
  39. package/dist/locjson.d.ts +287 -0
  40. package/dist/locjson.js +12 -0
  41. package/dist/locjson.js.map +1 -0
  42. package/dist/progress.cjs +16 -0
  43. package/dist/progress.cjs.map +1 -0
  44. package/dist/progress.d.cts +262 -0
  45. package/dist/progress.d.ts +262 -0
  46. package/dist/progress.js +16 -0
  47. package/dist/progress.js.map +1 -0
  48. package/dist/smartcat.cjs +287 -0
  49. package/dist/smartcat.cjs.map +1 -0
  50. package/dist/smartcat.d.cts +234 -0
  51. package/dist/smartcat.d.ts +234 -0
  52. package/dist/smartcat.js +287 -0
  53. package/dist/smartcat.js.map +1 -0
  54. package/dist/templates.cjs +12 -0
  55. package/dist/templates.cjs.map +1 -0
  56. package/dist/templates.d.cts +52 -0
  57. package/dist/templates.d.ts +52 -0
  58. package/dist/templates.js +12 -0
  59. package/dist/templates.js.map +1 -0
  60. package/package.json +101 -15
  61. package/src/actions/AddToProjectAction.tsx +258 -0
  62. package/src/export/export.test.ts +537 -0
  63. package/src/export/index.ts +393 -0
  64. package/src/form/TranslationStatusInput.tsx +212 -0
  65. package/src/import/import.test.ts +346 -0
  66. package/src/import/index.ts +418 -0
  67. package/src/index.ts +63 -0
  68. package/src/lib/constants.ts +23 -0
  69. package/src/lib/documentTitle.test.ts +43 -0
  70. package/src/lib/documentTitle.ts +30 -0
  71. package/src/lib/languageMap.test.ts +56 -0
  72. package/src/lib/languageMap.ts +71 -0
  73. package/src/lib/linkedDocuments.test.ts +56 -0
  74. package/src/lib/linkedDocuments.ts +100 -0
  75. package/src/lib/locjson/deserialize.ts +60 -0
  76. package/src/lib/locjson/fields.ts +355 -0
  77. package/src/lib/locjson/filename.ts +27 -0
  78. package/src/lib/locjson/index.ts +10 -0
  79. package/src/lib/locjson/locjson.test.ts +545 -0
  80. package/src/lib/locjson/paths.test.ts +41 -0
  81. package/src/lib/locjson/paths.ts +73 -0
  82. package/src/lib/locjson/portableText.ts +98 -0
  83. package/src/lib/locjson/serialize.ts +124 -0
  84. package/src/lib/locjson/types.ts +106 -0
  85. package/src/lib/log.ts +33 -0
  86. package/src/lib/projectItems.ts +17 -0
  87. package/src/lib/resolveConfig.test.ts +62 -0
  88. package/src/lib/resolveConfig.ts +47 -0
  89. package/src/lib/workflow.test.ts +37 -0
  90. package/src/lib/workflow.ts +48 -0
  91. package/src/lib/zip.fixtures.ts +75 -0
  92. package/src/lib/zip.test.ts +110 -0
  93. package/src/lib/zip.ts +164 -0
  94. package/src/progress/core.ts +387 -0
  95. package/src/progress/index.ts +7 -0
  96. package/src/progress/progress.test.ts +268 -0
  97. package/src/schema/settings.ts +55 -0
  98. package/src/schema/translatableOptions.ts +25 -0
  99. package/src/schema/translationProject.ts +264 -0
  100. package/src/smartcat/client.test.ts +222 -0
  101. package/src/smartcat/client.ts +351 -0
  102. package/src/smartcat/index.ts +10 -0
  103. package/src/smartcat/types.ts +99 -0
  104. package/src/templates/core.ts +55 -0
  105. package/src/templates/index.ts +5 -0
  106. package/src/templates/templates.test.ts +50 -0
  107. package/src/tool/Dashboard.tsx +44 -0
  108. package/src/tool/ItemProgress.tsx +176 -0
  109. package/src/tool/LogPanel.tsx +207 -0
  110. package/src/tool/ProjectView.tsx +975 -0
  111. package/src/tool/ProjectsView.tsx +208 -0
  112. package/src/tool/StatusBadge.tsx +33 -0
  113. package/src/tool/StudioInit.tsx +42 -0
  114. package/src/tool/WorkflowSelect.tsx +41 -0
  115. package/src/tool/data.ts +79 -0
  116. package/src/tool/index.ts +19 -0
  117. package/src/tool/processing.test.ts +623 -0
  118. package/src/tool/processing.ts +766 -0
  119. package/src/tool/useTemplates.ts +84 -0
  120. package/src/tool/useWorkflowSelection.ts +65 -0
  121. package/src/types.ts +77 -0
@@ -0,0 +1,84 @@
1
+ import {useCallback, useEffect, useRef, useState} from 'react'
2
+ import {useClient} from 'sanity'
3
+ import {API_VERSION, SETTINGS_DOC_ID, SETTINGS_TYPE} from '../lib/constants'
4
+ import type {SmartcatTemplate} from '../smartcat/types'
5
+
6
+ interface SettingsDoc {
7
+ templates?: SmartcatTemplate[]
8
+ templatesRequestedAt?: string
9
+ templatesSyncedAt?: string
10
+ }
11
+
12
+ const SETTINGS_QUERY = `*[_id == $id][0]{templates, templatesRequestedAt, templatesSyncedAt}`
13
+
14
+ /**
15
+ * A request older than this is treated as failed/abandoned, so a fresh refresh
16
+ * is allowed — self-heals a sync that never completed (e.g. a function error)
17
+ * instead of wedging the cache in a perpetual "in flight" state.
18
+ */
19
+ export const REQUEST_STALE_MS = 30_000
20
+
21
+ /**
22
+ * A refresh is in flight when a request is newer than the last sync *and* recent
23
+ * enough that the function could still be working on it.
24
+ */
25
+ export function isRefreshInFlight(
26
+ s: {templatesRequestedAt?: string; templatesSyncedAt?: string} | null,
27
+ ): boolean {
28
+ const requested = s?.templatesRequestedAt
29
+ if (!requested) return false
30
+ const pending = !s?.templatesSyncedAt || requested > s.templatesSyncedAt
31
+ if (!pending) return false
32
+ return Date.now() - Date.parse(requested) < REQUEST_STALE_MS
33
+ }
34
+
35
+ /**
36
+ * Reads the cached Smartcat templates from the settings singleton and keeps them
37
+ * live via `listen()`. `requestRefresh()` bumps the refresh trigger so the
38
+ * `smartcat-templates` Function re-fetches — skipped while a request is already
39
+ * in flight (debounce). Returns templates sorted by name.
40
+ */
41
+ export function useTemplates(): {
42
+ templates: SmartcatTemplate[]
43
+ loaded: boolean
44
+ requestRefresh: () => void
45
+ } {
46
+ const client = useClient({apiVersion: API_VERSION})
47
+ const [settings, setSettings] = useState<SettingsDoc | null>(null)
48
+ const [loaded, setLoaded] = useState(false)
49
+ const settingsRef = useRef<SettingsDoc | null>(null)
50
+ settingsRef.current = settings
51
+
52
+ useEffect(() => {
53
+ let cancelled = false
54
+ const load = () =>
55
+ client
56
+ .fetch<SettingsDoc | null>(SETTINGS_QUERY, {id: SETTINGS_DOC_ID})
57
+ .then((s) => {
58
+ if (cancelled) return
59
+ setSettings(s)
60
+ setLoaded(true)
61
+ })
62
+ .catch(() => {})
63
+ load()
64
+ const sub = client
65
+ .listen(SETTINGS_QUERY, {id: SETTINGS_DOC_ID}, {visibility: 'query'})
66
+ .subscribe({next: load, error: () => {}})
67
+ return () => {
68
+ cancelled = true
69
+ sub.unsubscribe()
70
+ }
71
+ }, [client])
72
+
73
+ const requestRefresh = useCallback(() => {
74
+ if (isRefreshInFlight(settingsRef.current)) return
75
+ client
76
+ .transaction()
77
+ .createIfNotExists({_id: SETTINGS_DOC_ID, _type: SETTINGS_TYPE})
78
+ .patch(SETTINGS_DOC_ID, (p) => p.set({templatesRequestedAt: new Date().toISOString()}))
79
+ .commit({visibility: 'async'})
80
+ .catch(() => {})
81
+ }, [client])
82
+
83
+ return {templates: settings?.templates ?? [], loaded, requestRefresh}
84
+ }
@@ -0,0 +1,65 @@
1
+ import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
2
+ import {isWorkflowValid} from '../lib/workflow'
3
+ import type {SmartcatTemplate} from '../smartcat/types'
4
+
5
+ const STORAGE_KEY = 'smartcat:lastWorkflow'
6
+
7
+ function readLastWorkflow(): string {
8
+ try {
9
+ return localStorage.getItem(STORAGE_KEY) ?? ''
10
+ } catch {
11
+ return ''
12
+ }
13
+ }
14
+
15
+ function writeLastWorkflow(value: string): void {
16
+ try {
17
+ localStorage.setItem(STORAGE_KEY, value)
18
+ } catch {
19
+ // Ignore unavailable / full storage; selection just won't persist.
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Workflow selection for a create dialog. Starts with **no selection** so the
25
+ * user must choose, then remembers the last pick per browser (localStorage).
26
+ *
27
+ * When `active` (the dialog is open) and the template cache is `loaded`, it
28
+ * prefills the saved value — but only if it's still valid: a `template:<id>`
29
+ * whose template no longer exists resets to no selection. It also clears the
30
+ * selection live if the chosen template later disappears from the cache.
31
+ */
32
+ export function useWorkflowSelection(
33
+ active: boolean,
34
+ templates: SmartcatTemplate[],
35
+ loaded: boolean,
36
+ ): readonly [string, (value: string) => void] {
37
+ const [workflow, setWorkflowState] = useState('')
38
+ const initialized = useRef(false)
39
+ const templateIds = useMemo(() => templates.map((t) => t.id), [templates])
40
+
41
+ // Prefill the saved selection once per open, after the cache has loaded.
42
+ useEffect(() => {
43
+ if (!active) {
44
+ initialized.current = false
45
+ return
46
+ }
47
+ if (initialized.current || !loaded) return
48
+ initialized.current = true
49
+ const saved = readLastWorkflow()
50
+ setWorkflowState(isWorkflowValid(saved, templateIds) ? saved : '')
51
+ }, [active, loaded, templateIds])
52
+
53
+ // If the selected template disappears from the cache, drop back to no selection.
54
+ useEffect(() => {
55
+ if (!active || !loaded || !workflow) return
56
+ if (!isWorkflowValid(workflow, templateIds)) setWorkflowState('')
57
+ }, [active, loaded, workflow, templateIds])
58
+
59
+ const setWorkflow = useCallback((value: string) => {
60
+ setWorkflowState(value)
61
+ if (value) writeLastWorkflow(value)
62
+ }, [])
63
+
64
+ return [workflow, setWorkflow] as const
65
+ }
package/src/types.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * A language an installation can translate into.
3
+ * `id` is the locale tag used in Sanity (and document-internationalization).
4
+ * `smartcatLanguage` is the code Smartcat expects; set it only when the Sanity
5
+ * tag differs from Smartcat's (e.g. id "PL-PL" → smartcatLanguage "pl"). Defaults to `id`.
6
+ *
7
+ * @public
8
+ */
9
+ export interface SmartcatLanguage {
10
+ id: string
11
+ title: string
12
+ smartcatLanguage?: string
13
+ }
14
+
15
+ /**
16
+ * Plugin configuration, supplied by the consuming Studio.
17
+ *
18
+ * @public
19
+ */
20
+ export interface SmartcatPluginConfig {
21
+ /**
22
+ * Document types the "Add to translation project" action and status line
23
+ * appear on. Omit to allow every document type (except the plugin's own
24
+ * `smartcat.*` types). This affects the Studio UI only — not which linked
25
+ * documents get pulled into a project (see `rootTypes`).
26
+ */
27
+ translatableTypes?: string[]
28
+
29
+ /**
30
+ * Root/standalone document types — the "include linked documents" crawl stops
31
+ * when it reaches one (they're translated on their own, as peers). Omit to
32
+ * inherit the explicitly-configured `translatableTypes`, or `[]` if that is
33
+ * also omitted (i.e. no boundary: pull in every transitively-linked
34
+ * translatable document).
35
+ */
36
+ rootTypes?: string[]
37
+
38
+ /**
39
+ * Source language tag for new projects. Required.
40
+ */
41
+ sourceLanguage: string
42
+
43
+ /**
44
+ * Languages content can be translated into. Used to populate
45
+ * a project's target languages. Optional at this phase.
46
+ */
47
+ languages?: SmartcatLanguage[]
48
+
49
+ /**
50
+ * Field name `@sanity/document-internationalization` stores each document's
51
+ * locale in. Must match that plugin's `languageField` setting. Defaults to
52
+ * `'language'`.
53
+ */
54
+ documentI18nPluginLanguageField?: string
55
+
56
+ /**
57
+ * How `sanity-plugin-internationalized-array` identifies the locale within an
58
+ * array field's members. `'language'` (default) for v5+; set to `'_key'` to
59
+ * support v4.x and lower, which keyed members by the language code.
60
+ */
61
+ fieldI18nPluginLanguageField?: string
62
+ }
63
+
64
+ /**
65
+ * Lifecycle of a translation project. Drives the export/import Functions.
66
+ *
67
+ * @public
68
+ */
69
+ export type TranslationProjectStatus =
70
+ | 'draft'
71
+ | 'queued'
72
+ | 'sent'
73
+ | 'translating'
74
+ | 'importing'
75
+ | 'downloaded'
76
+ | 'completed'
77
+ | 'error'