@sanity/document-internationalization 1.0.0 → 1.0.2
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 +1 -1
- package/README.md +89 -47
- package/lib/index.esm.js +2 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/src/index.d.ts +200 -0
- package/package.json +99 -43
- package/src/actions/DeleteWithi18nAction.tsx +118 -0
- package/src/actions/DuplicateWithi18nAction.tsx +77 -0
- package/src/actions/PublishWithi18nAction.ts +118 -0
- package/src/actions/index.ts +42 -0
- package/src/badges/LanguageBadge.tsx +22 -0
- package/src/constants/I18nDelimiter.ts +1 -0
- package/src/constants/I18nPrefix.ts +1 -0
- package/src/constants/IdStructure.ts +4 -0
- package/src/constants/LanguageCultures.ts +902 -0
- package/src/constants/ReferenceBehavior.ts +5 -0
- package/src/constants/SupportedEmojiFlagCodes.ts +251 -0
- package/src/constants/UiMessages.ts +56 -0
- package/src/constants/index.ts +7 -0
- package/src/documentI18n.tsx +33 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useConfig.ts +22 -0
- package/src/hooks/useDelayedFlag.ts +31 -0
- package/src/index.ts +6 -1
- package/src/language-select/components/LanguageSelect/LanguageConfigContext.tsx +4 -0
- package/src/language-select/components/LanguageSelect/LanguageSelect.tsx +188 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectContext.ts +16 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectLabel.tsx +12 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectList.tsx +84 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectListItem.tsx +251 -0
- package/src/language-select/components/LanguageSelect/index.tsx +25 -0
- package/src/language-select/components/SingleFlag/SingleFlag.tsx +44 -0
- package/src/language-select/components/SingleFlag/allEmojiFlagCodes.ts +251 -0
- package/src/language-select/components/SingleFlag/index.ts +1 -0
- package/src/language-select/components/SplitPaneIcon/SplitPaneIcon.tsx +25 -0
- package/src/language-select/components/SplitPaneIcon/index.ts +1 -0
- package/src/language-select/components/index.ts +2 -0
- package/src/language-select/hooks/index.ts +1 -0
- package/src/language-select/hooks/useLanguages.ts +37 -0
- package/src/language-select/hooks/useListeningQuery.ts +66 -0
- package/src/structure/IDefaultDocumentNodeStructureProps.ts +4 -0
- package/src/structure/components/MaintenanceTab/MaintenanceTab.tsx +13 -0
- package/src/structure/components/MaintenanceTab/MaintenanceTabContent.tsx +259 -0
- package/src/structure/components/MaintenanceTab/index.ts +1 -0
- package/src/structure/components/MaintenanceTabResult/MaintenanceTabResult.tsx +44 -0
- package/src/structure/components/MaintenanceTabResult/index.ts +1 -0
- package/src/structure/components/MaintenanceTabTypeSelector/MaintenanceTabTypeSelector.tsx +56 -0
- package/src/structure/components/MaintenanceTabTypeSelector/index.ts +1 -0
- package/src/structure/hooks/index.ts +1 -0
- package/src/structure/hooks/useDocumentsInformation.ts +110 -0
- package/src/structure/index.tsx +120 -0
- package/src/structure/utils/fixBaseDocumentRefs.ts +30 -0
- package/src/structure/utils/fixBaseLanguageMismatch.ts +21 -0
- package/src/structure/utils/fixIdStructureMismatchDocuments.ts +72 -0
- package/src/structure/utils/fixLanguageFields.ts +32 -0
- package/src/structure/utils/fixOrphanedDocuments.ts +22 -0
- package/src/structure/utils/fixTranslationRefs.ts +50 -0
- package/src/structure/utils/index.ts +6 -0
- package/src/types/IEditState.ts +6 -0
- package/src/types/IExtendedLanguageObject.ts +6 -0
- package/src/types/ILanguageObject.ts +4 -0
- package/src/types/ILanguageQuery.ts +13 -0
- package/src/types/IResolverProps.ts +10 -0
- package/src/types/ITranslationRef.ts +6 -0
- package/src/types/IType.ts +40 -0
- package/src/types/IUseDocumentOperationResult.ts +11 -0
- package/src/types/TFieldNamesConfig.ts +5 -0
- package/src/types/TLanguage.ts +3 -0
- package/src/types/TLanguagesOption.ts +4 -0
- package/src/types/TSchema.ts +6 -0
- package/src/types/Ti18nConfig.ts +33 -0
- package/src/types/Ti18nDocument.ts +14 -0
- package/src/types/Ti18nSchema.ts +13 -0
- package/src/types/index.ts +15 -0
- package/src/utils/applyConfig.ts +43 -0
- package/src/utils/baseToTop.ts +5 -0
- package/src/utils/buildDocId.ts +8 -0
- package/src/utils/createSanityReference.ts +11 -0
- package/src/utils/getBaseIdFromId.ts +20 -0
- package/src/utils/getBaseLanguage.ts +6 -0
- package/src/utils/getFlag.ts +38 -0
- package/src/utils/getLanguageFromDocument.ts +9 -0
- package/src/utils/getLanguageFromId.ts +13 -0
- package/src/utils/getLanguagesFromOption.ts +40 -0
- package/src/utils/getTranslationsForId.ts +39 -0
- package/src/utils/index.ts +14 -0
- package/src/utils/makeObjectKey.ts +3 -0
- package/src/utils/normalizeLanguageList.ts +26 -0
- package/src/utils/serializePath.ts +11 -0
- package/src/utils/updateIntlFieldsForDocument.ts +79 -0
- package/src/utils/useSanityClient.ts +6 -0
- package/src/validators/index.ts +1 -0
- package/src/validators/isSlugUnique.ts +56 -0
- package/src/withDocumentI18nPlugin.ts +24 -0
- package/v2-incompatible.js +1 -1
- package/lib/cjs/index.js +0 -539
- package/lib/cjs/index.js.map +0 -1
- package/lib/esm/index.js +0 -532
- package/lib/esm/index.js.map +0 -1
- package/lib/types/index.d.ts +0 -12
- package/lib/types/index.d.ts.map +0 -1
- package/src/LanguageManage.tsx +0 -25
- package/src/LanguageOption.tsx +0 -158
- package/src/LanguagePatch.tsx +0 -61
- package/src/MenuButton.tsx +0 -101
- package/src/badges/index.tsx +0 -23
- package/src/constants.ts +0 -1
- package/src/hooks/useLanguageMetadata.tsx +0 -21
- package/src/hooks/useOpenInNewPane.tsx +0 -27
- package/src/plugin.tsx +0 -104
- package/src/schema/translation/metadata.ts +0 -42
- package/src/types.ts +0 -24
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {EarthGlobeIcon} from '@sanity/icons'
|
|
2
|
+
import {Schema, SchemaType} from 'sanity'
|
|
3
|
+
import {DocumentListBuilder, ListItemBuilder, StructureBuilder} from 'sanity/desk'
|
|
4
|
+
import {applyConfig} from '../utils'
|
|
5
|
+
import {I18nDelimiter, I18nPrefix, IdStructure, UiMessages} from '../constants'
|
|
6
|
+
import {Ti18nConfig} from '../types'
|
|
7
|
+
import {createMaintenanceTabComponent} from './components/MaintenanceTab'
|
|
8
|
+
|
|
9
|
+
export interface StructureConfig {
|
|
10
|
+
S: StructureBuilder
|
|
11
|
+
config: Ti18nConfig
|
|
12
|
+
schema: Schema & {
|
|
13
|
+
i18n?: Ti18nConfig
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const hasIcon = (schemaType?: SchemaType | string): boolean => {
|
|
18
|
+
if (!schemaType || typeof schemaType === 'string') {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
return Boolean(schemaType.icon)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const getDefaultDocumentNode = (S: StructureBuilder) => {
|
|
25
|
+
return S.document()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const getDocumentTypes = (props: StructureConfig) => {
|
|
29
|
+
const {S, schema} = props
|
|
30
|
+
const listItemsWithouti18n: ListItemBuilder[] = []
|
|
31
|
+
const listItemsWithi18n = S.documentTypeListItems().filter((l) => {
|
|
32
|
+
let schemaType = l.getSchemaType()
|
|
33
|
+
schemaType = typeof schemaType === 'string' ? schema.get(schemaType) : schemaType
|
|
34
|
+
|
|
35
|
+
const hasi18n = schemaType && (schemaType as unknown as {i18n: boolean}).i18n
|
|
36
|
+
if (!hasi18n) listItemsWithouti18n.push(l)
|
|
37
|
+
return hasi18n
|
|
38
|
+
})
|
|
39
|
+
return {
|
|
40
|
+
withoutI18n: listItemsWithouti18n,
|
|
41
|
+
withI18n: listItemsWithi18n,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const getMaintenanceTabComponent = (props: StructureConfig) => {
|
|
46
|
+
const {S} = props
|
|
47
|
+
const MaintenanceTab = createMaintenanceTabComponent(props)
|
|
48
|
+
return S.component(MaintenanceTab)
|
|
49
|
+
.title(UiMessages.translationsMaintenance.title)
|
|
50
|
+
.id(`__i18n_translations_maintenance_tab`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const getMaintenanceListItem = (props: StructureConfig) => {
|
|
54
|
+
const {S} = props
|
|
55
|
+
return S.listItem()
|
|
56
|
+
.id(`__i18n_translations_maintenance_tab`)
|
|
57
|
+
.title(UiMessages.translationsMaintenance.title)
|
|
58
|
+
.icon(EarthGlobeIcon)
|
|
59
|
+
.child(getMaintenanceTabComponent(props))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const getFilteredDocumentTypeListItems = (props: StructureConfig) => {
|
|
63
|
+
const {S, config: pluginConfig, schema} = props
|
|
64
|
+
const config = applyConfig(
|
|
65
|
+
pluginConfig,
|
|
66
|
+
typeof schema.i18n === 'object' ? schema.i18n : undefined
|
|
67
|
+
)
|
|
68
|
+
const types = getDocumentTypes(props)
|
|
69
|
+
const filterFns = {
|
|
70
|
+
[IdStructure.SUBPATH]: (list: ListItemBuilder, doc: DocumentListBuilder) =>
|
|
71
|
+
doc.filter('!(_id in path($path)) && !(_id in path($drafts)) && _type == $type').params({
|
|
72
|
+
path: `${I18nPrefix}.**`,
|
|
73
|
+
drafts: `drafts.${I18nPrefix}.**`,
|
|
74
|
+
type: list.getId(),
|
|
75
|
+
}),
|
|
76
|
+
[IdStructure.DELIMITER]: (list: ListItemBuilder, doc: DocumentListBuilder) =>
|
|
77
|
+
doc.filter('!(_id match $id) && _type == $type').params({
|
|
78
|
+
id: `*${I18nDelimiter}*`,
|
|
79
|
+
type: list.getId(),
|
|
80
|
+
}),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const items = [
|
|
84
|
+
...types.withoutI18n,
|
|
85
|
+
...types.withI18n.map((l) => {
|
|
86
|
+
const schemaType = l.getSchemaType()
|
|
87
|
+
const schemaTypeName = typeof schemaType === 'string' ? schemaType : schemaType?.name
|
|
88
|
+
|
|
89
|
+
return l.child(
|
|
90
|
+
filterFns[config.idStructure](
|
|
91
|
+
l,
|
|
92
|
+
S.documentList()
|
|
93
|
+
.id(l.getId() || '')
|
|
94
|
+
.title(l.getTitle() || '')
|
|
95
|
+
.schemaType(schemaTypeName ?? '')
|
|
96
|
+
.menuItems([...(schemaTypeName ? S.orderingMenuItemsForType(schemaTypeName) : [])])
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
}),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
if (config.withTranslationsMaintenance) {
|
|
103
|
+
items.splice(0, 0, getMaintenanceListItem(props))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return items.map((item) => item.serialize())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getDocumentList(props: StructureConfig) {
|
|
110
|
+
const {S} = props
|
|
111
|
+
const types = getDocumentTypes(props)
|
|
112
|
+
if (types.withI18n.length === 0) return S.defaults()
|
|
113
|
+
|
|
114
|
+
const items = getFilteredDocumentTypeListItems(props)
|
|
115
|
+
return S.list()
|
|
116
|
+
.id('__root__')
|
|
117
|
+
.title('Content')
|
|
118
|
+
.items(items)
|
|
119
|
+
.showIcons(items.some((item) => hasIcon(item.schemaType)))
|
|
120
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type {SanityDocument, SanityClient, Transaction} from '@sanity/client'
|
|
2
|
+
import {ReferenceBehavior} from '../../constants'
|
|
3
|
+
import {ApplyConfigResult, createSanityReference, getBaseIdFromId} from '../../utils'
|
|
4
|
+
|
|
5
|
+
export const fixBaseDocumentRefs = (
|
|
6
|
+
sanityClient: SanityClient,
|
|
7
|
+
config: ApplyConfigResult,
|
|
8
|
+
translatedDocuments: SanityDocument[]
|
|
9
|
+
): Transaction => {
|
|
10
|
+
const transaction = sanityClient.transaction()
|
|
11
|
+
|
|
12
|
+
if (config.referenceBehavior !== ReferenceBehavior.DISABLED) {
|
|
13
|
+
const baseRefFieldName = config.fieldNames.baseReference
|
|
14
|
+
translatedDocuments.forEach((d) => {
|
|
15
|
+
if (!d[baseRefFieldName]) {
|
|
16
|
+
const baseId = getBaseIdFromId(d._id)
|
|
17
|
+
transaction.patch(d._id, {
|
|
18
|
+
set: {
|
|
19
|
+
[baseRefFieldName]: createSanityReference(
|
|
20
|
+
baseId,
|
|
21
|
+
config.referenceBehavior === ReferenceBehavior.WEAK
|
|
22
|
+
),
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return transaction
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {SanityDocument, SanityClient, Transaction} from '@sanity/client'
|
|
2
|
+
import {ApplyConfigResult, getBaseLanguage, getLanguagesFromOption} from '../../utils'
|
|
3
|
+
|
|
4
|
+
export const fixBaseLanguageMismatch = async (
|
|
5
|
+
sanityClient: SanityClient,
|
|
6
|
+
config: ApplyConfigResult,
|
|
7
|
+
basedocuments: SanityDocument[]
|
|
8
|
+
): Promise<Transaction> => {
|
|
9
|
+
const languages = await getLanguagesFromOption(sanityClient, config, config.languages)
|
|
10
|
+
const baseLanguage = getBaseLanguage(languages, config.base)
|
|
11
|
+
const langFieldName = config.fieldNames.lang
|
|
12
|
+
const transaction = sanityClient.transaction()
|
|
13
|
+
basedocuments.forEach((doc) => {
|
|
14
|
+
if (doc[langFieldName] !== baseLanguage?.id) {
|
|
15
|
+
transaction.patch(doc._id, {
|
|
16
|
+
set: {[langFieldName]: baseLanguage?.id}, // eslint-disable-line
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
return transaction
|
|
21
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {SanityDocument, Transaction} from '@sanity/client'
|
|
2
|
+
import chunk from 'just-split'
|
|
3
|
+
import {SanityClient} from '@sanity/client'
|
|
4
|
+
import {ReferenceBehavior} from '../../constants'
|
|
5
|
+
import {
|
|
6
|
+
ApplyConfigResult,
|
|
7
|
+
buildDocId,
|
|
8
|
+
createSanityReference,
|
|
9
|
+
getBaseIdFromId,
|
|
10
|
+
getLanguageFromDocument,
|
|
11
|
+
} from '../../utils'
|
|
12
|
+
|
|
13
|
+
export const fixIdStructureMismatchDocuments = (
|
|
14
|
+
sanityClient: SanityClient,
|
|
15
|
+
config: ApplyConfigResult,
|
|
16
|
+
schema: string,
|
|
17
|
+
documents: SanityDocument[]
|
|
18
|
+
): Transaction[] => {
|
|
19
|
+
const refsFieldName = config.fieldNames.references
|
|
20
|
+
|
|
21
|
+
// remove old refs
|
|
22
|
+
const existingBaseDocumentIds = new Set(documents.map((d) => getBaseIdFromId(d._id)))
|
|
23
|
+
const removeOldRefsTransaction = sanityClient.transaction()
|
|
24
|
+
existingBaseDocumentIds.forEach((id) => {
|
|
25
|
+
removeOldRefsTransaction.patch(id, {
|
|
26
|
+
set: {[refsFieldName]: []},
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// create new document ids
|
|
31
|
+
const newDocumentTransactions = chunk(
|
|
32
|
+
documents.filter((d) => d._id !== getBaseIdFromId(d._id)),
|
|
33
|
+
100
|
|
34
|
+
).map((documentsChunk) => {
|
|
35
|
+
const transaction = sanityClient.transaction()
|
|
36
|
+
documentsChunk.forEach((d) => {
|
|
37
|
+
const baseId = getBaseIdFromId(d._id)
|
|
38
|
+
const lang = getLanguageFromDocument(d, config)
|
|
39
|
+
if (lang) {
|
|
40
|
+
const newId = buildDocId(config, baseId, lang)
|
|
41
|
+
transaction.createIfNotExists({
|
|
42
|
+
...d,
|
|
43
|
+
_id: newId,
|
|
44
|
+
_type: schema,
|
|
45
|
+
})
|
|
46
|
+
transaction.delete(d._id)
|
|
47
|
+
|
|
48
|
+
// patch base document with updated refs
|
|
49
|
+
if (config.referenceBehavior !== ReferenceBehavior.DISABLED) {
|
|
50
|
+
transaction.patch(baseId, {setIfMissing: {[refsFieldName]: []}})
|
|
51
|
+
transaction.patch(baseId, {
|
|
52
|
+
insert: {
|
|
53
|
+
after: `${refsFieldName}[-1]`,
|
|
54
|
+
items: [
|
|
55
|
+
{
|
|
56
|
+
_key: lang,
|
|
57
|
+
...createSanityReference(
|
|
58
|
+
newId,
|
|
59
|
+
config.referenceBehavior === ReferenceBehavior.WEAK
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
return transaction
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return [removeOldRefsTransaction, ...newDocumentTransactions]
|
|
72
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type {SanityClient, SanityDocument, Transaction} from '@sanity/client'
|
|
2
|
+
import type {Schema} from 'sanity'
|
|
3
|
+
import {Ti18nSchema} from '../../types'
|
|
4
|
+
import {ApplyConfigResult, getLanguageFromId} from '../../utils'
|
|
5
|
+
|
|
6
|
+
export const fixLanguageFields = (
|
|
7
|
+
sanityClient: SanityClient,
|
|
8
|
+
config: ApplyConfigResult,
|
|
9
|
+
schemaRegistry: Schema,
|
|
10
|
+
documents: SanityDocument[]
|
|
11
|
+
): Transaction => {
|
|
12
|
+
const langFieldName = config.fieldNames?.lang
|
|
13
|
+
const transaction = sanityClient.transaction()
|
|
14
|
+
|
|
15
|
+
documents.forEach((d) => {
|
|
16
|
+
const schemaObject = schemaRegistry.get(d._type) as Ti18nSchema
|
|
17
|
+
const base =
|
|
18
|
+
(typeof schemaObject.i18n === 'object' ? schemaObject.i18n.base : undefined) || config.base
|
|
19
|
+
if (!d[langFieldName]) {
|
|
20
|
+
// @README keep the language from ID behavior
|
|
21
|
+
// because in this case we expect the language field not to be available
|
|
22
|
+
const language = getLanguageFromId(d._id) || base
|
|
23
|
+
transaction.patch(d._id, {
|
|
24
|
+
set: {
|
|
25
|
+
[langFieldName]: language,
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return transaction
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {SanityClient, SanityDocument, Transaction} from '@sanity/client'
|
|
2
|
+
import type {Reference} from 'sanity'
|
|
3
|
+
import {ApplyConfigResult} from '../../utils'
|
|
4
|
+
|
|
5
|
+
export const fixOrphanedDocuments = (
|
|
6
|
+
sanityClient: SanityClient,
|
|
7
|
+
config: ApplyConfigResult,
|
|
8
|
+
basedocuments: SanityDocument[],
|
|
9
|
+
translatedDocuments: SanityDocument[]
|
|
10
|
+
): Transaction => {
|
|
11
|
+
const transaction = sanityClient.transaction()
|
|
12
|
+
translatedDocuments.forEach((d) => {
|
|
13
|
+
const base = basedocuments.find(
|
|
14
|
+
(doc) =>
|
|
15
|
+
(Array.isArray(d?.[config.fieldNames.references]) &&
|
|
16
|
+
d?.[config.fieldNames.references]?.some((ref: Reference) => ref._ref === d._id)) ||
|
|
17
|
+
doc._id === d?.[config.fieldNames.baseReference]?._ref
|
|
18
|
+
)
|
|
19
|
+
if (!base) transaction.delete(d._id)
|
|
20
|
+
})
|
|
21
|
+
return transaction
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import chunk from 'lodash/chunk'
|
|
2
|
+
import compact from 'lodash/compact'
|
|
3
|
+
import {SanityClient, Transaction, SanityDocument} from '@sanity/client'
|
|
4
|
+
import type {Reference} from 'sanity'
|
|
5
|
+
import {ReferenceBehavior} from '../../constants'
|
|
6
|
+
import {
|
|
7
|
+
ApplyConfigResult,
|
|
8
|
+
createSanityReference,
|
|
9
|
+
getBaseIdFromId,
|
|
10
|
+
getLanguageFromDocument,
|
|
11
|
+
} from '../../utils'
|
|
12
|
+
|
|
13
|
+
export const fixTranslationRefs = (
|
|
14
|
+
sanityClient: SanityClient,
|
|
15
|
+
config: ApplyConfigResult,
|
|
16
|
+
baseDocuments: SanityDocument[],
|
|
17
|
+
translatedDocuments: SanityDocument[]
|
|
18
|
+
// eslint-disable-next-line max-params
|
|
19
|
+
): Transaction[] => {
|
|
20
|
+
const refsFieldName = config.fieldNames.references
|
|
21
|
+
const transactions = chunk(baseDocuments, 50).map((documentsChunk) => {
|
|
22
|
+
const transaction = sanityClient.transaction()
|
|
23
|
+
documentsChunk.forEach((d) => {
|
|
24
|
+
let translatedRefs: Reference[] = []
|
|
25
|
+
const relevantTranslations = translatedDocuments.filter(
|
|
26
|
+
(dx) => getBaseIdFromId(dx._id) === d._id
|
|
27
|
+
)
|
|
28
|
+
if (config.referenceBehavior !== ReferenceBehavior.DISABLED) {
|
|
29
|
+
translatedRefs = compact(
|
|
30
|
+
relevantTranslations.map((doc) => {
|
|
31
|
+
const lang = getLanguageFromDocument(doc, config)
|
|
32
|
+
if (!lang) return null
|
|
33
|
+
return {
|
|
34
|
+
_key: lang,
|
|
35
|
+
...createSanityReference(
|
|
36
|
+
doc._id,
|
|
37
|
+
config.referenceBehavior === ReferenceBehavior.WEAK
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
}, {})
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
transaction.patch(d._id, {
|
|
44
|
+
set: {[refsFieldName]: translatedRefs},
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
return transaction
|
|
48
|
+
})
|
|
49
|
+
return transactions
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {SanityDocument} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
export interface IResolverProps<T extends Record<string, any> = Record<string, any>> {
|
|
4
|
+
id: string
|
|
5
|
+
type: string
|
|
6
|
+
liveEdit: boolean
|
|
7
|
+
draft?: SanityDocument<T>
|
|
8
|
+
published?: SanityDocument<T>
|
|
9
|
+
onComplete?: () => void
|
|
10
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {TLanguagesOption} from './TLanguagesOption'
|
|
2
|
+
import {ObjectSchemaTypeWithOptions} from 'sanity'
|
|
3
|
+
export interface IField {
|
|
4
|
+
name: string
|
|
5
|
+
type: IType
|
|
6
|
+
readOnly?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IFieldSet {
|
|
10
|
+
name: string
|
|
11
|
+
title: string
|
|
12
|
+
description: string
|
|
13
|
+
single: boolean
|
|
14
|
+
options: ObjectSchemaTypeWithOptions['options']
|
|
15
|
+
field: IField
|
|
16
|
+
fields: IField[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IType {
|
|
20
|
+
name: string
|
|
21
|
+
title: string
|
|
22
|
+
description: string
|
|
23
|
+
hidden?: boolean
|
|
24
|
+
level: number
|
|
25
|
+
type: string
|
|
26
|
+
jsonType: string
|
|
27
|
+
fields: IField[]
|
|
28
|
+
fieldsets: IFieldSet[]
|
|
29
|
+
options: ObjectSchemaTypeWithOptions['options'] & {
|
|
30
|
+
base?: string
|
|
31
|
+
i18n?: boolean
|
|
32
|
+
languages?: TLanguagesOption
|
|
33
|
+
css?: (classNames: Record<string, string>) => string
|
|
34
|
+
messages?: {
|
|
35
|
+
loading?: string
|
|
36
|
+
missingTranslations?: string
|
|
37
|
+
}
|
|
38
|
+
[key: string]: any
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {SanityDocument, SchemaType} from 'sanity'
|
|
2
|
+
import type {ComponentType, FC} from 'react'
|
|
3
|
+
import {IdStructure, ReferenceBehavior} from '../constants'
|
|
4
|
+
import {TLanguagesOption} from './TLanguagesOption'
|
|
5
|
+
import {TFieldNamesConfig} from './TFieldNamesConfig'
|
|
6
|
+
import {ILanguageObject} from './ILanguageObject'
|
|
7
|
+
|
|
8
|
+
export type Ti18nConfig = {
|
|
9
|
+
base?: string
|
|
10
|
+
languages?: TLanguagesOption
|
|
11
|
+
idStructure?: IdStructure
|
|
12
|
+
referenceBehavior?: ReferenceBehavior
|
|
13
|
+
fieldNames?: TFieldNamesConfig
|
|
14
|
+
withTranslationsMaintenance?: boolean
|
|
15
|
+
languagesLoader?: (
|
|
16
|
+
languages: ILanguageObject[],
|
|
17
|
+
doc: SanityDocument | undefined
|
|
18
|
+
) => Promise<ILanguageObject[]> | ILanguageObject[]
|
|
19
|
+
shouldReload?: (doc: SanityDocument | undefined | null) => boolean
|
|
20
|
+
fallbackLanguageSelect?: FC<{
|
|
21
|
+
schemaType?: SchemaType & {
|
|
22
|
+
i18n?: boolean | Ti18nConfig
|
|
23
|
+
}
|
|
24
|
+
}>
|
|
25
|
+
customFlagComponents?: Record<
|
|
26
|
+
string,
|
|
27
|
+
ComponentType<{
|
|
28
|
+
className?: string
|
|
29
|
+
code?: string
|
|
30
|
+
langCulture?: string
|
|
31
|
+
}>
|
|
32
|
+
>
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type {SanityDocument} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
// @README these types are not per-se accurate as field names can
|
|
4
|
+
// be manually configured and are not always these defaults
|
|
5
|
+
export type Ti18nDocument<D extends Record<string, any> = Record<string, any>> =
|
|
6
|
+
SanityDocument<D> & {
|
|
7
|
+
__i18n_lang?: string
|
|
8
|
+
__i18n_refs?: {
|
|
9
|
+
_key: string
|
|
10
|
+
_type: 'reference'
|
|
11
|
+
_ref: string
|
|
12
|
+
_weak: boolean
|
|
13
|
+
}[]
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {SchemaType} from 'sanity'
|
|
2
|
+
import {Ti18nConfig} from './Ti18nConfig'
|
|
3
|
+
|
|
4
|
+
declare module '@sanity/types' {
|
|
5
|
+
// makes i18n property allowed on document when using defineTyp/defineField/defineArrayMember
|
|
6
|
+
export interface DocumentDefinition {
|
|
7
|
+
i18n?: boolean | Ti18nConfig
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type Ti18nSchema = SchemaType & {
|
|
12
|
+
i18n: boolean | Ti18nConfig
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './ILanguageObject'
|
|
2
|
+
export * from './IExtendedLanguageObject'
|
|
3
|
+
export * from './ILanguageQuery'
|
|
4
|
+
export * from './IType'
|
|
5
|
+
export * from './TLanguage'
|
|
6
|
+
export * from './IResolverProps'
|
|
7
|
+
export * from './TLanguagesOption'
|
|
8
|
+
export * from './TSchema'
|
|
9
|
+
export * from './Ti18nSchema'
|
|
10
|
+
export * from './Ti18nConfig'
|
|
11
|
+
export * from './Ti18nDocument'
|
|
12
|
+
export * from './TFieldNamesConfig'
|
|
13
|
+
export * from './IUseDocumentOperationResult'
|
|
14
|
+
export * from './ITranslationRef'
|
|
15
|
+
export * from './IEditState'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {IdStructure, ReferenceBehavior} from '../constants'
|
|
2
|
+
import type {Ti18nConfig} from '../types'
|
|
3
|
+
|
|
4
|
+
type ApplyConfigOptionalKeys =
|
|
5
|
+
| 'shouldReload'
|
|
6
|
+
| 'languagesLoader'
|
|
7
|
+
| 'fallbackLanguageSelect'
|
|
8
|
+
| 'customFlagComponents'
|
|
9
|
+
export type ApplyConfigResult = Omit<
|
|
10
|
+
Required<{
|
|
11
|
+
[K in keyof Ti18nConfig]: Required<Ti18nConfig[K]>
|
|
12
|
+
}>,
|
|
13
|
+
ApplyConfigOptionalKeys
|
|
14
|
+
> &
|
|
15
|
+
Pick<Ti18nConfig, ApplyConfigOptionalKeys>
|
|
16
|
+
|
|
17
|
+
export function applyConfig(
|
|
18
|
+
baseConfig?: Ti18nConfig,
|
|
19
|
+
incomingConfig?: Ti18nConfig | null
|
|
20
|
+
): ApplyConfigResult {
|
|
21
|
+
return {
|
|
22
|
+
base: incomingConfig?.base || baseConfig?.base || '',
|
|
23
|
+
languages: incomingConfig?.languages || baseConfig?.languages || [],
|
|
24
|
+
idStructure: baseConfig?.idStructure || IdStructure.DELIMITER,
|
|
25
|
+
referenceBehavior: baseConfig?.referenceBehavior || ReferenceBehavior.STRONG,
|
|
26
|
+
withTranslationsMaintenance: baseConfig?.withTranslationsMaintenance === true,
|
|
27
|
+
shouldReload: baseConfig?.shouldReload,
|
|
28
|
+
languagesLoader: baseConfig?.languagesLoader,
|
|
29
|
+
fallbackLanguageSelect: baseConfig?.fallbackLanguageSelect,
|
|
30
|
+
customFlagComponents: baseConfig?.customFlagComponents,
|
|
31
|
+
fieldNames: {
|
|
32
|
+
lang: incomingConfig?.fieldNames?.lang || baseConfig?.fieldNames?.lang || '__i18n_lang',
|
|
33
|
+
references:
|
|
34
|
+
incomingConfig?.fieldNames?.references ||
|
|
35
|
+
baseConfig?.fieldNames?.references ||
|
|
36
|
+
'__i18n_refs',
|
|
37
|
+
baseReference:
|
|
38
|
+
incomingConfig?.fieldNames?.baseReference ||
|
|
39
|
+
baseConfig?.fieldNames?.baseReference ||
|
|
40
|
+
'__i18n_base',
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {Ti18nConfig} from '../types'
|
|
2
|
+
import {I18nDelimiter, I18nPrefix, IdStructure} from '../constants'
|
|
3
|
+
|
|
4
|
+
export const buildDocId = (pluginConfig: Ti18nConfig, id: string, lang?: string | null): string => {
|
|
5
|
+
if (pluginConfig.idStructure === IdStructure.DELIMITER)
|
|
6
|
+
return `${id}${I18nDelimiter}${lang || '*'}`
|
|
7
|
+
return `${I18nPrefix}.${id}.${lang || '*'}`
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type {Reference} from 'sanity'
|
|
2
|
+
|
|
3
|
+
type Ref = Omit<Reference, '_type'> & {_type: 'reference'}
|
|
4
|
+
|
|
5
|
+
export function createSanityReference(id: string, weak = false): Ref {
|
|
6
|
+
return {
|
|
7
|
+
_type: 'reference' as const,
|
|
8
|
+
_ref: id.replace(`drafts.`, ``),
|
|
9
|
+
...(weak === true ? {_weak: true} : {}),
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {I18nPrefix, I18nDelimiter} from '../constants'
|
|
2
|
+
|
|
3
|
+
/* Generic to support both string -> string & string? -> string? */
|
|
4
|
+
export const getBaseIdFromId = <T extends string | undefined>(id?: T): T => {
|
|
5
|
+
if (!id) {
|
|
6
|
+
return undefined as T
|
|
7
|
+
}
|
|
8
|
+
const nonDraftId = id.replace(/^drafts\./, '')
|
|
9
|
+
|
|
10
|
+
// subpath
|
|
11
|
+
const rx = new RegExp(`${I18nPrefix}\\.([^.]+)\\.[^.]+`)
|
|
12
|
+
const match = nonDraftId.match(rx)
|
|
13
|
+
if (match && match.length === 2) return match[1] as T
|
|
14
|
+
|
|
15
|
+
// delimiter
|
|
16
|
+
const split = nonDraftId.split(I18nDelimiter)
|
|
17
|
+
if (split.length > 0) return split[0] as T
|
|
18
|
+
|
|
19
|
+
return nonDraftId as T
|
|
20
|
+
}
|